Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 53

Kern-Technik

,

Der Kthreadd ist der Vater aller Kernelthreads. Die von ihm abgeleiteten Codesequenzen erben einen eindeutigen Kontext. Für Entwickler von Vorteil: weniger Arbeit und weniger Fehler im Code.

Kernelthreads haben einen festen Platz im Betriebssystemkern. Ein Ubuntu 10.04 beispielsweise aktiviert - je nach Rechnerhardware - zwischen 50 und 80 derartiger Verarbeitungssequenzen. Sie sind unabhängig vom Userland, Linux weckt sie nur bei Bedarf auf und sie belasten das System kaum. Sie sind effizient und dank der Realzeit-Eigenschaften des Linux-Kernels nur mit geringen Latenzzeiten behaftet. Daher setzen die Kernelhacker sie ein, um Jobs zwischen Rechnerkernen zu verschieben, regelmäßig Daten des Pagecache auf die Festplatte zu sichern oder um Daten zu ver- beziehungsweise zu entschlüsseln.

Fußgängern droht Stolpergefahr

Da die rudimentäre Variante, einen Thread durch Aufruf der Funktion »kernel_thread()« zu erzeugen, fehlerträchtig ist, wie der Kasten "Kernelthreads" beschreibt, stellt Linux mit dem im Kernel laufenden Kthreadd ein risikolos verwendbares Framework bereit. Es sorgt nämlich anders als ein schlichter neuer Thread für eine eindeutige Erbmasse: Die vom Kthreadd erzeugten Threads belegen keine Ressourcen im Userspace, öffnen keine Dateien, blockieren alle Signale, dürfen auf jeder CPU eines Multicore rechnen und besitzen anfangs eine eindeutige Priorität.

Kernelthreads

Kernelthreads sind unabhängige Codesequenzen, die der Scheduler wie normale Applikations-Threads aktiviert und die CPU abarbeitet. Folglich besitzen sie einen eigenen Prozess-Kontrollblock (siehe Kasten "Prozess-Kontrollblock") und eine eigene PID.

Sie unterscheiden sich von normalen Applikations-Threads dadurch, dass Code und Daten im Kernelspace - und eben nicht im Userspace wie bei den Benutzerprogrammen - liegen. Außerdem wird ihre Abarbeitung im Kernel nur dann unterbrochen, wenn höher priore Aufgaben anliegen.

Kernelthreads erzeugt der Programmierer durch Aufruf der Funktion »int kernel_thread(int (*fn)(void*), void *arg, unsigned long flags)«. Das Argument »fn« bezeichnet die Adresse der im Rahmen des Thread zu startenden Funktion und »arg« ein Argument, das ihr der Kernel beim Starten übergibt. Der Parameter »flags« steuert die Thread-Erzeugung über verschiedene, in der Include-Datei »linux/sched.h« definierte Konstanten. Bei Kernelthreads verwenden Entwickler dazu typischerweise die Kombination aus »CLONE_FS«, »CLONE_FILES« und »CLONE_SIGHAND«, um sich der nicht mehr benötigten Filesystem-Information zu entledigen und zunächst die gleichen Filedeskriptoren und Signalhandler des "Erzeugers" (Elternprozess) zu übernehmen.

Innerhalb der Threadfunktion geben Programmierer durch Aufruf der Funktion »daemonize()« zum einen die geerbten Ressourcen im Userland frei (Code- und Datensegmente), zum anderen geben sie dem Kernelthread einen Namen. Verantwortungsbewusste Entwickler legen ihre Threads regelmäßig schlafen, damit der Scheduler auch gleich oder niedriger priorisierte Prozesse auswählen darf.

Schließlich verhindern ein Completion-Objekt und der Aufruf der Funktion »complete_and_exit()«, dass Linux den Threadcode entlädt, bevor der Thread sich beendet. Unterlässt der Programmierer das oder sorgt darüber hinaus nicht dafür, dass der neue Kernelthread eine eindeutige Priorität besitzt und auf allen Rechnerkernen einer Mehrprozessormaschine arbeiten darf, sind Fehlersituationen programmiert!

Daher delegieren clevere Entwickler die Aufgabe, einen neuen Thread zu starten, an die Komponente, die etwas davon versteht: den Kthreadd. Der Kernel aktiviert ihn als Vater aller Kernelthreads direkt nach dem Start des Init-Prozesses. Folglich besitzt der Threadverteiler auch die PID 2 (Abbildung 1). Er wartet auf Aufträge, neue Prozesse zu erzeugen und mit einem Namen zu versehen, um sie dann umgehend ausführen zu lassen.

Abbildung 1: Linux startet den Kthreadd als zweiten Prozess. Von ihm leitet das Betriebssystem die meisten übrigen Kernelthreads ab (im Bild alle nachfolgenden).

Abbildung 1: Linux startet den Kthreadd als zweiten Prozess. Von ihm leitet das Betriebssystem die meisten übrigen Kernelthreads ab (im Bild alle nachfolgenden).

Der Programmierer meldet dem Kthreadd drei Werte: erstens die Adresse der im Kontext des Thread abzuarbeitenden Funktion, zweitens ein Argument, das der Daemon der Funktion beim Aufruf übergibt, und drittens den neuen Threadnamen. Das zweite Argument ermöglicht es dem Programmierer, Threadfunktionen mehrfach zu verwenden: Er identifiziert damit die jeweilige Instanz.

Innerhalb der gewählten Funktion sind die für Kernelthreads üblichen Einschränkungen zu beachten: Der Programmierer gibt daher nur die Signale frei, die zu einer Unterbrechung führen dürfen. Sind Threads einmal aktiv, werden nur noch Geschwister sie unterbrechen, die eine höhere Priorität genießen. Daher verhält sich der Entwickler auch bei den über den Kthreadd erzeugten Threads kooperativ und legt seine Codesequenzen regelmäßig schlafen (siehe Abbildung 2). So hat jeder Thread eine Chance auf die CPU. Nach dem Aufwachen prüft er, ob ein Signal den Job geweckt hat. War dies der Fall, behandelt er das Signal und beendet normalerweise den Thread.

Abbildung 2: Der Funktionscode wertet »kthread_should_stop()« aus, um sich bei Signalen zu beenden.

Abbildung 2: Der Funktionscode wertet »kthread_should_stop()« aus, um sich bei Signalen zu beenden.

Reine Kernelthreads

Falls ein Kernelmodul den Thread startet, fängt der Programmierer noch eine Race Condition ab: Auf keinen Fall darf die Bearbeitungssequenz aktiv sein, wenn ein Anwender das Modul entlädt. Vorher beendet es seinen Thread beispielsweise mit Hilfe eines Signals und wartet auf eine Vollzugsmeldung. Dabei hilft der im Kasten "Completion-Objekt" beschriebene Mechanismus.

Completion-Objekt

Das Completion-Objekt »struct completion« synchronisiert einen Ablauf mit dem Ende einer Codesequenz und realisiert die beiden Methoden »void complete(struct completion *)« und »void wait_for_completion(struct completion *)«. Die erste setzt der Entwickler an das Ende des zu überwachenden Abschnitts, die zweite Methode ruft der wartende Kernelthread oder die wartende Treiberfunktion auf.

Die Variante »complete_and_exit()« der Methode »complete()« setzen Entwickler bei Kernelthreads ein. Das Ende mehrerer Codesequenzen zeigt »void complete_all(struct completion *)« an.

Zu »wait_for_completion()« gibt es drei Varianten: »unsigned long wait_for_completion_timeout(struct completion *, unsigned long timeout)«, »int wait_for_completion_interruptible(struct com-pletion *)« und »unsigned long wait_for_completion_interruptible_timeout(struct completion *, unsigned long timeout)«. Dazu kommen noch Makros und Funktionen, die das Objekt initialisieren. Das Makro »DECLARE_COMPLETION(struct completion *name)« legt für das Objekt statischen Speicher unter »name« an. Initialisiert der Programmierer ein Completion-Objekt zur Laufzeit, reserviert er erst den Speicher und übergibt dann die Adresse von »void init_completion(struct completion *)«.

Technisch steckt hinter dem Completion-Objekt eine Integer-Variable (Abbildung 6), die die Funktion »complete()« beim Aufruf inkrementiert. Die Funktion »wait_for_completion()« legt den Thread schlafen, wenn die Variable bereits 0 ist. Andernfalls dekrementiert sie die überwachende Variable.

Abbildung 6: Vereinfacht dargestellt schläft der Thread, der auf das Ende einer Codesequenz wartet, bis die Variable »done« positiv ist.

Abbildung 6: Vereinfacht dargestellt schläft der Thread, der auf das Ende einer Codesequenz wartet, bis die Variable »done« positiv ist.

Aktiviert der Kernel zum Beispiel erst einen Thread, der wegen »wait_for_completion()« auf das Ende einer anderen Codesequenz wartet, ist die Variable »done« noch 0. Folglich legt der Kernel den Thread schlafen. Er wird durch Aufruf von »complete()« wieder aufgeweckt. Ist die Reihenfolge jedoch andersrum - erst wird die Codesequenz mit »complete()« aufgerufen -, dann ist der Wert der Variablen »done« 1. Der später kommende Thread legt sich nicht schlafen, sondern dekrementiert nur »done«, sodass die Variable wieder den Wert 0 hat.

Der Trick mit der Variablen ermöglicht es, mit einem Objekt auch auf das Ende mehrerer Codesequenzen auf einmal zu warten. Die Funktionen, auf die das Objekt warten soll, führen jeweils am Ende »complete()« aus, während die abwartende Instanz mehrfach die Funktion »wait_for_completion()« aufruft.

Ein Programmierinterface macht die Auftragsvergabe leicht. Es besteht aus den in Tabelle 1 gelisteten Funktionen. Um einen neuen Kernelthread zu erzeugen und gleichzeitig auch zu aktivieren, wählt der Programmierer das Makro »kthread_run()«. Es bekommt mindestens drei Parameter übergeben.

Tabelle 1:
Funktionsübersicht

 

Funktion

Beschreibung

struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data, const char namefmt[], ...)

Erzeugt einen Kernelthread, zum Starten ist "wake_up_process()"
aufzurufen.

struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data, const char namefmt[], ...)

Makro. Erzeugt und startet einen Kernelthread.

int kthread_should_stop(struct task_struct *k);

Das Makro überprüft das per "kthread_stop()" gesetzte
Flag.

int kthread_stop(struct task_struct *k);

Ein gestarteter, aber noch nicht aktiverter Kernelthread wird
beendet, ohne dass die Threadfunktion abgearbeitet wird.

int kthread_bind(struct task_struct *k, unsigned int cpu);

Der Kernelthread "k" wird nur auf den in der Maske "cpu"
angegebenen Kernen abgearbeitet.

int wake_up_process(struct task_struct *process);

Aktiviert den Thread "process".

Mit dem ersten Parameter nennt er die Adresse der Funktion, die Linux im Rahmen des Thread ausführen soll. Zweitens übergibt er einen Parameter, den das Makro seinerseits der Threadfunktion überantwortet. Drittens schließlich spezifiziert er den Namen des Thread. Ihn gibt er - wie bei »printf()« - in Form eines Formatstrings mit nachfolgenden Parametern an: Die Funktion hat also tatsächlich eine variable Anzahl von Argumenten (Listing 1, Zeile 31).

Listing 1:
»kthread.c«

01 #include <linux/module.h>
02 #include <linux/kthread.h>
03 
04 static struct task_struct *thread_id;
05 static wait_queue_head_t wq;
06 static DECLARE_COMPLETION(on_exit);
07 
08 static int thread_code(void *data)
09 {
10    unsigned long timeout;
11    int i;
12 
13    allow_signal(SIGTERM);
14    for (i = 0; i < 5; i++) {
15       timeout = HZ; // wait 1 second
16       timeout = wait_event_interruptible_timeout(wq,
17                             (timeout == 0), timeout);
18       printk("thread_function: woke up ... %ldn",
19              timeout);
20       if (timeout==-ERESTARTSYS) {
21          printk("got signal, breakn");
22          break;
23       }
24    }
25    complete_and_exit(&on_exit, 0);
26 }
27 
28 static int __init kthread_init(void)
29 {
30    init_waitqueue_head(&wq);
31    thread_id = kthread_create(thread_code, NULL,
32                               "mykthread%d", 0);
33    if (thread_id == 0)
34       return -EIO;
35    wake_up_process(thread_id);
36    return 0;
37 }
38 
39 static void __exit kthread_exit(void)
40 {
41    kill_pid(task_pid(thread_id), SIGTERM, 1);
42    wait_for_completion(&on_exit);
43 }
44 
45 module_init(kthread_init);
46 module_exit(kthread_exit);
47 
48 MODULE_LICENSE("GPL");

Die eigentliche Funktion für das Anlegen des Kernelthread als Kindprozess des Kthreadd ist allerdings »kthread_create()«. Die Parameter sind identisch zum Makro »kthread_run()«, unterschiedlich ist allein, dass der Thread nur angelegt und die eigentliche Threadfunktion (produktiver Code) nicht direkt gestartet (ausgeführt) wird.

Diesen Artikel als PDF kaufen

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