2 Stimmen

Seltsames Verhalten von printk im Linux-Kernelmodul

Ich schreibe einen Code für ein Linux-Kernelmodul und erlebe ein seltsames Verhalten darin. Hier ist mein Code:

int data = 0;
void threadfn1()
{
    int j;
    for( j = 0; j < 10; j++ )
        printk(KERN_INFO "ICH BIN THREAD 1 %d\n",j);   
    data++;
}

void threadfn2()
{
    int j;
    for( j = 0; j < 10; j++ )
        printk(KERN_INFO "ICH BIN 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"); // läuft okay
            if( data >= 2 )
            {
                kthread_stop(t1);
                kthread_stop(t2);
                break;
            }
        }
        printk(KERN_INFO "HELLO WORLD\n");

 }

Grundsätzlich habe ich versucht zu warten, bis die Threads fertig sind, und dann etwas zu drucken. Der obige Code erreicht dieses Ziel, aber MIT "printk("debug\n");" nicht auskommentiert. Sobald ich printk("debug\n"); auskommentiere, um den Code ohne Debugging auszuführen und das Modul durch den insmod-Befehl zu laden, hängt das Modul fest und es scheint, als ob es sich in Rekursion verläuft. Ich weiß nicht, warum printk meinen Code so stark beeinflusst?

Jede Hilfe wäre geschätzt.

Mit freundlichen Grüßen.

4voto

Nils Pipenbrinck Punkte 80152

Sie synchronisieren den Zugriff auf die Datenvariable nicht. Was passiert, ist, dass der Compiler eine Endlosschleife erzeugt. Hier ist warum:

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

Der Compiler kann erkennen, dass der Wert von data sich innerhalb der while-Schleife nie ändert. Daher kann er die Überprüfung komplett aus der Schleife herausnehmen und Sie enden mit einem einfachen

 while (1) {} 

Wenn Sie printk einfügen, muss der Compiler davon ausgehen, dass die globale Variable data sich ändern kann (schließlich hat der Compiler keine Ahnung, was printk im Detail tut), daher funktioniert Ihr Code wieder (auf eine undefinierte Weise..)

Wie man das behebt:

Verwenden Sie ordentliche Thread-Synchronisierungsprimitive. Wenn Sie den Zugriff auf data in einem durch ein Mutex geschützten Codeblock umgeben, funktioniert der Code. Sie könnten auch die Variable data durch einen gezählten Semaphor ersetzen.

Bearbeiten:

Dieser Link erklärt, wie das Sperren im Linux-Kernel funktioniert:

http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html

1voto

mpe Punkte 2530

Mit dem Aufruf von printk() entfernt der Compiler die Optimierung der Schleife in while (1);. Wenn Sie den Aufruf von printk() hinzufügen, ist der Compiler sich nicht sicher, dass sich data nicht ändert, und überprüft daher den Wert bei jedem Durchlauf der Schleife.

Sie können eine Barriere in die Schleife einfügen, die den Compiler zwingt, data bei jeder Iteration neu zu evaluieren. z.B.:

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

        barrier();
}

0voto

Richard Pennington Punkte 19289

Vielleicht sollte die Daten als volatil deklariert werden? Es könnte sein, dass der Compiler nicht im Speicher nach Daten in der Schleife sucht.

0voto

ninjalj Punkte 40810

Die Antwort von Nils Pipenbrinck ist genau richtig. Ich werde nur einige Hinweise hinzufügen.

Rustys Unzuverlässiger Leitfaden zur Kernel-Sperrung (jeder Kernel-Hacker sollte diesen lesen).
Auf Wiedersehen Semaphoren?, Die Mutex-API (lwn.net Artikel zur neuen Mutex-API, die Anfang 2006 eingeführt wurde, zuvor verwendete der Linux-Kernel Semaphoren als Sperren).

Außerdem, da Ihre gemeinsamen Daten ein einfacher Zähler sind, können Sie einfach die atomare API verwenden (deklarieren Sie Ihren Zähler einfach als atomic_t und greifen Sie mit den atomic_* Funktionen darauf zu).

0voto

Duc Punkte 1

Volatil ist möglicherweise nicht immer eine "schlechte Idee". Man muss den Fall unterscheiden, wann volatile benötigt wird und wann ein gegenseitiger Ausschlussmechanismus benötigt wird. Es ist nicht optimal, wenn man einen Mechanismus für den anderen verwendet oder missbraucht. In dem oben genannten Fall würde ich für eine optimale Lösung vorschlagen, dass beide Mechanismen benötigt werden: Mutex um den gegenseitigen Ausschluss zu gewährleisten, volatile um dem Compiler anzuzeigen, dass "info" frisch von der Hardware gelesen werden muss. Andernfalls könnten Compiler in einigen Situationen (Optimierung -O2, -O3) versehentlich den benötigten Code weglassen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X