In diesem Post wird gezeigt, wie man einen Raspberry Pi Miniatur-Computer mit einem Root-Filesystem ausstattet, das auf zwei gespiegelten USB-Sticks liegt.
Der Raspberry Pi (Modell B) wird mit einem SD-Karten-Leser und zwei USB-Ports geliefert. Das Betriebssystem Raspbian, eine Variante von Debian, wird als Image heruntergeladen und auf eine SD-Karte kopiert. Diese enthält danach zwei Partitionen, eine für das /boot- und eine für das /-Filesystem. Nach dem Booten zeigt sich folgendes Bild:
root@raspberrypi:~# df
Filesystem 1K-blocks Used Available Use% Mounted on
rootfs 15118600 4141560 10209048 29% /
/dev/root 15118600 4141560 10209048 29% /
tmpfs 23656 312 23344 2% /run
tmpfs 5120 0 5120 0% /run/lock
tmpfs 47312 0 47312 0% /tmp
tmpfs 10240 0 10240 0% /dev
tmpfs 47312 0 47312 0% /run/shm
/dev/mmcblk0p1 57288 41192 16096 72% /boot
root@raspberrypi:~# ls -l /dev/root
lrwxrwxrwx 1 root root 3 Aug 30 21:17 /dev/root -> mmcblk0p2
Das /boot-Filesystem mit dem Kernel kernel.img kann nirgendwo anders liegen als in der ersten Partition der SD-Karte. Das /-Filesystem hingegen kann sich auf einem beliebigen Datenträger befinden. In einem ersten Schritt zeige ich, wie man es auf einen USB-Stick legt.
Beim Einstecken der Sticks wird man in /var/log/messages sehen, dass es ein neues Device /dev/sda gibt.
Aug 29 17:17:14 raspberrypi kernel: [ 7.784278] usb 1-1.3.7: New USB device found, idVendor=1f75, idProduct=0916
Aug 29 17:17:14 raspberrypi kernel: [ 7.802488] usb 1-1.3.7: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Aug 29 17:17:14 raspberrypi kernel: [ 7.817159] usb 1-1.3.7: Product: USB Device
Aug 29 17:17:14 raspberrypi kernel: [ 7.825269] usb 1-1.3.7: Manufacturer: innostor
Aug 29 17:17:14 raspberrypi kernel: [ 7.832820] usb 1-1.3.7: SerialNumber: 201205300000131
Aug 29 17:17:14 raspberrypi kernel: [ 7.852816] scsi0 : usb-storage 1-1.3.7:1.0
Aug 29 17:17:14 raspberrypi kernel: [ 8.863677] scsi 0:0:0:0: Direct-Access innostor USB 3.0 1.00 PQ: 0 ANSI: 6
Aug 29 17:17:14 raspberrypi kernel: [ 8.894315] sd 0:0:0:0: [sda] 30720000 512-byte logical blocks: (15.7 GB/14.6 GiB)
Aug 29 17:17:14 raspberrypi kernel: [ 8.921697] sd 0:0:0:0: [sda] Write Protect is off
Aug 29 17:17:14 raspberrypi kernel: [ 9.011710] sda:
Aug 29 17:17:14 raspberrypi kernel: [ 9.077682] sd 0:0:0:0: [sda] Attached SCSI removable disk
Der Stick kann also unter dem Namen /dev/sda angesprochen werden.
Mit gdisk (nicht mit fdisk!) wird eine Partition auf dem Stick angelegt. Beim ersten Aufruf erscheint möglicherweise folgende Meldung:
root@raspberrypi:~# gdisk /dev/sda
GPT fdisk (gdisk) version 0.5.1
Partition table scan:
MBR: MBR only
BSD: not present
APM: not present
GPT: not present
***************************************************************
Found invalid GPT and valid MBR; converting MBR to GPT format.
THIS OPERATON IS POTENTIALLY DESTRUCTIVE! Exit by typing 'q' if
you don't want to convert your MBR partitions to GPT format!
***************************************************************
Das geht in Ordnung, da der Stick mit dem GPT-Schema partitioniert werden soll. (GUID Partition Table hat gegenüber dem MBR-Partitionsschema den Vorteil, dass Partitionen eine eindeutige GUID bekommen)
Mit dem d-Kommando werden evt. vorhandene Partitionen dann gelöscht und mit dem n-Kommando eine neue angelegt.
Beim Speichern mit w erscheint wieder eine Warnung, die ebenfalls mit Y beantwortet werden kann.
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
MBR PARTITIONS!! THIS PROGRAM IS BETA QUALITY AT BEST. IF YOU LOSE ALL YOUR
DATA, YOU HAVE ONLY YOURSELF TO BLAME IF YOU ANSWER 'Y' BELOW!
Do you want to proceed, possibly destroying your data? (Y/N)
Das Wichtige ist jetzt, dass die Partition /dev/sda1 eine eindeutige UID bekommen hat. Mit dem Kommando i von gdisk lässt man sie sich anzeigen:
Command (? for help): i Using 1 Partition GUID code: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (Linux/Windows data) Partition unique GUID: 81E5BB0B-424E-4C19-8E7C-E678CBB3A588 First sector: 2048 (at 1024.0 KiB) Last sector: 7821278 (at 3.7 GiB) Partition size: 7819231 sectors (3.7 GiB) Attribute flags: 0000000000000000 Partition name: 'Linux/Windows data'
Auf der neuen Partition wird nun ein Filesystem erstellt, in das die Inhalte des derzeitigen Root-Filesystems hineinkopiert werden.
root@raspberrypi:~# mke2fs -t ext4 -L rootfs /dev/sda1
root@raspberrypi:~# mount /dev/sda1 /mnt
root@raspberrypi:~# rsync -axv / /mnt
Das neue Filesystem hat ebenfalls eine eindeutige ID erhalten. Man lässt sie sich mit dem tune2fs-Befehl anzeigen.
root@raspberrypi:~# tune2fs -l /dev/sda1 tune2fs 1.42.5 (29-Jul-2012) Filesystem volume name: rootfs Last mounted on: /mnt Filesystem UUID: 3ef6e847-75d2-4a31-a895-239ffe23a03c ...
Diese UUID ist auf keinen Fall zu verwechseln mit der GUID der Partition!
In /dev/disk/by-uuid erscheint z.B. die Filesystem-UUID. IMHO wäre es logischer, wenn hier die Partitions-GUID aufgeführt wären.
In /root/cmdline.txt befinden sich die Parameter, die dem Kernel beim Booten eines Raspberry Pi mitgegeben werden. Defaultmässig steht folgende Zeile drin:
dwc_otg.lpm_enable=0 rpitestmode=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait
/dev/mmcblk0p2 ist die zweite Partition der SD-Karte. Man ändert jetzt den Parameter root=, damit das neue Root-Filesystem auf dem Stick gemountet wird.
dwc_otg.lpm_enable=0 rpitestmode=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/sda1 rootfstype=ext4 rootwait
Eine Sache gibt es hierbei aber zu beachten. Wenn man einen zweiten USB-Stick anschliesst, lässt sich nicht vorhersagen, welcher der beiden nach einem Reboot /dev/sda und welcher /dev/sdb wird. In so einem Fall ist es besser, die GUID der Partition zu verwenden:
dwc_otg.lpm_enable=0 rpitestmode=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=PARTUUID=81E5BB0B-424E-4C19-8E7C-E678CBB3A588 rootfstype=ext4 rootwait
Damit wird die Root-Partition immer erkannt, egal wieviele Sticks angeschlossen wurden und wie die Reihenfolge der /dev/sd*-Einträge lautet.
Unter Umständen wird nach dem Einschalten der Kernel gestartet, noch bevor sich die USB-Devices gemeldet haben. Hängt man ein “rootdelay=5” an die Parameterliste, dann wird eine Pause von 5 Sekunden eingelegt, bevor versucht wird, das Rootfilesystem zu mounten. Bis dahin sollte sich der USB-Stick initialisiert haben.
Der Ordnung halber wird noch die /etc/fstab angepasst. Das Rootfilesystem bzw. die Device-Spalte kann auf eine der drei folgenden Arten angegeben werden:
/dev/sda1 / ext4 defaults,noatime,async 0 0
LABEL=rootfs / ext4 defaults,noatime,async 0 0
UUID=3ef6e847-75d2-4a31-a895-239ffe23a03c / ext4 defaults,noatime,async 0 0
Wie schon angemerkt, ist die Methode 1 nur dann angebracht, wenn nicht mehr als ein USB-Stick oder -Platte angeschlossen sind. Bei Methode 2 ist darauf zu achten, dass nur eins der existierenden Filesysteme das Label rootfs tragen darf. Methode 3 ist zwar am wenigsten leserlich, aber dafür am zuverlässigsten.
Wer sich eine Enttäuschung ersparen will, soll hier aufhören zu lesen und mit seinem Rootfilesystem auf USB glücklich sein. Wer wissen will, warum, der soll ans Ende dieses Postings springen. Wer schmerzbefreit ist, soll weitermachen, sich aber hinterher nicht beklagen.
Im nächsten Abschnitt wird das Rootfilesystem auf einen Spiegel, bestehend aus zwei USB-Sticks, installiert. Dazu muss zunächst ein neuer Kernel gebaut werden, der Raid bzw. MD-Devices ohne Zuhilfenahme einer Init-Ramdisk beherrscht. Also erstmal ein Abstecher in Richtung Kernel/Firmware-Updates und -Selbercompilieren.
Auch ohne diese Raid- und USB-Spielereien kann es nicht schaden, immer wieder mal den von Raspberry gebauten Kernel zu aktualisieren.
root@raspberrypi:~# mkdir raspberry
root@raspberrypi:~# cd raspberry
root@raspberrypi:~# git clone --depth 1 git://github.com/raspberrypi/firmware.git
root@raspberrypi:~# cd firmware/boot
root@raspberrypi:~# cp arm128_start.elf arm192_start.elf arm224_start.elf arm240_start.elf bootcode.bin loader.bin start.elf /boot
In den .elf-Dateien steckt die Firmware für die GPU. Sie entscheiden darüber, wieviel Speicher für die CPU und somit für Linux reserviert wird. Je nachdem, welche dieser Dateien nach start.elf kopiert wird, sind das 128, 192, 224 oder 240 Megabytes. Für das Kompilieren des Kernels sollte man sich so viel Speicher wie möglich sichern. Das geschieht mit
cp /boot/arm240_start.elf /boot/start.elf
Nach einem Reboot hat der Linux-Kernel den grösstmöglichen Anteil am eh schon knapp bemessenen Hauptspeicher.
Nun werden die Kernelsourcen heruntergeladen und so konfiguriert, dass der MD-Driver und Raid0 fest eincompiliert werden.
root@raspberrypi:~# cd raspberry
root@raspberrypi:~# git clone --depth 1 git://github.com/raspberrypi/linux.git
root@raspberrypi:~# cd linux
root@raspberrypi:~# zcat /proc/config.gz > .config
root@raspberrypi:~# make menuconfig
Im Schritt “make menuconfig” hangelt man sich durch folgende Menüpunkte:
Device Drivers —> Multiple devices driver support (RAID and LVM) —> RAID support
.config - Linux/arm 3.2.27 Kernel Configuration
──────────────────────────────────────────────────────────────────────────────
┌──────────── Multiple devices driver support (RAID and LVM) ─────────────┐
│ Arrow keys navigate the menu. <Enter> selects submenus --->. │
│ Highlighted letters are hotkeys. Pressing <Y> includes, <N> excludes, │
│ <M> modularizes features. Press <Esc><Esc> to exit, <?> for Help, </> │
│ for Search. Legend: [*] built-in [ ] excluded <M> module < > │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ --- Multiple devices driver support (RAID and LVM) │ │
│ │ {*} RAID support │ │
│ │ [*] Autodetect RAID arrays during kernel boot │ │
│ │ < > Linear (append) mode │ │
│ │ <M> RAID-0 (striping) mode │ │
│ │ {*} RAID-1 (mirroring) mode │ │
│ │ < > RAID-10 (mirrored striping) mode │ │
│ │ {M} RAID-4/RAID-5/RAID-6 mode │ │
Wichtig ist hier, dass bei “Raid Support” und “Raid-1” jeweils der Stern angewählt wird. Nach dem Abspeichern der Konfiguration werden dann der neue Kernel und die Module gebaut. Das kann etliche Stunden dauern.
root@raspberrypi:~# make
root@raspberrypi:~# make modules
root@raspberrypi:~# cp arch/arm/boot/Image /boot/kernel.img
root@raspberrypi:~# make ARCH=arm modules_install INSTALL_MOD_PATH=/
Nach einem Reboot kann man prüfen, ob der neue Kernel mit Raid-1 umgehen kann:
root@raspberrypi:~# dmesg|grep raid
[ 2.148591] md: raid1 personality registered for level 1
...
Als nächstes wird der zweite USB-Stick eingesteckt. Wenn man mit tail -f /var/log/messages die Systemmeldungen mitliest, sieht man, dass er unter /dev/sdb angesprochen werden kann. Da er ein Zwillingsbruder des ersten Sticks sein soll, muss dessen Partitionstabelle auf ihn übertragen werden.
root@raspberrypi:~# sgdisk --replicate /dev/sdb /dev/sda
The operation has completed successfully.
root@raspberrypi:~# sgdisk --randomize-guids /dev/sdb
The operation has completed successfully.
root@raspberrypi:~# sgdisk --typecode=1:fd00 /dev/sdb
Mit dem zweiten Kommando wurde für die Partition sdb1 eine neue GUID generiert, da sie beim Replizieren unverändert vom sda1 übernommen wurde. Abschliessend wird der Partitionstyp noch auf “fd00 = Linux RAID” gesetzt.
Mit dem mdadm-Befehl wird nun ein neues Device angelegt, das zunächst nur aus einer Spiegelhälfte besteht:
root@raspberrypi:~# mdadm --create /dev/md0 --level 1 --raid-devices=2 --metadata=0.90 missing /dev/sdb1
Auf dem Raid1-Device wird dann ein Filesystem angelegt, gemountet und mit dem Inhalt des bisherigen Rootfilesystems befüllt:
root@raspberrypi:~# mke2fs -t ext4 /dev/md0
root@raspberrypi:~# mount /dev/md0 /mnt
root@raspberrypi:~# rsync -axv / /mnt
Anschliessend wird das System so vorbereitet, dass es beim Booten den Spiegel als Rootfilesystem mountet. Dazu ändert man /boot/cmdline.txt folgendermassen:
dwc_otg.lpm_enable=0 rpitestmode=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/md0 rootfstype=ext4 rootwait rootdelay=5
Auch in der /etc/fstab wird /dev/md0 als Device für das Rootfilesystem eingetragen.
Mit dem Kommando “poweroff” schaltet man den Raspberry Pi nun ab und zieht den ersten Stick (mit dem bisherigen Rootfilesystem auf sda1) ab.
Nach einem Reboot sollte nun der Spiegel unter / gemountet sein.
root@raspberrypi:~# df
Filesystem 1K-blocks Used Available Use% Mounted on
rootfs 15118600 4141560 10209048 29% /
/dev/root 15118600 4141560 10209048 29% /
tmpfs 23656 312 23344 2% /run
tmpfs 5120 0 5120 0% /run/lock
tmpfs 47312 0 47312 0% /tmp
tmpfs 10240 0 10240 0% /dev
tmpfs 47312 0 47312 0% /run/shm
/dev/mmcblk0p1 57288 41192 16096 72% /boot
root@raspberrypi:~# ls -l /dev/root
lrwxrwxrwx 1 root root 3 Aug 30 22:11 /dev/root -> md0
Nun wird der Stick Nr.1 wieder eingesteckt. Diesmal wird er unter dem Namen /dev/sdb in /var/log/messages auftauchen. Nachdem auch hier der Partitionstyp auf “Linux RAID” geändert wurde, kann der Spiegel vervollständigt werden:
root@raspberrypi:~# sgdisk --typecode=1:fd00 /dev/sdb
root@raspberrypi:~# mdadm --manage /dev/md0 --add /dev/sdb1
In der Datei /proc/mdstat kann nun beobachtet werden, wie sich die beiden Spiegelhälften synchronisieren:
root@raspberrypi:~# cat /proc/mdstat
Personalities : [raid1]
md0 : active raid1 sdb1[2] sda1[1]
15359872 blocks [2/1] [_U]
[>....................] recovery = 0.3% (60032/15359872) finish=72.1min speed=3531K/sec
Und nun die schlechte Nachricht. Das ganze Zeugs läuft nicht stabil.
Aug 31 17:11:44 raspberrypi kernel: [ 149.862492] usb 1-1.3.4: reset high-speed USB device number 7 using dwc_otg
Aug 31 17:11:58 raspberrypi kernel: [ 163.743122] usb 1-1.3.7: reset high-speed USB device number 15 using dwc_otg
Aug 31 17:12:28 raspberrypi kernel: [ 194.584437] usb 1-1.3.7: reset high-speed USB device number 15 using dwc_otg
Aug 31 17:12:59 raspberrypi kernel: [ 225.045706] usb 1-1.3.7: reset high-speed USB device number 15 using dwc_otg
...
Ständig gibt es USB-Ausfälle und eine Hälfte des Spiegels verschwindet. Ich habe es sowohl mit USB3.0 als auch mit USB2.0-Sticks probiert. Gleiches Ergebnis. Nach ein paar Sekunden hängt “cat /proc/mdstat” und irgendwann bleibt es bei
Personalities : [raid1]
md0 : active raid1 sdb1[2](F) sda1[1]
3909504 blocks [2/1] [_U]
Etliche Versuche mit erweiterten Parametern für den dwc_otg-Treiber haben nichts gebracht. Die Hardware des Raspberry Pi ist wohl zu schwachbrüstig für so eine Installation. Auch das Abgleichen der beiden Spiegelhälften an einem anderen Rechner und Einbauen in den Raspberry Pi in synchronisiertem Zustand hat nichts geholfen.
Tut mir leid, bessere Nachrichten habe ich nicht. Ich bin jetzt selber sauer, dass es nicht funktioniert hat, aber das Geschriebene lasse ich trotzdem mal stehen. Vielleicht kann ja der eine oder andere ein paar Informationen davon brauchen.
Fazit: hochscrollen und mit einem einzelnen USB-Stick glücklich sein.