Странное поведение 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_*).

Другие вопросы по тегам