Linux plattformübergreifend: Die praktische Skriptsammlung Buildroot erzeugt im Handumdrehen einen Kernel für Embedded-Systeme mit ARM-CPU. Als Nebenprodukt fällt dabei ein komplettes, lauffähiges Linux-System ab, das sich im Emulator Qemu testen lässt.
In den Anfangstagen von Linux war es nicht abzusehen, dass der ursprünglich ausschließlich für i386-CPUs entwickelte Betriebssystemkern einmal mehr als 20 verschiedene Prozessoren unterstützen würde. Heute gehen dank Smartphone- und Tablet-Boom täglich mehr Linux-Systeme auf einer ARM-basierten Plattform in Betrieb als auf klassischen, x86-basierten Desktop- oder Serversystemen.
Host und Target
Der herkömmliche PC dient jedoch weiterhin als Entwicklungsrechner (Host), um darauf die Systemsoftware, einen Kernel und ein Root-Dateisystem für das Ziel (Target), etwa ein Android-Smartphone, zu generieren. Für diese so genannte Cross-Entwicklung braucht man Compiler, Linker und Bibliotheken, die Code für den anderen Prozessortyp erzeugen können.
Root-Dateisystem
Für ein funktionstüchtiges Linux ist neben dem Kernel noch ein Root-Dateisystem erforderlich. Es enthält die zum Betrieb notwendigen Programme, Bibliotheken und Konfigurationsdateien. Buildroot erstellt das Root-Filesystem als eine Datei, die per Loop-Device gemountet wird. Dadurch lässt sie sich auch nachträglich beliebig modifizieren.
Die zentrale Komponente des von Buildroot erstellten Dateisystems bildet das Multicall-Binary Busybox. Es vereint in einem einzigen Executable eine Shell mit allen wichtigen Systemkommandos, Editor, Logging und – falls konfiguriert – sogar Web- und FTP-Server. Der Name Multicall-Binary rührt daher, dass dieses einzelne Executable mit Hilfe vieler symbolischer Links unter allen benötigten Programm- und Kommandonamen ansprechbar ist. Busybox erkennt beim Aufruf, unter welchem Namen es gestartet wurde, und nimmt dann wie ein Chamäleon die zugehörige Funktionalität an.
Kein Wunder, dass dieses Schweizer Taschenmesser auch das Init-Programm (System-V-Init), den ersten vom Kernel beim Booten gestarteten Prozess, im generierten Linux stellt. Die Konfiguration von Init befindet sich im Root-Dateisystem unter »/etc/inittab« , die zugehörigen Skripte, die zum Beispiel das Netzwerk konfigurieren, unter »/etc/init.d/« .
Als Gerätedateien finden sich typischerweise »/dev/console« , »/dev/null« und »/dev/zero« im Dateisystem, weitere Einträge lassen sich nach Bedarf anlegen. Außerdem braucht das Root-Filesystem noch ein temporäres Verzeichnis (»/tmp/« ) und ein Logverzeichnis (»/var/log/« ). Sinnvollerweise legt man zudem Mountpoints für die virtuellen Dateisysteme »/proc/« und »/sys/« an. Das Mounten selbst erledigt eines der per Init gestarteten Skripte.
Verantwortlich für das Bauen eines Kernels ist das zum Linux-Quellcode gehörende Kernel-Build-System. Es benötigt die üblichen Entwicklungswerkzeuge (unter Ubuntu die Pakete »ncurses-dev« und »build-essentials« ) sowie Cross-Compiler und Cross-Linker (in der Regel GCC und den GNU-Linker »ld« ) auf dem Entwicklungsrechner.
Dann kann das System mit Hilfe der Environment-Variablen beziehungsweise Aufrufparameter »ARCH« , »CROSS_COMPILE« und »O« den Code für die Zielplattform generieren. »ARCH« spezifiziert die Zielplattform – bei Kernel 3.0 stehen die in Abbildung 1 gelisteten Architekturen zur Auswahl. »CROSS_COMPILE« spezifiziert den Teilstring, den das Kernel-Build-System vor den Namen des Compilers und des Linkers einfügt.

Abbildung 1: Im Verzeichnis »arch« des Kernel-Quellcodes finden sich die von Linux unterstützten Architekturen.
Es liegt im Namen
Typischerweise haben die Cross-Entwicklungswerkzeuge einen Namen, der unter anderem auf die Zielplattform hinweist: »arm-linux-gcc« beispielsweise steht für einen Compiler (»gcc« ), der Code für einen ARM-Prozessor generiert. Der Namensbestandteil »CROSS_COMPILE=arm-linux-« etwa weist das Kernel-Build-System dazu an, eine Cross-Generierung mit diesem Werkzeug durchzuführen – vorausgesetzt die Pfadvariable schließt das Verzeichnis ein, in dem sich der gewünschte Compiler befindet.
Nicht unbedingt notwendig, aber hilfreich ist schließlich die Variable »O« , die das Ausgabeverzeichnis bestimmt. Damit ist es möglich, Konfigurations- und Generierungs-Informationen vom eigentlichen Quellcode des Kernels zu trennen. Liegen die Kernelquellen beispielsweise im Verzeichnis »/usr/src/linux-3.0/« und gibt es ein Verzeichnis »/usr/src/linux-3.0-arm/« , lässt sich der Kernel mit dem folgenden Befehl konfigurieren, wobei das GUI-Tool Menuconfig zum Einsatz kommt (siehe Abbildung 2):

Abbildung 2: Je nach Plattform zeigt Menuconfig bereits im Hauptmenü unterschiedliche Konfigurationsoptionen (links x86_64, rechts ARM).
cd /usr/src/linux-3.0/ make ARCH=arm CROSS_COMPILE=arm-linux- O=/usr/src/linux-3.0-arm menuconfig
Das nächste Kommando veranlasst anschließend die Übersetzung:
cd /usr/src/linux-3.0/ make ARCH=arm CROSS_COMPILE=arm-linux- O=/usr/src/linux-3.0-arm bzImage modules
Das Kernel-Build-System legt den übersetzten Kernel in »/usr/src/linux-3.0-arm/arch/arm/boot/zImage« ab.
Leider liegen auf dem Weg zum funktionierenden Kernel noch ein paar Stolpersteine aus, zunächst die korrekte Konfiguration: ARM-basierte Plattformen sind deutlich vielgestaltiger als die bekannte x86-Plattform (siehe Kasten “Advanced Risc Machine (ARM)”).
Advanced Risc Machine (ARM)
Eine der zurzeit wichtigsten Prozessorarchitekturen firmiert unter dem Namen Advanced Risc Machine (ARM). Von den im Jahr 2010 nach Schätzungen rund 13 Milliarden verkauften (Mikro-)Prozessoren verarbeiteten wohl beinahe 50 Prozent den ARM-Befehlssatz ([2], [3])Fast alle Android-Smartphones und Tablets oder auch Navigationsgeräte sind typischerweise mit mehr als einer ARM-CPU realisiert, ebenso Apples Mobilgeräte. Das liegt daran, dass diese Architektur hohe Leistung bei niedrigem Stromverbrauch bietet.
Die Entwicklung begann bereits 1983, der erste Prozessor stand 1987 mit dem ARM 2 zur Verfügung. Es handelte sich schon damals um einen 32-Bit-Prozessor, der sich mit erstaunlich wenigen Transistoren und damit Silizium bauen lässt. Bei der implementierten Risc-Architektur handelt es sich um eine so genannte 3-Register-Maschine, die das Verknüpfungsergebnis der Inhalte zweier Register in einem dritten Register ablegt.
Im Laufe der Zeit hat der Hersteller verschiedene Befehlssätze spezifiziert und implementiert, unter anderem mit Jazelle auch einen, der auf die Verarbeitung von Javacode spezialisiert ist. ARM-Prozessoren haben allerdings in Normalausführung keine Floating Point Unit.
Bei der Namensgebung der Prozessoren hat ARM den großen Vorbildern Intel und AMD nachgeeifert, sie ist – mit einem Wort gesagt – unübersichtlich. Es werden zunächst vier Architekturen (Befehlssätze) unterschieden: ARMv4, ARMv5, ARMv6 und ARMv7. Letztere ist aktuell. Darüber hinaus gibt es etwa sechs Familien: Arm7, Arm9, Arm11, Cortex R, Cortex M und Cortex A. Der Namensvorsatz Cortex scheint reines Marketing zu sein, M steht dagegen für Mikrocontroller, also eine CPU samt notwendigster Peripherie, R für Realtime und A für Application.
Außerdem werden noch Implementierungsvariationen an den Architekturnamen gehängt. T etwa steht für einen zusätzlichen, Speicherplatz sparenden Befehlssatz (Thumb), D für Hardware-technische Debugmöglichkeiten, M für Multiply-Enhanced und I für eine eingebettete ICE-Makrozelle.
In den aktuellen Topmodellen der Smartphones befindet sich als High-End-Prozessor ein Cortex A9. Diese Version ist mehrkernfähig. Typischerweise bauen die Hersteller damit Dualcore-Prozessoren auf, beispielsweise den in den aktuellen Tablets vorwiegend eingesetzten Tegra von Nvidia (Abbildung 4).
Besonders interessant ist, dass ARM selbst keine Prozessoren herstellt, sondern nur das Design erledigt. Die Firma verdient ihr Geld allein auf Basis von Lizenzen. Die Lizenznehmer, Qualcomm, Nvidia, Apple und diverse andere, nehmen das Basisdesign, erweitern oder modifizieren es und bauen dann die hauseigenen Prozessoren daraus. Ob ARM seine Vormachtstellung im Segment der Mobilgeräte halten kann, ist noch fraglich. Denn der Hunger nach Rechenleistung und Speicherausbau wird immer größer und ARM hat noch keine 64-Bit-Versionen angekündigt.
Unübersichtlich wie die Welt der ARM-Prozessoren ist auch der architekturspezifische Quellcode im Linux-Kernel. Linus Torvalds hat daher zu einer Aufräumaktion aufgerufen. Erste Patches sind in Kernelversion 3.0 eingeflossen.
Das Kernel-Build-System bietet zum Ausgleich immerhin eine Reihe von Default-Konfigurationen für verschiedenste ARM-Systeme an. Welche das sind, lässt sich mit Hilfe des Aufrufs
cd /usr/src/linux-3.0/ make help ARCH=arm
anzeigen und durch »make Default-Konfiguration« aktivieren. Die zweite Stolperfalle sind die Cross-Entwicklungswerkzeuge selbst. Auf eventuell vorhandene vorkompilierte Pakete der gerade verwendeten Distribution zurückzugreifen ist frustrierend. Ebenso wenig Erfolg versprechend ist es, Pakete für Compiler und Linker gemäß diverser Anleitungen aus dem Internet selbst zu generieren.
Lust statt Frust
Zum Glück gibt es mit dem Tool »buildroot« jedoch einen nahezu narrensicheren Weg, an eine funktionierende Werkzeugkette zu kommen. Mehr noch, quasi als Geschenk generiert Buildroot gleich den Kernel und erzeugt zudem noch ein Root-Dateisystem. Buildroot ist nämlich eine Sammlung von Skripten und Programmen, mit denen man auf sehr einfache Weise ein eigenes Linux-System für verschiedenste Zielplattformen konfigurieren und erzeugen kann.
Nach der Konfiguration lädt Buildroot die aktuellen Quellcode-Pakete sämtlicher Werkzeuge von deren Ursprungsserver, patcht diese wenn notwendig, generiert sie und benutzt sie, um damit das komplette Linux – einschließlich Bootloader, Kernel und Root-Dateisystem – anzufertigen (siehe Abbildung 3).
Buildroot wird vom Uclib-Projekt gehostet. Dieses Projekt ist für den Quellcode einer C-Bibliothek verantwortlich, die für den Einsatz in eingebetteten Systemen optimiert ist und auch in Buildroot zum Einsatz kommt. Von der Projektwebseite lässt sich das Buildroot-Paket herunterladen [1] und in einem Verzeichnis, beispielsweise dem Home Directory auspacken. Das alles und auch die weiteren Aktionen können unter einer normalen Benutzer-ID erfolgen; Superuser-Rechte sind nicht erforderlich.
Ein »make help« – im Hauptverzeichnis von »buildroot/« aufgerufen – listet unter anderem die unterstützten Plattformen auf, immerhin 16 in der Version »buildroot-2011.08« . Für die ersten Versuche bietet sich die Plattform »qemu_arm_versatile« an, denn damit lässt sich das erzeugte System mit Hilfe des Emulators Qemu gleich auf dem Entwicklungsrechner testen. Die passende Konfiguration erzeugt der Befehl »make qemu_arm_versatile_defconfig« .
Konfigurieren
Per »make menuconfig« lässt sich die voreingestellte Version anpassen. Interessant ist es, unter dem Punkt »Kernel | Kernel version« an Stelle von »2.6.38.8« als aktuelleren Kernel »3.0« einzustellen. Außerdem sollte man unter dem Menüpunkt »System Configuration | Port to run a getty (login prompt) on« an Stelle von »ttyAMA0« lieber »tty1« einstellen. Unter »Build options | Number of jobs to run simultaneously« lässt sich die Anzahl der Prozessoren angeben, die die Entwicklungsmaschine zur Verfügung stellt. Wer also beispielsweise einen Hexacore sein Eigen nennt, trägt hier »6« ein. Nach dem Speichern der Konfiguration startet das Kommando »make« den Vorgang der Generierung.
Als Erstes lädt Buildroot über das Internet die Quellcodepakete der benötigten Komponenten herunter. Diese landen im Buildroot-Unterverzeichnis »dl/« . Anschließend generiert Buildroot die Toolchain und mit deren Hilfe sowohl das Root-Filesystem als auch den Kernel. Jetzt ist Zeit für eine ausgiebige Kaffeepause, denn das Übersetzen aller Komponenten kann abhängig von der Leistungsstärke der eingesetzten Hardware ohne Weiteres eine Stunde in Anspruch nehmen. Alle Ergebnisse des Prozesses landen im Unterverzeichnis »output« – unter »output/images« erscheinen das Root-Filesystem (»rootfs.ext2« ) und der Kernel (»zImage« ).
Für einen Test, ob Kernel und Root-Dateisystem funktionstüchtig sind, ist ein Emulator nützlich, der mit Qemu aber nicht für alle Plattformen bereitsteht. Wichtig ist es, mindestens die Qemu-Version 14.0 zu verwenden, denn ältere Versionen verhindern in einigen Fällen einen korrekten Bootvorgang.
Das folgendem Kommando bootet das neue System in Qemu:
cd ~/buildroot-2011.08/ qemu-system-arm -M versatilepb -m 128 -kernel output/images/zImage output/images/rootfs.ext2 -append "root=/dev/sda"
Die Option »-M« spezifiziert die zu emulierende Hardwareplattform, »-m« die Größe des zur Verfügung stehenden Hauptspeichers (hier 128 MByte), die Angabe »-kernel« bestimmt den Kernel, »-append« die dem Kernel zu übergebenden Parameter. Hier ist es notwendig, dem Kernel zumindest das Gerät mitzuteilen, über das er auf die Rootpartition zugreifen soll.
Geht alles gut, erscheint ein neues Fenster mit einem Login. Als User »root« eingeloggt (siehe Abbildung 5), steht dem Anwender ein rudimentäres System im Konsolenmodus zur Verfügung. Dieses kann er per Buildroot-Konfiguration und Neugenerierung umfassend an die eigenen Bedürfnisse anpassen. Um beispielsweise den Kernel zu konfigurieren, gibt der Benutzer im Hauptverzeichnis von Buildroot das Kommando »make linux-menuconfig« ein. Viele weitere Möglichkeiten sind in der Buildroot-Dokumentation aufgeführt, die im HTML-Format im Unterverzeichnis »docs« von Buildroot liegt.
Wer Buildroot lediglich verwendet, um damit an Cross-Entwicklungswerkzeuge zu kommen, muss wissen, dass sich die gewünschten Programme nach einem Generierungslauf im Buildroot-Unterverzeichnis »output/host/usr/bin/« befinden. Entweder erweitert der Benutzer seine »PATH« -Variable um diesen Pfad oder er befüllt »CROSS_COMPILE« mit »/home/user/buildroot-2011.08/output/host/usr/bin/arm-linux-« . Um das Eingeben der Parameter »CROSS_COMPILE« und »ARCH« bei jedem Make-Aufruf zu vermeiden, kann man diese auch als Environment-Variable ablegen:
cd /usr/src/linux-3.0/ export CROSS_COMPILE=arm-linux- export ARCH=arm
Achtung: Diese Variablen sind für nachfolgend aufgerufene Programme nur in jener Shell sichtbar, in der sie auch exportiert wurden.
Weniger ist mehr
Um einen möglichst kompakten Kernel zu bekommen, ist es im Übrigen sinnvoll, von einer minimalen Konfiguration auszugehen. Dazu dient der Aufruf »make allnoconfig« . Anschließend lassen sich per »make menuconfig« genau jene Treiber und Subsysteme aktivieren, die benötigt beziehungsweise von der Hardware unterstützt werden. Ein weiterer Vorteil bei diesem Vorgehen ist die kurze Generierungszeit: Auf einem schnellen Rechner vergeht kaum eine Minute, bis ein solch kompakter Kernel entstanden ist, und selbst auf einem Netbook bleibt die Wartezeit erträglich.
Mein Linux
Dank Buildroot und der Emulatorsoftware Qemu lässt sich in kurzer Zeit ein eigenes Linux-System aufbauen, und das nicht nur für die bekannte x86-Plattform, sondern auch für ganz andere Architekturen. Das selbst kompilierte System bleibt überschaubar – wie in den Anfangstagen von Linux ist es möglich, jede installierte Komponente zu verstehen. Genau das Richtige also für den technikinteressierten Linux-Fan. (mhu)
Infos
- Download-Adresse von Buildroot: http://buildroot.uclibc.org/download.html
- Automotive Microcontrollers Market Report, August 2010: http://www.electronics.ca/publications/products/Automotive-Microcontrollers-Market-Report.html
- Heise Online, “ARM freut sich über kräftig sprudelnde Lizenzeinnahmen”, Oktober 2010: http://www.heise.de/ct/meldung/ARM-freut-sich-ueber-kraeftig-sprudelnde -Lizenzeinnahmen-1125958.html








