Studienarbeit
Booten eines Linux-Kernels mittels eines VxWorks Bootloaders
Michael Stickel
<michael@cubic.org>
Hamburg 14. April 2004
Hochschule für Angewandte Wissenschaften
Gegenstand dieser Studienarbeit ist ein ARM-basierter Rechner der Firma ADS.
Es handelt sich dabei um den ADS-Graphics Client Plus.
Dieser Rechner verfügt über einen Bootloader, der in der Lage ist, einen
VxWorks-Kernel zu laden. Ziel dieser Studienarbeit ist es, dieses Board
in die Lage zu versetzen, einen Linux-Kernel zu laden, ohne dass etwas an
der Hardware oder der Firmware des Systems verändert wird.
Die Aufgabe besteht darin, den Linux-Kernel derart anzupassen, dass er von
einem VxWorks-Bootloader geladen und gestartet werden kann, und ihn derart zu
konfigurieren, dass er sein Netzwerk-Interface mittels BOOTP oder DHCP
konfiguriert und sein Root-Filesystem über NFS mounted.
Im folgenden Kapitel wird der Bootvorgang eines Linux-Kernels vom Einschalten
des Rechners bis zu dem Zeitpunkt, zu dem die ersten Programme geladen werden,
beschrieben.
Das Kapitel 3 gibt einen Überblick über das COFF Format. In diesem Format
muss der Kernel vorliegen, damit der VxWorks Bootloader in der Lage ist, ihn
zu laden. Im gleichen Kapitel wird auch kurz das ELF Format vorgestellt.
Im Kapitel 4 wird dargstellt, wie die oben gestellte Aufgabe gelöst
werden kann.
Wie die in Kapitel 4 diskutierte Lösung umzusetzen ist, beschreibt Kapitel 5.
Kapitel 6 gibt einen Ausblick auf die Möglichkeiten und möglichen
Anwendungen, die sich mit diesem System eröffnen. Es wird das Einbinden eines
Gerätetreibers beschrieben, der zum Ansprechen des auf dem Board befindlichen
CAN-Bus Controllers dient. Des Weiteren wird eine kleine Applikation mit
grafischer Benutzeroberfläche beschrieben, die diesen Treiber nutzt.
Der Bootvorgang ist von System zu System unterschiedlich, abhängig von der Art
und dem Hersteller des Rechners sowie von dem zu ladenden Betriebssystem.
Das erste Programm, das nach dem Einschalten des Rechners ausgeführt wird,
befindet sich in einem EPROM. EPROM wird in diesem Dokument als Synonym
für ROM, EPROM, EEPROM oder Flash-EPROM verwandt. Diese Begriffe werden
im Anhang näher beschrieben. Die Firmware wird bei PC's auch als BIOS
(Basic Input Output System) bezeichnet.
Die Aufgabe der Firmware besteht darin, die Hardware, die für den Bootvorgang benötigt wird,
in einen definierten und brauchbaren Zustand zu versetzen.
Bei einigen Systemen, wie beim iPAQ unter Linux oder einer SPARC unter Solaris,
ist die Firmware für den gesamten Vorgang zuständig: das Initialisieren der Hardware,
das Laden und das Übergeben der Kontrolle an den UNIX-Kern.
Die Firmware kann in diesem Fall auch als ``single stage bootloader'' bezeichnet
werden, da keine weiteren Programme außer des UNIX-Kerns nachgeladen werden.
Ist die Firmware nicht aus eigenen Kräften in der Lage, den UNIX-Kern zu laden,
sondern sie hierfür weitere Programme nachladen muss, so nennt sich dieser Zusammenschluss
``multi-stage bootloader''. Jede folgende Stufe baut auf der vorherigen auf und
ist stärker als diese auf die Eigenschaften des speziellen UNIX-Kerns
ausgelegt. Die Übergabe von Parametern an einen Linux-Kern kann zum Beispiel anders
aussehen, als bei einem BSD-Kern oder bei einem SINIX.
In diesem Abschnitt soll kurz der Bootvorgang vorgestellt werden, wie er bei einem PC stattfindet,
um einen Eindruck über das Zusammenspiel der einzelnen Komponenten zu vermitteln.
Nachdem man einen PC eingeschaltet hat, wird zunächst das im (Flash-)ROM befindliche
BIOS zur Ausführung gebracht. Dieses initialisiert zunächst die Hardware, die sich auf dem Mainboard
befindet und ruft anschließend Routinen des VGA-BIOS auf, das sich auf einem EPROM auf der
Grafikkarte befindet. Die Routinen konfigurieren die Grafikkarte, so dass das BIOS in der
Lage ist, Informationen in textueller Form auf dem Bildschirm auszugeben.
Daraufhin werden Funktionen zur Initialisierung ausgeführt, die sich auf
zusätzlichen EPROM's auf den Erweiterungskarten (wie Netzwerkkarten) befinden.
Zum Schluss wird das Laufwerk ermittelt, von dem aus ``gebootet'' wird.
Von diesem Laufwerk werden die ersten 512 Byte des ersten Sectors in das RAM gelesen
und ausgeführt. Nun kommt es darauf an, welchen Bootloader man verwendet.
In den meisten Fällen reichen 512 Byte nicht aus, um den Linux-Kern zu laden.
Dies ist bei GRUB , einem sehr leistungsfähigen, dadurch aber auch größerem,
Bootloader der Fall. Die 512 Byte dienen in diesem Fall dazu, den eigentlichen
Bootloader aus dem, bei heutigen Festplatten größerem, ersten Sector zu laden, der
um einiges größer ist als nur 512 Byte. GRUB ist auf Grund seines modularen
Aufbaus in der Lage, direkt auf das Dateisystem des UNIX-Systems zuzugreifen,
da er für die gängigsten Dateisysteme, deren Beschreibung verfügbar ist, ein
Treibermodul besitzt. So kann er direkt auf ein ext2 oder ext3 oder reiserfs
Dateisystem zugreifen und sich von dort seine Konfiguration beschaffen und
auch den Kernel von dort aus laden. GRUB bietet zudem die Möglichkeit, anders als
bei LILO, auch andere als nur die vorkonfigurierten Kerne zu laden.
Bei dem ADS-GC+ findet ein zweistufiger Bootvorgang statt.
Die Firmware befindet sich in einem kleinen FLASH-ROM, das vor versehentlichem
Löschen geschützt ist und elementare Hardwarekomponenten initialisiert.
Nach der Initialisierung wird der VxWorks-Bootloader ausgeführt, der dann die Kontrolle übernimmt.
Der VxWorks-Bootloader befindet sich in dem frei zugänglichen und beschreibbaren Flash-ROM .
Er ist unter anderem in der Lage, den VxWorks-Kern über den auf dem Rechner
befindlichen Netzwerk-Controller von einem im Netzwerk befindlichen Rechner
zu laden. Der VxWorks-Kern muß im COFF Format vorliegen, ebenso wie es bei
unserem Linux-Kern später der Fall sein wird. Aus diesem Grund wird in einem
der folgenden Kapitel das COFF Format angesprochen. Von welchem Rechner
der Kern geladen werden soll und wie er heißt, wird über BOOTP oder DHCP
ermittelt. Das eigentliche Laden des Kerns findet über TFTP statt.
Nachdem der Linux-Kern vom Bootloader in den Speicher geladen und zur
Ausführung gebracht wurde, gibt es zwei Möglichkeiten: Der Linux-Kern
liegt entweder in komprimierter Form (zImage oder bzImage ) oder in unkomprimierter
Form vor. Bei einem zImage oder bzImage wird der Kernel beim Übersetzen
komprimiert und ein kleines Programmstück wird diesem vorgehängt, dass
das Entpacken des Kernels und den Einsprung in diesen übernimmt.
Liegt der Linux Kernel in unkomprimierter Form vor, so wird der Einsprung-Punkt
direkt aufgerufen.
Auf dem Strong-ARM wird der Einsprung-Punkt (ENTRY) in
[arch/arm/kernel/head-armv.S] (32 Bit Entry Code) angesprungen.
Zum Zeitpunkt des Einsprungs in den Kernel bei der ARM-Architektur ist die
MMU abgeschaltet und im Register R1 befindet sich die Kennung der Architektur; in
R0 muß der Wert 0 stehen.
Hier wird die serielle Schnittstelle, die als Konsole verwendet wird,
eingerichtet. Der DRAM-Controller wird konfiguriert und der Stackpointer
gesetzt.
Zudem werden noch einige prozessorspezifische Einstellungen vorgenommen.
Der Typ des Prozessors wird zu diesem Zeitpunkt ermittelt, damit der Kernel
auf unterschiedlichen Typen der Strong-ARM Prozessorfamilie lauffähig ist.
Als letzter Schritt wird das Symbol ``start_kernel'' angesprungen, das sich
in der Datei ``init/main.c'' befindet.
Angekommen in start_kernel wird die Konfiguration des Systems weiter
vorgenommen. Dieser Teil des Ablaufs ist generisch. Er wird von allen Architekturen
(X86, ARM, SPARC, ...) verwendet.
- Verriegeln des Kernels.
- Einsprung in prozessorspezifische Initialisierung.
- Ermitteln des Root-Devices.
- Mounten und laden der ``Initial-Ramdisk''.
- Mounten des Root-Filesystems.
- Mounten des Device-Filesystems - Das Device-Filesystem wird im Kernel 2.6
wieder abgeschafft.
- Ausführen von ``/linuxrc'', wenn eine Initial-Ramdisk geladen wurde und
wechseln nach ``/initrd''.
- Die Sektion _init_ wird freigegeben.
- Die Kernel-Verriegelung wird aufgehoben.
- Öffnen der Konsole für die File-Deskriptoren 0, 1 und 2 (STDIN , STDOUT und
STDERR ).
Anschließend erfolgt der Versuch, die Kontrolle dem Init-Prozess zu übergeben.
Hierzu wird zunächst der Inhalt der Variablen ``execute_command'' herangezogen.
Der Inhalt kann mittels der Kernel-Option ``init=<cmd>'' definiert werden.
Enthält execute_command eine NULL, ist also undefiniert, so wird in folgender Reihenfolge
fortgefahren:
Zuerst wird versucht ``/sbin/init'' aufzurufen; gelingt dies nicht,wird der
Versuch mit ``/etc/init'', ``/bin/init'' und als letztes mit ``/bin/sh''
fortgesetzt.
Schlagen alle Bemühungen fehl, so wird der Text ``No init found.
Try passing init= option to kernel.''
ausgegeben. Dies kann der Fall sein, wenn die oben aufgeführten Dateien nicht
existieren oder nicht ausführbar sind; sei es, dass die Dateien nicht auf dem
Root-Filesystem
vorhanden sind oder aber das Root-Filesystem nicht gemountet wurde.
Wie schon zuvor besprochen1 wird versucht, ``/linuxrc'' auszuführen,
wenn und nachdem eine Initial-Ramdisk geladen wurde.
Die Datei ``/linuxrc'' muss ausführbar sein. Es kann ein Shellscript oder eine
Binärdatei sein. In diesem Script werden in der Regel Vorbereitungen
getroffen, die nicht statisch auf die Initial-Ramdisk geschrieben werden können,
wie das Laden von Modulen oder das Mounten von Dateisystemen.
Hier folgt das Beispiel einer /linuxrc Datei einer RedHat-9 Distribution:
#!/bin/nash
echo "Loading scsi_mod.o module"
insmod /lib/scsi_mod.o
echo "Loading sd_mod.o module"
insmod /lib/sd_mod.o
echo "Loading aic7xxx.o module"
insmod /lib/aic7xxx.o
echo "Loading jbd.o module"
insmod /lib/jbd.o
echo "Loading ext3.o module"
insmod /lib/ext3.o
echo Mounting /proc filesystem
mount -t proc /proc /proc
echo Creating block devices
mkdevices /dev
echo Creating root device
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc
Nachdem das System vorbereitet ist, wird die Kontrolle an ``init''
weitergegeben, wie es bereits beschrieben wurde2. Der Init-Prozess
ist der initiale Prozess, welcher immer die Prozess-Nummer 0 hat.
Nachdem der Init-Prozess /sbin/init oder eines der anderen, zuvor angesprochenen
Programme ausgeführt hat, ist eine Hälfte der Arbeit getan - die des Kernels.
Es erfolgt der Übergang in den Userspace. Bei einem Standard-System wird init nacheinander
einige Scripte abarbeiten, die das System Schritt für Schritt einrichten.
Da das Wissen über die Initialisierung des Systems in Scripten und Konfigurationsdateien
steht, sind diese für ein X86 System genauso verwendbar wie für ein ARM- oder MIPS-basiertes
System. Welche Scripte ausgeführt werden sollen, wird der Datei ``/etc/inittab'' entnommen.
Die Datei /etc/inittab ist eine der elementaren Stellen zur Steuerung des Systems.
In dieser Datei wird festgelegt, unter welchen Bedingungen oder zu welchen Ereignissen
Prozesse gestartet oder angehalten werden sollen. Zu diesen Ereignissen zählen das Starten
des Systems, Wegfall der Stromversorgung, Wiederherstellung der Stromversorgung oder
Wechsel des ``Runlevels'' . Es gibt mehrere Runlevel. Jeder der Runlevel hat eine
festgelegte Aufgabe. Der initiale Runlevel, der nach dem Aufstarten des Systems
aktiviert werden soll, wird mittels des Schlüsselwortes initdefault festgelegt:
id:3:initdefault:
Der folgende Text ist ein Auszug aus der Datei /etc/inittab und beschreibt, welche
Aufgaben die einzelnen Runlevel übernehmen.
# 0 - halt (Do NOT set initdefault to this)
# 1 - Single user mode
# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
Runlevel 0 ist also zum Anhalten und Runlevel 6 zum Neustarten des Systems zuständig.
Würde man initdefault auf einen der beiden Runlevel 0 oder 6 setzen, so würde das System
direkt nachdem es aufgestartet wurde, wieder angehalten oder neu gestartet werden.
Runlevel 1 wird in der Regel für Wartungsaufgaben verwendet. In diesem Runlevel werden
alle Login-Prozesse angehalten und alle angemeldeten Benutzer abgemeldet. Dadurch
kann man sicher sein, dass kein Benutzer die Arbeit an dem System stört und dass auch kein
Benutzer durch die Arbeiten, die man durchführt, gestört wird.
Die Runlevel 2 bis 5 erlauben das Anmelden von Benutzern (Multiuser Modus) und bieten
unterschiedliche Möglichkeiten. Welche Terminals in welchen Runleveln aktiv sind, wird
ebenfalls in der /etc/inittab festgelegt. Dafür ist der folgende Auszug verantwortlich:
# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
# Run xdm in runlevel 5
x:5:respawn:/etc/X11/prefdm -nodaemon
Das Format ist solchermaßen aufgebaut, dass ein Eintrag eine Zeile umfasst und die
einzelnen Attribute mittels eines Doppelpunktes getrennt sind.
Das erste Attribut stellt eine Art Schlüssel für den Eintrag dar.
Das zweite gibt die Runlevel an, in denen der Prozess laufen soll.
Das dritte Attribut definiert eine Aktion, die darstellt, welche Aufgabe dieser Eintrag hat.
Das vierte und letzte Attribut legt den Namen der Applikation fest, die der
Prozess ausführen soll, sowie die Argumente, die der Applikation übergeben werden
sollen. Sind mehrere Einträge zu einem Ereignis vorhanden, werden sie in der Reihenfolge
ausgeführt, in der sie in der Datei stehen.
Die drei Einträge die zum Systemstart ausgeführt werden, werden in folgender
Reihenfolge abgearbeitet: sysinit , boot und zuletzt bootwait .
Der folgende Abschnitt zeigt die für den Systemstart und den Reboot nötigen Einträge.
Der wichtigste ist dabei ``si''. Nachdem init die Kontrolle über das System bekommen hat,
wird sofort die Aktion sysinit ausgeführt, die in diesem Fall das Script ``/etc/rc.d/rc.sysinit''
zur Ausführung bringt. Da hier kein boot und bootwait definiert ist, wird danach sofort
in den default Runlevel, in unserem Fall 3, gewechselt, was ``/etc/rc.d/rc 3'' zur Ausführung bringt.
Hierbei handelt es sich wiederum um ein Shellscript, das alles ausführt was notwendig ist, um
den Wechsel aus dem aktuellen in den neuen Runlevel zu vollführen.
# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
Der Kern dessen, was in ``/etc/rc.d/rc'' passiert, ist im folgenden kurz dargestellt:
01 #!/bin/bash
02 argv1="$1"
03 set `/sbin/runlevel`
04 runlevel=$2
05 export runlevel
06
07 # Get first argument. Set new runlevel to this argument.
08 [ -n "$argv1" ] && runlevel="$argv1"
09
10 for i in /etc/rc$runlevel.d/K* ; do
11 $i stop
12 done
13
14 for i in /etc/rc$runlevel.d/S* ; do
15 $i start
16 done
In Zeile 2 wird der erste Parameter gesichert. Dies ist der neue Runlevel.
In Zeile 8 wird geprüft, ob ein Parameter übergeben wurde. Ist dies der Fall,
wird er als der neue Runlevel verwendet, andernfalls wird der durch
``/sbin/runlevel'' ermittelte Runlevel benutzt.
In den Zeilen 10 bis 12 werden alle Dienste angehalten, zu denen definiert wurde,
dass sie in diesem Runlevel nicht laufen sollen. Mittels der Zeilen
14 bis 16 werden alle Dienste gestartet, die in diesem Runlevel laufen sollen.
Mögliche Namen für die Aktionen sind für Linux die unten folgenden.
Die meisten werden auch von anderen UNIX-Derivaten unterstützt.
Bei einigen, wie kbrequest und ctrlaltdel, handelt es sich aber
um Linux-spezifische Aktionen. Eine genaue Beschreibung findet sich in [11] wieder.
- respawn
Der angegebene Prozess wird beim Eintritt in einen der angegebenen Runlevel
aufgestartet und erneut aufgestartet, sobald er sich beendet. Beendet sich
ein Prozess zu schnell, so wird eine Meldung darüber ausgegeben und das
Aufstarten wird für einige Minuten verzögert. Dadurch wird verhindert, dass
ein Prozess mit Fehlfunktionen das System überlasten könnte.
- wait
Der angegebene Prozess wird beim Eintritt in einen der angegebenen
Runlevel aufgestartet und init wartet mit der Ausführung darauf,
dass sich der Prozess beendet.
- once
Der Prozess wird beim Eintritt in den angegebenen Runlevel nur einmalig
aufgestartet und nach seinem Beenden nicht nochmals ausgeführt.
Damit wäre es zum Beispiel möglich, eine Anzeige anzusteuern, die den
aktuellen Runlevel eines Systems ausgibt:
rld0:0:once:/sbin/displayrunlevel 0
rld1:1:once:/sbin/displayrunlevel 1
rld2:2:once:/sbin/displayrunlevel 2
rld3:3:once:/sbin/displayrunlevel 3
rld4:4:once:/sbin/displayrunlevel 4
rld5:5:once:/sbin/displayrunlevel 5
rld6:6:once:/sbin/displayrunlevel 6
- boot
Der Prozess wird während des System-Starts, direkt nach ``sysinit'', ausgeführt
und das Runlevel-Feld wird ignoriert.
- bootwait
Dieser Prozess wird während des Systemstarts, direkt nach ``boot'', ausgeführt.
Das Runlevel-Feld wird auch hier ignoriert.
- off
Ein Eintrag, der diese Aktion enthält, wird ignoriert. Er ist quasi abgeschaltet.
- ondemand
Dieser Prozess wird auf Abruf (on demand) immer dann gestartet, wenn der
entsprechende Runlevel aufgerufen wird. Es gibt freie on-demand Runlevels.
Diese sind a, b und c. Die Aktion ondemand verhält sich wie once, nur dass
nicht in den entsprechenden Runlevel gewechselt wird.
- initdefault
Dieser Eintrag legt den Runlevel fest, der nach dem Systemstart aktiviert werden soll.
Fehlt der Eintrag, so wird an der Systemkonsole nach einem Runlevel gefragt.
Das Prozessfeld wird bei diesem Eintrag ignoriert.
- sysinit
Dieser Prozess wird während des Systemstarts ausgeführt. Er wird vor den
boot oder bootwait Einträgen ausgeführt. Das Runlevel-Feld wird auch hier
ignoriert.
- ctrlaltdel
Diese Aktion wird ausgeführt, wenn init das Signal SIGINT empfängt, was
der Fall ist wenn die Tastenkombination CTRL-ALT-DEL gedrückt wird. Hier kann
ein Shutdown oder ein Poweroff ausgeführt werden. Diese Tastenkombination
kann auch einfach ignoriert werden.
Für die Einträge ``1'' bis ``6'' wird für die Runlevels 2, 3, 4 und 5 die
Applikation /sbin/mingetty aufgestartet. Für den Eintrag ``x'' wird die
Applikation /etc/X11/prefdm gestartet, die X aufstartet und einen Login unter X
verursacht.
DHCP / BOOTP
Es existieren drei Protokolle zum Ermitteln einer Konfiguration mittels der Hardware-Adresse
(MAC bei Ethernet): RARP , BOOTP und DHCP .
RARP ist allerdings nicht von Bedeutung, da es erstens nur die IP-Adresse liefert, die zu der MAC-Adresse gehört.
Weiterhin verwendet es einen Link-Layer Broadcast und kein IP und wird damit nicht von Routern weitergeleitet.
BOOTP und DHCP nutzen IP und UDP als Protokolle und lassen sich damit auch routen.
Ein weiterer Vorteil von BOOTP und DHCP gegenüber RARP besteht darin, dass der BOOTP oder der DHCP Server als
normaler UNIX-Prozess realisierbar ist, da er einen UDP Socket öffnen kann. DHCP basiert
auf BOOTP und ersetzt diese.
BOOTP wird im RFC1542 und DHCP im RFC1541 beschrieben. Eine BOOTP Anfrage und auch die Antwort
ist 300 Byte lang, zuzüglich des Headers für UDP und IP. Die Antwort bei BOOTP liefert Informationen
wie zum Beispiel die IP-Adresse des Client Rechners, die Server Adresse (Bootserver), die Gateway
Adresse, den Hostnamen, den Namen der Boot-Datei sowie herstellerspezifische Erweiterungen.
Bei DHCP wurde die Größe der Anfrage und der Antwort auf 312 Bytes erhöht. Wenn der Rechner bereits
die IP-Adresse für das entsprechende Interface kennt, verwendet er diese als Absender; andernfalls
verwendet er 0. Als Empfänger wird 255.255.255.255 verwendet, weshalb das Paket als Broadcast Paket
versendet wird.
Wird eine Anfrage zu einer MAC-Adresse ins Netz abgesetzt und existiert ein Server, der einen
entsprechenden Eintrag aufweist, so werden die in diesem Eintrag vorhandenen Informationen dem
anfragenden Rechner zugesandt. Es können auch mehrere BOOTP oder DHCP Server in einem Netz
vorkommen. Diese können als primäre oder sekundäre Server gekennzeichnet sein, so dass eine
Redundanz gewährleistet ist. Auch können die unterschiedlichen Server für unterschiedliche Bereiche
verwendet werden. So ist es zum Beispiel möglich, dass alle Produktionsmaschinen über einen
Server und alle experimentellen Systeme über einen anderen Server abgewickelt werden.
Damit lassen sich Ausfälle bei den Produktionsmaschinen durch eine Fehlkonfiguration des Servers
für die experimentellen Systeme ausschließen. Das BOOTP- und das DHCP-Protokoll werden auch kurz
in [2, Kapitel 16 - BOOTP] beschrieben.
NFS steht für Network File System und ist ein netzwerkbasiertes Dateisystem.
Dabei liegen die Daten auf einem Server, dem NFS-Server, und der Client greift über das Netzwerk auf diesen
Server zu. Dabei unterstützt das NFS alle Eigenschaften, die für ein UNIX-basiertes Dateisystem
benötigt werden. Als Kommunikation zwischen Client und Server kommt RPC zum Einsatz. Der Server
implementiert einen Satz an Funktionen, die der Client nutzen kann, um zum Beispiel Dateien zu
öffnen, zu schreiben, zu lesen und zu schließen. Dabei werden die Sicherheitsinformationen, die
benötigt werden, um auf das Dateisystem zuzugreifen, jedes mal mit übergeben. NFS ist im Grunde
ein veraltetes, da sehr unsicheres Netzwerk-Dateisystem. Es existieren andere, sehr viel sicherere
Netzwerk-Dateisysteme, wie zum Beispiel AFS. Innerhalb eines Unternehmens ist es hinter einer Firewall jedoch durchaus
akzeptabel, NFS einzusetzen. Sobald es allerdings notwendig wird, über das Netzwerk auf entfernte Dateisysteme
zuzugreifen, ohne dabei ein VPN einzusetzen, bietet AFS sehr viel mehr Sicherheit.
Ein Netzwerk-Dateisystem wie NFS als Root-Filesystem bietet die Möglichkeit, ein
System auf dem Server vorzubereiten oder zu reparieren, ohne eine wirkliche Installation
vorzunehmen: Man kann eine bereits vorhandene Installation kopieren oder eine Installation aus einem Archiv
verwenden, wie es später in diesem Dokument der Fall sein wird.
Es ist für die Installation nicht notwendig, dass das Zielsystem, für das die Installation
vorgenommen wird, zur selben Zeit läuft; es können zu jedem Zeitpunkt Änderungen vorgenommen werden. Auch ist
eine Inkonsistenz des Root-Filesystem durch einen Absturz des Zielsystems so gut wie ausgeschlossen, da die
Verzeichnisstrukturen von dem serverseitigen Dateisystem verwaltet werden. Es kann daher nur dazu kommen, dass
Dateien noch nicht komplett geschrieben oder Dateien oder Verzeichnisse noch nicht gelöscht oder erzeugt wurden.
Daher ist eine NFS als Root-Filesystem für Entwicklungszwecke sehr vorteilhaft.
Außerdem wird das Einspielen neuer Programmversionen während der Entwicklung erheblich erleichtert.
Das COFF Format im Überblick
Coff steht für Common Object File Format. Es ist ein Dateiformat, das für die Kodierung von Objektdateien
genutzt wird. COFF unterstützt Sections, Symboltabellen und Debug-Informationen. COFF bildete unter anderem die
Grundlage für EXE Dateien unter DOS oder für alle Arten von Objektdateien unter
Windows (EXE, SYS, DLL, ...); letzteres Format wird als ``PE COFF'' bezeichnet.
Einige Felder im Header werden für andere Zwecke verwendet, als es im COFF Format vorgesehen ist.
Weiterhin wird ein optionaler Header, der so genannte PE Header eingefügt. Auch das .NET Framework von Microsoft
verwendet das ``PE COFF'' als Objektformat, wobei der Loader .NET Dateien an die CLR (Common Language Runtime)
des .NET Framework weitergibt.
COFF wurde bei UNIX-Derivaten verwendet, bevor das ELF (Executable and Linkable Format ) Einzug hielt.
Die a.out Dateien unter UNIX sind COFF codiert und enthalten zusätzlich zum COFF Header noch einen a.out Header.
Das PE COFF Format wird in [7] und [9] beschrieben.
[8] beschreibt das COFF Format, wie es bei einem True64 UNIX verwendet wird.
Bei dem VxWorks Bootloader von ``Wind River Systems'' existiert eine Einschränkung: Die Anzahl Einträge für eine
COFF Section darf 65535 nicht überschreiten. Dies wird in [10, Anhang G.2, Abschnitt: Boot Loader Changes] beschrieben.
Bei einem Linux-Kernel kann das ein Problem hervorrufen, für das eine einfache Lösung existiert:
Es wird ein komprimiertes Kernel-Image generiert, das sehr viel
weniger Einträge aufweist, da es nur den Programmcode zum Entpacken sowie das Kernel-Image
enthält.
Laden von Linux mittels VxWorks-Bootloader
Um Linux auf dem ADS-GraphicsClientPlus vom VxWorks Bootloader laden zu können, muss
das Linux im COFF/ECOFF Format vorliegen.
Das Problem, das sich hierbei ergibt, ist, dass das COFF Format
mit einer großen Anzahl von Symbolen in einer Section nicht zurechtkommt.
Dieses Problem wurde im vorliegenden Fall dadurch umgangen, dass ein komprimierter Kernel
im COFF Format generiert wurde. Dieses COFF Image enthält die Symbole und Sections
für den Entpacker und das zu entpackende Kernel-Image, wodurch sich die Anzahl Symbole
stark verringert. Um dies zu realisieren, benötigen wir einen Linker, der das COFF
Format unterstützt.
Notwendige Modifikationen
Um den Kernel für den ADS-GC+ zu übersetzen, kann, mit einer Einschränkung, eine Standard-Toolchain für
die ARM-Archirektur verwendet werden. Die Einschränkung besteht darin, dass die Applikation objcopy
als Ausgabeformat ``coff-arm-little'' unterstützen muss. Da diese Applikation zu den Binutils gehört,
sind letztere so zu übersetzen, dass sie das ``coff-arm-little'' Format unterstützen.
Die Vorgehensweise zum Generieren einer solchen Toolchain beschreibt der folgende Abschnitt.
Zunächst ist zu überprüfen, welche Version die installierte ARM-Toolchain hat.
Nehmen wir an, es handelt sich um eine Crosskompilation und der Prefix der Toolchain sei
``arm-linux-''. In diesem Fall können wir die Version mittels des Aufrufes ``/usr/local/arm-linux/bin/arm-linux-objcopy -version''
herausfinden, unter der Annahme, die Toolchain befindet sich im Verzeichnis /usr/local/arm-linux.
Es sollte dann eine Ausgabe ähnlich der folgenden zu sehen sein.
GNU objcopy 2.11.2
Copyright 1997, 98, 99, 2000, 2001 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License. This program has absolutely no warranty.
Letztere Meldung zeigt uns, dass die Binutils mit der Version 2.11.2 installiert sind.
Um sicher zu gehen, werden wir die gleiche Binutil Version zur Anpassung verwenden.
cd $HOME
mkdir -p arm
mkdir -p arm/binutils-build
cd arm/
wget ftp://ftp.gnu.org/gnu/binutils/binutils-2.11.2.tar.gz
tar vxfz binutils-2.11.2.tar.gz
cd binutils-build
../binutils-2.11.2/configure \
--prefix=\$HOME/arm/binutils \
--target=arm-linux \
--host=i386 \
--enable-targets=arm-*-coff,strongarm-*-elf,strongarm-*-coff
make
make install
Dieser Code ist auch in einer Textdatei auf der CD vorhanden,
damit Fehler beim Abtippen ausgeschlossen werden können.
Nachdem diese Schritte durchgeführt wurden und keine Fehler aufgetreten sind, haben
wir im Verzeichnis $HOME/arm/binutils/bin/ die Applikation arm-linux-objcopy stehen,
die COFF als Ausgabeformat versteht.
Ob die selbstgenerierte Applikation arm-linux-objcopy das COFF Format unterstützt, läßt sich herausfinden, indem man
``$HOME/arm/binutils/bin/arm-linux-objcopy -help'' ausführt.
In den letzten Zeilen steht ``supported targets:'' mit einer Liste von Targets, die unterstützt werden.
Notwendig ist es, dass im folgenden die neuen Binutils statt der bereits vorhandenen
verwendet werden. Dafür bedienen wir uns der Umgebungsvariablen PATH und passen sie
derart an, dass unsere gerade generierten Binutils vor den bereits vorhandenen erscheinen.
Ich gehe davon aus, dass die ARM-Toolchain nicht in der Variablen PATH auftaucht, was
mittels ``echo $PATH'' überprüft werden kann. Ich gehe weiterhin davon aus,
dass sich die Toolchain im Verzeichnis /usr/local/arm-linux befindet.
Unter diesen Voraussetzungen muss die PATH Variable folgendermaßen gesetzt werden:
export PATH=$HOME/arm/binutils/bin/:/usr/local/arm/2.95.3/bin/:\$PATH
Es ist auch möglich, die bisherigen Binutils mit den eigenen zu überschreiben,
indem der ``Prefix'' entsprechend gesetzt wird.
Ab diesem Zeitpunkt verfügen wir über eine Toolchain, die in der Lage ist, Dateien
im COFF Format zu generieren.
Mit Hilfe des vorangegangenen Kapitels besitzen wir die notwendige Toolchain.
Im folgenden geht es darum, den Kernel zu entpacken, die benötigten Patches einzuspielen
und die Anpassungen vorzunehmen, die es uns ermöglichen, den Kernel im COFF
Format zu erhalten.
Um den Kernel zu entpacken, wechseln wir wieder in unser build Verzeichnis und gehen wie folgt vor:
cd $HOME/arm
tar xfj /cdrom/archives/kernel/linux-2.4.9.tar.bz2
cd linux
Für dieses Beispiel ist die CD im Verzeichnis /cdrom gemountet.
Mit Hilfe der folgenden Anweisungen spielen wir einige Patches ein:
zcat /cdrom/archives/kernel/patch-2.4.9-ac10.gz | patch -p1
zcat /cdrom/archives/kernel/patch-2.4.9-ac10-rmk2.gz | patch -p1
zcat /cdrom/archives/kernel/diff-2.4.9-ac10-rmk2-np1.gz | patch -p1
zcat /cdrom/archives/kernel/diff-2.4.9-ac10-rmk2-np1-ads3.gz | patch -p1
Die einzelnen Patches bauen aufeinander auf und müssen in der hier angegebenen Reihenfolge
verwendet werden.
``ac'' steht für Alan Cox, ``rmk'' für Russell King und ``np'' für Nicolas Pitre.
Die ``ads'' Patches wurden von der Firma ADS erstellt.
Nun besitzen wir einen Kernel, der auf dem ADC-Graphics Client Plus lauffähig ist.
Als nächstes müssen die Erweiterungen eingebaut werden, die es ermöglichen, das
Kernel-Image im COFF Format zu erhalten. Dafür sind drei Dinge notwendig:
Zum einen ist die folgende Regel einzuführen, die als Vorbedingung den
komprimierten Kernel im ELF Format benötigt und ihn im COFF Format schreibt.
zImage.ecoff : $(CONFIGURE) compressed/vmlinux
$(OBJCOPY) -I elf32-littlearm -O coff-arm-little compressed/vmlinux $@
Des Weiteren muß die ZTEXTADDR von 0xC0200000 auf 0xC0008000 gesetzt werden,
was in der Datei ``arch/arm/boot/Makefile'' vorzunehmen ist.
Als letztes ist die Regel zu erweitern, mit deren Hilfe die Kernel-Images
generiert werden. Sie steht in der Datei ``arch/arm/Makefile'' und muss
um das Ziel ``zImage.ecoff'' erweitert werden:
bzImage zImage.ecoff zImage zinstall Image bootpImage install: vmlinux
@$(MAKEBOOT) $@
Die oben geschilderten Schritte übernimmt der Patch ``patch-2.4.9-ac10-rmk2-np1-ads3-ecoff1.gz'',
der auf der CD im Ordner ``archives/kernel'' liegt und folgendermaßen angewendet wird:
zcat /cdrom/archives/kernel/patch-2.4.9-ac10-rmk2-np1-ads3-ecoff1.gz \
| patch -p1
Damit der Kernel das Netzwerk-Interface über BOOTP oder DHCP konfigurieren
kann, muss der Punkt ``Kernel auto config'' eingeschaltet werden.
Im Menü ``Networking options'' ist unter dem Punkt ``TCP/IP networking'' der Unterpunkt
``IP: kernel level autoconfiguration'' und darunter einer der beiden Punkte
``IP: DHCP support'' oder ``IP: BOOTP support'' einzuschalten.
In der Datei config-ads-nfsroot ist BOOTP aktiviert.
Weiterhin ist im Menü ``File systems'' -> `` Network File Systems''
der Punkt ``NFS file system support`` und darunter der Punkt ``Root file system on NFS''
zu aktivieren.
Unter ``General setup'' ist ``root=/dev/nfs ip=::::::'' als ``Default kernel command string:''
anzugeben, damit NFS als Root-Filesystem verwendet wird und die Netzwerkschicht das
Netzwerkinterface automatisch über BOOTP oder DHCP konfiguriert.
All diese Anpassungen bezogen auf die Standardkonfiguration des ADS-GC+ liegen
auf der CD in der Datei archives/kernel/config-ads-nfsroot vor. Die Konfiguration
kann wie folgt vorgenommen werden:
cp /cdrom/archives/kernel/config-ads-nfsroot .config
make oldconfig
Mit Hilfe der folgenden Anweisung werden der Kernel und die Module übersetzt und die Module
werden in den Ordner ``install'', abgehend vom aktuellen Verzeichnis, installiert:
make INSTALL_PATH=`pwd`/install/ INSTALL_MOD_PATH=`pwd`/install/ \
dep zImage.ecoff modules modules_install
Der Ordner ``install/lib'' muss später in das Root-Filesystem kopiert werden und der Kernel,
der in der Datei ``arch/arm/boot/zImage.ecoff'' vorliegt, ist in das Verzeichnis ``/tftpboot''
zu kopieren, von der aus der Bootloader es mittels des tftp Protokolls läd.
Der bootpd, der das BOOTP Protokoll implementiert, verwendet die Datei /etc/bootptab
als Quelle für die Konfigurationen. Ein Eintrag könnte folgendermaßen aussehen:
arm1.my.domain:\
:ha=00600c002b9b:\
:ip=192.168.10.30:\
:sm=255.255.255.0:\
:dn=my.domain:\
:ns=192.168.10.254:\
:ds=192.168.10.3:\
:hd=/tftpboot:\
:bf=linux.ecoff:\
:rp=/export/adsgcplus-opie/:
Den oben angegebenen Eintrag habe ich für meine Experimente verwendet.
In ihm müssen für den Laboreinsatz die IP-Adressen angepasst werden.
Das Format und die möglichen Attribute werden in der Manpage bootptab(5) beschrieben.
Die Konfiguration hängt stark von dem verwendeten DHCP oder BOOTP-Server ab.
Für den Versuchsaufbau wurde ein bootpd verwendet.
In den meisten Fällen werden zu exportierende Root-Filesysteme von festplattenlosen Systemen
unter dem Ordner ``/export'' abgelegt, da hierdurch die Möglichkeit eines Einbruchs in den File-Server
verringert wird. Die Möglichkeit des Einbruchs in die Systeme, die im Export-Ordner auf dem Fileserver
liegen, wird ebenfalls erschwert.
Für die Angabe der freizugebenden Verzeichnisbäume ist die Datei ``/etc/exports'' zuständig.
Das Format für einen Eintrag sieht folgendermaßen aus:
/mein/pfad mein-host(optionen) [ mein-host(optionen)]*
Der linke Teil gibt den freizugebenden Pfad an, wobei damit auch
alle darunterliegenden Dateien und Verzeichnisse freigegeben werden.
Der rechte Teil ist eine, durch ein oder mehrere Leerzeichen getrennte,
Liste von Rechnern, für die dieser Pfad freigegeben werden soll.
In runden Klammern folgt, direkt auf jede Rechnerkennung, eine durch Komma separierte
Liste von Optionen, die den Zugriff regeln.
Die Rechnerkennung kann ein einzelner Hostname oder aber eine Maske sein, die eine Gruppe von Hosts definiert.
/export hostname.local0(rw)
/export 192.168.10.0/255.255.255.240(rw)
/export 192.168.10.0/28(rw)
/export *.local0(rw) *.local1(ro)
/pub (ro)
/export/adsgcplus-opie my-hostname(rw,no_root_squash)
In der ersten Zeile ist direkt der Hostname angegeben worden.
In der zweiten und dritten Zeile wurden Netzmasken verwendet;
die Einträge sind equivalent. Die vierte Zeile zeigt,
dass man auch mehr als eine Rechnerkennung je Pfad angeben kann.
In der vorletzten Zeile ist zu sehen, dass kein Hostname angegeben wurde.
Letzteres bedeutet, dass ausnahmslos alle Rechner einen Lesezugriff auf das Verzeichnis /pub haben.
Die letzte Zeile benötigen wir für unser System, wobei my-hostname
durch den Rechnernamen oder die IP-Adresse unseres Testsystems
ersetzt werden muss.
Ohne die Angabe der Option no_root_squash werden alle Zugriffe als root
unter einem anderen Benutzer, wie zum Beispiel nobody, vorgenommen.
Auf diese Weise ist es nicht möglich, Dateien zu ändern oder zu löschen oder
in Verzeichnissen Dateien anzulegen, die dem Benutzer root gehören
und deren Rechte die Änderung nicht zulassen.
Durch Angabe der Option no_root_squash hingegen, wird diese ``Umlenkung'' deaktiviert - was
notwendig ist, wenn wir den Pfad als Root-Filesystem verwenden wollen - zugleich aus oben
angegebenen Gründen aber auch sehr gefährlich ist.
Zu näheren Informationen wird die Manpage von export(5) empfohlen.
Es befindet sich ein Root-Filesystem für ein Handhelds-basiertes System auf der CD, das
manuell ausgepackt und zusammengestellt wurde. Es ist auch möglich, entsprechende
Werkzeuge zu erstellen, die eine Installation realisieren, wie man dies von anderen
Systemen her kennt. Zum Installieren des Beispielsystems sind folgende Schritte notwendig
und müssen als Benutzer root ausgeführt werden:
mkdir -p /export
cd /export
tar xfz /cdrom/archives/rootfs/adsgcplus-opie.tgz
Anschließend findet sich im Ordner /export/adsgcplus-opie das Root-filesystem unseres
Testsystems wieder.
Ein Zielsystem benötigt neben dem Systemkern mindestens die folgenden Komponenten:
- init (zum Beispiel mittels Busybox)
- die Init-Scripte
- eine libc (glibc, uClibc, newlib, dietlibc)
- die UNIX-Tools (zum Beispiel mittels Busybox)
Für Embedded-Systeme werden gern Busybox und uClibc aufgrund der geringen Größe
verwendet.
Zusammenfassung
Es wurde in dieser Arbeit erläutert, wie Linux auf einem PC gebootet wird und wie selbiges
unter einem Embedded-System geschieht, das eigentlich für einen VxWorks vorgesehen ist.
Gleichzeitig wurde nachgewiesen, dass es möglich ist, einen Linux-Kernel derart anzupassen,
dass er von einem VxWorks-Bootloader geladen werden kann. Es wurden weiterhin BOOTP, DHCP
und NFS vorgestellt und gleichzeitig deren Notwendigkeit zur Lösung des vorgegebenen
Problems erklärt.
Ziel der Arbeit war auch, aufbauend auf der Darstellung des UNIX-Boot-Vorganges die
Möglichkeit zu eröffnen, eigene (minimalistische) Root-Filesysteme aufzusetzen.
-
- 1
- Allesandro Rubini and Jonathan Corbet, Linux Device Drivers: Second Edition,
O'Reily & Associates, Inc., Februar 2001,1998
- 2
- Richard W. Stevens TCP/IP Illlustrated, Volume 1, The Protocols
Addison-Wesley Co., Inc, Reading, ©1994
- 3
- James F. Kurose, Keith W. Ross Computernetze, Ein Top-Down-Ansatz mit Schwerpunkt Internet
Addison Wesley Longman, ©2001
- 4
- Andrew S. Tannenbaum Computer Networks, Third Edition
Prentice-Hall, Inc., ©1996
- 5
- Andrew S. Tannenbaum Operating Systems - Design And Implementation
Prentice-Hall, Inc., ©1987
- 6
- The Virtual Machine in .Net Framework,
http://www.code101.com/Code101/DisplayArticle.aspx?cid=29
- 7
- Win32 PE executable with DJGPP GNU C/C++ compiller,
http://home.lanck.net/mf/win_pe.shtml
- 8
- Tru64 UNIX Object File/Symbol Table Format Specification,
http://www.phys.uu.nl/DU/Tru64_5.0/HTML/SUPPDOCS/OBJSPEC/DOCU_001.HTM
- 9
- PE File Structure,
http://jfmasmtuts.blowsearch.ws/Ch2/pefile.htm
- 10
- VxWorks Programmer's Guide 5.4 - G.2 ARM-Building Applications
http://spacegrant.colorado.edu/training/vxworks/docs/vxworks/guide/x-arm2.html#85631
- 11
- inittab(5) - Manpage zur beschreibung der Datei /etc/inittab.
- .NET
- Das COFF Format im
- a.out
- Das COFF Format im
- boot
- Der Init Prozess und
| Der Init Prozess und
- BOOTP
- Der Bootvorgang von VxWorks
| DHCP / BOOTP
- bootwait
- Der Init Prozess und
| Der Init Prozess und
- bzImage
- Der Bootvorgang von Linux
- COFF
- Das COFF Format im
- ctrlaltdel
- Der Init Prozess und
- DHCP
- Der Bootvorgang von VxWorks
| DHCP / BOOTP
- DOS
- Das COFF Format im
- DRAM-Controller
- Der Bootvorgang von Linux
- ELF
- Das COFF Format im
- EXE
- Das COFF Format im
- Executable and Linkable Format
- Das COFF Format im
- Flash-ROM
- Der Bootvorgang von VxWorks
- GRUB
- Der Bootvorgang bei einem
- init
- Der Init Prozess und
- Init-Prozess
- Der Init Prozess und
- initdefault
- Der Init Prozess und
- Initial-Ramdisk
- Die Initial Ramdisk und
- inittab
- Der Init Prozess und
- MMU
- Der Bootvorgang von Linux
- Network File System
- NFS als Root-Filesystem
- NFS
- NFS als Root-Filesystem
- off
- Der Init Prozess und
- ondemand
- Der Init Prozess und
- PE COFF
- Das COFF Format im
- RARP
- DHCP / BOOTP
- Runlevels
- Der Init Prozess und
- Stackpointer
- Der Bootvorgang von Linux
- STDERR
- Der Bootvorgang von Linux
- STDIN
- Der Bootvorgang von Linux
- STDOUT
- Der Bootvorgang von Linux
- Strong-ARM
- Der Bootvorgang von Linux
| Der Bootvorgang von Linux
- sysinit
- Der Init Prozess und
| Der Init Prozess und
- TFTP
- Der Bootvorgang von VxWorks
- UDP
- DHCP / BOOTP
- VGA-BIOS
- Der Bootvorgang bei einem
- zImage
- Der Bootvorgang von Linux
|