Странное поведение printk в модуле ядра Linux
Я пишу код для модуля ядра Linux и испытываю странное поведение в нем. Вот мой код:
int data = 0;
void threadfn1()
{
int j;
for( j = 0; j < 10; j++ )
printk(KERN_INFO "I AM THREAD 1 %d\n",j);
data++;
}
void threadfn2()
{
int j;
for( j = 0; j < 10; j++ )
printk(KERN_INFO "I AM THREAD 2 %d\n",j);
data++;
}
static int __init abc_init(void)
{
struct task_struct *t1 = kthread_run(threadfn1, NULL, "thread1");
struct task_struct *t2 = kthread_run(threadfn2, NULL, "thread2");
while( 1 )
{
printk("debug\n"); // runs ok
if( data >= 2 )
{
kthread_stop(t1);
kthread_stop(t2);
break;
}
}
printk(KERN_INFO "HELLO WORLD\n");
}
По сути, я пытался дождаться окончания потоков и затем что-то напечатать. Приведенный выше код достигает этой цели, но с "printk("debug\n");"
не комментируется. Как только я закомментирую printk("debug\n");
чтобы запустить код без отладки и загрузить модуль с помощью команды insmod, модуль зависает, и кажется, что он теряется в рекурсии. Я не понимаю, почему printk так сильно влияет на мой код?
Любая помощь будет оценена.
С уважением.
5 ответов
С призывом к printk()
убрал компилятор, оптимизирующий цикл в while (1);
, Когда вы добавляете вызов printk()
компилятор не уверен, что data
не изменяется и поэтому проверяет значение каждый раз в цикле.
Вы можете вставить барьер в цикл, который заставляет компилятор переоценивать data
на каждой итерации. например:
while (1) {
if (data >= 2) {
kthread_stop(t1);
kthread_stop(t2);
break;
}
barrier();
}
Вы не синхронизируете доступ к переменной-данным. Что происходит, так это то, что компилятор сгенерирует бесконечный цикл. Вот почему:
while( 1 )
{
if( data >= 2 )
{
kthread_stop(t1);
kthread_stop(t2);
break;
}
}
Компилятор может обнаружить, что значение данных никогда не изменяется в цикле while. Поэтому он может полностью переместить проверку из цикла, и вы получите простой
while (1) {}
Если вы вставите printk, компилятор должен предположить, что данные глобальной переменной могут измениться (в конце концов - компилятор не имеет представления о том, что printk делает подробно), поэтому ваш код снова начнет работать (с неопределенным поведением).
Как это исправить:
Используйте правильные примитивы синхронизации потоков. Если вы поместите доступ к данным в секцию кода, защищенную мьютексом, код будет работать. Вы также можете заменить переменные данные и использовать подсчитанный семафор.
Редактировать:
Эта ссылка объясняет, как работает блокировка в ядре Linux:
http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html
Может быть, данные должны быть объявлены волатильными? Возможно, компилятор не собирается в память для получения данных в цикле.
Волатильность не всегда может быть "плохой идеей". Необходимо выделить случай, когда необходима изменчивость и когда необходим механизм взаимного исключения. Это не оптимально, когда один использует или неправильно использует один механизм для другого. В приведенном выше случае. Я бы предложил для оптимального решения, что оба механизма необходимы: мьютекс, чтобы обеспечить взаимное исключение, изменчивый, чтобы указать компилятору, что "информация" должна быть прочитана только с аппаратного обеспечения. В противном случае в некоторых ситуациях (оптимизация -O2, -O3) компиляторы могут непреднамеренно пропустить необходимые коды.
Ответ Нильса Пипенбринка на месте. Я просто добавлю несколько указателей.
Ненадежное руководство Расти по блокировке ядра (каждый хакер ядра должен прочитать это).
Прощай семафоры?, API мьютекса (статьи http://lwn.net/ о новом API мьютекса, представленные в начале 2006 года, до этого ядро Linux использовало семафоры в качестве мьютексов).
Кроме того, поскольку ваши общие данные являются простым счетчиком, вы можете просто использовать атомарный API (в основном, объявить свой счетчик как atomic_t и получить к нему доступ с помощью функций atomic_*).