Debuggen von Systemfehlern

Selbst wenn Sie alle Beobachtungs- und Debugging-Techniken verwendet haben, bleiben manchmal noch Fehler im Treiber, die zu einem Systemfehler führen, wenn der Treiber ausgeführt wird. Wenn so etwas passiert, ist es wichtig, so viele Informationen wie möglich zu sammeln, um das Problem zu lösen.

Beachten Sie, daß “Fehler” hier nicht gleich “Panik” bedeutet. Der Linux-Code ist robust genug, um die meisten Fehler sanft abfedern zu können: Ein Fehler führt meistens nur zur Beendigung des aktuellen Prozesses, aber das System läuft weiter. Das System kann jedoch in Panik geraten, wenn ein Fehler außerhalb eines Prozeßkontextes auftritt oder ein lebenswichtiger Teil des Systems betroffen ist. Aber wenn das Problem nur ein Treiberfehler ist, dann wird normalerweise lediglich der Prozeß abgeschossen, der den Treiber unseligerweise benutzt hat. Der einzige nicht wiedergutzumachende Schaden, der auftritt, wenn ein Prozeß zerstört wird, ist der mögliche Verlust des vom Prozeß allozierten Speichers. Beispielsweise könnten via kmalloc allozierte dynamische Listen verloren sein. Da aber der Kernel für jedes offene Gerät close aufruft, kann der Treiber alles freigeben, was in open alloziert wurde.

Wir haben bereits erwähnt, daß eine informative Meldung auf der Konsole ausgegeben wird, falls sich Kernel-Code nicht richtig verhält. Im nächsten Abschnitt wird erklärt, wie man diese Meldungen entschlüsselt und benutzt. Obwohl diese Meldungen für den Anfänger ziemlich obskur aussehen, sind diese Prozessor-Dumps voller interessanter Informationen und oft schon ausreichend, um einen Programmfehler zu finden, ohne daß noch zusätzlich getestet werden muß.

Oops-Meldungen

Die meisten Fehler treten auf, wenn ein NULL-Zeiger dereferenziert oder ein anderer inkorrekter Zeiger-Wert verwendet wird. Das führt normalerweise zu einer Oops-Meldung.

Jede Adresse, die der Prozessor verwendet, ist eine virtuelle Adresse, die über eine komplexe Struktur aus sogenannten Seitentabellen (page tables, siehe in Kapitel 13) auf physikalische Adressen abgebildet wird. Wenn ein ungültiger Zeiger dereferenziert wird, kann der Seitenaustausch-Algorithmus den Zeiger nicht auf eine physikalische Adresse abbilden, und der Prozessor meldet dem Betriebssystem einen Seitenfehler (page fault). Wenn die Adresse nicht gültig ist, dann kann der Kernel die fehlende Seite nicht laden. Passiert dies, während der Prozessor im Supervisor-Modus ist, erzeugt der Kernel eine Oops-Meldung.

Interessanterweise war die erste nach der Version 2.0 eingeführte Verbesserung die automatische Fehlerbehandlung ungültiger Adressen beim Transportieren von Daten in und aus dem User-Space. Linus entschloß sich dazu, die Hardware fehlerhafte Speicherreferenzen abfangen zu lassen, so daß der Normalfall (die Adressen sind korrekt) effizienter abgehandelt wird.

Eine Oops-Meldung zeigt den Prozessor-Status zum Zeitpunkt des Fehlers an. Dazu gehören der Inhalt der CPU-Register, die Lage der Seitendeskriptoren-Tabellen und andere, auf den ersten Blick unverständliche Informationen. Die Meldung wird durch printk-Anweisungen in der Fehlerbehandlungsroutine (arch/*/kernel/traps.c) ausgegeben und, wie weiter vorn in Abschnitt “Printk” beschrieben, weitergeleitet.

Lassen Sie uns eine solche Meldung ansehen. Hier sehen Sie, was geschieht, wenn ein NULL-Zeiger auf einem PC refenziert wird, der die Version 2.4 des Kernels verwendet. Die wichtigste Information ist hier der Anweisungszeiger (EIP), also die Adresse der fehlerhaften Anweisung.


Unable to handle kernel NULL pointer dereference at virtual address \
     00000000
 printing eip:
c48370c3
*pde = 00000000
Oops: 0002
CPU:    0
EIP:    0010:[<c48370c3>]
EFLAGS: 00010286
eax: ffffffea   ebx: c2281a20   ecx: c48370c0   edx: c2281a40
esi: 4000c000   edi: 4000c000   ebp: c38adf8c   esp: c38adf8c
ds: 0018   es: 0018   ss: 0018
Process ls (pid: 23171, stackpage=c38ad000)
Stack: 0000010e c01356e6 c2281a20 4000c000 0000010e c2281a40 c38ac000 \
            0000010e
       4000c000 bffffc1c 00000000 00000000 c38adfc4 c010b860 00000001 \
            4000c000
       0000010e 0000010e 4000c000 bffffc1c 00000004 0000002b 0000002b \
            00000004
Call Trace: [<c01356e6>] [<c010b860>]
Code: c7 05 00 00 00 00 00 00 00 00 31 c0 89 ec 5d c3 8d b6 00 00

Die obige Meldung wurde das durch Schreiben auf ein Gerät provoziert, das dem faulty-Modul gehört, einem Modul, in das wir absichtlich Fehler eingebaut haben. Die Implementation der write-Methode von faulty.c ist trivial:


 
ssize_t faulty_write (struct file *filp, const char *buf, size_t count,
                loff_t *pos)
{
    /* Durch Dereferenzieren eines NULL-Zeigers ganz einfach einen Fehler hervorrufen */
    *(int *)0 = 0;
    return 0;
}

Wie Sie sehen, dereferenzieren wir hier einen NULL-Zeiger. Weil 0 nie ein zulässiger Zeigerwert ist, tritt ein Fehler auf, den der Kernel in die oben gezeigte Oops-Meldung umwandelt. Der aufrufende Prozeß wird dann beendet.

Das Modul faulty enthält noch weitere interessante Fehlerfälle in seiner read-Implementation:


char faulty_buf[1024];

ssize_t faulty_read (struct file *filp, char *buf, size_t count,
                     loff_t *pos)
{
    int ret, ret2;
    char stack_buf[4];

    printk(KERN_DEBUG "read: buf %p, count %li\n", buf, (long)count);
    /* Die naechste Zeile fuehrt bei 2.0 zu einem Oops, ab 2.2 nicht mehr */
    ret = copy_to_user(buf, faulty_buf, count);
    if (!ret) return count; /* we survived */

    printk(KERN_DEBUG "didn't fail: retry\n");
    /* Ein Puffer-Ueberlauf sollte auch bei 2.2 und 2.4 wirken  */
    sprintf(stack_buf, "1234567\n");
    if (count > 8) count = 8; /* copy 8 bytes to the user */
    ret2 = copy_to_user(buf, stack_buf, count);
    if (!ret2) return count;
    return ret2;
}

Diese Funktion liest zuerst aus einem globalen Puffer, ohne die Größe der Daten zu überprüfen, und erzeugt dann durch Schreiben in einen lokalen Puffer einen Buffer Overrun. Die erste Situation führt nur in der Version 2.0 des Kernels zu einem Oops, weil spätere Versionen sich automatisch um die Kopierfunktionen zum oder vom User-Space kümmern. Der Puffer-Überlauf führt in allen Kernel-Versionen zu einem Oops; weil aber die return-Anweisung den Anweisungszeiger ins Niemandsland bringt, ist diese Art von Fehlern sehr viel schwieriger aufzuspüren, und Sie können etwa folgendes bekommen:

EIP:    0010:[<00000000>]
[...]
Call Trace: [<c010b860>]
Code:  Bad EIP value.

Wenn Benutzer mit Oops-Meldungen konfrontiert werden, besteht das Hauptproblem darin, daß die hexadezimalen Werte nur wenig eigentliche Bedeutung enthalten; damit sie für den Programmierer eine Bedeutung bekommen, müssen sie in Symbole aufgelöst werden. Dafür gibt es Hilfsprogramme: klogd und ksymoops. Ersteres erledigt die Decodierung der Symbole ganz automatisch, wenn es gerade läuft, letzteres muß vom Benutzer explizit aufgerufen werden. In der folgenden Darstellung verwenden wir die Daten aus unserem ersten Oops-Beispiel, in dem wir einen NULL-Zeiger dereferenziert haben.

klogd benutzen

Der klogd-Daemon kann Oops-Meldungen decodieren, bevor sie überhaupt in den Protokolldateien auftauchen. klogd kann alle Informationen liefern, die Entwickler brauchen, um ein Problem einzukreisen, auch wenn die Entwickler dafür manchmal ein bißchen nachhelfen müssen.

Ein Auszug der Oops-Meldung für faulty sieht im Systemprotokoll folgendermaßen aus (beachten Sie die decodierten Symbole in der EIP-Zeile und im Stack Trace):

Unable to handle kernel NULL pointer dereference at virtual address \
     00000000
 printing eip:
c48370c3
*pde = 00000000
Oops: 0002
CPU:    0
EIP:    0010:[faulty:faulty_write+3/576]
EFLAGS: 00010286
eax: ffffffea   ebx: c2c55ae0   ecx: c48370c0   edx: c2c55b00
esi: 0804d038   edi: 0804d038   ebp: c2337f8c   esp: c2337f8c
ds: 0018   es: 0018   ss: 0018
Process cat (pid: 23413, stackpage=c2337000)
Stack: 00000001 c01356e6 c2c55ae0 0804d038 00000001 c2c55b00 c2336000 \
            00000001
       0804d038 bffffbd4 00000000 00000000 bffffbd4 c010b860 00000001 \
            0804d038
       00000001 00000001 0804d038 bffffbd4 00000004 0000002b 0000002b \
            00000004
Call Trace: [sys_write+214/256] [system_call+52/56]
Code: c7 05 00 00 00 00 00 00 00 00 31 c0 89 ec 5d c3 8d b6 00 00

klogd liefert den größten Teil der notwendigen Information, um das Problem einzukreisen. In diesem Fall sehen wir, daß sich der Anweisungszeiger (EIP) in der Funktion faulty_write befand, wissen also, wo wir mit der Suche anfangen müssen. Der String 3/576 teilt uns mit, daß sich der Prozessor an Byte 3 einer Funktion befand, die 576 Bytes lang zu sein scheint. Beachten Sie, daß die Werte dezimal, nicht hexadezimal sind.

Man muß allerdings als Entwickler etwas vorsichtig sein, um nützliche Informationen über Fehler zu bekommen, die in ladbaren Modulen auftreten. klogd lädt alle verfügbaren Symbolinformationen beim Start und verwendet danach diese Symbole. Wenn Sie ein Modul laden, nachdem klogd initialisiert worden ist (was normalerweise beim Systemstart geschieht), dann stehen klogd keine Informationen über die Symbole Ihres Moduls zur Verfügung. Um klogd zu zwingen, sich diese Informationen zu holen, müssen Sie dem klogd-Prozeß nach dem Laden (oder Neuladen) Ihres Moduls ein SIGUSR1-Signal schicken, bevor Sie etwas unternehmen, was zu einem Oops führen könnte.

Es ist auch möglich, klogd mit der Option –p ("paranoid") zu starten, die zum Neuladen der Symbolinformationen führt, wann immer klogd auf eine Oops-Meldung stößt. Die Manual-Page von klogd rät aber davon ab, weil klogd den Kernel nach Informationen fragen muß, nachdem das Problem aufgetreten ist. Eine so erlangte Information kann schlicht und einfach falsch sein.

Damit klogd korrekt funktionieren kann, muß eine aktuelle Version der Symboltabellendatei System.map vorliegen. Normalerweise steht diese Datei in /boot; wenn Sie einen Kernel aus einem vom Standard abweichenden Verzeichnis gebaut und installiert haben, müssen Sie eventuell System.map nach /boot kopieren oder klogd mitteilen, woanders zu suchen. klogd verweigert das Decodieren der Symbole, wenn die Symboltabelle nicht zum aktuellen Kernel paßt. Wenn ein Symbol im Systemprotokoll decodiert worden ist, dann können Sie ziemlich sicher sein, daß es korrekt ist.

ksymoops verwenden

Manchmal reicht klogd nicht aus, um ein Problem einzukreisen. Gewöhnlich brauchen Sie sowohl die hexadezimale Adresse als auch das dazugehörige Symbol, und oft brauchen Sie auch die Offsets als hexadezimale Zahlen sowie mehr Informationen als das reine Decodieren der Adreßinformation. Es ist auch nicht ungewöhnlich, daß der klogd-Prozeß während des Fehlers abgeschossen wird. In solchen Situationen braucht man ein kraftvolleres Oops-Analysewerkzeug. ksymoops ist so ein Werkzeug.

Vor der 2.3-Entwicklungsserie wurde ksymoops mit den Kernel-Quellen im Verzeichnis scripts verteilt. Heutzutage befindet es sich auf einem eigenen FTP-Server und wird unabhängig vom Kernel gepflegt. Selbst wenn Sie mit einem älteren Kernel arbeiten, sollten Sie sich am besten von ftp://ftp.ocs.com.au/pub/ksymoops die neueste Version dieses Werkzeugs holen.

Um sein Potential voll ausschöpfen zu können, braucht ksymoops neben der Fehlermeldung eine Reihe zusätzlicher Informationen; mit Kommandozeilenoptionen können Sie mitteilen, wo sich diese befinden. Das Programm braucht:

Eine System.map-Datei

Diese Tabelle muß zu dem Kernel passen, der lief, als der Oops auftrat. Der Default ist /usr/src/linux/System.map.

Eine Liste von Modulen

ksymoops muß wissen, welche Module gerade geladen waren, als der Oops auftrat, um die symbolischen Informationen daraus extrahieren zu können. Wenn Sie diese Liste nicht angeben, holt ksymoops sie sich aus /proc/modules.

Eine Liste der zum Zeitpunkt des Oops definierten Kernel-Symbole

Defaultmäßig wird diese Liste aus /proc/ksyms geholt.

Eine Kopie des gerade laufenden Kernel-Images

Beachten Sie, daß ksymoops ein normales Kernel-Image benötigt, nicht eine der komprimierten Versionen vmlinz, zImage oder bzImage, die die meisten Systeme zum Booten verwenden. Defaultmäßig wird kein Kernel-Image verwendet, weil die meisten Leute keines haben. Wenn Sie das passende Image parat haben, dann sollten Sie dem Programm mit der Option -v mitteilen, wo sich dieses befindet.

Die Lage der Objektdateien aller geladenen Kernel-Module

ksymoops sucht in den Standardverzeichnissen für Module, aber während der Entwicklung müssen Sie fast immer sagen, wo Ihre Module gerade liegen; dies geschieht mit der Option -o.

Obwohl ksymoops die Dateien aus /proc verwendet, um einige Informationen zu suchen, die es benötigt, können diese Ergebnisse unzuverlässig sein. Wahrscheinlich ist das System seit dem Oops neugestartet worden, und die Informationen aus /proc stimmen möglicherweise nicht mit denen überein, die gültig waren, als der Fehler auftrat. Wenn es möglich ist, ist es besser, /proc/modules und /proc/ksyms zu sichern, bevor der Oops auftritt.

Wir empfehlen allen Treiberentwicklern, die Manual-Page von ksymoops zu lesen; dies ist ein sehr informatives Dokument.

Das letzte Argument auf der Kommandozeile von ksymoops ist die Lage der Oops-Meldung; wenn diese nicht angegeben wird, liest das Programm in bester Unix-Tradition von stdin. Die Meldung kann mit Glück aus dem Systemprotokoll geholt werden; bei einem ganz üblen Crash müssen Sie sie möglicherweise vom Bildschirm abschreiben und später wieder eintippen (sofern Sie keine serielle Konsole verwenden, die bei der Kernel-Entwicklung sehr praktisch ist).

Beachten Sie, daß sich ksymoops von einer Oops-Meldung, die bereits von klogd verarbeitet worden ist, irritieren läßt. Wenn Sie klogd verwenden und Ihr System auch nach dem Oops noch läuft, dann können Sie oft mit dem Befehl dmesg eine saubere Oops-Meldung bekommen.

Wenn Sie nicht die gesamten hier genannten Informationen explizit angeben, wird ksymoops Warnungen ausgeben. Dies geschieht auch, wenn Module geladen werden, die keine Symbole definieren, und bei ähnlichen Problemen. Es ist selten, daß man ksymoops ohne Warnung ausführen kann.

Die Ausgabe von ksymoops sieht oft etwa so aus:


>>EIP; c48370c3 <[faulty]faulty_write+3/20>   <=====
Trace; c01356e6 <sys_write+d6/100>
Trace; c010b860 <system_call+34/38>
Code;  c48370c3 <[faulty]faulty_write+3/20>
00000000 <_EIP>:
Code;  c48370c3 <[faulty]faulty_write+3/20>   <=====
   0:   c7 05 00 00 00    movl   $0x0,0x0   <=====
Code;  c48370c8 <[faulty]faulty_write+8/20>
   5:   00 00 00 00 00
Code;  c48370cd <[faulty]faulty_write+d/20>
   a:   31 c0             xorl   %eax,%eax
Code;  c48370cf <[faulty]faulty_write+f/20>
   c:   89 ec             movl   %ebp,%esp
Code;  c48370d1 <[faulty]faulty_write+11/20>
   e:   5d                popl   %ebp
Code;  c48370d2 <[faulty]faulty_write+12/20>
   f:   c3                ret
Code;  c48370d3 <[faulty]faulty_write+13/20>
  10:   8d b6 00 00 00    leal   0x0(%esi),%esi
Code;  c48370d8 <[faulty]faulty_write+18/20>
  15:   00

Wie Sie sehen, liefert ksymoops Informationen über EIP und den Kernel-Stack fast genauso wie klogd, allerdings genauer und in hexadezimaler Schreibweise. Beachten Sie, daß die Funktion faulty_write korrekterweise mit einer Länge von 0x20 Bytes gemeldet wird. Dies ist möglich, weil ksymoops die Objektdatei Ihres Moduls liest und alle notwendigen Informationen extrahiert.

In diesem Fall bekommen Sie darüber hinaus auch noch einen Assembler-Dump des Codes, bei dem der Fehler auftrat. Diese Information kann oft verwendet werden, um herauszufinden, was eigentlich passiert ist; hier handelt es sich offensichtlich um eine Anweisung, die eine 0 an die Adresse 0 schreibt.

Es ist ein interessantes Feature von ksymoops, daß dieses Programm auf fast alle Plattformen portiert worden ist, auf denen Linux läuft, und die bfd-Bibliothek (Binary Format Description) verwendet, um mehrere Computer-Architekturen gleichzeitig zu unterstützen. Um aus der PC-Welt herauszukommen, schauen wir uns jetzt die gleiche Oops-Meldung auf der SPARC64-Plattform an (mehrere Zeilen sind aus Layout-Gründen umbrochen worden):

Unable to handle kernel NULL pointer dereference
tsk->mm->context = 0000000000000734
tsk->mm->pgd = fffff80003499000
              \|/ ____ \|/
              "@'/ .. \`@"
              /_| \_ _/ |_\
                 \_ _U_/
ls(16740): Oops
TSTATE: 0000004400009601 TPC: 0000000001000128 TNPC: 0000000000457fbc \
Y: 00800000
g0: 000000007002ea88 g1: 0000000000000004 g2: 0000000070029fb0 \
g3: 0000000000000018
g4: fffff80000000000 g5: 0000000000000001 g6: fffff8000119c000 \
g7: 0000000000000001
o0: 0000000000000000 o1: 000000007001a000 o2: 0000000000000178 \
o3: fffff8001224f168
o4: 0000000001000120 o5: 0000000000000000 sp: fffff8000119f621 \
ret_pc: 0000000000457fb4
l0: fffff800122376c0 l1: ffffffffffffffea l2: 000000000002c400 \
l3: 000000000002c400
l4: 0000000000000000 l5: 0000000000000000 l6: 0000000000019c00 \
l7: 0000000070028cbc
i0: fffff8001224f140 i1: 000000007001a000 i2: 0000000000000178 \
i3: 000000000002c400
i4: 000000000002c400 i5: 000000000002c000 i6: fffff8000119f6e1 \
i7: 0000000000410114
Caller[0000000000410114]
Caller[000000007007cba4]
Instruction DUMP: 01000000 90102000 81c3e008 <c0202000> \
30680005 01000000 01000000 01000000 01000000

Beachten Sie, daß der Anweisungsauszug nicht bei der Anweisung anfängt, die den Fehler verursachte, sondern drei Anweisungen davor. Dies liegt daran, daß RISC-Plattformen mehrere Anweisungen parallel ausführen und Ausnahmen eventuell verzögern müssen; deswegen muß man in der Lage sein, die letzten paar Anweisungen zu sehen.

Hier sehen Sie die Ausgabe von ksymoops, wenn man die Eingabedatei ab der TSTATE-Zeile übergibt:


>>TPC; 0000000001000128 <[faulty].text.start+88/a0>   <=====
>>O7;  0000000000457fb4 <sys_write+114/160>
>>I7;  0000000000410114 <linux_sparc_syscall+34/40>
Trace; 0000000000410114 <linux_sparc_syscall+34/40>
Trace; 000000007007cba4 <END_OF_CODE+6f07c40d/????>
Code;  000000000100011c <[faulty].text.start+7c/a0>
0000000000000000 <_TPC>:
Code;  000000000100011c <[faulty].text.start+7c/a0>
   0:   01 00 00 00       nop
Code;  0000000001000120 <[faulty].text.start+80/a0>
   4:   90 10 20 00       clr  %o0     ! 0 <_TPC>
Code;  0000000001000124 <[faulty].text.start+84/a0>
   8:   81 c3 e0 08       retl
Code;  0000000001000128 <[faulty].text.start+88/a0>   <=====
   c:   c0 20 20 00       clr  [ %g0 ]   <=====
Code;  000000000100012c <[faulty].text.start+8c/a0>
  10:   30 68 00 05       b,a   %xcc, 24 <_TPC+0x24> \
                        0000000001000140 <[faulty]faulty_write+0/20>
Code;  0000000001000130 <[faulty].text.start+90/a0>
  14:   01 00 00 00       nop
Code;  0000000001000134 <[faulty].text.start+94/a0>
  18:   01 00 00 00       nop
Code;  0000000001000138 <[faulty].text.start+98/a0>
  1c:   01 00 00 00       nop
Code;  000000000100013c <[faulty].text.start+9c/a0>
  20:   01 00 00 00       nop

Um diesen disassemblierten Code auszugeben, mußten wir ksymoops zunächst das Ziel-Dateiformat und die Ziel-Architektur angeben (weil die native Architektur des SPARC64-User-Space 32 Bit breit ist). In diesem Fall brauchten wir dafür die Optionen -t elf64-sparc -a sparc:v9.

Vielleicht beschweren Sie sich jetzt, daß dieser Aufruf-Trace keine interessanten Informationen enthält; die SPARC-Prozessoren speichern aber nicht den gesamten Aufruf-Trace auf dem Stack; die Register O7 und I7 enthalten die Anweisungszeiger der letzten beiden aufrufenden Funktionen, weswegen diese hier in der Nähe des Aufruf-Traces gezeigt werden. In diesem Falle befand sich die fehlerhafte Anweisung in einer Funktion, die von sys_write aufgerufen wurde.

Beachten Sie, daß unabhängig von der Plattform und der Architektur das verwendete Format im disassemblierten Code immer das gleiche wie das von objdump verwendete ist. objdump ist ein hilfreiches Werkzeug; wenn Sie sich die gesamte fehlerhafte Funktion anschauen wollen, können Sie den Befehl objdump –d faulty.o verwenden (auch hier brauchen Sie für SPARC64 spezielle Optionen: —target elf64-sparc—architecture sparc:v9). Weitere Informationen zu objdump und den Kommandozeilenoptionen dieses Programms finden Sie in dessen Man-Page.

Man braucht ein wenig Übung sowie gewisse Kenntnisse über den verwendeten Prozessor und die Konventionen des verwendeten Assemblers, um Oops-Meldungen decodieren zu können, aber es lohnt sich. Die Zeit, diese Technik zu erlernen, macht sich schnell bezahlt. Selbst, wenn Sie sich vorher schon mit PC-Assembler auf Nicht-Unix-Betriebssystemen beschäftigt haben, müssen Sie einiges neu lernen, weil sich die Unix-Syntax von der Intel-Syntax unterscheidet. (Eine gute Beschreibung der Unterschiede finden Sie in der Info-Datei von as im Kapitel “I386-specific”.)

Das System bleibt stehen

Obwohl die meisten Fehler im Kernel-Code als Oops-Meldungen enden, können sie manchmal auch zu einem völligen Stillstand des Systems führen. Wenn das System stehenbleibt, wird auch keine Meldung ausgegeben. Wenn der Code beispielsweise in eine Endlosschleife gerät, hört der Kernel mit der Zuweisung des Prozessors an die Prozesse auf, und das System antwortet auf keinerlei Eingaben mehr, auch nicht auf die magische Kombination Strg-Alt-Entf. Sie haben zwei Möglichkeiten, mit stehengebliebenen Systemen umzugehen: Entweder Sie verhindern das Stehenbleiben von vornherein, oder Sie können den Fehler hinterher finden.

Sie können Endlosschleifen verhindern, indem Sie schedule-Aufrufe an strategischen Punkten einfügen. Wie Sie sich denken können, ruft schedule den Scheduler auf und ermöglicht es so anderen Prozessen, ebenfalls CPU-Zeit abzubekommen. Wenn ein Prozeß aufgrund eines Fehlers in Ihrem Treiber im Kernel-Space in einer Endlosschleife läuft, dann können Sie diesen Prozeß wegen der schedule-Aufrufe noch mit kill beenden — natürlich, nachdem Sie herausgefunden haben, was passiert ist.

Sie dürfen natürlich nicht vergessen, daß jeder schedule-Aufruf eine potentielle zusätzliche Quelle von reentranten Aufrufen Ihres Treibers ist, weil andere Prozesse ausgeführt werden können. Diese Reentranz sollte normalerweise kein Problem sein, wenn Sie die passenden Sperren in Ihrem Treiber verwenden. Stellen Sie aber sicher, daß Ihr Treiber nie schedule aufruft, wenn er über ein Spinlock verfügt.

Wenn Ihr Treiber wirklich das System zum Stillstand gebracht hat und Sie nicht wissen, wo Sie die schedule-Aufrufe einfügen sollen, dann ist es am besten, einige Ausgaben auf die Konsole einzufügen (indem Sie den console_loglevel-Wert verändern).

Manchmal scheint das System zu hängen, obwohl dies gar nicht der Fall ist. Das kann beispielsweise passieren, wenn die Tastatur auf irgendeine merkwürdige Art und Weise gesperrt bleibt. Dieses vermeintliche Hängenbleiben können Sie aufspüren, indem Sie die Ausgabe eines Programms beobachten, das Sie nur zu diesem Zweck betrachten. Eine Uhr oder eine Systemlastanzeige sind eine gute Statusanzeige; solange diese aktualisiert, arbeitet auch der Scheduler noch. Wenn Sie keinen grafischen Bildschirm verwenden, können Sie den Scheduler durch ein Programm kontrollieren, das die Tastatur-LEDs blinken, den Motor des Diskettenlaufwerks periodisch anlaufen oder den Lautsprecher knacken läßt — normale Beeps sind ziemlich lästig und sollten vermieden werden; benutzen Sie statt dessen den ioctl-Befehl KDMKTONE. Ein Beispielprogramm, das die Tastatur-LED wie einen Herzschlag aufblinken läßt, ist in misc-progs/heartbeat.c in den Quellen auf dem FTP-Server von O'Reilly zu finden.

Wenn Ihre Tastatur keine Eingaben entgegennimmt, melden Sie sich am besten über Ihr Netzwerk beim System an und beenden alle kritischen Prozesse oder setzen die Tastatur (mit kbd_mode –a) zurück. Festzustellen, daß es sich lediglich um eine aufgehängte Tastatur handelt, ist aber wenig nützlich, wenn Sie kein Netzwerk zur Verfügung haben. In diesem Fall könnten Sie ein alternatives Eingabegerät einrichten, um wenigstens das System sauber neu zu starten. Ein Herunterfahren und Neustarten ist eine geringere Belastung für Ihren Computer als der sogenannte große rote Knopf und erspart Ihnen das zeitraubende fsck-Überprüfen Ihrer Festplatten.

Ein solches alternatives Eingabegerät kann beispielsweise die Maus sein. Version 1.10 und neuere Versionen des gpm-Maus-Servers enthalten eine Kommandozeilenoption, die eine ähnliche Funktionalität einschaltet, aber nur im Textmodus funktioniert. Wenn Sie keine Netzwerkverbindung haben und im Grafikmodus arbeiten, empfehlen wir Ihnen, eine eigene Lösung zu verwenden; etwa einen Schalter, der mit dem DCD-Pin der seriellen Schnittstelle verbunden ist und ein Skript, das diesen Status abfragt.

Ein unersetzliches Werkzeug in diesen Situationen ist die “magische SysRq-Taste”, die in 2.2 und neueren Kerneln auf mehr Architekturen als früher zur Verfügung steht. Die magische SysRq-Taste wird durch eine Kombination der Tasten ALT und SysRq auf der PC-Tastatur oder durch die Tasten ALT und Stop auf SPARC-Tastaturen ausgelöst. Eine dritte Taste, die gemeinsam mit diesen beiden gedrückt wird, führt eine aus einer Reihe nützlicher Aktionen aus, die in der folgenden Liste angegeben sind:

r

Schaltet den rohen Tastaturmodus in Situationen aus, in denen Sie kbd_mode nicht verwenden können.

k

Ruft die “Secure Attention”-(SAK)-Funktion auf. SAK beendet alle Prozesse, die in der aktuellen Konsole laufen, und hinterläßt Ihnen so ein sauberes Terminal.

s

Führt eine Notsynchronisation aller Festplatten durch.

u

Versucht, alle Festplatten nur lesbar neu zu mounten. Diese Operation, die normalerweise direkt nach einem s verwendet wird, kann eine Menge Zeit zum Überprüfen der Dateisysteme sparen, wenn das System in ernsthaften Schwierigkeiten ist.

b

Startet das System unmittelbar neu. Achten Sie darauf, vorher die Festplatten zu synchronisieren und neu zu mounten.

p

Gibt die aktuellen Registerinformationen aus.

t

Gibt die aktuelle Task-Liste aus.

m

Gibt Speicherinformationen aus.

Es gibt noch weitere magische SysRq-Funktionen; die vollständige Liste finden Sie in der Datei sysrq.txt im Verzeichnis Documentation in den Kernel-Quellen. Beachten Sie, daß SysRq explizit in der Kernel-Konfiguration eingeschaltet sein muß und daß dies in den meisten Distributionen aus offensichtlichen Sicherheitsgründen nicht der Fall ist. Auf einem System, auf dem Treiber entwickelt werden, lohnt es sich aber, sich die Mühe zu machen, einen neuen Kernel zu kompilieren, um SysRq zu ermöglichen. SysRq muß dann zur Laufzeit mit einem Befehl wie dem folgenden eingeschaltet werden:

echo 1 > /proc/sys/kernel/sysrq

Eine weitere Vorsichtsmaßnahme beim Reproduzieren von Systemaufhängern ist das nur lesbare Mounten (oder Entmounten) aller Festplatten. Wenn die Festplatten nur zum Lesen oder gar nicht gemountet sind, besteht nicht die Gefahr, daß die Dateisysteme beschädigt werden oder in einen inkonsistenten Zustand geraten. Eine andere Möglichkeit ist es, einen Computer zu verwenden, der alle seine Dateisysteme über NFS, das Network File System, mountet. Dazu muß "NFS-Root" im Kernel eingeschaltet sein und es müssen spezielle Parameter beim Booten übergeben werden. In diesem Fall können Sie selbst ohne SysRq jede Beschädigung von Dateisystemen vermeiden, weil die Kohärenz des Dateisystems von dem NFS-Server sichergestellt wird, den Ihr Gerätetreiber nicht mit in den Abgrund reißt.