Auch dieser Ansatz hat aber Nachteile: Hoher Aufwand beim Erstellen der Initial RAM-Disk, Overhead durch Blockdevice und Filesystem sowie der schwierige Übergang aus dem kleinen Root-Filesystem der Initial RAM-Disk auf das neu eingehängte Root-Filesystem des Clients. Den Übergang organisiert »pivot_root«, »freeramdisk« gibt am Ende den Speicher der RAM-Disk wieder frei.

Abbildung 5: Die im Artikel vorgestellten Techniken lassen sich durchaus kombinieren. Blockgeräte tragen meist blockbasierte Dateisysteme. Sonderfälle sind Cowloop oder Union-FS, die – basierend auf Blockgeräten oder Dateisystemen – eine neue beschreibbare Schicht bilden.
Early Userspace
Die jüngste Entwicklung namens Early Userspace wird mittelfristig sowohl Kernel-Root auf NFS als auch die Initial RAM-Disk ablösen. Es handelt sich um spezielle Kernelstrukturen (Initram-FS), die ein einheitliches Root-Filesystem abbilden. Es lässt sich beispielsweise als Temp-FS direkt in den Kernel einkompilieren oder (ähnlich wie die Initial RAM-Disk) als CPIO-Archiv auch separat halten und laden.
Für den Übergang zum einzubindenden Root-Filesystem sorgt seit Kernel 2.6.15 nicht mehr »pivot_root«. Es genügt nun, den Mountpoint nach »/« zu verschieben. Die Inhalte, die vorher an dieser Stelle lagen, löscht das kleine Programm »run-init« (zu finden in der Klibc).
Bordmittel allein genügen nicht, um NBD zu verwenden, das spätere Root-Filesystem aus mehreren Teilen zusammenzusetzen und alles mittels Union-FS zu verbinden. Die einzelnen Mount-Vorgänge und das Laden der entsprechenden Module fasst am besten ein Shellskript zusammen (gekürzte Fassung in Listing 3), das den Namen »init« erhält und im Initram-FS steht.
|
Listing 3: |
|---|
01 #!/bin/sh
02
03 # IP-, NFS- und (D)NBD-Parameter aus der Kernel-Kommandozeile lesen
04 for opts in ${KCMDLINE} ; do
05 case ${KCMDLINE} in
06 ip=*)
07 # IP-Konfiguration: client-ip:server-ip:gateway:netmask
08 IPINFO=${opts#ip=};;
09 nfsroot=*)
10 # NFS-Server und Pfad
11 NFSROOT=${opts#nfsroot=};;
12 nbdroot=*)
13 # NBD-Konfiguration: server:port
14 NBD=nbd # name of kernel module
15 NBDOPT=${opts#nbdroot=};;
16 dnbdroot=*)
17 # DNBD-Konfiguration: server:port
18 NBD=dnbd # name of kernel module
19 NBDOPT=${opts#dnbdroot=};;
20 dcsize=*)
21 # RAM-Cache-Größe für DNBD
22 DNBDCACHESIZE=${opts#dcsize=};;
23 esac
24 done
25 RWDIR=/dev/shm
26
27 # Root-Filesystem via NBD/DNBD importieren
28 if [ -n "${NBD}" ] ; then
29 nbdhost=${NBDOPT%:*}
30 nbdopt=${NBDOPT#*:}
31 nbdport=${nbdopt%,*}
32 nbdfs=${nbdopt#$nbdport*}
33 if [ -z "$nbdfs" ]; then
34 RFST=ext2;
35 else
36 RFST=${nbdfs#*,};
37 fi
38 echo "Diskless client using ${NBD} on $nbdhost:$nbdport with $RFST"
39 modprobe ${RFST}
40 case "${NBD}" in
41 nbd)
42 # Network Block Device des Standardkernels
43 sleep 2
44 nbd-client $nbdhost $nbdport /dev/nbd0
45 RDEV=/dev/nbd0;;
46 dnbd)
47 # DNBD (Distributed Network Block Device)
48 if [ -z $nodnbdcache ] ; then # variable not really used yet
49 mkdir /dnbd
50 mount -n -o 'size=$dcsize' -t tmpfs tmpfs ${RWDIR}
51 cat /dev/zero > ${RWDIR}/cache 2>/dev/null
52 clientopt="-c ${RWDIR}/cache"
53 fi
54 sleep 2
55 while ! dnbd-client -b $nbdhost -d /dev/dnbd0 $clientopt ; do
56 sleep 1
57 done
58 RDEV=/dev/dnbd0;;
59 esac
60 RWRO="ro"
61 if [ -n "${COWLOOP}" -a -x /bin/cowdev ] ; then
62 modprobe cowloop
63 echo "Using Copy-on-Write block device for rw access"
64 mount -n -t tmpfs -o size=50% ramfs ${RWDIR}
65 mkdir /dev/cow && cp -a /tmp/ctl /dev/cow
66 ln -s /dev/cowloop0 /dev/cow/0
67 cowdev -a ${RDEV} ${RWDIR}/nbd.cow
68 usleep 10
69 RWRO="rw"
70 RDEV=/dev/cow/0
71 fi
72 sleep 2
73 mount -n -t $RFST -o $RWRO $RDEV /mnt
74
75 # Root-Filesystem via NFS importieren
76 elif [ -n NFS ]
77 modprobe nfs
78 portmap
79 NFSRO=nfs
80 mount -t nfs [...]
81 fi
82
83 # Union-FS aktivieren
84 echo "Using UnionFS for rw access"
85 RWDIR=/dev/shm
86 mkdir -p ${RWDIR}/union ${RWDIR}/uniontmp
87 mount -n -t tmpfs none ${RWDIR}/uniontmp
88 mount -n --move /mnt /root
89 mount -n -t unionfs -o dirs=${RWDIR}/uniontmp=rw:/root=${NFSRO}ro none /mnt
90 mkdir -p /mnt/uniontmp
91 mount -n --move ${RWDIR}/uniontmp /mnt/uniontmp
92 mount -n --move /root /mnt/mnt
93
94 # Pivoting nach neuer Art
95 exec run-init -c dev/console /mnt /sbin/init
|
NFS und NBD/DNBD im Initram-FS
Das Skript muss einige wichtige Konfigurationsdaten erhalten. Wer diese Parameter nicht in einer Datei im Initram-FS speichern möchte, weil er dann für jede bootende Maschine ein eigenes Filesystem-Image braucht, übergibt die Werte am Bootprompt an den Kernel. Hier kann er beliebige Strings hineinschreiben, die der Kernel zwar nicht versteht, die aber das Init-Skript anschließend auswertet. Diese Taktik verwendet beispielsweise auch Knoppix, um Konfigurationsoptionen von der Benutzerauswahl im Bootmenü in das später laufende Linux-System zu transportieren.
Die Zeilen 4 bis 24 von Listing 3 werten die Kommandozeile aus und schreiben die Parameter in Variablen. Der Kernelstring für NFS (»nfsroot= Server:Pfad«) dient als Vorbild für das Format von Root on NBD/DNBD. Bei NBD ist die Angabe der Portnummer Pflicht, die Option ist »nbdroot= Server:Port,Filesystem«. Bei DNBD heißt das Schlüsselwort »dnbdroot=…«. Die Portnummer dahinter darf entfallen, das Komma muss aber bleiben. Zeile 22 liest die erlaubte Größe für das DNBD-Cachefile. Es lassen sich noch weitere Parameter übergeben, etwa für das Diff-File von Cowloop.
Theoretisch könnte auch ein DHCP-Server die Konfigurationsdaten verteilen [11]. Dazu wäre jedoch ein DHCP-Client wie »dhclient« nötig, der benutzerdefinierte Variablen kennt. Bei der IP-Konfiguration im Initram-FS kommt aber meist ein recht simpler Client aus dem Klibc-Paket [15] zum Einsatz.
Ziel-Root
Die Zeilen 28 bis 59 von Listing 3 holen das Root-Filesystem vom Server per NBD oder DNBD. Bei NBD genügt der Aufruf in Zeile 44; DNBD verlangt etwas mehr Arbeit. Der Block in den Zeilen 61 bis 71 verwendet auf Wunsch Cowloop, um das Blockdevice beschreibbar zu machen. Diese Tatsache vermerkt Zeile 69 in der Variablen »RWRO«.




