Странное поведение printk в модуле ядра Linux - PullRequest
2 голосов
/ 06 ноября 2010

Я пишу код для модуля ядра 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 ]

4 голосов
/ 06 ноября 2010

Вы не синхронизируете доступ к переменной-данным.Что происходит, так это то, что компилятор сгенерирует бесконечный цикл.Вот почему:

  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

1 голос
/ 06 ноября 2010

С удаленным вызовом printk() компилятор оптимизирует цикл в while (1);. Когда вы добавляете вызов к printk(), компилятор не уверен, что data не изменился, и поэтому проверяет значение каждый раз в цикле.

Вы можете вставить барьер в цикл, который заставит компилятор переоценивать data на каждой итерации. например:

while (1) {
        if (data >= 2) {
                kthread_stop(t1);
                kthread_stop(t2);
                break;
        }

        barrier();
}
0 голосов
/ 11 февраля 2011

Волатильность не всегда может быть "плохой идеей". Нужно отделить случай, когда необходима изменчивость и когда взаимное исключение Механизм нужен. Это не оптимально, когда кто-то использует или злоупотребляет один механизм для другого. В приведенном выше случае. Я бы предложил для оптимального решения необходимы оба механизма: мьютекс обеспечить взаимное исключение, изменчивое, чтобы указать компилятору, что «Информация» должна быть прочитана только с аппаратного обеспечения. Иначе в некоторых ситуация (оптимизация -O2, -O3), компиляторы могут случайно пропустите необходимые коды.

0 голосов
/ 06 ноября 2010

Ответ Нильса Пипенбринка точен.Я просто добавлю несколько указателей.

Ненадежное руководство Расти по блокировке ядра (это должен прочитать каждый хакер ядра).
Прощай, семафоры? , API мьютекса ( lwn.net статьи о новом API мьютекса, представленные в начале 2006 года, до того, как ядро ​​Linux использовало семафорыкак мьютексы).

Кроме того, поскольку ваши общие данные являются простым счетчиком, вы можете просто использовать атомарный API (в основном, объявить свой счетчик как atomic_t и получить к нему доступ с помощью функций atomic_ *).

0 голосов
/ 06 ноября 2010

Может быть, данные должны быть объявлены волатильными? Возможно, компилятор не собирается в память для получения данных в цикле.

...