TuX as LLG logo
Software
DMX4Linux
Driver Suite for Linux
csv2iif.pl suite
convert PayPal transactions to IIF, OFX, QIF
Hardware
DMX30 Interface
128Ch SPP
DMX43 Interface
2out 2in EPP
LED Hardware
for Linux and Windows
EPROM Sampler
for 8 bits of sound
Misc
CatWeasel
Linux drivers for MK3/4 PCI
pg_trompe
PostgreSQL replication
trycatch
C exception/signal handling lib
Patches
to various software
Tools
and small scripts
Docs
misc documents
Links
to lighting stuff

Studienarbeit
Booten eines Linux-Kernels mittels eines VxWorks Bootloaders

Michael Stickel

<michael@cubic.org>

Hamburg 14. April 2004

Hochschule für Angewandte Wissenschaften

Inhalt

Einleitung

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

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.

Der Bootvorgang bei einem PC

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.

Der Bootvorgang von VxWorks auf dem ADS-GC+

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.

Der Bootvorgang von Linux

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.

Die Initial Ramdisk und /linuxrc

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

Der Init Prozess und /etc/inittab

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 als Root-Filesystem

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.

Einschränkungen von COFF unter VxWorks

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

Die Werkzeuge

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.

Die Anpassungen am Linux Kernel

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

Die Konfiguration des Linux Kernel

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.

BOOTP / DHCP Enrichten

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.

NFS Einrichten

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.

Root-Filesystem einrichten

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.

Literatur

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.

Index

.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

Search:
https://llg.cubic.org © 2001-2024 by Dirk Jagdmann