Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 11

Kern-Technik

,

Fertige Bibliotheken nehmen Programmierern Arbeit ab. Auch der Kernel bietet Hilfsfunktionen, obwohl er keinen Zugriff auf Libraries wie die Glibc hat. Von der Stringumwandlung bis zur Listenverwaltung findet sich allerlei Nützliches, das dieser Artikel erklärt und übersichtlich auflistet.

Wer ein Anwendungsprogramm schreibt, muss nicht jede Funktion neu erfinden. Ein- und Ausgabe, Zeichen umwandeln, Strings verarbeiten - solche Aufgaben werden in Funktionen ausgelagert und in Bibliotheken gesammelt. Im Kernel fällt die Unterscheidung zwischen System- und Bibliotheksfunktion schwer (siehe Abbildung 1 und Kasten "Bibliotheks- und Systemfunktionen im Kernel"): Es gibt dort keine Bibliotheken, alles ist Systemcode. Dennoch lassen sich auch im Kernel Systemdienste von Hilfsfunktionen unterscheiden.

Abbildung 1: Applikationen nutzen Bibliotheksfunktionen und Systemdienste; die Letzteren stellt der Betriebssystemkern zur Verfügung.

Abbildung 1: Applikationen nutzen Bibliotheksfunktionen und Systemdienste; die Letzteren stellt der Betriebssystemkern zur Verfügung.

Bibliotheks- und
Systemfunktionen im Kernel

Aus Sicht einer Linux-Anwendung ist die Abgrenzung einfach: Systemfunktionen oder auch Systemcalls arbeitet der Kernel im Auftrag einer Applikation im privilegierten Modus ab. Routinen, die im Kontext der eigenen Applikation ablaufen (User-Kontext), sind Bibliotheks- oder Hilfsfunktionen.

Mathematische Funktionen, beispielsweise »sin()« für Sinus, zählen also zu den Bibliotheksfunktionen. Eine Ausgabe per »write()« ruft dagegen über die Systembibliothek einen Systemcall auf, der im Kernelmodus abläuft, weil er nur so auf die (sonst geschützte) Hardware zugreifen darf (siehe Abbildung 1). Dieser einfachen Definition zufolge kann es im Betriebssystemkern keine Bibliotheksfunktionen geben - schließlich läuft hier jeder Code im Kernel ab.

Dennoch ist eine Differenzierung hinsichtlich des Funktionsablaufs möglich: Routinen, die auf interne Datenstrukturen zurückgreifen und diese gar verändern, sind Systemfunktionen. Routinen, die zustandslos arbeiten, werden hier Hilfsfunktionen genannt. Im Quellcode des Kernels findet sich mit dem Ordner »lib« sogar ein eigenes Verzeichnis dafür.

Hilfe ohne Bibliotheken

Obwohl der Kernel eine Reihe nützlicher Hilfsfunktionen mitbringt, gibt es keine Übersicht, die beschreibt, wie sie heißen und was sie tun. Reichhaltig ist die Auswahl zum Beispiel für die Stringformatierung, -verarbeitung und -konvertierung. Um Listen anzulegen und zu verändern, gibt es ebenfalls einen umfangreichen Funktionssatz. Daneben bietet der Kernel Funktionen für spezielle Aufgaben, etwa um große Bitfelder zu verarbeiten oder den Taktzyklenzähler (Time Stamp Counter) auszulesen. Für mathematische Operationen stellt der Kernel dagegen nur eine einzige Funktion zur Verfügung.

Viele Hilfsfunktionen stammen aus dem Bereich der Applikationen und sind den Programmierern meist prinzipiell bekannt. Manche Kernelvarianten unterscheiden sich von ihren Verwandten allerdings in Details: zum Beispiel hinsichtlich ihres Namens, aber auch bei den Parametern und ihrer Bedeutung.

Ausgabe über Syslog

Die Funktion »printf()« beispielsweise kommt im Kernel nur in abgewandelter Form vor und heißt dort »printk()«. Wie »printf()« ist sie für die Ausgabe in Ascii zuständig. Der Unterschied besteht aber nicht nur im Namen. So gibt es bei der Kernelversion keine Standardausgabe, wie sie »printf()« von Haus aus verwendet. Die Funktion »printk()« gibt die Daten mit dem Kernel-Log-Daemon »klogd« an den Syslog-Daemon »syslogd«, der sie in eine Logdatei schreibt.

Damit der Syslog-Daemon die Dringlichkeit der Nachricht einstufen kann, bietet »printk()« die Möglichkeit, so genannte Magics im Formatstring unterzubringen. Ein solches Magic besteht aus einer Ziffer von 1 (Emergence-Meldung) bis 7 (Debug-Meldung), die in spitzen Klammern steht. Es muss unbedingt als Erstes im Formatstring auftauchen. Eine Debugmeldung sieht damit beispielsweise so aus:

printk("<7>Irq=%dn", irq);


Natürlich muss sich der Programmierer nicht die Nummern merken, denn die Headerdatei »linux/kernel.h« definiert für jede Dringlichkeitsstufe einen symbolischen Namen. Damit lässt sich die Debug-Ausgabe auch folgendermaßen kodieren:

printk(KERN_DEBUG "Irq=%dn", irq);


Mit den Makros »pr_debug()« und »pr_info()« geht es noch einfacher (siehe Tabelle 1). Die Routine »pr_debug()« setzt nicht nur »KERN_DEBUG« vor den Formatstring, sondern schaltet die Debug-Meldungen ganz aus, wenn das Symbol »DEBUG« beim Kompilieren nicht definiert ist. Die obige Debug-Ausgabe lautet mit diesem Makro:

pr_debug("Irq=%dn", irq);


Die Funktion »pr_info()« unterscheidet sich von »pr_debug()« in der Dringlichkeit: Sie verwendet die nächstdringliche Stufe 6 für »Info«. Außerdem lässt sich ihre Ausgabe nicht durch den Compiler ausschalten.

Tabelle 1:
Ausgabefunktionen in »linux/kernel.h«

 

Name und Parameter

Aufgabe

int printk (const char *fmt, ...)

Formatierte Ausgabe über Syslog

int pr_debug (const char *fmt, ...)

Formatierte Debugausgabe

int pr_info (const char *fmt, ...)

Formatierte Ausgabe von normalen Infos

Sollen Ausgaben nur im Speicher und nicht direkt für den Syslog-Daemon aufbereitet werden, stehen dem Kernelprogrammierer die von der C-Bibliothek bekannten Funktionen »sprintf()«, »snprintf()«, »vsprintf()« und »vsnprintf()« zur Verfügung (siehe Tabelle 2). Deren Parameterliste und Wirkungsweise entsprechen genau den gleichnamigen Funktionen auf Anwendungsebene. Deshalb helfen bei Problemen die Manpages der Bibliotheksfunktionen weiter. Mittlerweile dürfte es sich herumgesprochen haben, dass die Varianten mit einem zusätzlichen n wie »snprintf()« und »vsnprintf()« den ursprünglichen Versionen aus Sicherheitsgründen vorzuziehen sind, um das Risiko für Buffer Overflows zu verkleinern. Allerdings verleiten auch diese Funktionen den Programmierer zu Fehlern, beispielsweise dann, wenn er mit mehreren Aufrufen von »snprintf()« in ein und denselben Puffer schreibt.

Tabelle 2:
Stringformatierung in »linux/kernel.h«

 

Name und Parameter

Aufgabe

int sprintf (char *buf, const char *fmt, ...)

Formatierte Ausgabe in »buf«

int snprintf (char *buf, size_t size, const char *fmt,
...)

Formatierte Ausgabe in »buf«, maximal
»size« Zeichen

int vsprintf (char *buf, const char *fmt, va_list args)

Formatierte Ausgabe in »buf«

int vsnprintf (char *buf, size_t size, const char *fmt, va_list
args)

Formatierte Ausgabe in »buf«, maximal
»size« Zeichen

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