Suchen

Ein Embedded- Echtzeit-Linux-System aufsetzen, (Teil 2)

| Autor / Redakteur: Andreas Klinger * / Martina Hafner

In diesem Embedded-Linux-Tutorial erfahrt Ihr, wie ihr einen Bootloader erstellt und modifiziert. Außerdem kümmern wir uns um den Linux-Kernel und das Root-Filesystem.

Firmen zum Thema

Dieses Tutorial zu Embedded Echtzeit Linux entstammt den Inhalten der beliebten Embedded Linux Woche der ELEKTRONIKPRAXIS Akademie
Dieses Tutorial zu Embedded Echtzeit Linux entstammt den Inhalten der beliebten Embedded Linux Woche der ELEKTRONIKPRAXIS Akademie
(Bild: Vogel Communications Group)

HINWEIS: Der erste Teil des Embedded-Linux-Tutorials beschreibt, wie man von der Hardware in Form eines Embedded-Boards über JTAG und den Bootloader U-Boot bis zum fertigen Embedded-Echtzeit-Linux-System kommt.

Als erster Schritt wird der Bootloader U-Boot erstellt. Mit ihm wird anschließend der Linux-Kernel per RAM-Disk gestartet und auch geflasht. Der laufende Linux-Kernel dient dann dazu, um das Root-Filesystem auf dem Flash-Speicher einzurichten. Für die Erstellung von Bootloader, Linux-Kernel und Root-Filesystem wird eine Entwicklungsumgebung benötigt, die Maschinencode für unser Target erstellen, linken und debuggen kann. Diese Toolchain enthält ferner Bibliotheken und Tracing-Werkzeuge für das Zielsystem.

Die Cross-Development-Toolchain zusammenstellen

Ein einfacher Weg, um zu diesen Werkzeugen zu kommen, ist es, buildroot zu verwenden. Damit wurden im vorliegenden Beispiel neben dem GNU-C-Compiler die Binutils sowie die C-Bibliothek uCLibC erstellt.

cd /home/linux/

tar -xvzf buildroot-2011.11.tar.gz

cd buildroot-2011.11

make sheevaplug_defconfig

make menuconfig

make

In der buildroot-Konfiguration ist der Pfad der generierten Cross-Toolchain anzupassen (Host dir auf /usr/arm-linux einstellen). Weiters sollte unter Filesystem images das ext2-Filesystem ausgewählt werden. Dies generiert ein Root-Filesystem in einer Datei zur Verwendung als RAM-Disk oder auch zum Loopback-Mounten. Für die später benötigten Flash-Tools sollten unter Package selection Hardware handling die mtd/jffs-Utilities ausgewählt werden. Möchte man per SSH auf das Target zugreifen, empfiehlt es sich, dropbear im Menü Package selection networking applications dazuzunehmen. Weitere Anpassungen können iterativ erfolgen.

Der Bootloader U-Boot hat die Aufgabe, die Hardware zu initialisieren sowie den Kernel-Bootvorgang vorzubereiten und zu starten. Ihm kommt noch eine weitere Funktion zu, nämlich die des ersten Kommunikationspartners vom Target zum Entwicklungsrechner. Mit ihm starten wir das erste Linux-System und flashen den Kernel.

Den Bootloader U-Boot an das Board anpassen

Katalysator: Der Bootloader U-Boot hat die Aufgabe, die Hardware zu initialisieren sowie den Kernel-Bootvorgang vorzubereiten und zu starten.
Katalysator: Der Bootloader U-Boot hat die Aufgabe, die Hardware zu initialisieren sowie den Kernel-Bootvorgang vorzubereiten und zu starten.
( IT-Klinger)

Damit der Bootloader das Board initialisieren kann, muss er eventuell daran angepasst werden. Falls das für das zu verwendende Board noch nicht geschehen ist, muss ein Patch erstellt werden. Dazu werden eine neue Variante für das Board angelegt und die dazu gehörigen Initialisierungen vorgenommen. Die von buildroot erstellte Toolchain wird mit folgendem Source-File genutzt:

#!/bin/sh

# Datei: armenv.sh

export PATH=

/usr/arm-linux/usr/bin:$PATH

export ARCH=arm

export CROSS_COMPILE=arm-linux-

Die exportierten Umgebungsvariablen werden gesourced:

source armenv.sh

Die Bootloader-Sourcen werden entpackt:

tar -xvzf u-boot-2010.06.tar.gz

cd u-boot-2010.06

Nun wird eine Konfiguration ausgewählt, die dem Zielsystem am nächsten kommt. Bei dem verwendeten Board ist es die Sheevaplug-Konfiguration:

make sheevaplug_config

Der Bootloader wird erstellt mit

make u-boot.kwb

Dolmetscher: OpenOCD fungiert als Kommunikationsinstanz zwischen Entwicklungssystem und Target
Dolmetscher: OpenOCD fungiert als Kommunikationsinstanz zwischen Entwicklungssystem und Target
( IT-Klinger)

Nach dem Erstellen kann der Bootloader mittels OpenOCD auf das RAM des Zielsystems heruntergeladen, getestet und auch debugged werden. Dazu gibt es das ausführbare ELF-Binary mit dem Namen u-boot.

Wenn das verwendete Board nicht exakt der hier vorgeschlagenen pure.box 2 von Wiesemann & Theis entspricht, ist es erforderlich, den Bootloader daran anzupassen. Zu den typischen Anpassungen zählen:

  • Serielle Schnittstelle (UART-Nr. und Pin-Belegung)
  • Belegung von Multi-Purpose-Pins (MPP)
  • CPU-Identifikation

Katalysator: Der Bootloader U-Boot hat die Aufgabe, die Hardware zu initialisieren sowie den Kernel-Bootvorgang vorzubereiten und zu starten.
Katalysator: Der Bootloader U-Boot hat die Aufgabe, die Hardware zu initialisieren sowie den Kernel-Bootvorgang vorzubereiten und zu starten.
( IT-Klinger)

Durch das Bootloader-Debugging können die im vorhandenen Board auftretenden Probleme beim Booten gut nachvollzogen und behoben werden. Auf der Homepage des Autors (www.it-klinger.de) ist der Patch für das Beispielboard zu finden.

Sobald der Bootloader angepasst und auf das Target überspielt wurde, kann er als weiterer Kommunikationspartner zum Aufsetzen des Embedded-Linux-Systems verwendet werden. Insbesondere stehen jetzt Funktionen zum komfortablen Herunterladen und Testen von Images (zum Beispiel Kernel und/oder Root-Filesystem) über das Netzwerk sowie zum Flashen zur Verfügung.

Den Echtzeit-Linux-Kernel erzeugen

Das Betriebssystem ist Linux mit einer Echtzeiterweiterung. Dazu wird der RT-Preemption-Patch verwendet, der auf der offiziellen Kernel-Homepage verfügbar ist. Für das Erstellen des Linux-Kernels benötigt man die Cross-Development-Toolchain, die wie oben gezeigt gesourced wird:

source armenv.sh

Die Kernel-Sourcen werden entpackt, mit dem Patch versehen und für die Kirkwood-Default-Konfiguration vorbereitet:

tar -xvzf linux-3.0.14.tar.gz

cd linux-3.0.14

patch -p1 < ../patch-3.0.14-rt31

make kirkwood_defconfig

make menuconfig

make

Das fertige und gepackte Image befindet sich im Unterverzeichnis arch/arm/boot als Datei mit dem Namen zImage. Das Image kann im mkimage-Aufruf von U-Boot verwendet werden.

Analog zum Bootloader bleibt es nicht aus, den Kernel an das Target anzupassen. Häufig müssen beim Kernel ähnliche Einstellungen wie beim Bootloader vorgenommen werden. Dabei ist zu beachten: Boardspezifische Einstellungen, die dazu führen, dass der Kernel nur noch für das gewählte Board eingesetzt werden kann, haben im Kernel selbst nichts verloren. Auf der Homepage des Autors findet sich ein Kernel-Patch mit den nötigen Anpassungen für das Beispielboard.

Der nächste Schritt besteht darin, ein für den U-Boot verarbeitbares Image zu erstellen. U-Boot benötigt zum Laden von Images ein spezielles Format, das mit dem U-Boot-eigenen Programm mkimage erstellt wird. Als Root-Filesystem wird das von buildroot generierte verwendet (im buildroot-Verzeichnis output/images). Zum Erstellen eines Kernel-Images mit einem Root-Filesystem als RAM-Disk benutzt man:

mkimage -A arm -O linux -T multi

-C none -a 0x00008000 -e 0x00008000

-n tux -d zImage:rootfs.ext2 wut.img

Dabei repräsentiert zImage das Linux-Kernel-Image, rootfs.ext2 das von buildroot erstellte Root-Filesystem als RAM-Disk und wut.img das neu zu erstellende U-Boot-Image. Dem Kernel muss dabei die entsprechende Kommandozeile mitgeteilt werden. Interaktiv lässt sich das in U-Boot einstellen:

> setenv bootargs

'console=ttyS1,115200 root=/dev/ram0'

> printenv

> saveenv

Um das Image auf das Zielsystem zu laden, kann man es per TFTP-Server auf dem Entwicklungsrechner bereitstellen. Im U-Boot muss dazu die entsprechende Server- sowie die Target-IP-Adresse eingestellt werden:

> setenv ipaddr 192.168.0.102

> setenv serverip 192.168.0.71

> saveenv

> tftp wut.img

Sofern der Download mit lauter #-Zeichen abläuft, funktioniert er. Sollten * oder T erscheinen, ist eventuell der TFTP-Server nicht verfügbar oder es blockiert die Firewall.

Das Root-Filesystem und der Linux-Kernel

Der Linux-Kernel beinhaltet Betriebssystem-Funktionalität bis hin zu Treibern für das Netzwerk, Dateisysteme oder andere Geräte. Er enthält allerdings keine Programme und Dämonen, die das System benutzbar machen. Hierfür ist das Root-Filesystem, das erste vom Kernel beim Booten unter / gemountete Dateisystem, verantwortlich. Seine wichtigsten Inhalte sind:

  • init-Dämon: Er startet Dämonen, System-einstellungen sowie Login-Programme.
  • Shell-Programme und Dämonen
  • System-Konfiguration
  • Device-Nodes
  • Bibliotheken und Kernel-Module

Für die Zusammenstellung des Root-Dateisystems wird im Beispiel buildroot verwendet, das busybox, uClibC sowie weitere Programmpakete integriert.

Das Root-Filesystem mit JFFS2 erstellen

Das Root-Filesystem kann direkt mittels buildroot erstellt werden. Zu diesem Zweck werden die busybox sowie zusätzliche Pakete (MTD-Utilities, dropbear, u. a.) ausgewählt. Als Zielsystem wird ext2 gewählt, um das erstellte Root-Filesystem einfach als RAM-Disk verwenden zu können.

Sobald das System zusammen mit einem Linux-Kernel mit RAM-Disk startet, kann man daran gehen, das Linux-System auf dem Flash persistent einzurichten. Voraussetzung ist, dass die Flash-Bereiche dem Kernel als MTD-Partitionen bekannt sind.

Diese Einstellung wird entweder mit der Kernel-Kommandozeile zum Beispiel auf die folgende Weise

mtdparts=orion_nand:896k(u-boot),

128k(u-boot-env),

8m@1m(kernel),64m@9m(rootfs) rw

oder bei der Erstellung des Kernels als struct mtdpartition angegeben.

Das Beispiel geht davon aus, dass der Flash-Bereich mit dem Root-Dateisystem /dev/mtd3 heißt. Diese Bezeichnung ergibt sich aus der Definition von mtdparts.

Möchte man dagegen lediglich ein Kernel-Image herunterladen und verwenden, weil sich das Root-Filesystem auf dem Flash befindet (JFFS2- oder UBI-Filesystem), so muss ein U-Boot-Image mit nur dem Linux-Kernel erstellt werden:

mkimage -A arm -O linux -T kernel

-C none -a 0x00008000 -e 0x00008000

-n wut-kernel -d zImage wut.kernel

Im laufenden Linux-System mit RAM-Disk wird der Flash erased, gemountet und beschrieben:

flash_erase /dev/mtd3 0 0

mount -t jffs2 /dev/mtdblock3 /mnt

Wird nun das zukünftige Root-Filesystem auf das Target kopiert und loopback-gemountet, dann kann dieses Dateisystem einfach in das noch leere JFFS2-Filesystem kopiert werden. Beim Kopieren ist die Option -a besonders wichtig, da ansonsten symbolische Links aufgelöst und nicht wie beabsichtigt als Link kopiert werden.

mount -o loop rootfs.ext2 /mnt2

cp -av /mnt2/* /mnt

umount /mnt2

umount /mnt

Vorteil dieser Methode ist, dass das Root-Filesystem mit dem gleichen Linux-Treiber erstellt wird, der ihn später im laufenden Betrieb auch verwendet. Daher sind keine Probleme mit unterschiedlichen Einstellungen bezüglich dem Flash zu erwarten. Bei der Kernel-Kommandozeile muss noch das Root-Filesystem eingestellt werden:

root=/dev/mtdblock3 rw rootfstype=jffs2

Die entsprechende Kommandozeile für den Kernel lautet dann in U-Boot:

> setenv bootargs

'console=ttyS1,115200

root=/dev/mtdblock3 rootfstype=jffs2'

> printenv

> saveenv

Variante: Ein UBI-Dateisystem auf dem Flash erzeugen

Das Flash-Dateisystem UBIFS (Unsorted Block Image File System) ist die Neuauflage von JFFS2 und weist einige Fortschritte auf:

  • Kürzere Mount-Zeit
  • Geringerer RAM-Bedarf
  • Skaliert besser für große Dateisysteme
  • Kann gecacht/ungecacht schreiben

Das UBI-Design sieht zwei Schichten für das Dateisystem vor. Der UBI-Layer baut auf die MTD-Schicht im Kernel auf und bietet logische Erase-Blöcke.

UBIFS ist das eigentliche Dateisystem. Es setzt auf den UBI-Layer auf und liefert das Dateisystem. Daher gliedert sich auch die Erstellung des Root-Filesystems als UBIFS in mehrere Schritte.

Zuerst wird der Flash gelöscht und mit UBI formatiert. Es wird die vierte Partition (= /dev/mtd3) des Flashs verwendet, wobei sich die Partitionsnummer aus dem Kernel-Kommandozeilenargument mtdparts ergibt.

flash_erase /dev/mtd3 0 0

ubiformat /dev/mtd3

Mit dem Tool ubinfo wird abgefragt, welche Major- und Minor-Nummern für das UBI-Control-Device verwendet werden, dafür ein Device-Node angelegt und das UBI-Device eingehängt. Die Option -m bei ubiattach gibt an, welche Partition eingehängt werden soll; hier ist es die vierte.

ubinfo

...

UBI control device major/minor: 10:62

...

mknod /dev/ubictl0 c 10 62

ubiattach /dev/ubictl0 -m 3

Nun wird im /sys-FS abgefragt, welche Major- und Minor-Nummern für das eingehängte UBI-Volume verwendet werden, und dieses anschließend erstellt. Der Name (hier myrootfs) kann frei vergeben werden.

cat /sys/class/ubi/ubi0/dev

253:0

mknod /dev/ubi0 c 253 0

ubimkvol /dev/ubi0 -N myrootfs -m

Das UBI-Volume kann jetzt als UBI-Dateisystem eingehängt werden. Zu beachten ist, dass dabei der oben gewählte UBI-Volume-Name verwendet wird.

mount -t ubifs ubi0:myrootfs /mnt

Das Root-FS kann jetzt wieder wie oben gezeigt von einem Loopback-gemounteten Root-FS auf das noch leere UBI-FS kopiert werden. Soll der Linux-Kernel das erstellte UBI-FS als Root-FS verwenden, dann muss ihm mitgeteilt werden, auf welcher Partition dies erfolgt und wie das UBI-Volume heißt.

Die Flash-Partition wird über den Namen aus der mtdparts-Kernel-Kommandozeile angesprochen (hier rootfs). In unserem Beispiel lautet die Kommandozeile:

ubi.mtd=rootfs root=ubi0:myrootfs rootfstype=ubifs

* Andreas Klinger ist selbstständiger Diplom-Ingenieur (FH) und bietet Seminare zu Embedded- und Echtzeit-Linux an. Er ist außerdem beliebter Referent der Embedded Linux Woche. Kontakt: ak@it-klinger.de

(ID:32310710)