Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 45

Kern-Technik

,

Wenn sonst nichts mehr hilft, sorgen automatisierte Neustarts für kurze Downtimes. Um Watchdogs optimal zu nutzen, brauchen Entwickler Kenntnisse über Aufbau und Anwendung der Treiber.

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.

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.

Tabelle 1:
IO-Controls in »linux/watchdog.h«

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.

Listing 1: Applikation zum
Triggern des Watchdog

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, "

Listing 2: Unterstützte
Treiberfunktionen

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)â&euro;"<â&euro;"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.

Tabelle 2:
Watchdog-Settings

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.

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).

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).

Listing 3:
»watchdog_template.c«

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)

Eigene Treiber
schreiben

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.

Infos

[1] Christer Weingel, "Linux Watchdog Driver API": Linux-Kernel-Dokumentation, [http://lxr.linux.no/linux/Documentation/watchdog/watchdog-api.txt]

[2] Oliver Frommel, Jens-Christoph Brendel, "Dynamisches Gerätemanagement mit Udev": Linux-Magazin 09/06, S. 32

Die Autoren

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.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook