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.
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).
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.
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.
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
- Kern-Technik 1: https://www.linux-magazin.de/Ausgaben/2003/08/Kern-Technik
- “RPi Kernel Compilation”: http://elinux.org/RPi_Kernel_Compilation
- Kernelrepository der Raspberry Pi Foundation: https://github.com/raspberrypi/linux.git
- Quade, Kunst: Kern-Technik, Folge 59: Linux-Magazin 11/11, S. 96
- Raspbian: http://www.raspbian.org





