Seit ich Rust studiere, habe ich einen x86-Bootloader (selbsternannt: Krabs) erstellt, der in den Winterferien das Linux-Bootprotokoll spricht. In diesem Artikel möchte ich über die Motivation für die Entwicklung, die Merkmale und Mechanismen der von mir erstellten Krabben und darüber, worüber ich mich während der Entwicklung gefreut habe, schreiben.
Krabs ist ein Kettenlader mit einer 4-stufigen Raketenkonfiguration für x86 / x86_64 (Legacy BIOS), geschrieben in Rust. Sie können den mit bzip2 komprimierten Kernel im ELF-Format starten. Nachdem Sie das komprimierte bzip2-Image dekomprimiert und das als nächstes extrahierte ELF-Image neu angeordnet haben, wird der Kernel gestartet. Intern wird die Bibliothek libbzip2 C verwendet, aber alles andere ist in Rust geschrieben.
GitHub - ellbrid/krabs: An x86 bootloader written in Rust.
Es hat die folgenden Funktionen.
Das folgende Beispiel zeigt das Booten von 64-Bit-VMlinux unter Verwendung der Kernel-Befehlszeile und von initrd.
./tools/build.sh -k vmlinux -i initramfs.cpio.gz -c "clocksource=tsc" disk.img
qemu-system-x86_64 --hda disk.img -m 1G
Ziel ist es, Rust zu üben und sich mit Linux vertraut zu machen. Ich dachte, dass die Codierung auf der Ebene unterhalb des Betriebssystemstapels durch die Verwendung von Rust moderner gestaltet werden könnte. Ebenfalls. Vom Prozess bis zum Booten des Linux-Kernels wollte ich auch nur die minimal notwendige Essenz extrahieren und schließlich eine Boot-Umgebung erstellen, in der es für mich keine Black Box gibt. Grundsätzlich bestand das Ziel darin, die folgenden verwirrenden und verstopften Teile zu entfernen.
In diesem Sinne gab ich das Lesen des Bootloaders und das Verfolgen des bzImage-Codes auf und beschloss, den Bootloader selbst in Rust zu schreiben.
Es kann schwierig sein, den Linux-Kernel-Bootmechanismus von einem bzImage- oder GRUB-Bootloader aus zu betrachten, aber die Realität ist überraschend einfach. Das Grundlegende sind zwei Punkte. Extrahieren Sie das komprimierte Image, extrahieren Sie das ELF-Format-Image, verschieben Sie es gemäß dem Programm-Header und Das Linux / x86-Boot-Protokoll. Führen Sie die Initialisierungsverarbeitung gemäß (/x86/boot.html) durch und legen Sie die Parameter fest. Nur das.
Es mag unglaublich einfach erscheinen, aber speziell werden die folgenden vier Arten der Initialisierungsverarbeitung ausgeführt.
** Hardware-Initialisierung: **
** Software-Initialisierung: **
** Kommunikation mit dem Kernel: **
** Bildplatzierung: **
Krabs erledigt die obige Verarbeitung mit einem Programm, das in vier Stufen unterteilt ist.
Krabs unterstützt Festplatten und SSDs, Sie müssen jedoch über einen MBR verfügen. Außerdem muss für eine der Partitionen das Startflag gesetzt sein. Stage3,4, Kernel und initrd werden mit dem Bootflag in der Boot-Partition gespeichert.
Übrigens fragen Sie sich vielleicht, warum es das ältere BIOS unterstützt. Lassen Sie mich dies hinzufügen. Dieses Mal wird aus den folgenden drei Gründen nur das ältere BIOS unterstützt.
Rust ist viel einfacher als C, um Code auf niedriger Ebene zu schreiben. Das ist mein persönlicher Eindruck.
Selbst wenn ein Problem auftritt, müssen Sie wirklich nur an dem "unsicheren" Teil zweifeln. Diesmal war das wahr.
Sie müssen nicht frustriert sein, mit welcher Objektdatei Sie wie C verknüpfen möchten.
Der Low-Level-Code von no_std ist ebenfalls relativ modern. Außerdem muss ich mir nicht so viele Sorgen machen, dass ich es nicht benutzen kann oder nicht benutzen kann. Es ist ein Gefühl, aber die Entwicklungsgeschwindigkeit kann schneller als C sein? Das Beste ist, dass es Spaß macht, Rust zu schreiben. Es ist gut, mit dem Typ zu schreiben. Es macht auch Spaß, verschiedene Funktionen zu nutzen.
Ich werde dich anrufen. 16bit / 32bit können vom Kettenlader ohne große Unannehmlichkeiten geladen werden. Aufgrund der Struktur des Kettenladers ist es notwendig, den unsicheren Teil zu beschreiben, um zur nächsten Stufe zu gelangen. Es ist jedoch gut, dass die in C häufig verwendete Technik genauso verwendet werden kann wie im unsicheren Teil. Ich dachte. Seien Sie jedoch vorsichtig mit Makros und Schließungen im Bootsektor. Ich habe das Gefühl, dass die Größe leicht zu explodieren ist.
Ich finde es sehr wunderbar, dass FFI die Vermögenswerte von C ohne viel Bewusstsein nutzen kann.
Dieses Mal verwende ich libbzip2 intern, aber es war einfach von Rust aus zu verwenden.
Im Gegenteil, es war einfach, C mit Rusts Vermögen zu versorgen. Sie benötigen malloc
, um libbzip2 zu verwenden, aber ich konnte der C-Seite eine einfache Implementierung in Rust bieten. /src/stage_4th/src/bz2d.rs#L45).
Richten Sie zum Festlegen der Seitentabelle das Linker-Skript und die [Strukturattribute]( Ich habe versucht, es unter https://doc.rust-lang.org/reference/type-layout.html#representations festzulegen, aber keiner von ihnen hat funktioniert. .. .. (Es sah so aus, als wären die anderen Datenstrukturen beschädigt). Rusts Ausrichtung bleibt unverändert und fragt sich, ob etwas nicht stimmt. Am Ende habe ich den Bereich, in dem ich die Seitentabelle festlegen wollte, manuell gesichert und die Adresse dort verfestigt. Beispiel
Für einige Zeit, nachdem ich Krabs erstellen und einen einfachen Kernel im ELF-Stil booten konnte, konnte vmlinux aus irgendeinem Grund nicht richtig booten und die Entwicklung wurde für kurze Zeit gestoppt. Während dieser Zeit schrieb ich README auf Englisch, testete einfache Betriebsbeispiele, unterstützte den Langmodus und bewarb auf Englisch auf Twitter. Ich fragte mich, warum es nicht jeden Tag funktionierte und eines Tages, als ich die Quelle von bzImage lud, passierte etwas sehr Glückliches.
In einem Tweet, den ich beiläufig auf Japanisch murmelte, ist ein super großer Ingenieur von AWS @msw und x86 / x86-64 Linux-Kernelbaum als langjähriger Betreuer [@LinuxHPA] bekannt. (https://twitter.com/LinuxHPA) (Hans Peter Anvin - Wikipedia) antwortete und gab mir Ratschläge! Ich war so glücklich, dass ich so glücklich war. Dank dieses Ratschlags wurde das Problem sofort gelöst und ich erneuerte meine Entschlossenheit, die Multi-Boot-Spezifikation nicht zu unterstützen (die Multi-Boot-Spezifikation, die ursprünglich Parameter in den Kernel einbettet, gefällt mir nicht wirklich).
Diesmal antwortete er auf Japanisch, aber ich war verbunden, nachdem ich eine Reaktion von dem erhalten hatte, was ursprünglich auf Englisch beworben wurde. Ich dachte noch einmal, dass es wichtig ist, es auf Englisch zu senden.
Wenn Sie auf Englisch murmeln, wird sich die Häufigkeit des Empfangs von DMs direkt auf Twitter und des Versendens von Supportnachrichten per Antwort erhöhen. Es wird sehr ermutigend sein. Ich möchte eine fröhliche Nachricht vorstellen. Das Schreiben eines Betriebssystems in Rust hat mir auch eine Nachricht gesendet.
i will study this to understand better low level programing and try "upgrading " templeOS
— rotten lung (@satanacio666) February 8, 2020
Very cool seeing @rustlang being used to modernize the lower levels of the OS stack https://t.co/CpXXoWDt2t
— Dino A. Dai Zovi (@dinodaizovi) February 8, 2020
Awesome! I will definitely check it out when I have some time.
— Philipp Oppermann (@phil_opp) February 4, 2020
... es ist eine Verschwendung, damit zu enden. Um Krabs kennenzulernen, möchte ich ein Beispiel für das endgültige Erstellen eines minimalen Linux-Systems und das Booten mit Krabs behandeln.
Ich denke es wird hilfreich sein. (Im Folgenden arbeite ich an CentOS7)
1: Bringen Sie die Linux-Quelle mit.
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.5.2.tar.gz
tar xf linux-5.5.2.tar.gz
cd linux-5.5.2
2: Stellen Sie die Linux-Konfiguration ein.
make allnoconfig
make menuconfig
Verwenden Sie die folgenden Einstellungen.
64-bit kernel ---> yes
General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support ---> yes
General setup ---> Configure standard kernel features ---> Enable support for printk ---> yes
Executable file formats / Emulations ---> Kernel support for ELF binaries ---> yes
Executable file formats / Emulations ---> Kernel support for scripts starting with #! ---> yes
Enable the block layer ---> yes
Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev ---> yes
Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs ---> yes
Device Drivers ---> Character devices ---> Enable TTY ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port ---> yes
Device Drivers ---> Block devices ---> yes
Device Drivers ---> PCI Support --> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Intel ESB, ICH, PIIX3, PIIX4 PATA/SATA support ---> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Generic ATA support ---> yes
Device Drivers ---> SCSI device support ---> SCSI disk support
File systems ---> The Extended 4 (ext4) filesystem ---> yes
File systems ---> Pseudo filesystems ---> /proc file system support ---> yes
File systems ---> Pseudo filesystems ---> sysfs file system support ---> yes
Alternativ habe ich meine empfohlene Konfiguration vorbereitet, wobei das oben Gesagte bereits festgelegt wurde. Geben Sie dies also in ".config" ein. Ich werde es kopieren.
wget https://raw.githubusercontent.com/ellbrid/krabs/master/resources/.config -O .config
make menuconfig
3: Erstellen Sie vmlinux.
make vmlinux
4: Sie haben vmlinux
in Ihrem aktuellen Verzeichnis.
1: Erstellen Sie zuerst das Verzeichnis . / Src / initramfs
und erstellen Sie hier das Basisverzeichnis.
cd ..
mkdir --parents src/initramfs/{bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}
2: Kopieren Sie auch die grundlegenden Geräteknoten.
1. sudo cp --archive /dev/{null,console,tty,tty[0-4],sda,sda[1-8],mem,kmsg,random,urandom,zero} src/initramfs/dev/
3: Verwenden Sie Busybox, anstatt eine dynamische Bibliothek zu installieren und eine Umgebung zu erstellen.
curl -L 'https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64' > src/initramfs/bin/busybox
sudo chmod +x src/initramfs/bin/busybox
./src/initramfs/bin/busybox --list | sed 's:^:src/initramfs/bin/:' | xargs -n 1 ln -s busybox
4: Bereiten Sie das Init-Skript vor.
cat >> src/initramfs/init << EOF
#!/bin/sh
mount -t devtmpfs devtmpfs /dev
mount -t proc proc /proc
mount -t sysfs sysfs /sys
sleep 2
cat <<END
Boot took $(cut -d' ' -f1 /proc/uptime) seconds
_____ _ __ _
| __|___ ___ _| |_ _ | | |_|___ _ _ _ _
|__ | .'| | . | | | | |__| | | | |_'_|
|_____|__,|_|_|___|_ | |_____|_|_|_|___|_,_|
|___|
Welcome to Sandy Linux
END
exec sh
EOF
sudo chmod +x src/initramfs/init
5: Erstellen Sie initramfs.
cd src/initramfs
find . | cpio -o -H newc | gzip > ../../initramfs.cpio.gz
1: Erstellen Sie eine Bilddatei mit qemu-img
. Sie können auch dd
verwenden.
qemu-img create disk.img 512M
2: Erstellen Sie eine Partition mit fdisk
.
1st partition:
Command (m for help): n
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-1048575, default 2048): 2048
Last sector, +sectors or +size{K,M,G} (2048-1048575, default 1048575): 206848
Partition 1 of type Linux and of size 100 MiB is set
Erstellen Sie ein Boot-Flag in der ersten Partition:
Command (m for help): a
Selected partition 1
2nd partition:
Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p): p
Partition number (2-4, default 2):
First sector (206849-1048575, default 208896):
Using default value 208896
Last sector, +sectors or +size{K,M,G} (208896-1048575, default 1048575):
Using default value 1048575
Partition 2 of type Linux and of size 410 MiB is set
write out:
Command (m for help): w
The partition table has been altered!
Syncing disks.
3: Erstellen Sie ein ext4-Dateisystem in der zweiten Partition
$ sudo kpartx -av disk.img
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sr0 11:0 1 1024M 0 rom
loop0 7:0 0 512M 0 loop
├─loop0p1 253:2 0 100M 0 part
└─loop0p2 253:3 0 410M 0 part
$ sudo mkfs.ext4 /dev/mapper/loop0p2
$ sudo kpartx -d disk.img
Führen Sie einfach den folgenden Befehl aus: Dadurch komprimiert bzip2 vmlinux und schreibt es zusammen mit dem Bootloader auf disk.img.
$ pwd
path/to/krabs
$ ./tools/build.sh -k path/to/vmlinux -i path/to/initramfs.cpio.gz path/to/disk.img
Lasst uns beginnen!
$ qemu-system-x86_64 --hda disk.img -m 1G
Übrigens wurde ich in einem Kommentar von reddit gefragt, warum ich weder xz noch gzip verwendet habe. Ich habe nicht zu viel darüber nachgedacht, aber der Grund ist, dass es einfach war, bzip2 einzubringen. Ich habe auch Neuigkeiten über die Portierung von bzip2 nach Rust gehört, was einer der Gründe ist, warum ich mich darauf freue. Wie Sie jedoch denjenigen entnehmen können, die das obige Beispiel für das Booten von Minimal Linux ausprobiert haben, ist bzip2 sehr langsam. Daher besteht die Möglichkeit, in Zukunft auf gzip oder xz zu migrieren.
Übrigens mag ich Sponge Bob und leihe mir den Namen dieses Bootloaders von Mr. Kani und Plankton aus. Mein geheimes Ziel ist es, Krabs anstelle von GRUB auf dem Linux zu verwenden, das ich boote. (Ich plane, mein eigenes Betriebssystem namens Sponge zu erstellen).
Ich möchte mit diesem Bootloader auf Amazon EC2 (auch bekannt als Sandy Linux AMI lol) auch einen originellen Linux-AMI-Witz erstellen. Ich fragte mich, ob ich Rusts systemd rustysd für init für Rusts coreutils verwenden sollte. nachdenken über. Ist ssh cool mit this? Deine Träume werden sich verbreiten.
Außerdem fand ich, dass der Stern auf Github wirklich schön war, also beschloss ich, von nun an immer mehr zu spielen.
Vielen Dank für das Lesen bis zum Ende. Wenn Sie Likes, Kommentare, Ratschläge, Probleme, Pull-Anfragen usw. haben, zögern Sie bitte nicht, uns zu kontaktieren.
GitHub - ellbrid/krabs: An x86 bootloader written in Rust.
Recommended Posts