Sehr naive Anwender vertrauen dem Reset-Button, denn sie wissen: Ein Reboot macht alles wieder gut. So bringen sie ihre Systeme wieder in einen definierten Zustand. Auch wenn neugierige Linux-Admins Ursachenforschung dieser brachialen Methode vorziehen, hat sie Vorzüge für alle, die auf hohe Verfügbarkeit angewiesen sind: Hängt eine Anwendung, so zieht mancher Admin die kurze Downtime des Neustarts einer stockenden Anwendung vor. Besonders bei Embedded Systems ist das Chaos eines Kaltstarts schnell behoben.
Sofern ein Reboot also Heilwirkung hat, liegt es nahe, ihn auf kritischen Systemen automatisch auszulösen. Linux identifiziert mit Hilfe einer Watchdog-Applikation den undefinierten Zustand und startet neu. Wie ein Lokführer regelmäßig den Totmannschalter betätigt, meldet sich auch die Watchdog-Applikation regelmäßig beim Kernel. Bleibt die Meldung eine Zeit lang aus, startet Linux neu.
Programmierer setzen Watchdogs in der Informatik reichlich ein. Alles, was sie dazu benötigen, ist ein Rückwärtszähler. Wenn dieser bei null ankommt, leitet der Wachhund einen Neustart ein. Im günstigsten Fall stellt die Hardware so einen Timer zur Verfügung und verbindet ihn mit dem Reset-Schalter. Solch ein Gerät zählt selbst dann weiter, wenn der gesamte Kern - aus welchen Gründen auch immer - zum Stillstand gekommen ist. Läuft alles wie gewünscht, setzt eine Anwendung den Zähler rechtzeitig wieder zurück. Linux unterstützt Watchdogs seit ewigen Kernelgenerationen. Das Verzeichnis »drivers/watchdog« in den Kernelquellen enthält Code sowohl für Hard- als auch Software-Implementationen.
Harter Hund zählt zurück
Passende Hardware vorausgesetzt, laden die meisten Distributionen automatisch einen passenden Watchdog-Treiber. Bei halbwegs moderner Hardware, zum Beispiel Intels Centrino-Plattform, stehen die Chancen dafür gut. Falls die Hardware die Technik nicht unterstützt, springt der Software-Watchdog ein. Den dazu notwendigen Treiber lädt der Anwender mit »insmod softdog«. Den Zähler implementiert er als Timer im Kernel. Läuft er ab, ruft er eine Callback-Funktion auf, die den Rechner neu startet. Ein solcher Watchdog bringt allerdings nur Anwendungen zur Räson, die massiv andere Prozesse behindern - insbesondere die Watchdog-Applikation. Hängt der Kernel selbst, hilft dieser Ansatz nicht.
Sobald der Kernel das Character-Device »/dev/watchdog« anbietet, lassen sich seine Funktionen nutzen. Aufgepasst! Wer unbedacht mit Systemkommandos wie »cat« auf die Gerätedatei zugreift, aktiviert unversehens den Watchdog. Nach kurzer Wartezeit überrascht ein schwarzer Bildschirm alle, die ihn nicht rechtzeitig wieder deaktiviert haben.
Ansonsten ist der Einsatz von Watchdogs - egal für welchen Treiber - recht einfach. Abbildung 1 zeigt das Prinzip der Userland-Anwendung, Listing 1 setzt es um: Sobald das Programm »/dev/watchdog« sich per »open()« öffnet, ist der Wachhund scharf und lässt sich auch durch ein einfaches »close()« nicht ausschalten. Das Schreiben eines beliebigen Zeichens mittels »write()« auf die Gerätedatei setzt den zugehörigen Zähler zurück. Kommt dieser Aufruf nicht rechtzeitig, startet das System neu. Der Timeout beträgt in der Voreinstellung je nach Treiber zwischen 30 und 60 Sekunden.
Abbildung 1: Solange eine Anwendung noch in der Lage ist, regelmäßig ein vereinbartes Zeichen in die Watchdog-Gerätedatei zu schreiben, läuft Linux weiter. Bleibt das Signal aus, rebootet der Treiber.
Einige Treiber implementieren die Option »MAGICCLOSE«, um den Watchdog wieder zu deaktivieren. Ob das der Fall ist, findet der Anwender per IO-Control heraus. Er darf dann den Zeichencode für den Buchstaben »V« in die Gerätedatei schreiben. Die Wahl des Buchstabens ist rein willkürlich und hat keine weitere Bedeutung. Sobald die Applikation die Gerätedatei mittels »close()« schließt, deaktiviert Linux den Watchdog. Wer dieses Feature nicht wünscht, lädt den Treiber mit der Option »nowayout«. Dann bleibt der Watchdog bis zum nächsten Reboot unweigerlich aktiv.
IO-Controls justieren fein
Neben diesem intuitiven Interface dürfen sich Programmierer auch bestimmter IO-Controls bedienen, um Parameter wie den Timeout in Sekunden zu justieren. Ein modernes Proc- oder Sys-FS-Interface gibt es dazu leider nicht. Tabelle 1 listet die zur Verfügung stehenden IO-Controls auf. Die Kommandos »WDIOC_GETSUPPORT«, »WDIOC_SETTIMEOUT« und »WDIOC_GETTIMEOUT« kennen alle Treiber, die übrigen sind optional.
Listing 2 zeigt, wie Entwickler mit Hilfe von »WDIOC_GETSUPPORT« in Erfahrung bringen, welche Funktionalität zur Verfügung steht. Der Software-Watchdog-Treiber antwortet beispielsweise mit dem Hexcode 0x8180, aus dem sich wie in Abbildung 2 »WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE« dekodieren lässt. Achtung: Wer vor dem Aufruf der IO-Controls den Treiber öffnet, aktiviert damit auch den Watchdog selbst! Es gilt also, danach rechtzeitig den Zähler zurückzusetzen oder aber den Watchdog wieder zu deaktivieren, wie in den Zeilen 22 und 23 ausgeführt.
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <fcntl.h>
05
06 int main(void) {
07 int fd = open("/dev/watchdog", O_WRONLY);
08 if (fd < 0) {
09 perror("watchdog");
10 return -1;
11 }
12 while (write(fd, "
|
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <fcntl.h>
05 #include <sys/ioctl.h>
06 #include <linux/watchdog.h>
07
08 int main(void) {
09 struct watchdog_info wdinfo;
10 int fd = open("/dev/watchdog", O_WRONLY);
11
12 if (fd < 0) {
13 perror("wdinfo");
14 return -1;
15 }
16 if (ioctl(fd, WDIOC_GETSUPPORT, &wdinfo)â€"<â€"0)
17 return -1;
18 printf("options=0x%x version=0x%x "
19 "identity=%sn", wdinfo.options,
20 wdinfo.firmware_version,
21 wdinfo.identity);
22 write(fd, "V", 1); // Watchdog deaktivieren
23 return close(fd);
24 }
|
Die in Hardware ausgeführten Karten überwachen einerseits das System und starten es bei Bedarf neu, denn sie sind direkt mit der Resetleitung des Computers verbunden. Bei ihnen aktiviert der Anwender einfach per IO-Control die Funktion (Tabelle 2), danach hat der Kernel damit keine Arbeit mehr - außer die regelmäßigen Trigger an die Hardware weiterzuleiten. So können diese Geräte auch einen Neustart einleiten, wenn der Kernel selbst oder einige seiner Subsysteme nicht mehr funktionieren.
Abbildung 2 zeigt außerdem, dass manche Watchdog-Karten mehr als nur einen Countdown bieten. Einige überwachen die Betriebsspannung (»WDIOF_POWERUNDER«, »WDIOF_POWEROVER«), die Temperatur (»WDIOF_OVERHEAT«) oder den Lüfter (»WDIOF_FANFAULT«). Manche Geräte verfügen sogar über separate Event-Eingänge. Liegen auf diesen Eingängen definierte elektrische Signale an, führt das ebenfalls zu einem Reset. Solche leistungsfähige Hardware ist typischerweise in der Lage, die Ursache für einen erfolgten Neustart über den Reboot hinaus abzuspeichern. Per »WDIOC_GETBOOTSTATUS« erfährt der Programmierer den Grund, sobald das System wieder läuft.
Abbildung 2: Die Bits der Struktur »watchdog_info«, die der IO-Control »WDIOC_GETSUPPORT« liefert, kodieren die Ursache für einen Neustart und die durch den Watchdog angebotenen Features.
Ein Blick in die Quellen des Treibers schadet aber nicht, um die Verlässlichkeit dieser Informationen abzuschätzen: Ähnlich wie bei »WDIOC_GETSTATUS« implementieren zwar fast alle Treiber den IO-Control, geben aber nur 0 an die Applikation zurück. Übrigens: Wenn der Treiber mit »WDIOC_CARDRESET« antwortet, war der abgelaufene Watchdog-Timer selbst die Ursache des letzten Reboot. In der Theorie bietet der Linux-Watchdog-Treiber weitere Funktionalität [1]: Sofern die Hardware dies unterstützt, darf der Anwender einen Pretimeout einstellen. Gerät das System in einen ungesunden Zustand, löst es vor dem Reset einen Interrupt aus - zumeist einen nicht maskierbaren. Das tut er genau in dem zeitlichen Abstand des Pretimeout (Abbildung 3).
F Abbildung 3: Ist der Watchdog aktiviert, tickt die Uhr. Setzt der Anwender den Timer nicht rechtzeitig mit einem Trigger zurück, startet Linux neu. Vorher darf es noch letzte Maßnahmen treffen (Pretimeout).
001 #include <linux/module.h>
002 #include <linux/miscdevice.h>
003 #include <linux/watchdog.h>
004 #include <linux/fs.h>
005 #include <linux/reboot.h>
006 #include <asm/uaccess.h>
007
008 static int timeout = 60; /* in seconds */
009 static int nowayout = 0;
010 static char expect_close;
011 static unsigned long open_once=0;
012
013 /* Module parameter registration */
014 module_param(timeout, int, 0);
015 module_param(nowayout, int, 0);
016
017 /* Watchdog functions */
018 static void
019 watchdog_fire(unsigned long data) {
020 printk("reboot...n");
021 emergency_restart();
022 }
023
024 static int reset_watchdogtimer(void) {
025 printk("reset_watchdogtimer()n");
026 return 0;
027 }
028
029 static int
030 start_watchdogtimer(void) {
031 printk("start_watchdogtimer()n");
032 return 0;
033 }
034
035 static int
036 stop_watchdogtimer(void) {
037 printk("stop_watchdogtimer()n");
038 return 0;
039 }
040
041 /* File operations */
042 static int
043 driver_open(struct inode *inode,
044 struct file *file) {
045 if (test_and_set_bit(0, &open_once))
046 return -EBUSY;
047 start_watchdogtimer();
048 return nonseekable_open(inode, file);
049 }
050
051 static int
052 driver_release(struct inode *inode,
053 struct file *file) {
054 if (expect_close == 42) {
055 stop_watchdogtimer();
056 }
057 clear_bit(0, &open_once);
058 expect_close = 0;
059 return 0;
060 }
061 static ssize_t
062 driver_write(struct file *file,
063 const char __user *data,
064 size_t len, loff_t *ppos) {
065 if (len) {
066 if (!nowayout) {
067 size_t i;
068 expect_close = 0;
069
070 for (i = 0; i < len; i++) {
071 char c;
072 if (get_user(c, data + i))
073 return -EFAULT;
074 if (c == 'V')
075 expect_close = 42;
076 }
077 }
078 reset_watchdogtimer();
089 }
080 return len;
081 }
082
083 static long
084 driver_ioctl(struct file *file,
085 unsigned int cmd,
086 unsigned long arg) {
087 void __user *argp = (void __user *)arg;
088 int __user *p = argp;
089 static const struct
090 watchdog_info ident = {
091 .options = WDIOF_SETTIMEOUT |
092 WDIOF_KEEPALIVEPING |
093 WDIOF_MAGICCLOSE,
094 .firmware_version = 0,
095 .identity ="Watchdog-Template",
096 };
097 switch (cmd) {
098 case WDIOC_GETSUPPORT:
099 return copy_to_user(argp, &ident,
100 sizeof(ident)) ? -EFAULT : 0;
101 case WDIOC_GETSTATUS:
102 case WDIOC_GETBOOTSTATUS:
103 return put_user(0, p);
104 case WDIOC_KEEPALIVE:
105 reset_watchdogtimer();
106 return 0;
107 case WDIOC_SETTIMEOUT:
108 if (get_user(timeout, p))
109 return -EFAULT;
110 reset_watchdogtimer();
111 /* Fall */
112 case WDIOC_GETTIMEOUT:
113 return put_user(timeout, p);
114 default:
115 return -ENOTTY;
116 }
117 }
118
119 static int
120 reboot_callback(struct notifier_block *this,
121 unsigned long code,
122 void *unused) {
123 if (code == SYS_DOWN ||
124 code == SYS_HALT)
125 stop_watchdogtimer();
126 return NOTIFY_DONE;
127 }
128
129 static const struct file_operations
130 driver_fops = {
131 .owner = THIS_MODULE,
132 .write = driver_write,
133 .unlocked_ioctl = driver_ioctl,
134 .open = driver_open,
135 .release = driver_release,
136 };
137
138 static struct miscdevice
139 watchdog_miscdev = {
140 .minor = WATCHDOG_MINOR,
141 .name = "watchdog",
142 .fops = &driver_fops,
143 };
144
145 static struct notifier_block
146 reboot_notifier = {
147 .notifier_call = reboot_callback,
148 };
149
150 static int __init watchdog_init(void) {
151 int ret;
152
153 ret = misc_register(&watchdog_miscdev);
154 if (ret) {
155 printk("misc_register() "
156 "failed with %dn", ret);
157 return ret;
158 }
159 // init_watchdogtimer(watchdog_fire); ...
160 ret = register_reboot_notifier(
161 &reboot_notifier);
162 if (ret) {
163 misc_deregister(&watchdog_miscdev);
164 printk("register_reboot_notifier()"
165 " failedn");
166 return ret;
167 }
168 return 0;
169 }
170
171 static void __exit
172 watchdog_exit(void) {
173 unregister_reboot_notifier(
174 &reboot_notifier);
175 misc_deregister(&watchdog_miscdev);
176 }
177
178 module_init(watchdog_init);
179 module_exit(watchdog_exit);
180 MODULE_LICENSE("GPL");
|
Die Zeit bis zum tatsächlichen Neustart sollte das Betriebssystem nutzen, um wichtige Daten zu sichern und Applikationen gezielt herunterzufahren. Auch wenn das API dies vorsieht, implementiert gegenwärtig kein einziger Treiber im Standardkernel diese Funktion.
Aus diesem Grund hinterlässt das Subsystem ein zweigeteiltes Bild: Einerseits hat es eine sinnvolle, aber nur in Spezialfällen nutzbare Funktionalität. Andererseits fehlen eindeutig implementierte Rückgabewerte, gute Dokumentation und ein Proc-Interface. Immerhin ist das Datei-Interface so intuitiv und universell, dass sich jeder Programmierer schnell eine für ihn angepasste Watchdog-Applikation schreiben kann. (mg)
|
Ein Watchdog-Treiber im Kernel umfasst nur wenige Hundert Zeilen Code, ein Grundgerüst gibt Listing 3 vor. Für eine einfache Version sind nur die Funktionen »start_watchdogtimer()«, »stop_watchdogtimer()« und »reset_watchdogtimer()« zu implementieren (ab Zeile 24). Für einen Software-Watchdog implementiert der Kernelentwickler noch die Reboot-Funktion »watchdog_fire()«. Aufwändigere Treiber passen auch die File Operations an.
Das Modul meldet der Programmierer beim Laden in »watchdog_init()« bei Misc-Subsystem an (ab Zeile 150). So benötigt es keine eigene Major-Nummer. Der Entwickler übergibt dem Subsystem ein »struct miscdevice« und initialisiert es mit dem Namen »watchdog«. Es erzeugt die Gerätedatei im Verzeichnis »/dev« mit Hilfe von Udev [2] ohne weiteres Zutun. Wettrennen um den Shutdown
Um eine Race Condition beim normalen Reboot und aktiviertem Watchdog zu verhindern, deaktiviert sich der Treiber in diesem Fall automatisch, damit er nicht einen von ihm selbst ausgelösten Shutdown abbricht. Ohne rechtzeitigen Trigger, den er in »driver_write()« bearbeitet (ab Zeile 61), löst er aber einen Reset aus (Zeile 21). Dazu registriert er einen Reboot Notifier mit Hilfe der Kernelfunktion »register_reboot_notifier()« in Zeile 160. Die zugehörige Callback-Funktion deaktiviert den Watchdog ab Zeile 119. Um das Modul zu entladen, ruft der Kernel »watchdog_exit()« auf und bestellt alle Registrationen wieder ab (Zeile 171). Bitte nur einzeln eintreten
Öffnet die Watchdog-Applikation das zugehörige Device, ruft der Kernel »driver_open()« auf (ab Zeile 43). Die Funktion stellt mit Hilfe des Flag »open_once« sicher, dass nur eine Applikation den Watchdog bedient. Außerdem initialisiert und startet »driver_open()« den Rückwärtszähler. Da ein sauber programmierter Watchdog-Treiber einer Applikation nicht die Verwendung der Funktion »lseek()« erlaubt, konfiguriert der Programmierer durch »nonseekable_open()« die notwendigen Datei-Flags in Zeile 48.
Unterstützt der Treiber »WDIOC_MAGICCLOSE«, dann überprüft »driver_release()«, ob die Watchdog-Applikation vorher das magische Zeichen »V« geschrieben hat. Dies ist der Fall, wenn die Modul-globale Variable »expect_close« den Wert 42 hat. Dann stoppt der Rückwärtszähler. Ansonsten läuft er weiter und setzt den Kernel zurück, falls nicht zeitnah eine andere Watchdog-Applikation das Triggern übernimmt. Außerdem macht der Treiber in Zeile 57 den Weg frei für die nächste Applikation (»open_once«).
Der Treiber sollte minimal die IO-Controls »WDIOC_GETSUPPORT«, »WDIOC_SETTIMEOUT« und »WDIOC_GETTIMEOUT« unterstützen. Das implementiert der Modulhacker durch die Funktion »driver_ioctl()« ab Zeile 83. Die meisten Programmierer implementieren auch »WDIOC_GETSTATUS«, wobei das Modul bei Aufruf grundsätzlich 0 zurückgibt. Ein Hardware-Watchdog löst typischerweise einen Reset aus. Ist die Watchdog-Funktionalität in Software implementiert, leitet »watchdog_fire()« mit Hilfe der globalen Funktion »emergency_restart()« den Neustart ein. Vier globale Variablen speichern den Status
Der Treiber signalisiert durch »expect_close«, dass er den Watchdog deaktivieren will. Mit »open_once« verhindert er, dass zwei Applikationen parallel den Watchdog-Treiber triggern. Die maximale Zeit bis zum nächsten Trigger legt »timeout« fest, »nowayout« verfügt, dass niemand einen aktivierten Watchdog wieder deaktivieren darf. Das Makro »modul_param« markiert die letzten beiden Optionen als Übergabeparameter an das Modul.
|
|
Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. Unter dem Titel "Linux Treiber entwickeln" haben sie zusammen ein Buch zum Kernel 2.6 veröffentlicht.
|