Embedded Linux Grundlagen Embedded Linux – Kernel, Aufbau, Toolchain

Aktualisiert am 07.08.2023 Von Diplom-Ingenieur (FH) Andreas Klinger Lesedauer: 8 min |

Anbieter zum Thema

Wenn Linux in einem Embedded System eingesetzt werden muss, sind die Ressourcen deutlich beschränkter als in einem Desktop- oder Server-System. Worauf kommt es im Embedded Linux hinsichtlich Kernel, Bootloader oder Root-Filesystem genau an?

(Bild: Clipdealer)

Woraus besteht ein Linux-System?

Linux-Systeme bestehen aus drei Komponenten: Bootloader, Linux-Kernel und Root-Filesystem.

Der Bootloader hat die Aufgabe beim Booten die Hardware zu initialisieren, den Kernel ins RAM zu kopieren, ihn mit erforderlichen Informationen zu versorgen und zum Starten zu bringen. Ist dies erfolgt, geht sein Ausführungspfad in den Kernel-Bootvorgang über.

Der Linux-Kernel beinhaltet die Speicherverwaltung, Scheduling, Interprozess-Kommunikation sowie eine riesige Auswahl an Treibern. Jeglicher Zugriff auf Hardware erfolgt mithilfe von Kerneltreibern, zumindest um ihn einzurichten.

Am Ende des Bootvorganges hängt der Kernel das Root-Filesystem ein. Dies wird als "Mounten" bezeichnet. Dieses ist ab dem Root-Verzeichnis / sichtbar. In ihm befindet sich der Init-Dämon. Dieser setzt den Bootvorgang im Userspace fort. Dazu werden weitere Dateisysteme gemountet, Systemeinstellungen vorgenommen und Dämonen (Hintergrundprozesse) gestartet. Außerdem startet er Login-Programme, welche nach Authentifizierung eines Benutzers jeweils eine Shell starten.

Worin unterscheiden sich nun Embedded-Linux-Systeme von Desktop- und Server-Linux-Systemen? Um diese Frage zu klären werden nachfolgend die erforderlichen Komponenten beschrieben und ein Vergleich von Standard- und Embedded-Linux hergestellt.

SEMINAR-TIPP

Embedded-Linux-Woche

Embedded-Linux-Woche

In den Einsteigerkursen der Embedded-Linux-Woche führen unsere Referenten Sie Schritt für Schritt in die Linux-Welt ein. Die Basis bildet der Grundlagen-Kurs. Im darauf aufbauenden Seminar "Embedded Linux" kann das Gelernte vertieft werden. Erlangen Sie in nur fünf Tagen das notwendige Wissen, um vom Linux-Neuling zum Fortgeschrittenen zu werden!

Weitere Details und Termine

Bootloader in Embedded Linux

Welcher Bootloader eingesetzt wird hängt von der Architektur ab. Auf Intel-Systemen kommt meist der Bootloader grub zum Einsatz, während bei Nicht-Intel-Systemen meist u-boot oder barebox eingesetzt werden. Diese Unterteilung ist zwar nicht fix vorgegeben und die Bootloader können theoretisch auch auf anderen Architekturen laufen. Praktisch hat dies jedoch nur wenig Relevanz.

Grund dafür dürfte vor allem sein, dass sich Intel-Architekturen in der Art und Weise, wie Hardwareinformationen an den Bootloader und den Linux-Kernel geliefert werden, auszeichnen. Bei Intel gibt es dazu das BIOS, welches mittels ACPI diese Informationen an die Software liefert. Darauf aufbauend kann diese dann die entsprechenden Treiber laden und verwenden. Dadurch ist es möglich, dass ein und dasselbe Bootloader-Executable auf unterschiedlichen Boards der Architektur Intel eingesetzt wird, wie dies bei grub der Fall ist.

Ist keine generische Informationsquelle vorhanden, aus welcher die Hardwarekonfiguration ausgelesen werden kann, muss der Bootloader diese Informationen zumindest teilweise fix einkompilliert haben. Das ist bei den aus dem Mikrocontrollerbereich stammenden Bootloadern u-boot und barebox der Fall. Diese werden immer für eine bestimmte (Unter-)Architektur erstellt, häufig sogar für ein spezielles Board mit einer spezifischen Hardware-Konfiguration.

Ob es sich also nun um ein Embedded-, Desktop- oder Server-System handelt, spielt bei der Auswahl des Bootloaders also eine untergeordnete Rolle.

Aufbau eines Embedded-Linux-Systems.
Aufbau eines Embedded-Linux-Systems.
(Bild: Andreas Klinger)

Zusammenstellung von Kernel und Rootfilesystem

Für die Zusammenstellung eines Embedded-Linux-Systems gibt es zwei konträre Vorgehensweisen. Beim Top-Down-Ansatz wird eine Distribution, welche bereits eine große Anzahl an Softwarepaketen enthält auf die Notwendigkeiten des Zielsystems reduziert.

Dies funktioniert natürlich nur, wenn eine Distribution für die entsprechende Architektur verfügbar ist. Für Embedded-Systeme existiert zum Beispiel das System elbe, welches nach diesem Prinzip funktioniert.

Beim Bottom-Up-Ansatz wird dagegen das komplette System aus den Sourcen heraus erstellt. Dies wird jedoch meist nicht komplett "From-The-Scratch" durchgeführt. Meist lässt sich der Entwickler von einem Buildsystem unterstützen. Dieses hat die Aufgabe, den Erstellvorgang angefangen von der Cross-Development-Toolchain über Bootloader, Kernel und Root-Filesystem zu automatisieren. Der Entwickler wählt die Architektur, Softwarepakete, Versionen und so weiter aus und überlässt den Erstellvorgang dem Buildsystem. Beispiele dafür sind buildroot und yocto.

SEMINAR-TIPP

Diagnose von Linux

Systemfehler können sich vielfältig äußern – doch wie findet man heraus, wo genau die Ursache liegt? Im Online-Seminar "Diagnose von Linux" zeigt Ihnen Referent Andreas Klinger geeignete Werkzeuge, um Probleme, die beim Entwickeln von Linux-Systemen entstehen können, zu lösen.

Seminardetails und Termine

Toolchain für Embedded Linux

Beim Bottom-Up-Ansatz benötigt man eine Development-Toolchain, mit welcher es möglich ist, die Komponenten zu erstellen. Sehr häufig wird auf einem Entwicklungssystem mit anderer Architektur als dem Zielsystem erstellt. In diesem Fall wird eine Cross-Development-Toolchain benötigt. Bei den Buildsystemen sind diese Toolchains mit enthalten.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Bestandteil der Toolchain ist neben dem Compiler und den Binutils (Assembler, Linker, Objdump, ...) auch die Libraries des Zielsystems, gegen welche die Programme des Root-Filesystems gelinkt werden. Vor allem die sogenannte C-Library ist hier von zentraler Bedeutung.

Für Desktop- und Server-Systeme wird als C-Library fast ausschließlich die GNU-C-Library, kurz glibc, verwendet. Diese enthält immer die aktuellsten Features, welche vom Kernel angeboten werden.

Bei Embedded-Linux kann ebenso die glibc verwendet werden. In Fällen jedoch, in denen die Größe dieser Library störend ist, weil ein kleines System benötigt wird, ist sie oftmals zu groß. Dafür existieren schlankere Re-Implementierungen. Diese sind viel kleiner durch das Weglassen von Debugging-, Tracing- und Profiling-Funktonalitäten. Auch sind sie konfigurierbar in ihrem Funktionsumfang.

Die Konsequenz ist jedoch, dass Programme des Root-Filesystems immer gegen genau die korrekte C-Library gelinkt werden müssen. Ansonsten kann es passieren, dass sie nicht funktionieren. Die glibc dagegen wird binarkompatibel gehalten, so dass Binärprogramme ausgetauscht werden können. Dies funktioniert bei den abgespeckten Libraries nur sehr bedingt. Beispiele für kleine C-Libraries sind uClibc und musl.

Mainline-Kernel, zubehöriger Patch, oder doch ein Manufacturer-Kernel? Welcher Kernel  für ein Embedded-System eingesetzt werden kann hängt massgeblich davon ab, wie gut es Hersteller oder Community gelungen ist, Architekturanpassungen und Treiber vorzunehmen und zu veröffentlichen.
Mainline-Kernel, zubehöriger Patch, oder doch ein Manufacturer-Kernel? Welcher Kernel für ein Embedded-System eingesetzt werden kann hängt massgeblich davon ab, wie gut es Hersteller oder Community gelungen ist, Architekturanpassungen und Treiber vorzunehmen und zu veröffentlichen.
(Bild: Andreas Klinger)

Embedded Linux Kernel

Distributionen, wie sie vor allem für Desktop- und Server-Systeme eingesetzt werden liefern sowohl einen Linux-Kernel als auch ein Root-Filesystem mit. Die darin enthaltenen Kernel sind zunächst Mainline-Kernel plus einer häufig sehr grossen Anzahl an Patches. Diese Patches werden zur Behebung von Bugs oder zur Schliessung von Sicherheitslücken eingebracht und mit der Distribution verteilt. Die Zusammenstellung und Erstellung dieser Patches ist eine wichtige Aufgabe der Distributionshersteller.

Für Embedded-Systeme kommen Distributions-Kernel meistens nicht in Betracht. Dies liegt zum einen an der eingesetzten Architektur. Zum anderen sind Embedded Boards selbst bei gleicher Architektur meist spezifischer, ihre Hardwarebestückung entsprechend oft nicht von verfügbaren Distributionen abgedeckt.

Welcher Kernel nun für ein Embedded-System eingesetzt werden kann hängt maßgeblich davon ab, wie gut es dem Hersteller bzw. der Community es bisher gelungen ist, Architekturanpassungen und Treiber upstream zu übermitteln. Sprich, ob Kernelanpassungen überhaupt erstellt und wenn ja, diese dann in den Mainline-Kernel aufgenommen wurden. An diesem Punkt gibt es große Unterschiede zwischen den SOC- und Board-Herstellern.

Im Idealfall kann man einen Mainline-Kernel nehmen und hat dann die freie Auswahl, welche Version man verwenden möchte, ob ggf. noch ein Echtzeit-Patch eingesetzt werden soll, oder ob man in der Zukunft einfach auf eine neuere Version wechseln kann.

Existiert diese Möglichkeit nicht, ist man häufig auf einen sogenannten Manufacturer-Kernel angewiesen. Dies ist ein Mainline-Kernel, welcher durch Patches des Chipherstellers und weiter durch den Boardhersteller angepasst wurde, so dass der Kernel auf dem entsprechenden Board läuft. In diesem Fall ist man auf die verfügbaren Versionen angewiesen und hat es sehr schwer auf eine andere (aktuellere) Kernel-Version zu wechseln, wenn diese nicht gleichzeitig vom Hersteller bereitgestellt wird. Erfahrungsgemäß werden in solch einem Fall nur einige wenige Kernelversionen bereitgestellt und irgendwann in der Zukunft gar keine neuen Versionen mehr.

Dann sitzt man als Entwickler auf diesem Kernel und hat kaum Freiheitsgrade. So kann es passieren, dass man einen Treiber benötigt, ein neues Kernel-Feature einsetzen oder eine Sicherheitslücke schließen möchte. In all diesen Fällen ist es schwierig vorhandene Lösungen in das eigene System zu integrieren und meist mit erheblichem Entwicklungsaufwand verbunden.

Man kann bereits vor Auswahl eines Boards anhand des Umfanges der notwendigen Patches und daran wie tiefschürfend die Änderungen sind, erkennen, wie weit man sich durch dessen Einsatz von der Mainline entfernt und damit Freiheitsgrade aufgibt.

Root-Filesystem

Das Root-Filesystem besteht aus einem Verzeichnissystem mit den wichtigsten Programmen sowie Device-Nodes und Libraries. Die benötigten Programme können entweder die Originalprogramme oder reduzierte Varianten sein. In Embedded-Linux-Systemen werden aus Platzgründen sehr häufig reduzierte Varianten eingesetzt. Diese werden dann oftmals mithilfe der Busybox erstellt. Dies ist eine Re-Implementierung der wichtigsten Programme mit dem Ziel möglichst kleiner Footprint. Dies wird durch Weglassen von Funktionalität erreicht. Zudem ist der Umfang an Programmen sowie deren Features konfigurierbar.

Eine weitere Auswahl betrifft den Init-Dämon. Bei Desktop-Systemen hat sich inzwischen der systemd durchgesetzt. Bei Embedded-Systemen kommen alternativ das klassische System-V-Init und das daran angelehnte, aber reduzierte Busybox-Init in Frage.

Häufig erstellt man vom Embedded-Linux zwei unterschiedliche Systeme für die gleiche Hardware. Eine Variante, welche das eigentliche Produkt werden soll sowie eine zweite Variante für Entwicklungszwecke. Bei der Entwicklungsvariante findet man einen angepassten Kernel sowie Programme für das Debuggen und Tracen. Dies kommt daher, dass die Auswahl an Programmen, die für das fertige System benötigt werden, meist sehr überschaubar ist.

Embedded-Linux-Distributionen

Bei Standard-Linux werden in den meisten Fällen Distributionen eingesetzt. Diese beinhalten neben der Zusammenstellung der Software-Pakete, eine ihnen eigene Paketverwaltung, Verzeichnisaufbau und so weiter.

Das Pendant für Embedded-Linux sind sogenannte Buildsysteme. Mit ihnen können ganze Embedded-Linux-Systeme gebaut werden, das heißt sie bilden den gesamten Erstellvorgang von der Cross-Development-Toolchain über den Bootloader, den Linux-Kernel bis hin zum Root-Filesystem ab. Der komplette Erstellvorgang kann mit ihnen abgebildet werden.

Dabei ist es möglich, im Buildsystem alle notwendigen Vorgaben zu machen, wie die Architektur, welche Compiler-Version, welche Libraries, welche Programme ins Root-Filesystem sollen und so weiter. Beispiele dafür sind crosstool-ng, buildroot, yocto und elbe.

Mit crosstool-ng kann eine Cross-Toolchain für ein Target-System erstellt werden. Die anderen Komponenten (Bootloader, Kernel, Root-Filesystem) können damit nicht erstellt werden.

Bei buildroot ist der komplette Erstellvorgang mit allen Komponenten abgebildet. Dieser wird mittels einer semigraphischen Oberfläche eingestellt und mit Makefiles durchgeführt.

yocto hingegen arbeitet mit sogenannten Rezepten. Diese beinhalten die Erstellvorschriften für die einzelnen Komponenten. Beim Erstellen werden dann diese Rezepte mit dem Tool bitbake abgearbeitet.

Mit elbe wird das Embedded-System nicht "from-the-scratch" erstellt, sondern mithilfe von debootstrap in einer virtualisieren Umgebung das Zielsystem aus Debian-Paketen heraus erstellt.

Zusammenfassung

Desktop- bzw. Server-Linux-Systemen sind mit einer Reihe an Distributionen abgedeckt und in Ihrem Aufbau vergleichsweise homogen. Embedded-Linux-Systeme dagegen sind sehr viel heterogener. Gründe dafür sind in der verfügbaren Hardware und den Anforderungen an das fertige Embedded-System zu suchen und können zu ganz unterschiedlichen Systemen führen.

(ID:45326335)