Aus Linux-Magazin 08/2003

Kernel- und Treiberprogrammierung mit dem künftigen Kernel 2.6

Die neue Serie "Kern-Technik" untersucht den kommenden Linux-Kernel 2.6 und seine Bestandteile. Wer Treiber programmieren, Kernelfunktionen verändern oder einfach die Vorgänge im Inneren von Linux verstehen will, der findet hier einen praktischen Einstieg.

Kernel- und Treiberprogrammierung ist das Thema der neuen Kern-Technik-Serie: Wie schreiben künftige Kernel-Gurus ihr erstes Modul, etwa einen zeichenorientierten Gerätetreiber für Kernel 2.6? Welche Anpassungen sind erforderlich, um einen 2.4-Treiber auf 2.6 zu portieren? Wie realisiert der neue Kernel Hardwarezugriffe? Und welche Werkzeuge und Techniken sind zur Treiberprogrammierung nötig?

An kurzen, abgeschlossenen Beispielen beschreiben die Artikel Technik, Hintergründe und Anwendung. Wer C kennt, mit Betriebssystemen vertraut ist und Basiskenntnisse über Computerhardware besitzt, kann sofort mit den aktuellen Kernelquellen loslegen. Die Struktur der Module, das Kernel-Build-System[3] sowie das Laden und Entladen von Modulen unter der Kernelgeneration 2.6 sind das Thema der ersten Folge.

Module sind die übliche Form bei Kernelerweiterungen. Sie lassen sich zur Laufzeit laden und wieder entladen. Der entscheidende Vorteil: Das Modul belegt – im Gegensatz zu einer statisch eingebundenen Komponente – nur dann kostbare Ressourcen im Kernel, wenn es unbedingt erforderlich ist.

Code im Kernel

Vorab eine Warnung: Die Beispiele in dieser Artikelserie sind Kernel-Code, also Code, der Zugriff auf sämtliche Systemressourcen hat. Anders als bei einer Anwendung ist es sehr einfach möglich, das gesamte System – nicht nur eine Applikation – zum Absturz zu bringen. Programmierer sollten daher sehr sorgfältig vorgehen, auf so manchen Reboot müssen sie sich dennoch einstellen.

Beim Programmieren sind kaum Unterschiede zwischen dem Entwickeln von Anwendungssoftware und Kernelerweiterungen (zum Beispiel Treiber) festzustellen. Bemerkenswert ist, dass der Entwickler kein eigenständiges Stück Software schreibt, sondern eine zusätzliche Komponente für ein bestehendes und sogar laufendes Programm, den Kernel. Da muss der schnelle Griff zu Funktionen aus der C-Standardbibliothek unterbleiben. Gleitkomma-Operationen sind generell tabu.

Die Kernelentwickler haben viel getan, um die Unterschiede zwischen Kernel- und Anwendungsprogrammierung unsichtbar werden zu lassen. Dass Funktionsaufrufe vielfach Makros sind oder sich dahinter gar Assembler-Code verbirgt, erfährt der Entwickler erst, wenn er in Kernelcode und Headerdateien stöbert. Zum Programmierstil sei auf das sehr lesenswerte Dokument “Linux kernel coding style”[4] verwiesen; es ist auch in den Kernelquellen unter »Documentation/CodingStyle« zu finden. Insbesondere der Anfang könnte dem einen oder anderen ein Lächeln entlocken. Dass im Kernel Goto-Aufrufe erlaubt und sogar erwünscht sind, mag befremdlich wirken – eine spätere Folge wird dazu mehr erklären.

Starthilfe

Um loslegen zu können, müssen ein aktueller Entwicklerkernel und neue Modutils installiert sein (siehe Kasten “Entwicklerkernel installieren”). Neben dem Kernelimage benötigt jeder Programmierer den zugehörigen Quellcode und – das ist bereits neu – auch die Kernelkonfiguration. Ein heikler Punkt, denn bisher genügten Headerdateien und ein selbst geschriebenes Makefile. Die Konfiguration ist der Preis, der für die außergewöhnliche Skalierbarkeit und Portierbarkeit zu zahlen ist: Der Compiler benötigt unterschiedliche Flags, abhängig davon,

  • für welche Plattform er den Kernel übersetzen
    soll,
  • ob der Kernel für eine Ein-Prozessor- oder eine
    Mehr-Prozessor-Maschine gedacht ist und
  • ob innerhalb des Kernels Unterbrechungen (Preemption)
    zugelassen sind.

Dieses Wissen ist im so genannten Kernel-Build-System hinterlegt. Diese in 2.5 komplett überarbeitete Systemkomponente ist für das Generieren des Kernels und seiner Module zuständig. Um sich darin einzuklinken, muss das Makefile den Pfad zu den konfigurierten Kernelquellen kennen. Linus Torvalds hat festgelegt, dass die Quellen über das Module-Verzeichnis zu finden sein sollen: »/lib/modules/ Version/build/«. Meist ist dies ein Symlink auf das bekannte Directory »/usr/src/linux- Version«.

Modul-Interna

Der Aufbau eines Moduls ist seit Kernel 2.4 im Wesentlichen unverändert. Der Entwickler muss nach wie vor je eine Funktion zur Modul-Initialisierung (mit Namen »init_module«) sowie eine zur Deinitialisierung (»cleanup_module«) schreiben. Beim Laden des Moduls mit »insmod« oder »modprobe« wird »init _module« aufgerufen.

War die Initialisierung erfolgreich, gibt die Funktion »0« zurück. Andernfalls returniert sie einen negativen Wert, beispielsweise »-EBUSY«. »EBUSY« ist eine von vielen Fehlerkonstanten, die in den Headerdateien »asm/errno.h« beziehungsweise »asm-generic/errno-base.h« spezifiziert sind. Die Funktion »cleanup_module« hat hingegen keinen Rückgabewert. Die Prototypen beider Funktionen befinden sich in der Headerdatei »linux/module.h«.

Für ein minimalistisches Modul (siehe Listing 1) fehlen nur noch eine Versionsinformation und die Lizenzform. Um die Versionsnummer einzubinden, genügt es, die Headerdatei »linux/version.h« zu inkludieren. Die Modullizenz lässt sich mit dem Makro »MODULE_LICENSE« angeben, es nimmt den Namen der Lizenz als Parameter entgegen. Der Grund für diesen Schritt ist, dass der Linux-Kernel unter der GPL steht. Diese verlangt, dass der Entwickler bei erweiterten oder modifizierten Programmen auch den Quellcode veröffentlicht. Kernelmodule liegen dabei in einer Grauzone, schließlich muss man den Kernel für ein Modul nicht neu kompilieren.

Die Lizenzangabe im Modul soll hier Klarheit schaffen. Der Kernel wertet diese auch aus: Ein Modul, das unter der GPL oder der BSD-Lizenz steht, wird durch den Linux-Kernel respektive die Kernelentwickler uneingeschränkt unterstützt. Ein Modul mit proprietärer Lizenz markiert der Kernel dagegen als “tainted” (verdorben). In diesem Fall darf man bei der Fehlersuche nicht auf die Hilfe der Kernelhacker hoffen. Auch darf ein solches Modul nicht auf sämtliche, insbesondere nicht auf neuere Kernelfunktionen zugreifen.

Abbildung 1: Kernel 2.5.x zeigt bei »make xconfig« ein neues GUI. Es ist mit Qt realisiert und deutlich übersichtlicher als die bisherige Oberfläche.

Abbildung 1: Kernel 2.5.x zeigt bei »make xconfig« ein neues GUI. Es ist mit Qt realisiert und deutlich übersichtlicher als die bisherige Oberfläche.

Kernel-Build-System

Um das Modul zu übersetzen, benötigt das Kernel-Build-System den Namen der Quellcodedatei. Diesen Namen ermittelt es anhand der Variablen »obj-m« im Makefile. Wer mehrere Module übersetzen will, initialisiert diese Variable mit den Namen aller Module. Das Makefile muss exakt den Namen »Makefile« tragen, wobei strikt auf Groß- und Kleinschreibung zu achten ist. Zudem muss es im selben Verzeichnis wie der Quellcode liegen. Falls die Quelldatei »mod1.c« heißt, besteht das Makefile nur aus der folgenden Zeile:

obj-m   := mod1.o

Das Kernel-Build-System kann nun mit seiner Arbeit beginnen. Es lässt sich per Make-Kommando mit drei Parametern starten: dem Pfad zu den Kernelquellen, dem Verzeichnis mit dem Modulcode und dem Makefile sowie mit dem Target. Das Target entspricht dem Make-Kommando, das das Build-System ausführen soll, im vorliegenden Fall also »modules«. Für den Kernel 2.5.70 lautet der Aufruf:

make -C /lib/modules/2.5.70/build 
  SUBDIRS=`pwd` modules

Möglicherweise ist in dem Kommando noch der Pfad zum Kernelquellcode anzupassen. Zudem muss man Make von jenem Verzeichnis aus aufrufen, in dem sich das Makefile und der eigene Quellcode befinden. Wenn alles stimmt, flitzen jetzt viele Ausgaben über den Bildschirm.

Listing 1:
Minimalistisches Modul

01 #include <linux/version.h>
02 #include <linux/module.h>
03 
04 MODULE_LICENSE("GPL");
05 
06 int init_module(void)
07 {
08         return 0;
09 }
10 
11 void cleanup_module(void)
12 {
13         return;
14 }

Listing 2:
Makefile

01 ifneq ($(KERNELRELEASE),)
02 obj-m   := mod1.o
03 
04 else
05 KDIR    := /lib/modules/$(shell uname -r)/build
06 PWD     := $(shell pwd)
07 
08 all:
09         $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
10 endif

Makefile für Bequeme

Findige Programmierer geben sich mit einem derart langen Kommando nicht zufrieden, es jedes Mal eingeben zu müssen, nur um ein Modul zu generieren, ist unnötig umständlich. Listing 2 zeigt ein Makefile, das Abhilfe schafft. Damit genügt ein »make«-Aufruf ohne weitere Parameter.

Dieses Makefile bemerkt, ob es durch das Kernel-Build-System aufgerufen wurde oder direkt vom User. In letzterem Fall ist die Variable »KERNELRELEASE« nicht gesetzt, sodass der zweite Teil des Makefile aktiviert ist. Es feuert die Default-Regel mit Namen »all«, die mit den notwendigen Parametern das Build-System startet (Zeile 9). Dies lädt wiederum das Makefile, nur ist jetzt die Variable »KERNELRELEASE« nicht mehr leer, sondern mit einem Wert initialisiert. Folglich setzt das Makefile die Variable »obj-m« mit den Namen der zu generierenden Module.

Das Makefile in Listing 2 geht davon aus, dass der aktuelle Kernel auch jener Kernel ist, für den es das Modul erstellen soll. Es verwendet nämlich das Kommando »uname -r«, um die Kernelversion zu ermitteln, die es in den Pfad zu den Kernelquellen einsetzt. Sollten die Kernelquellen nicht unter »/lib/modules/ Kernelversion/build« zu finden sein, dann ist Zeile 5 anzupassen.

Das Kernel-Build-System ruft die notwendigen Werkzeuge auf, insbesondere den Compiler. Damit erzeugt es die Objektdatei »mod1.o« und linkt sie mit dem Kernelversions-File »init/vermagic .o« zum Modul »mod1.ko«. Die Zeit, in der Module die Erweiterung ».o« hatten, ist mit 2.5 vorbei. Ab jetzt lautet die Erweiterung ».ko« für Kernel Object.

Mit Root-Rechten lässt sich das neue Modul wie gewohnt laden: »insmod mod1.ko«. Diese Operation kann nur gelingen, wenn die Entwicklerversion des Kernels aktiv ist. Schlägt das Kommando fehl, gibt Insmod eine entsprechende Fehlermeldung aus. Bei Erfolg sollte »lsmod« nun unter anderem das Modul »mod1« anzeigen:

Module       Size  Used by
mod1         1152  0
3c59x       34760  1

Die Liste wird in der Regel noch deutlich länger sein.

Entwicklerkernel
installieren

Alle nötigen Werkzeuge, im Wesentlichen Compiler und Make, sind in den meisten Distributionen bereits enthalten. Der Entwickler muss nur sicherstellen, dass er die richtigen Versionen benutzt; genaue Angaben stehen in den Kernelquellen bei »Documentation/Changes«. Beim Compiler sollte es nach offiziellen Angaben GCC 2.95.3 sein. Torvalds gibt auch für Make eine Versionsnummer vor: GNU Make 3.78. Meist sind neuere Versionen unkritisch, nur die neuesten Releases sind gelegentlich ungeeignet. Bei den Autoren haben sich GCC 3.2.3 und GNU Make 3.80 bewährt.

Alles neu: Das Kernel-Build-System

Der Quellcode des aktuellen Linux-Kerns steht auf[1] bereit, im Folgenden dient 2.5.70[2] als Beispiel. Um aus 32 MByte Archiv knapp 220 MByte Quellcode in »/usr/src/linux-2.5.70« abzulegen, benötigt der Rechner einige Zeit.

cd /usr/src
tar xvfj /Downloads/linux-2.5.70.tar.bz2

Trotz komplett überarbeiteter Makefiles (das neue Build-System) lässt sich der Kernel wie üblich über »make menuconfig« oder »make xconfig« konfigurieren. Letzteres benötigt jetzt auch Qt (Abbildung 1) – je nach installierter Compilerversion kann das durchaus eine Stolperfalle sein. Nur wenn die Qt-Bibliotheken mit einer passenden Compilerversion übersetzt wurden, lässt sich das Programm »qconf« generieren. Grund ist eine geänderte Namenscodierung für C++-Methoden.

Trotz der vielen neuen Optionen ist die Konfiguration übersichtlicher geworden. Da bei einem Entwicklerkernel oft einige Subsysteme nicht stabil arbeiten, ist man gut beraten, anfangs nur die wirklich nötigen Komponenten auszuwählen.

Geduld beim Übersetzen

Der Aufruf von »make bzImage« startet wie gewohnt den Compile-Vorgang. Jetzt ist Geduld angesagt, denn selbst bei einem schnellen Rechner dürften gut fünf Minuten vergehen, bis der Kernel generiert ist. Bricht der Compile-Vorgang mit einem Fehler ab, ist sehr wahrscheinlich eine einzelne Komponente der Auslöser. Die einfachste Lösung ist es, sie in der Konfiguration zu deaktivieren. Wer eine andere Ursache vermutet, sollte einen Blick in die »README«-Datei und in »Documentation/Changes« werfen.

Nach dem Generieren ist der Kernel nach bewährtem Muster zu installieren, etwa durch Kopieren in das Verzeichnis »/boot«. Danach gilt es, die Konfiguration des Bootmanagers (hier Lilo) anzupassen:

cp arch/i386/boot/bzImage /boot/linux-2.5.70
vi lilo.conf
lilo

Im Anschluss kommen die Module an die Reihe, generieren und installieren:

make modules
make modules_install

Der wohl schwierigste Teil ist das Installieren der neuen Modutils. Kernel 2.5 und damit auch Kernel 2.6 benötigen eigene Versionen, die auf der Kernel-Webseite von Rusty Russell[5] zu holen sind. Zwei Dinge sind zu beachten: Die neuen Versionen sollen die alten nicht ersetzen, sondern zusätzlich installiert werden. Außerdem hat sich das Format der Konfigurationsdatei »modules.conf« geändert.

Neue Modutils nötig

Wie die neuen Modutils (etwa »module-init-tools-0.9.12.tar.bz2«) zu installieren sind, ist in der »README«-Datei gut beschrieben. Per Default landen sie in »/usr/local/sbin«; das zweite Kommando erzeugt in diesem Verzeichnis auch Symlinks zu den Originalversionen:

./configure
make links
make
make install

./generate-modprobe.conf /etc/modprobe.conf

Wer das Device-Filesystem (Devfs) verwendet, sollte die Datei »modprobe.devfs« aus den Modutils-Init-Tools in das Verzeichnis »/etc« kopieren.

Abbildung 2: Kernelmodule aus Version 2.6 lassen sich nicht in Kernel 2.4 laden. Wer es trotzdem versucht, stößt auf die abgebildete Warnung.

Abbildung 2: Kernelmodule aus Version 2.6 lassen sich nicht in Kernel 2.4 laden. Wer es trotzdem versucht, stößt auf die abgebildete Warnung.

Abbildung 3: Weil das Makefile den falschen Namen trägt, wird es vom Kernel-Build-System nicht gefunden.

Abbildung 3: Weil das Makefile den falschen Namen trägt, wird es vom Kernel-Build-System nicht gefunden.

Bessere Vorlage

Das minimalistische Modul aus Listing 1 ist kaum als Basis für weitere Entwicklungen geeignet. Gerade Entwickler von eingebetteten Systemen benötigen Kernelerweiterungen, beispielsweise Treiber, die direkt in den Kernel integriert sind und nicht erst zur Laufzeit geladen werden. Diese so genannten Kerneltreiber unterscheiden sich von den Modultreibern im Wesentlichen durch unterschiedliche Namenskonventionen für die Initialisierungs- und Deinitialisierungsfunktionen. Während die Namen bei Modultreibern festgelegt sind, kann sie der Programmierer eines Kerneltreibers frei wählen.

Diese Verschiedenheit lässt sich durch zwei Makros aufheben. Der Entwickler gibt beiden Funktionen einen beliebigen Namen und ruft – meist am Ende des Moduls – das Makro »module_init« auf, dem er den Namen der Initialisierungsfunktion übergibt. Ebenso verfährt er mit dem Makro »module_exit« für die Deinitialisierungsfunktion. Listing 3 zeigt ein Gerüst, das sich als Basis für Kernelerweiterungen eignet.

Das Codestück verwendet zwei neue Direktiven: »__init« und »__exit«. Diese kernelspezifischen Schlüsselwörter sind in der Headerdatei »linux/init.h« definiert. Sie optimieren den Speicherverbrauch, indem sie dem Compiler zeigen, dass es sich um Initialisierungs- oder Deinitialisierungs-Code handelt. Der Initialisierungs-Code wird genau ein Mal durchlaufen, danach nie wieder. Es wäre Ressourcenverschwendung, diesen Programmabschnitt die ganze Laufzeit über im Speicher zu halten. Zumindest bei Kerneltreibern entfernt Linux diese Teile, sobald sie abgearbeitet sind.

Soviel zum Einstieg in den neuen Linux- Kernel. Die nächste Folge wird zeichenorientierte Gerätetreiber unter Kernel 2.5 respektive 2.6 vorstellen. (fjl)

Listing 3:
Code-Gerüst

01 #include <linux/version.h>
02 #include <linux/module.h>
03 #include <linux/init.h>
04 
05 MODULE_LICENSE("GPL");
06 
07 static int __init ModInit(void)
08 {
09         return 0;
10 }
11 
12 static void __exit ModExit(void)
13 {
14         return;
15 }
16 
17 module_init( ModInit );
18 module_exit( ModExit );

Infos:

[1] Kernelquellen: [http://www.kernel.org]

[2] Kernel 2.5.70: [http://www.kernel.org/pub/linux/kernel/v2.5/linux-2.5.70.tar.bz2]

[3] Kernel-Build-System: [http://lxr.linux.no/source/Documentation/kbuild/?v=2.5.56]

[4] Linux kernel coding style: [http://lxr.linux.no/source/Documentation/CodingStyle?v=2.5.56]

[5] Neue Modutils: [http://www.kernel.org/pub/linux/kernel/people/rusty/modules/]

[6] Weitere Infos siehe Kernel-Schwerpunkt im Linux-Magazin 4/03

Die
Autoren

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, gehören seit den Anfängen von Linux zu den Fans von Open Source.

Erste
Hilfe

Scheitert das Generieren oder Laden des Moduls, dann ist Fehlersuche angesagt – die folgenden Hinweise helfen dabei, die Ursache einzugrenzen. Ein Fehler kann sich allerdings auf verschiedenen Plattformen unterschiedlich auswirken.

Symptom: »mod1.ko: couldn\’t find the kernel version the module was compiled for« (siehe Abbildung 2). Fehler: Es wurde versucht, ein Modul eines Kernels der nächsten Generation unter einem stabilen Kernel (zum Beispiel 2.4.20) zu laden. Abhilfe: Booten Sie einen Entwicklerkernel.

Symptom: »/tmp/modultest/Makefile: No such file or directory« (siehe Abbildung 3). Fehler: »Makefile« ist falsch geschrieben. Abhilfe: Korrigieren Sie den Namen des Makefile. Der erste Buchstabe muss groß geschrieben werden, die weiteren Buchstaben klein.

Symptom: »insmod: QM_MODULES: Function not implemented«. Fehler: Sie haben versucht, das Modul mit einer alten Version von »insmod« zu laden. Abhilfe: Installieren Sie die neue Version der Modutils (siehe Kasten “Entwicklerkernel installieren”).

Symptom: Im Syslog taucht die Meldung auf: »mod1: no version magic, tainting kernel«. Fehler: Es handelt sich nur um eine Warnung, der Kernel hat das Modul dennoch geladen. Er konnte aber die Version nicht prüfen, da er das Modul nicht als »mod1.ko«, sondern als »mod1.o« erhielt. Abhilfe: Laden Sie das Modul »mod1.ko«.

Symptom: Im Syslog taucht die Meldung auf: »mod1: version magic \’2.5.70 preempt PENTIUMII gcc-2.95\’ should be \’2.5.70 preempt PENTIUMII gcc-3.2\’«. Fehler: Hierbei handelt es sich um eine Warnung, der Kernel hat das Modul dennoch geladen. Der Kernel ist mit GCC 3.2, das Modul jedoch mit GCC 2.95 übersetzt worden. Abhilfe: Installieren Sie GCC 3.2 als Default-Compiler.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben