Embedded Rootfilesystem
Dieses Dokument beschreibt die Details zur Erzeugung eines minimales Linux Rootfilesystem. An der GSI wird man ueblicherweise sein Dateisystem aus bereits compilierten Komponenten mit Hilfe eines Scriptes zusammenstellen (
EmbeddedFilesystemGenerator)
Allgemein
Dies
Die meisten Programme (init, shell, etc) werden durch busybox zur ve
Busybox
Das Filesystem basiert auf busybox (
http://www.busybox.net/). Es besteht aus einem einzelnen Binary, welches entweder gegen die uLibc, gegen glibc oder statisch compiliert wird. Dieses Binary stellt eine Vielzahl der unter Linux verwendeten tools (sh, ifconfig, dhcp client, grep, awk, vi...) zur Verfügung. Teilweise mit reduziertem Funktionsumfang.
Compilieren Busybox
Sollte ein cross compiler Verwendung finden, so sind die Umgebungsvariablen (CROSS_COMPILE, PATH) entsprechend zu initialisieren
.
Download und auspacken von busybox. Anschliessend erfolgt die Konfiguration ähnlich wie beim linux kernel.
# default config erzeugen
user@host$ make defconfig
user@host$ make menuconfig
busybox 19.x braucht ubi-mtd.h, das ist unter rhel5 noch nicht dabei. Daher muss man dass von seinem kernel kopieren. z.B. cp -rva /common/usr/embedded/kernel/microioc/linux-2.6.33.6-gsi02/include/mtd/ $BUSYBOX/include
i386
cd $BUSYBOX
unset CROSS_COMPILE
cp -rva /common/usr/embedded/kernel/microioc/linux-2.6.33.6-gsi02/include/mtd include
EXTRA_CFLAGS="-m32" EXTRA_LDFLAGS="-m32" make defconfig
EXTRA_CFLAGS="-m32" EXTRA_LDFLAGS="-m32" make -j 8
EXTRA_CFLAGS="-m32" EXTRA_LDFLAGS="-m32" make install
ppc
cd $BUSYBOX
unset CROSS_COMPILE
cp -rva /common/usr/embedded/kernel/microioc/linux-2.6.33.6-gsi02/include/mtd include
. /common/usr/eldk/eldk_init 6xx
make defconfig
make -j 8
make install
Ggf. eintragen des Crosscompiler prefixes unter Busybox Settings -> Build Options -> Cross Compiler Prefix
Ansonsten ist die default Konfiguration für unsere Zwecke ausreichend. Wir benötigen keine selteneren Features und haben keine engen Vorgaben was Speicherplatz betrifft, können daher unbenutzte Features verkraften.
Compilieren mit
make
und installieren mit
make install
.
Es entsteht das Verzeichnis
_install
. In diesem liegt die busybox und eine Reihe von symlinks, welche jeweils auf busybox verweisen.
user@host$ ls _install/
bin
linuxrc -> bin/busybox
sbin
usr
und bin sieht dann so aus
user@host$ ls _install/bin
addgroup -> busybox
adduser ->busybox
cp -> busybox
...
In der default config wird Busybox gegen die libc des Compilers gelinkt. Um das _install Verzeichnis als Basis für ein rootfilesystem zu verwenden müssen die Bibliotheken dort vorhanden sein.
abhängige Bibliotheken bestimmen
Sämtliche dynamisch gelinkten binaries brauchen immer ein ld.so (ld-linux.so, ld-2.5.so, ...)
Um für ein vorhandenes binary die verwenden Bibliotheken zu bestimmen kann man
ldd
oder
objdumpp
benutzen. Der Einsatz von ldd ist nur möglich wenn ein entsprechendes cross ldd zur Verfügung steht. Objdump kann mit sämtlichen elf basierten binaries umgehen. (bzw es kann mit allen binaries im selben binärformat wie der host umgehen und das ist üblicherweise elf).
user@host$ objdump -x _install/bin/busybox | grep NEEDED
NEEDED libm.so.6
NEEDED libc.so.6
user@host$ ${CROSS_COMPILE}ldd _install/bin/busybox
libm.so.6 => ELDK/ppc_6xx/lib/libm.so.6
libc.so.6 => ELDK/ppc_6xx/lib/libc.so.6
ld.so.1 => ELDK/ppc_6xx/lib/ld.so.1
Die verwendeten Bibliotheken sind meist symlinks. Es müssen die Quellen der Links kopiert werden. Die entsprechenden symlinks erzeugt später
ldconfig
ldconfig
Bibliotheken liegen im Roofilesystem unter den beiden Standardpfaden
ROOTFS/lib
und
ROOTFS/usr/lib
. Um die zugehörigen Symlinks zu erzeugen wird ldconfig verwendet
Für X86 Systeme kann das ldconfig des Host Systems verwendet werden
user@host$ /sbin/ldconfig -N -r ROOTFS
Die option
-N
verhindert das anlegen eines library caches. Dieser wird nicht benötigt da sich alle Bibliotheken in Standardpfaden befinden. Die Option
-r
sucht Biblotheken relativ zum Rootfilesystem, also nicht im Host System.
Für PPC Systeme muss entweder ein cross-ldconfig (so vorhanden) oder das ldconfig des Zielsystems ausgeführt werden. Ausführung des Zielsystem ldconfigs erfolgt mit dem Emulator qemu
user@host$ qemu-ppc SYSROOT/sbin/ldconfig -N -r ROOTFS
SYSROOT ist das zur cross-compiler toolchain gehörige sysroot Verzeichnis.
Rootfilesystem
Basis des rootfilesystems ist die busybox mit den zugehörigen Bibliotheken. Diese Dokumentation verwendet ROOTFS als Platzhalter für dieses Verzeichnis und SYSROOT als Platzhalter für das zu einem cross-compiler gehörende Systemverzeichnis. Für eldk ist dies
/common/usr/eldk/ppc_6xx
, für ein x86/i386 system kann der host verwendet werden
/
. Bei 64bit hosts ist darauf zu achten, dass die 32bit Bibliotheken verwendet werden, bei Redhat
/lib
bei neueren Debian Systemen
/lib32
.
Bibliotheken der libc werden mit versionsnamen erzeugt. So ist
libdl-2.6.so
die zur libc Version 2.6 gehörende
dl
. Als Platzhalter wird LIBCVERSION verwendet.
Erzeugen grundlegender Verzeichnisse
user@host$ cd ROOTFS
user@host$ mkdir etc # Konfigurationsdateien
user@host$ mkdir proc # mountpoint für proc filesystem
user@host$ mkdir tmp # temporäre Dateien
user@host$ mkdir -p var/run # PID Files werden hier abgelegt
user@host$ mkdir -p var/lock # lockfiles
Die folgenden Bibliotheken sind systemnah und werden von nahezu jedem Programm die auf dem System ausgeführt werden könnte benötigt (ausser der busybox...). Es ist daher sinnvoll sie direkt in das rootfilesystem zu integrieren.
- libdl (dynamic linker)
- libpthreads (Posix Threads)
user@host$ cp -P SYSROOT/lib/libdl-LIBCVERSION.so ROOTFS/lib
user@host$ cp -P SYSROOT/lib/libpthread-LIBCVERSION.so ROOTFS/lib
Damit ist an Programmen (fast) alles vorhanden was für ein minimales Linux benötigt wird. Fehlt die Konfiguration. Aber testweise könnte mit dieser Ramdisk unter (Verwendung von /bin/sh als init) bereits gebootet werden.
Basiskonfiguration Rootfs
Accounts
Hinzufügen von User Accounts
Ein paar Standard Accounts in
ROOTFS/etc/passwd
root:PWHERE:0:0:root:/:/bin/sh
bin:*:1:1:bin:/bin:
daemon:*:2:2:daemon:/sbin:
halt:*:7:0:halt:/sbin:/sbin/halt
nobody:*:99:99:Nobody:/:
Passwort-hash erzeugen mit
user@host$ openssl passwd -1 -salt blahblah
und in
ROOTFS/etc/passwd
eintragen.
Standard Gruppen in
ROOTFS/etc/group
root:*:0:root
bin:*:1:root,bin,daemon
daemon:*:2:root,bin,daemon
sys:*:3:root,bin
tty:*:5:
disk:*:6:root
lp:*:7:daemon
mem:*:8:
kmem:*:9:
nobody:*:99:
Inittab
Linux startet üblicherweise einen Mutterprozess namens init mit der Prozessid 1. Von diesem Prozess werden alle weiteren Anwendungen als Subprozess gestartet. Das in busybox integrierte init wertet die Datei /etc/inittab aus um Subprozesse zu starten. Im Unterschied zu einem normalen init kennt busybox keine runlevel.
Die folgende inittab führt zunächst das script etc/rc.sysinit.sh aus. Dieses wird verwendet um ein frisch gebootetes System einsatzfähig zu machen, also Dateisysteme mounten, Netzwerk konfigurieren, etc. Danach startet auf der Console ein getty um nach Benutzername und Passwort zu fragen. Der Getty Prozess wird automatisch neu gestartet sobald er sich beendet, z.B. durch logout oder crash.
Inhalt von
ROOTFS/etc/inittab
::sysinit:/etc/rc.sysinit.sh
::respawn:/sbin/getty 19200 /dev/console
Damit ein root login auf der console erlaubt ist muss die Datei
ROOTFS/etc/securetty
existieren. Hier rudimentär damit zumindest console, echte tty, und Pseudoterminals (alter BSD und neuer UNIX98 Bauart) funktionieren.
console
tty1
tty2
ttyS0
ttyp0
ttyp1
pts/0
pts/1
...
Alle dem System bekannten Login Shells müssen in
ROOTFS/etc/shells
aufgeführt sein.
/bin/sh
/bin/ash
Eine bash steht nicht zur Verfügung. Ash ist die
http://en.wikipedia.org/wiki/Almquist_shell Almquist-Shell.
Als
ROOTFS/etc/rc.sysinit.sh
sagen wir erstmal nur Hello World
#!/bin/sh
echo "rc.sysinit.sh was here"
Mit dieser Ramdisk bootet der Embedded Rechner, führt rc.sysinit.sh aus und wartet auf der Console auf einen login.
rc.sysinit.sh und Netzwerk
Grundlegene Systemscripts.
ROOFS/etc/rc.sysinit.sh
wird beim booten einmalig ausgeführt. Im Ersten Teil ein paar Konfigurationen
#!/bin/sh
#
# Initialize system
echo "executing /etc/rc.sysinit.sh"
PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
export PATH
# mounting proc
/bin/mount -t proc proc /proc
# mounting rootfs rw, by default mounted ro
/bin/mount -o remount,rw /
# mounting devpts, pseudo terminals
mkdir /dev/pts
/bin/mount -t devpts none /dev/pts
Per DHCP holen wir uns eine Netzwerkadresse. Der in der busybox integrierte client heisst udhcpc. Dieser führt dhcp anfragen aus. erhaltene Antworten werden geparst und als Umgebungsvariablen an ein script übergeben.
Weiter in der
ROOTFS/etc/rc.sysinit.sh
echo "bringing up the network"
# bring up network interfaces
/sbin/ifconfig lo 127.0.0.1
/sbin/ifconfig eth0 up
echo "sleeping a second, to allow interfaces to settle"
sleep 1
echo "running dhcp" # requesting the additional dhcp fields rootpath and tftp
/sbin/udhcpc -Orootpath -Otftp
# this will execute script /usr/share/udchpc/default.script
echo "finished /etc/rc.sysinit.sh"
Das script
ROOTFS/usr/share/udchpc/default.script
führt ein ifconfig aus, speichert die DNS Server in /etc/resolv.conf. Daneben werden noch Netzwerkrouten gesetzt, usw. Aufgrund des Umfangs hier nicht aufgeführt.
Namensauflösung (DNS, userid -> namen, etc) wird durch die libc bereitgestellt. Diese greift auf nsswitch (Nameservice Switch) zu. Nsswitch wiederum verwendet Dateien in
ROOTFS/etc/
und DNS. Und für diese Funktionen werden noch ein paar Bibliotheken benötigt.
user@host$ cp -P SYSROOT/lib/libresolv-LIBCVERSION.so ROOTFS/lib
user@host$ cp -P SYSROOT/lib/libnss_{files,dns}-LIBCVERSION ROOTFS/lib
wie Nummern in Werte umgesetzt werden entnimmt nsswitch der Datei
ROOTFS/etc/nsswitch.conf
passwd: files # accounts und benutzernamen aus /etc/passwd
group: files # gruppennamen aus /etc/groups
hosts: files dns # hostname erst aus /etc/hosts und wenn da nicht, dann dns
protocols: files # ip protokolle in /etc/protocols
services: files # bekannte/benannte ip services in /etc/services
passwd und group haben wir bereits angelegt.
ROOTFS/etc/hosts
127.0.0.1 localhost
den eigenen Hostnamen fügt das dhcp script mit ein.
ROOTFS/etc/protocols
enthält protocol-name zu nummer also tcp = 6, udp = 17...
ip 0 IP # internet protocol, pseudo protocol number
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # internet group multicast protocol
ggp 3 GGP # gateway-gateway protocol
tcp 6 TCP # transmission control protocol
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
idp 22 IDP # WhatsThis?
raw 255 RAW # RAW IP interface
ROOTFS/etc/services
enthält service-name zu nummer also telnet = 23, http = 80...
tcpmux 1/tcp
echo 7/tcp
echo 7/udp
...
telnet 23/tcp
...
http 80/tcp
...
AccAlarm 54321/udp # BEL multicasts
Damit kann der Embedded Rechner sein Netzwerk konfigurieren und danach DNS Namensauflösung.
inetd und telnet
Man kann telnet auch ohne inetd betreiben. Hier wird aber gleich der inetd mit konfiguriert.
ROOTFS/etc/inetd.conf
konfiguriert die Dienste, die vom inetd gestartet werden
telnet stream tcp nowait root /usr/sbin/telnetd /usr/sbin/telnetd -i
Telnet muss als service in ROOTFS/etc/services definiert sein.
Starten von inetd aus der
ROOTFS/etc/inittab
...
::respawn:/usr/sbin/inetd -f /etc/inetd.conf
...
Damit ist der Rechner netzwerkfähig und kann per telnet erreicht werden.