Aus Linux-Magazin 08/2013

Kernel- und Treiberprogrammierung mit dem Linux-Kernel – Folge 69

© psdesign1, Fotolia

Wer den Minicomputer Raspberry Pi mit eigenen Kernelmodulen und Treibern voll ausschöpfen möchte, braucht erst einmal einen Eigenbau-Kernel. Mit dem passenden Quelltext und ein paar Kommandos ist der einfach zu bauen – auf dem PC oder direkt auf dem Pi.

Theoretisch ist es unerheblich, für welche Plattform man Kernelcode entwickelt, ob für ein x86-basiertes PC-System, ein Board mit ARM-Prozessor, Power PC oder einem der vielen anderen Prozessortypen, auf denen Linux lauffähig ist. In der Praxis jedoch sind herkömmliche PC-Systeme durch ausgereifte Distributionen wie Ubuntu besser unterstützt und die Hardware ist zudem deutlich leistungsfähiger als die eines Embedded-Geräts – wenngleich nicht so stromsparend und effizient. Eingebetteten Systemen wiederum fehlt häufig ein Monitorausgang oder auch ein Tastatureingang; Speicherplatz und Rechenleistung stehen nur in begrenztem Umfang zur Verfügung.

In einem solchen Fall nutzt der Entwickler häufig den PC als Entwicklungsrechner und erzeugt darauf mit Hilfe von Cross-Development-Werkzeugen den Code für die Zielplattform.

Abgesehen von der nicht immer trivialen Installation der Entwicklungswerkzeuge muss er den Code nach dem Übersetzen auf das Target-System übertragen und dort testen – ein unter Umständen ebenfalls zeitaufwändiger Vorgang.

Bemerkenswerter Zwitter

Eine Ausnahme in der Embedded-Landschaft ist der Raspberry Pi. Der preiswerte Minicomputer ist mit HDMI-Interface und USB-Tastatur kaum von einem PC zu unterscheiden und spielt dank leistungsfähiger Grafik Videos sogar in Full-HD-Qualität ab. In puncto Rechenleistung hinkt er einem aktuellen PC jedoch signifikant hinterher. Mit dem Raspberry Pi ist damit eine normale Entwicklung zwar möglich, für umfangreichere Projekte empfiehlt sich aber auch hier die Cross-Entwicklung.

Ob cross-kompiliert oder nativ: Wer einen Gerätetreiber schreiben oder eigenen Kernelcode entwickeln möchte, muss im ersten Schritt einen Kernel kompilieren [2]. Das eröffnet zugleich die Möglichkeit, auf einen aktuellen Kernel aufzurüsten.

Aktueller Kernel mit Extras

Einfach, aber zeitaufwändig ist die Methode, den Kernel direkt auf dem Raspberry Pi zu generieren (siehe Abbildung 1a). Wie auf einem PC installiert der Anwender dafür die Kernelquellen, konfiguriert sie und stößt den Übersetzungsvorgang an. Allerdings ergeben sich im Detail Unterschiede. Geeignete Kernelquellen beispielsweise stammen nicht vom offiziellen Kernel.org, sondern aus einem besonderen Github-Repository [3]. Dort bietet die in England ansässige Raspberry Pi Foundation angepasste Kernelquellen an.

Abbildung 1: So baut man einen Kernel für den Raspberry Pi und installiert ihn auf dem Minicomputer: Direkt auf dem Gerät übersetzt (links) oder auf einem leistungsstarken Desktoprechner cross-kompiliert und mittels SD-Karte (Mitte) oder per Rsync (rechts) übertragen.

Abbildung 1: So baut man einen Kernel für den Raspberry Pi und installiert ihn auf dem Minicomputer: Direkt auf dem Gerät übersetzt (links) oder auf einem leistungsstarken Desktoprechner cross-kompiliert und mittels SD-Karte (Mitte) oder per Rsync (rechts) übertragen.

Der Ein-Platinen-Computer benötigt nämlich einige Treiber und Patches, die im Standardkernel nicht vorhanden sind. Zusätzlich befinden sich im Repository mehrere leicht anwendbare Default-Konfigurationen.

Git klont

Der Profi installiert die 1,4 GByte Kernelquellen per »git« (siehe Listing 1, Zeile 3) auf dem Raspberry Pi unter »/usr/src« , auf das nur der Superuser schreiben darf. Nach dem Klonen des Kernelrepository erfolgt die Konfiguration. Im Verzeichnis »/usr/src/linux« ruft der Entwickler dazu »make bcmrpi-defconfig« auf. Alternativ stehen die in Tabelle 1 aufgeführten Konfigurationen zur Verfügung. Per »make menuconfig« lassen sich die Konfigurationen noch anpassen, »make« startet schließlich die Generierung.

Tabelle 1

Vordefinierte Konfigurationen

bcmrpi_defconfig

Normale Konfiguration

bcmrpi_cutdown_defconfig

Auf die wesentlichen Funktionen reduziert: kein Debugging, Tracing oder Firewalling

bcmrpi_quick_defconfig

Schlanker Kernel, kaum Module, wenig Features

bcmrpi_emergency_defconfig

Schlanker Kernel für das Notsystem

Listing 1

Bauen auf dem Raspberry Pi

01 pi@raspberrypi ~ $ sudo su
02 root@raspberrypi:/home/pi# cd /usr/src
03 root@raspberrypi:/usr/src# git clone \
04   https://github.com/raspberrypi/linux.git\
05   -b rpi-3.9.y
06 root@raspberrypi:# cd linux
07 root@raspberrypi:# make bcmrpi-defconfig
08 root@raspberrypi:# make menuconfig
09 root@raspberrypi:# make
10 root@raspberrypi:# make modules_install
11 root@raspberrypi:# cp arch/arm/boota/zImage\
12    /boot/linux-3.9.y
13 root@raspberrypi:# echo "kernel=linux-3.9.y"\
14    >>config.txt

Es ist eine gute Idee, diesen Vorgang abends zu starten, dann sind Kernel und Module zum Frühstück fertig. Wie gesagt, diese Methode ist zeitaufwändig: Das Generieren von Kernel und Modulen dauert mehrere Stunden.

Die Module und die generierten Firmware-Dateien installiert man mit »make modules_install« . Bei der Installation des Kernels muss der Bastler etwas Handarbeit leisten. Er kopiert den Kernel in das Verzeichnis »/boot« unter dem Namen »linux-3.9.y« . Die Bootloader-Konfigurationsdatei lässt sich beispielsweise durch Anfügen einer Zeile mit

echo "kernel=linux-3.9.y" >>/boot/config.txt

anpassen. Dabei darf er beim Schlüsselwort »kernel« statt des Defaultnamens »kernel.img« auch einen alternativen Dateinamen für das Kernelimage verwenden. Beim nächsten Reboot startet der proprietäre Bootloader den Eigenbau-Kernel.

Auf der Überholspur

Wer ungeduldig ist oder mit der Kernelkonfiguration experimentieren möchte, generiert den Kernel per Cross-Compile auf einem PC. Dank leistungsfähiger PC-Hardware mit mehreren Prozessorkernen kann man die Generierungszeit deutlich verkürzen, im Idealfall dauert der Vorgang nur noch einige Minuten.

Für dieses Vorgehen installiert der Experimentator die Kernelquellen von Github auf dem Desktoprechner, etwa in dem zu erstellenden Verzeichnis »/usr/src/arm/« . Außerdem benötigt er einen geeigneten Cross-Compiler, der sich beispielsweise unter Ubuntu einfach per »apt-get« installieren lässt (Listing 2, Zeile 3). Zum Konfigurieren und Kompilieren ruft er »make« mit dem zusätzlichen Parameter »ARCH =arm« auf [4]. Der Übersetzungsvorgang benötigt außerdem noch den zusätzlichen Parameter »CROSS_COMPILE =arm-linux-gnueabi-« .

Listing 2

Befehlsfolge für Cross-Compile

01 user@host# mkdir /usr/src/arm
02 user@host# cd /usr/src/arm
03 user@host# apt-get install gcc-arm-linux-gnueabi ncurses-dev
04 user@host# git clone https://github.com/raspberrypi/linux.git
05 user@host# cd linux
06 user@host# git branch -a
07 user@host# git checkout -b rpi-3.9.y
08 user@host# make ARCH=arm bcmrpi-defconfig
09 user@host# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j6

Nun kommen die richtigen Cross-Compile-Werkzeuge dran, deren Namen sich aus dem im Parameter angegebenen Namensvorsatz »arm-linux-gnueabi-« und dem Werkzeugnamen ergeben. Der Name des Cross-Compilers lautet demnach »arm-linux-gnueabi-gcc« . Der in Listing 1, Zeile 9 sichtbare Parameter »-j6« teilt »make« übrigens mit, dass es alle sechs Kerne eines Multicore-Prozessors (Hexacore) verwenden soll.

Kopierarbeit

Ist das Übersetzen abgeschlossen, muss der Anwender Module, Firmwaredateien und den Kernel auf den Raspberry Pi kopieren. Dazu separiert er zunächst Module und Firmware von den Kernelquellen. Dazu gibt er dem Kommando »make modules_install« neben den Parametern »ARCH=arm« und »CROSS_COMPILE=arm-linux-gnueabi-« noch

INSTALL_MOD_PATH=/tmp/lib/

mit. Dann kopiert es die Dateien unterhalb des Verzeichnisses »/tmp/lib/« . Schiebt man die SD-Karte des Raspberry Pi in den Entwicklungsrechner (Abbildung 1b), mountet Ubuntu die Partitionen automatisch. Bei einem Raspbian der Version »2013-02-09-wheezy-raspbian.img« [5] findet sich das Bootverzeichnis beispielsweise unter »/media/C522-EA52« eingehängt, das Root-Filesystem steht unter »/media/62ba9ec9-47d9-4421-aaee-71dd6c0f3707« .

Listing 3 zeigt die Befehle, die den Kernel unter dem Namen »linux-3.9.y« auf der Bootpartition ablegen, die Bootkonfiguration um den erwähnten Parameter »kernel« erweitern und schließlich Module und Firmware auf der Rootpartition installieren. Die SD-Karte ist mit dem neuen Kernel präpariert, man muss sie jedoch vor dem Umstecken in den Raspberry Pi noch ordnungsgemäß mit »umount« aushängen.

Listing 3

Installation auf SD-Karte

01 cp arch/arm/boot/zImage /media/C5*/linux-3.9.y
02 echo "kernel=linux-3.9.y" >>/media/C5*/config.txt
03 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- \
04         INSTALL_MOD_PATH=/media/62ba*/lib/modules/ modules_install
05 unmount /media/C522-EA52
06 unmount /media/62ba9ec9-47d9-4421-aaee-71dd6c0f3707

Aus der Ferne

Ist der Raspberry Pi per Netzwerk erreichbar, kann die SD-Karte alternativ auch in dem Minicomputer stecken bleiben (Abbildung 1c). In diesem Fall loggt der Anwender sich auf dem Raspberry Pi ein, wird mit Hilfe des Kommandos »sudo su« Root, installiert »rsync« und kopiert damit Module, Firmware und schließlich noch den Kernel vom Entwicklungsrechner auf den Kleinstrechner (siehe Kasten “Mit Rsync kopieren”).

Mit Rsync kopieren

Mit dem Kommando »rsync« lassen sich effizient Daten lokal, aber vor allem auch über das Netzwerk kopieren beziehungsweise abgleichen. Effizient deshalb, weil »rsync« nur jene Daten transferiert, die im Zielverzeichnis noch nicht vorhanden sind oder sich geändert haben. Auch dabei überträgt es nicht zwangsläufig die ganze Datei, vielmehr kopiert »rsync« nur deren geänderte Teile.

Rsync folgt dem klassischen Schema für Kopierprogramme mit Angabe der Optionen, der Quelle und dem Ziel. Diese dürfen auf einem anderen Rechner liegen und werden in Form eines URI (Uniform Resource Identifier) angegeben. Bei einem Remote-Transfer kommt »ssh« zum Einsatz, und die Daten auf dem Raspberry Pi muss man als Root ablegen. Da der Superuser sich aber nicht remote einloggen darf, startet »rsync« am einfachsten auf dem Raspberry Pi. Dort kann der Anwender »rsync« ohne Weiteres als Root aufrufen und dann seinen normalen Loginnamen auf dem Entwicklungsrechner angeben.

Die wichtigste Option für »rsync« ist »-a« . Sie schaltet den Archiv-Modus ein, der gewährleistet, dass Unterverzeichnisse, symbolische Links, Datei-Attribute, Zugriffsrechte und Besitzverhältnisse übernommen werden. Alternativ kopiert »-r« lediglich rekursiv. Ein »-v« für “verbose” macht das Programm auskunftsfreudiger.

Dazu sind ein Loginname sowie der Name oder die IP-Adresse des Hostsystems erforderlich. Es ist sinnvoll, den Kernel im Verzeichnis »/boot/« mit dem Namen »linux-3.9.y« abzulegen. Natürlich ist dann auch dieser Dateiname in die Bootkonfiguration »config.txt« einzutragen (Listing 4, Zeile 6).

Listing 4

Netzwerkinstallation des Kernels

01 root@raspberry# apt-get install rsync
02 root@raspberry# scp\
03 Login@Hostname:/usr/src/arm/linux/arch/arm/boot/zImage\
04 /boot/linux-3.9.y
05 root@raspberry# rsync -av Login@Hostname:/tmp/lib/ /lib/
06 root@raspberry# echo "kernel=linux-3.9.y" >>/boot/config.txt

Umzug komplett

Ist der Kernel generiert, installiert und das System damit gebootet, kopiert der Anwender die cross-kompilierten Kernelquellen – rund 2,1 GByte – auf den Raspberry Pi. Bei einer SD-Karte mit nur 2 GByte Speicherplatz muss er allerdings etwas sparsamer vorgehen: Abbildung 2 zeigt die Kommandofolge, mit der er per »tar« und »find« die benötigten Dateien zusammenpackt, auf den Raspberry Pi verschiebt und dort wieder entpackt.

Abbildung 2: Relevanten Kernelquellcode kopieren – auf dem Entwicklungsrechner (oben) oder Raspberry Pi (unten).

Abbildung 2: Relevanten Kernelquellcode kopieren – auf dem Entwicklungsrechner (oben) oder Raspberry Pi (unten).

Kernelskripte übersetzen

Unabhängig davon, ob der Bastler den gesamten Quellcode oder nur relevante Auszüge kopiert hat, muss er anschließend noch die für den PC generierten, zum Kernel-Buildsystem gehörenden Hilfsprogramme (»scripts« ) auf dem ARM per »make scripts« im Quellcodeverzeichnis neu übersetzen. Danach kann er auf dem Minicomputer selbst Kernelcode entwickeln und testen.

Wer die Geschwindigkeitsvorteile des PC nutzen will, setzt bei der Kernelprogrammierung weiterhin auf Cross-Entwicklung. Damit erspart er sich auch das Kopieren des Kernel-Quellcodes. Allerdings ist hierfür ein angepasstes Makefile notwendig, das die Variablen »ARCH« und »CROSS_COMPILE« vorbelegt. Listing 5 zeigt ein solches Makefile, das durch den Aufruf des Linux-Kommandos »uname -m« erkennt, ob »make« auf dem Entwickler-Host oder auf dem Raspberry Pi gestartet wurde. Abhängig davon kompiliert es ein Kernelmodul (hier »hello.c« ) cross oder nativ.

Listing 5

Makefile für den Raspberry Pi

01 ARCH  := arm
02 UNAME := $(shell uname -m)
03 ifeq ($(UNAME),armv6l)
04         KDIR    := /usr/src/linux/
05 else
06         CROSS_COMPILE:=arm-linux-gnueabi-
07         KDIR    := /usr/src/linux/arm
08 endif
09
10 ifneq ($(KERNELRELEASE),)
11 obj-m    := gpio.o
12
13 else
14
15 default:
16         $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
17                 -C $(KDIR) M=$(PWD) modules
18 endif
19
20 clean:
21         rm -rf *.ko *.o .*.cmd .tmp_versions Module.symvers
22         rm -rf modules.order *.mod.c

Listing 6 zeigt den Code für einen einfachen Treiber zu Demonstrationszwecken, der beim Laden per »insmod« mit Hilfe von »udev« die Gerätedatei »/dev/hello« erzeugt. Ein Lesezugriff auf diese Datei – auf der Konsole beispielsweise mit »cat /dev/hello« – gibt den bekannten String “Hello World” zurück (siehe Abbildung 3). Den Treibercode lässt das flexible Makefile immer für die Plattform Raspberry Pi übersetzen.

Listing 6

Kernelmodul hello.c

01 #include <linux/module.h>
02 #include <linux/fs.h>
03 #include <linux/cdev.h>
04 #include <linux/device.h>
05 #include <asm/uaccess.h>
06
07 static char hello_world[]="Hello World\n";
08
09 static dev_t hello_dev_number;
10 static struct cdev *driver_object;
11 static struct class *hello_class;
12 static struct device *hello_dev;
13
14 static ssize_t driver_read(struct file *instanz,char __user *user,
15     size_t count, loff_t *offset )
16 {
17     unsigned long not_copied, to_copy;
18
19     to_copy = min( count, strlen(hello_world)+1 );
20     not_copied=copy_to_user(user,hello_world,to_copy);
21     return to_copy-not_copied;
22 }
23
24 static struct file_operations fops = {
25     .owner= THIS_MODULE,
26     .read= driver_read,
27 };
28
29 static int __init mod_init( void )
30 {
31     if( alloc_chrdev_region(&hello_dev_number,0,1,"Hello")<0 )
32         return -EIO;
33     driver_object = cdev_alloc(); /* Anmeldeobjekt reserv. */
34     if( driver_object==NULL )
35         goto free_device_number;
36     driver_object->owner = THIS_MODULE;
37     driver_object->ops = &fops;
38     if( cdev_add(driver_object,hello_dev_number,1) )
39         goto free_cdev;
40     /* Eintrag im Sysfs, damit Udev die Geraetedatei erzeugt */
41     hello_class = class_create( THIS_MODULE, "Hello" );
42     if( IS_ERR( hello_class ) ) {
43         pr_err( "hello: no udev support\n");
44         goto free_cdev;
45     }
46     hello_dev = device_create(hello_class,NULL,hello_dev_number,
47         NULL, "%s", "hello" );
48     dev_info( hello_dev, "mod_init called\n" );
49     return 0;
50 free_cdev:
51     kobject_put( &driver_object->kobj );
52 free_device_number:
53     unregister_chrdev_region( hello_dev_number, 1 );
54     return -EIO;
55 }
56
57 static void __exit mod_exit( void )
58 {
59     dev_info( hello_dev, "mod_exit called\n" );
60     /* Loeschen des Sysfs-Eintrags und damit der Geraetedatei */
61     device_destroy( hello_class, hello_dev_number );
62     class_destroy( hello_class );
63     /* Abmelden des Treibers */
64     cdev_del( driver_object );
65     unregister_chrdev_region( hello_dev_number, 1 );
66     return;
67 }
68
69 module_init( mod_init );
70 module_exit( mod_exit );
71 MODULE_LICENSE("GPL");
Abbildung 3: Sind die Kernelquellen installiert, kann der Anwender den ersten eigenen Treiber auf dem Raspberry Pi entwickeln.

Abbildung 3: Sind die Kernelquellen installiert, kann der Anwender den ersten eigenen Treiber auf dem Raspberry Pi entwickeln.

Ein simpler Treiber

Der Modulcode selbst weist keine Auffälligkeiten auf. Die Funktion »driver_init()« wird beim Laden des Moduls aufgerufen und meldet das Modul als Treiber beim Betriebssystem an. Dazu lässt es sich eine freie Gerätenummer geben. Durch die Integration im Sys-Filesystem legt Linux automatisiert eine Datei mit dieser Gerätenummer als Inhalt an. Udev tritt in Aktion und erstellt daraufhin die Gerätedatei im Verzeichnis »/dev/« . Die Funktionen »driver_open()« und »driver_close()« sind für dieses Beispiel nicht nötig.

Die Funktion »driver_read()« kommt zum Aufruf, wenn eine Anwendung – beispielsweise »cat« – die Gerätedatei »/dev/hello« öffnet und per »read« liest. Innerhalb der Lesefunktion stellt die »min« -Funktion sicher, dass das Betriebssystem nicht mehr Bytes in den Userspace kopiert, als Platz im Puffer ist. Die Daten – hier “Hello World” – kopiert »copy_to_user()« dann in den Puffer der Applikation, und die Anzahl der kopierten Bytes kommt als Returnwert zurück.

Ausbaufähig

Ob cross-kompiliert oder direkt auf dem Raspberry Pi übersetzt – mit dem selbst gebauten Kernel und einem geeigneten Makefile können Bastler eigenen Kernelcode und Treiber für den preisgünstigen kleinen Embedded-Rechner entwickeln. Das ist die Voraussetzung, um effizient mit den Allzweck-Kontaktstiften (General Purpose Input/Output, GPIO), die in Abbildung 4 vorne rechts zu sehen sind, auf externe Hardware zuzugreifen.

Abbildung 4: Programmierbar: Rechts vorne am Raspberry Pi sind die GPIO-Kontaktstifte zu sehen. Mit ihrer Hilfe können interessierte Hardware-Bastler eigenes Zubehör an den Minicomputer anschließen.

Abbildung 4: Programmierbar: Rechts vorne am Raspberry Pi sind die GPIO-Kontaktstifte zu sehen. Mit ihrer Hilfe können interessierte Hardware-Bastler eigenes Zubehör an den Minicomputer anschließen.

Eine Menge Zubehör

Mit Schaltern, verschiedenen Sensoren, Leuchtdioden und Schrittmotoren lassen sich dank GPIO spannende Hardware-Eigenbauprojekte umsetzen. Die fertigen Interfaces und Bibliotheken, die dafür im Internet zu finden sind, weisen aber Defizite auf. Abhilfe schafft die nächste Folge der Kern-Technik. (mhu)

69

Jubiläum

Mit der aktuellen Ausgabe geht diese Artikelreihe in ihr elftes Jahr. Die allererste Kern-Technik erschien im Linux-Magazin 08/03 [1] und gab eine Vorschau auf den damals angekündigten Kernel 2.6.

Infos

  1. Kern-Technik 1: https://www.linux-magazin.de/Ausgaben/2003/08/Kern-Technik
  2. “RPi Kernel Compilation”: http://elinux.org/RPi_Kernel_Compilation
  3. Kernelrepository der Raspberry Pi Foundation: https://github.com/raspberrypi/linux.git
  4. Quade, Kunst: Kern-Technik, Folge 59: Linux-Magazin 11/11, S. 96
  5. Raspbian: http://www.raspbian.org

Der Autor

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. In der Zwischenzeit ist die dritte Auflage ihres Buches “Linux Treiber entwickeln” erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
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