Aus Linux-Magazin 09/2019

Ein Tool, das kundenspezifische Linux-Distributionen baut

© Konstantin Pelikh, 123RF

Yocto ist ein Werkzeug, mit dem sich individuell zugeschnittene Linux-Images für Embedded Devices herstellen lassen. Das schafft für den Anwender größere Spielräume, als vorgefertigte Distributionen dies könnten, allerdings um den Preis einer höheren Komplexität.

Yocto [1] versteht sich selbst als Distributionshersteller. Statt eine Distribution von der Stange zu verwenden, macht es das Tool für seine Anwender möglich, Images kundenspezifisch zuzuschneiden. Das Ergebnis, ein hochoptimiertes Image, erscheint besonders im Embedded-Umfeld verlockend, wo Hardware-Ressourcen beschränkt sind.

Dieser Artikel demonstriert das Erzeugen eines Image für den Raspberry Pi und erklärt, was bei Yocto anders als bei einer Standarddistribution läuft und warum.

Zu Beginn

Das Tool baut Images auf der Grundlage von Rezepten (Recipes) statt durch Herunterladen vorgefertigter Pakete. Dadurch lässt sich in allen Einzelheiten festlegen, wie die Pakete beschaffen sein sollen. Das geht bis hin zu der Frage, welcher Compiler für die Übersetzung des Quellcodes zu verwenden ist. Die Recipes fasst Yocto zu so genannten Layern zusammen.

Yocto bringt eine Referenzdistribution namens Poky mit, die viele vorgefertigte Recipes beinhaltet und auf Open Embedded [2] beruht. Wer den Poky-Layer mit einem Hardware-spezifischen Board-Support-Layer kombiniert, kann eine komplette Distribution zusammenbauen. Das Werkzeug dafür heißt »bitbake«.

Eine der Stärken von Yocto sind die vielen von vornherein verfügbaren Layer, darunter solche von den meisten Boardherstellern und auch von zahlreichen Open-Source-Projekten. Die Abbildung 1 zeigt den umfangreichen und durchsuchbaren Open Embedded Layer Index [3]. Daneben bringt Yocto auch ein hervorragendes Handbuch [4] mit, das alle Aspekte im Detail erklärt.

Abbildung 1: Der Open Embedded Layer Index.

Abbildung 1: Der Open Embedded Layer Index.

Das Ziel dieses Artikels ist es, ein Image für den Raspberry Pi zu bauen, das auf Poky basiert und zusätzlich ein Recipe aus einem kundenspezifischen Layer enthält. Damit wird zugleich die Methode demonstriert, mit der man Yocto durch eigene Layer seinen Bedürfnissen anpassen kann.

Ein Image-Bastler startet am besten mit dem Herunterladen von »poky« und »meta-raspberrypi« durch Klonen der jeweiligen Git-Repositories (Listing 1). Die Konvention dabei ist, alle Layer unterhalb von »poky« zu platzieren. Dabei ist darauf zu achten, immer den »Warrior«-Zweig aller Layer zu klonen. »Warrior« ist der Name der derzeit aktuellen Poky-Version. Die Namen der Layer beginnen normalerweise mit »Meta-«. Tatsächlich enthält Poky eine ganze Reihe solcher »Meta-*«-Verzeichnisse, besteht es doch aus mehreren Layern.

Listing 1

Klonen der Repositories

01 git clone -b warrior git://git.yoctoproject.org/ poky.git
02 cd poky
03 git clone -b warrior git://git.yoctoproject.org/meta-raspberrypi

Nach dem Klonen schafft sich der Anwender ein passendes Environment, indem er den Befehl

source oe-init-build-env

aufruft. Damit wird zugleich auch die »PATH«-Variable aktualisiert, sodass sich die Yocto-Tools von überallher aufrufen lassen. Außerdem legt der Befehl ein Verzeichnis »build« mit einem Unterverzeichnis »conf« an. Im letztgenannten Verzeichnis finden sich zwei interessante Dateien: »bblayers.conf«, eine Auflistung der verwendeten Layer, und »local.conf«, eine Anweisung für Yocto, was wie zu bauen ist.

In der »bblayers.conf«-Datei editiert der Anwender die Zeile mit »meta-yocto-bsp« und setzt dort den Speicherort des geklonten »meta-raspberrypi«-Layers ein. In »local.conf« fügt der Anwender ganz am Anfang die Zeile

MACHINE ?= "raspberrypi3"

ein. Sie sagt Yocto, für welche spezielle Maschine der Build sein soll. Die Namen der vom Raspberry-Pi-Layer unterstützten Maschinen finden sich unter [5].

Am Anfang minimal

Damit sind alle Vorkehrungen getroffen. Um zu überprüfen, ob alles korrekt eingestellt ist, gilt es zunächst, ein »core-image-minimal« zu bauen. Das gelingt mit folgendem Aufruf aus dem Buildverzeichnis heraus:

bitbake core-image-minimal

Vom Zeit- und Platzbedarf dieser Aktion sollte sich der Anwender nicht schockieren lassen. Für ein Yocto-Image wird nämlich alles neu erzeugt, was dafür nötig ist – und so wird dabei nicht nur eine kleine Linux-Distribution übersetzt, sondern Yocto baut auch den Compiler und weitere Tools selbst. Der nächste »build«-Aufruf arbeitet dann inkrementell, das heißt, die jetzt erzeugten Tools lassen sich wiederverwenden.

Der Grund dafür, alle verwendeten Komponenten neu zu kompilieren, liegt in der so garantierten binären Reproduzierbarkeit. Das bedeutet, dass weitere Versuche den identischen binären Output erzeugen. Wenn man es ganz streng nimmt, stimmt das allerdings nicht hundertprozentig, weil manches Paket zum Beispiel Zeitstempel enthält.

Yocto arbeitet stetig an einer Verbesserung der Reproduzierbarkeit. Damit soll sich ein bestimmtes Verhalten der Anwendung – das kann auch ein Fehler sein – wiederholen. So lässt sich der Fehler beheben, wobei die verbesserte Version immer noch auf dem alten Softwarestack beruht. Damit das klappt, sind ältere Versionen gewissenhaft zu archivieren.

Wenn der Buildvorgang abgeschlossen ist, lässt sich das »build«-Verzeichnis untersuchen. Dort finden sich nun vier neue Unterverzeichnisse: »cache«, »downloads«, »sstate-ache« und »tmp«. Das interessanteste davon ist »tmp«, in dem unter anderem das resultierende Image seinen Platz einnimmt. Unter »downloads« liegen heruntergeladene Files, und »sstate-cache« sowie »cache« verwendet der Buildprozess als Zwischenspeicher. In komplexeren Szenarien können sich auch mehrere Rechner »sstate-cache« teilen.

Das »tmp«-Directory hat seinerseits eine Reihe von Unterverzeichnissen, eines davon heißt »deploy«. Es enthält alle für die Plattform verwendeten Pakete und deren Lizenzen sowie das erzeugte Image. Im vorliegenden Fall findet sich dies unter »tmp/deploy/images/raspberrypi3/«. Das File selbst heißt »core-image-minimal-raspberrypi3.rpi-sdimg«. Es ist ein Link auf den letzten erfolgreichen Build.

Mit Hilfe des »dd«-Kommandos lässt sich das Image anschließend einfach auf eine SD-Karte übertragen. Dieser Vorgang ist allerdings insofern nicht ganz ungefährlich, als dass dabei das Kopierziel, das mit dem Parameter »of« an das Kommando zu übergeben ist, bedingungslos überschrieben wird. Der Anwender sollte sich daher doppelt vergewissern, dass hier bestimmt das richtige Gerät angegeben ist und nicht etwa ungewollt das Betriebssystem überschrieben wird.

Zu diesem Zweck kann der Anwender zum Beispiel nach Einsetzen der SD-Karte in den Ausgaben von »dmesg« nach Nachrichten des zuletzt hinzugefügten Geräts suchen, dessen Name bei dieser Gelegenheit in der Ausgabe auftaucht. Die resultierende Kommandozeile lautet dann etwa:

sudo dd if=core-image-minimal-raspberrypi3.rpi-sdimg of=/dev/mmcblk0 bs=1MB

Darin verweist »of« auf das Ausgabedevice und »if« auf das Eingabefile, »bs« ist die Blockgröße.

Nachdem die Micro-SD-Karte präpariert ist, kann der Anwender sie in einen Raspberry Pi einsetzen, Tastatur und Monitor anschließen und booten. Danach ist ein Login als Root ohne Passwort möglich. Wie der Name schon verrät, startet damit allerdings nur ein Minimalsystem.

Das Image anpassen

Das vorliegenden Image gilt es nun zu erweitern. Das ist ein dreistufiger Prozess: Zuerst wird ein neuer Layer erzeugt, danach ein Recipe für eine neu zu integrierende Anwendung und danach ein neues Image-Recipe.

Für den ersten Schritt, den neuen Layer, führt der Anwender im Unterverzeichnis Poky das folgende Kommando aus:

bitbake-layers create-layer meta-lm

Das erzeugt den neuen Layer mit einem Beispiel-Recipe, das noch nichts tut. Danach fügt der Anwender diesen Layer der Liste in »bblayers.conf« hinzu, was entweder manuell geschehen kann oder durch das Kommando »bitbake-layers«:

cd Path/to/Build
bitbake-layers add-layer ../meta-lm

Noch macht der neue Layer, wie gesagt, nichts. Um das zu ändern, muss der Anwender ein Recipe ergänzen. Im vorliegenden Fall soll das Rezept ein einfaches kleines C-Programm im Hello-World-Stil sein: »lm-hello.c« (Listing 2).

Listing 2

lm-hello.c

01 /*      HelloWorld.c    */
02
03 #include <stdio.h>
04
05 main()
06 {
07  printf("Hello Linux-Magazin\n");
08 }

Für das Hello-World-Programm ist anschließend ein Recipe zu erstellen. Dafür ist ein Verzeichnis »lm-hello« unter »recipes-lm« anzulegen und das Beispiel-Recipe zu entfernen. In das neue Verzeichnis »lm-hello« gehört eine Datei »lm-hello_1.0.bb« mit dem Inhalt aus Listing 3. Die Datei-Endung »bb« steht für Bitbake Recipe, an den Unterstrich im Dateinamen muss sich eine Versionsnummer anschließen, die später noch Verwendung findet, wenn »bitbake« nach dem Rezept ein Paket baut.

Listing 3

lm-hello_1.0.bb

01 SUMMARY = "Hello World program for Linux Magazin"
02 SECTION = "examples"
03 LICENSE = "GPLv2+"
04 LIC_FILES_CHKSUM = "file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263"
05
06 SRCREV = "6c2970ab5277e23f55de9853a38c6c173d0b04ee"
07 SRC_URI = "git://github.com/e8johan/lm-hello.git"
08
09 PV = "1.0+git${SRCPV}"
10
11 S = "${WORKDIR}/git/"
12
13 EXTRA_OEMAKE = "'CC=${CC}' 'CFLAGS=${CFLAGS}' 'LDFLAGS=${LDFLAGS}'"
14 TARGET_CC_ARCH += "${LDFLAGS}"
15
16 do_install() {
17     install -d ${D}${bindir}
18     install -m 0755 lm-hello ${D}${bindir}
19 }

Die Datei selbst besteht aus Variablendeklarationen und Funktionen. Die Variablen »SUMMARY« und »SECTION« fungieren als Metadaten, die den Inhalt beschreiben. Darauf folgen »LICENSE« und »LIC_FILES_CHKSUM«. Letztere beinhaltet eine MD5-Checksumme des Lizenzfiles, die sicherstellen soll, dass sich dessen Inhalt seit der Niederschrift des Recipe nicht geändert hat.

Es schließt sich ein Verweis auf den Sourcecode an, hier in Form eines URI (»SRC_URI«) und einer bestimmten Revision (»SRCREV«) eines Git-Repository. Auf diese Weise dürfen verschiedene Versionen eines Recipe existieren, die jeweils auf unterschiedliche Revisionen verweisen. Die Versionsnummer des Pakets gibt »PV« vor, in diesem Fall stammt sie aus der Versionskennung im Filenamen.

Die folgenden Zeilen gelten Einstellungen für den Buildprozess. Das »lm-hello«-Projekt basiert auf einem Makefile. Yocto kann dabei eine ganze Reihe von Buildsystemen verwenden, darunter Cmake, Autotools oder individuelle Files. Zu ihrer Unterstützung exportiert das File die Variablen »EXTRA_OEMAKE« und »TARGET_CC_ARCH«. Sie stellen sicher, dass der richtige Compiler mit den richtigen Flags zum Zuge kommt.

Immer wenn Bitbake ein Recipe abarbeitet, durchläuft es eine Reihe von Schritten. Das hier vorliegende Recipe kann für die meisten dieser Schritte Defaultwerte benutzen, außer für den Schritt Installation. Der braucht eine kundenspezifische Funktion, die zuerst ein »bin«-Verzeichnis (»${bindir}«) unterhalb der Stage Area (»${D}«) anlegt, in das sie dann das Binary von »lm-hello« installiert.

Sobald das Recipe vorbereitet ist, lässt es sich außerhalb eines Image mit »bitbake« testen:

cd Path/to/Build
bitbake lm-hello

Das Resultat ist ein Paket anstelle eines Image. Dieses Paket findet sich unter »build/tmp/deploy/rpm«.

Zwei Dinge sind hier besonders beachtenswert: Erstens produziert selbst das einfachste Rezept immer gleich drei Pakete: Ein Installationspaket ohne Anhängsel im Dateinamen, ein Development-Paket mit »dev« im Namen und ein Debug-Package, das mit »dbg« im Namen gekennzeichnet ist.

Zweitens finden sich die Pakete immer in einem CPU-spezifischen Unterverzeichnis von »build/tmp/deploy/rpm«, im Beispiel ist das »cortexa7t2hf_neon_vfpv4«, und nicht etwa in einem Unterverzeichnis, das nach dem Bord benannt ist. Das bedeutet, das Paket ist auf allen Rechnern mit der gleichen CPU verwendbar.

Ein neues Image erzeugen

Wer das »lm-hello«-Programm einem Image hinzuzufügen will, braucht ein Image-Recipe. Das ist eine andere Variante eines Bitbake-Recipe, die allerdings deutlich von der schon besprochenen Form abweicht.

Das Image-Recipe soll in diesem Fall »lm-image-minimal.bb« heißen und es muss unterhalb des »meta-lm«-Layers im Unterverzeichnis »recipes-lm/images« liegen:

cd Path/to/poky/meta-lm
mkdir -p recipes-lm/images
vim recipes-lm/images/lm-image-minimal.bb

Den Inhalt der Datei zeigt Listing 5. Das Recipe inkludiert das Recipe »core -image-minimal.bb«, das der Artikel anfangs verwendet hat. Es fügt dann eine weitere »DESCRIPTION« ein, bevor es das »lm-hello«-Paket zu der Variablen »IMAGE_INSTALL« hinzufügt, wodurch das Paket im Image landet.

Listing 4

Ausgefilterte Dateien

01 "lm-hello" [label="lm-hello\n:1.0+gitAUTOINC+6c2970ab52-r0\n/home/e8johan/work/linuxmagazine/yocto/build/poky/meta-lm/recipes-lm/lm-hello/lm-hello_1.0.bb"]
02 "lm-hello" -> "binutils-cross-arm"
03 "lm-hello" -> "dwarfsrcfiles-native"
04 "lm-hello" -> "gcc-cross-arm"
05 "lm-hello" -> "gcc-runtime"
06 "lm-hello" -> "glibc"
07 "lm-hello" -> "libgcc"
08 "lm-hello" -> "linux-libc-headers"
09 "lm-hello" -> "pseudo-native"
10 "lm-hello" -> "quilt-native"
11 "lm-hello" -> "rpm-native"

Listing 5

lm-image-minimal.bb

01 require recipes-core/images/core-image-minimal.bb
02 DESCRIPTION = "Minimal image for Linux Magazin"
03 IMAGE_INSTALL_append = " lm-hello"

Wie das Paket an »IMAGE_INSTALL« angehängt wird, ist eine der Besonderheiten von Yocto. In den meisten anderen Sprachen wäre es naheliegend, die Variable »lm-hello« zur Variablen »IMAGE_INSTALL« mit einem Operator wie »+=« oder »= … + lm-hello« hinzuzufügen. Doch Bitbake würde dadurch verwirrt. Es verlangt stattdessen nach einer zweiten Variablen mit dem Namenszusatz »_append«, deren Inhalt der gleichnamigen Variablen ohne Zusatz hinzugefügt wird. Wichtig ist hier auch das Leerzeichen vor dem Paketnamen. Das resultierende Image heißt dann »core-image-minimal-raspberrypi3.rpi-sdimg«.

Fazit

In diesem Beitrag kam es darauf an, Yocto als Werkzeug vorzustellen, mit dem sich kundenspezifische Distributionen bauen lassen, die weit mehr Freiheitsgrade nutzen, als das mit einer vorgefertigten Distribution möglich wäre, die sich bestenfalls um neue Pakete erweitern lässt. Diese zusätzlichen Möglichkeiten erkauft sich der Anwender allerdings auch mit einer deutlich vergrößerten Komplexität und mit erhöhtem Zeitbedarf.

Abhängigkeiten verstehen

Bei der Erstellung von Yocto-Images besteht eine der komplexeren Aufgaben darin, zu verstehen, wie einzelne Pakete voneinander abhängen. Es ist nicht ungewöhnlich, dass sich der Anwender mit einem Image wiederfindet, das unerwünschte Pakete enthält, und er dann zu verstehen versucht, warum diese Pakete dort zu finden sind.

Dazu muss er zuerst feststellen, welche Komponenten genau gebaut werden. Diese Information findet sich in der Manifest-Datei, die jedem Image beiliegt. Sie enthält eine Liste aller inkludierten Pakete nebst deren Versionsnummer und Angaben zur Architektur.

Für das Image »core-image-minimal-raspberrypi3.rpi-sdimg« steht dort beispielsweise die Datei »core-image-minimal-raspberrypi3.manifest«. In ihr ist auch eine Zeile für das »lm-hello«-Paket enthalten, und in dieser Zeile steht:

lm-hello cortexa7t2hf_neon_vfpv4 1.0+git0  +6c2970ab52

Im nächsten Schritt ist es nötig, festzustellen, wovon »lm-hello« abhängt. Das gibt das Bitbake-Kommando mit der Option »-g« preis:

bitbake -g lm-hello

Dieses Kommando erzeugt die beiden Dateien »task-depends.dot« und »recipe-depends.dot«. Die erste beschreibt, in welcher Reihenfolge Yocto die Bitbake-Aufgaben ausführt, während die zweite die Abhängigkeiten zwischen den Rezepten beschreibt. Die ».dot«-Dateien lassen sich zu PNGs rendern, sind aber nicht schnell lesbar. Für das Paket »lm-hello« gibt es mehr als 400 Abhängigkeiten. Wer die Übung für »lm-image-minimal« wiederholt, erhält mehr als 6000 Abhängigkeiten.

Der Nutzer kann in den ».dot«-Files nach Informationen suchen. Wer zum Beispiel alle Zeilen ausfiltert, die mit »lm-hello« beginnen, erhält ein Ergebnis wie in Listing 4. Daraus lässt sich ableiten , dass »lm-hello« von den rechts aufgeführten Paketen abhängt. Unter den übrigen Zeilen finden sich beispielsweise Header, die nicht deployt werden, ebenso wie die C-Bibliothek und andere Laufzeitabhängigkeiten des C-Programms.

Im nächsten Schritt geht es darum, zu verstehen, warum eine Abhängigkeit besteht. Und die hohe Schule wäre schließlich, unnötige oder unerwünschte Abhängigkeiten sauber zu entfernen. Das bedarf aber großer Erfahrung.

Der Autor

Johan Thelin arbeitet als Systemarchitekt. In den letzten 15 Jahren hat er Embedded-Systeme für verschiedene Branchen wie medizinische Laboratorien, Heimautomation, Sicherheit und Automotive entwickelt. Er schrieb außerdem ein Buch über die Grundlagen von Qt sowie ein QML-Buch. In seiner Freizeit organisiert er die FOSS-North-Konferenz, die jährlich in Göteborg, Schweden, stattfindet.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 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
Nach oben