Depuis que j'ai commencé à étudier Rust, j'ai créé un chargeur de démarrage x86 (auto-proclamé: Krabs) qui parle le protocole de démarrage Linux pendant les vacances d'hiver. Dans cet article, j'aimerais écrire sur la motivation pour le développement, les fonctionnalités et le mécanisme des Krabs que j'ai créés, et ce qui m'a fait plaisir pendant le développement.
Krabs est un chargeur de chaîne avec une configuration de fusée en 4 étapes pour x86 / x86_64 (BIOS hérité) écrit en Rust. Vous pouvez démarrer le noyau au format ELF compressé avec bzip2. Après avoir décompressé l'image compressée bzip2 et réorganisé l'image ELF qui a été extraite ensuite, le noyau démarre. En interne, il utilise la bibliothèque C libbzip2, mais tout le reste est écrit en Rust.
GitHub - ellbrid/krabs: An x86 bootloader written in Rust.
Il présente les caractéristiques suivantes.
Voici un exemple de démarrage de vmlinux 64 bits qui utilise la ligne de commande du noyau et initrd.
./tools/build.sh -k vmlinux -i initramfs.cpio.gz -c "clocksource=tsc" disk.img
qemu-system-x86_64 --hda disk.img -m 1G
Le but est de pratiquer Rust et de vous familiariser avec Linux. Je pensais que le codage au niveau inférieur à la pile du système d'exploitation pourrait être rendu plus moderne en utilisant Rust. Aussi. Du processus au démarrage du noyau Linux, je voulais aussi extraire uniquement le minimum d'essence nécessaire et enfin créer un environnement de démarrage où il n'y a pas de boîte noire pour moi. Fondamentalement, l'objectif était de supprimer les pièces déroutantes et obstruées suivantes.
Dans cet esprit, j'ai abandonné la lecture du bootloader et le suivi du code bzImage, et j'ai décidé d'écrire le bootloader moi-même dans Rust.
Regarder le mécanisme de démarrage du noyau Linux à partir d'un chargeur de démarrage bzImage ou GRUB peut être difficile, mais la réalité est étonnamment simple. La chose fondamentale est deux points. Extraire l'image compressée, extraire l'image au format ELF, la déplacer selon l'en-tête du programme et Le protocole de démarrage Linux / x86 Effectuez le traitement d'initialisation conformément à (/x86/boot.html) et définissez les paramètres. Seulement ça.
Cela peut sembler incroyablement facile, mais spécifiquement, les quatre types de traitement d'initialisation suivants sont effectués.
** Initialisation matérielle: **
** Initialisation du logiciel: **
** Communiquer avec le noyau: **
** Emplacement de l'image: **
Krabs gère le traitement ci-dessus avec un programme divisé en quatre étapes.
0x07C0: 0x0200
et passez à l'adresse 0x07C0: 0x0280
. A la fin de 2 octets de stage1, il y a une zone pour stocker la longueur de secteur (512 octets) du programme stage2. Même Rust peut écrire un chargeur de 1er étage qui tient dans 446 octets!0x07C0: 0x6000
. L'image du noyau compressée bzip2 est chargée à l'adresse de la zone de mémoire étendue 0x0350_0000
et initrd est chargée à 0x0560_0000
. Nous avons utilisé un tampon de piste de 4K octets à partir de l'adresse 0x07C0: 0xEE00
pour transférer ces fichiers. Stockez temporairement ce que vous avez lu sur le disque ici, puis utilisez la fonction BIOS ʻINT 15h
0x87hpour le transférer à l'adresse appropriée. Après avoir chargé les images de noyau stage3, initrd et compressées, passez à l'adresse
0x07C0: 0x6000`. La ligne de commande du noyau se trouve dans la zone de 120 octets à partir de l'adresse «0x280»..bss
. Après une série d'initialisations matérielles et logicielles, préparez les informations de la page zéro de «0x07C0: 0x0000» à «0x07C0: 0x0FFF». Activez la ligne A20, changez le bus d'adresse sur 32 bits et passez en mode protégé. Appelez la fonction de décompression bzip2 pour restaurer l'image du noyau ELF compressée bzip2 à l'adresse mémoire étendue «0x100000» ou version ultérieure. Ensuite, analysez et chargez le fichier ELF32 / ELF64. Si la cible est ELF64, définissez le tableau de la page de démarrage 4G et passez en mode long. Enfin, passez au point d'entrée et démarrez le noyau. À ce stade, définissez l'adresse physique (0x00007C00) des informations Zero Page préparées dans la mémoire inférieure du registre ESI ou RSI.Krabs prend en charge les disques durs et les SSD, mais vous devez avoir un MBR. En outre, l'une des partitions doit avoir l'indicateur de démarrage défini. Stage3,4, kernel et initrd sont stockés dans la partition de démarrage avec le drapeau de démarrage.
Au fait, vous vous demandez peut-être pourquoi il prend en charge le BIOS hérité, alors laissez-moi ajouter ceci. Seul le BIOS hérité est pris en charge cette fois pour les trois raisons suivantes.
Rust est beaucoup plus facile que C pour écrire du code de bas niveau. Telle est mon impression personnelle.
Même si un problème survient, il suffit de douter de la partie «non sûre». Cette fois, c'était vrai.
Vous n'avez pas à être frustré par le fichier objet avec lequel créer un lien, comme C.
Le code de bas niveau de no_std est également relativement moderne. De plus, je n'ai pas à m'inquiéter autant de ne pas pouvoir l'utiliser ou de ne pas pouvoir l'utiliser. C'est un sentiment, mais la vitesse de développement peut être plus rapide que C? Mieux encore, Rust est amusant à écrire. Il est bon d'écrire en pensant au type. Il est également amusant d'utiliser diverses fonctions.
Je t'appellerai. 16 bits / 32 bits peuvent être chargés par le chargeur de chaîne sans trop d'inconvénients. En raison de la structure du chargeur à chaîne, il est nécessaire de décrire la partie non sécurisée pour passer à l'étape suivante, mais il est bon que la technique souvent utilisée en C puisse être utilisée telle quelle dans la partie non sécurisée. J'ai pensé. Cependant, soyez prudent avec les macros et les fermetures à l'intérieur du secteur de démarrage. J'ai l'impression que la taille est facile à exploser.
Je pense qu'il est très merveilleux que FFI puisse utiliser les actifs de C sans trop s'en rendre compte.
J'utilise libbzip2 en interne cette fois, mais c'était facile à utiliser depuis Rust.
Au contraire, il était facile de fournir à C les actifs de Rust. Vous avez besoin de malloc
pour utiliser libbzip2, mais j'ai pu fournir au côté C une implémentation facile dans Rust. /src/stage_4th/src/bz2d.rs#L45).
Pour définir la table des pages, alignez le Linker Script et [struct Attributes]( J'ai essayé de le configurer sur https://doc.rust-lang.org/reference/type-layout.html#representations), mais aucun d'entre eux n'a fonctionné. .. .. (Il semblait que les autres structures de données avaient été corrompues). L'alignement de Rust est laissé tel quel, se demandant si quelque chose ne va pas. En fin de compte, j'ai sécurisé manuellement la zone où je voulais définir le tableau des pages et y solidifié l'adresse. Exemple
Pendant un certain temps après avoir été en mesure de créer Krabs et de démarrer un simple noyau de style ELF, vmlinux n'a pas pu démarrer correctement pour une raison quelconque et le développement s'est arrêté pendant un court laps de temps. Pendant ce temps, j'ai écrit README en anglais, testé des exemples d'opérations simples, pris en charge le mode long et annoncé en anglais sur Twitter. Je me demandais pourquoi cela ne fonctionnait pas tous les jours, et un jour, alors que je chargeais la source de bzImage, j'étais très heureux.
Dans un tweet que j'ai murmuré en japonais avec désinvolture, quel super grand ingénieur d'AWS @msw et de l'arbre du noyau Linux x86 / x86-64 est célèbre comme mainteneur de longue date @LinuxHPA (Hans Peter Anvin --Wikipedia) a répondu et m'a donné des conseils! J'étais si heureuse d'être si heureuse. Grâce à ces conseils, le problème a été résolu aussitôt, et j'ai renouvelé ma détermination à ne pas supporter la spécification multi-boot (je n'aime pas vraiment la spécification multi-boot, qui embarque à l'origine des paramètres dans le noyau).
Cette fois, il a répondu au japonais, mais j'ai été connecté après avoir reçu une réaction de ce qui était initialement annoncé en anglais. J'ai de nouveau pensé qu'il était important de l'envoyer en anglais.
De plus, si vous murmurez en anglais, je pense que la fréquence de réception de DM directement sur Twitter et d'envoi de messages d'assistance par réponse augmentera. Ce sera très encourageant. Je voudrais introduire un message heureux. L'écriture d'un OS dans Rust m'a également envoyé un message.
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
... c'est un gaspillage de finir avec ça, donc pour le plaisir de faire l'expérience de Krabs, j'aimerais couvrir un exemple de construction finale d'un système Linux minimal et de le démarrer avec Krabs.
Je pense que ce sera utile. (Par la suite, je travaille sur CentOS7)
1: Apportez la source Linux.
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: Définissez la configuration Linux.
make allnoconfig
make menuconfig
Utilisez les paramètres suivants.
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
Sinon, j'ai préparé ma configuration recommandée avec ce qui précède déjà défini, alors mettez-le dans .config
. Je vais le copier.
wget https://raw.githubusercontent.com/ellbrid/krabs/master/resources/.config -O .config
make menuconfig
3: Construisez vmlinux.
make vmlinux
4: Vous avez vmlinux
dans votre répertoire actuel.
1: Tout d'abord, créez le répertoire . / Src / initramfs
et construisez le répertoire de base ici.
cd ..
mkdir --parents src/initramfs/{bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}
2: Copiez également les nœuds de périphérique de base.
1. sudo cp --archive /dev/{null,console,tty,tty[0-4],sda,sda[1-8],mem,kmsg,random,urandom,zero} src/initramfs/dev/
3: Utilisez busybox au lieu d'installer une bibliothèque dynamique et de créer un environnement.
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: Préparez le script ʻinit`.
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: Créez des initramfs.
cd src/initramfs
find . | cpio -o -H newc | gzip > ../../initramfs.cpio.gz
1: Créez un fichier image avec qemu-img
. Vous pouvez également utiliser dd
.
qemu-img create disk.img 512M
2: Créez une partition avec 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
Créez un indicateur de démarrage dans la première 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: Créez un système de fichiers ext4 dans la deuxième 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
Exécutez simplement la commande suivante: Cela compressera bzip2 vmlinux et l'écrira sur disk.img avec le chargeur de démarrage.
$ pwd
path/to/krabs
$ ./tools/build.sh -k path/to/vmlinux -i path/to/initramfs.cpio.gz path/to/disk.img
Commençons!
$ qemu-system-x86_64 --hda disk.img -m 1G
Au fait, dans un commentaire de reddit, on m'a demandé pourquoi je n'utilisais pas xz ou gzip. Je n'y ai pas trop réfléchi, mais la raison en est qu'il était facile d'intégrer bzip2. Une autre raison est que j'ai hâte d'entendre les nouvelles concernant le portage de bzip2 sur Rust. Cependant, comme vous pouvez le voir de ceux qui ont essayé l'exemple ci-dessus de démarrage de Minimal Linux, bzip2 est très lent. Par conséquent, il est possible de migrer vers gzip ou xz à l'avenir.
Au fait, j'aime Sponge Bob et emprunte le nom de ce chargeur de démarrage à M. Kani et Plankton. Mon objectif secret est d'utiliser Krabs au lieu de GRUB sur le Linux que je démarre. (Je prévois de commencer à créer mon propre système d'exploitation nommé sponge).
Je vise également à créer une AMI Linux blague originale en utilisant ce chargeur de démarrage sur Amazon EC2 (également connu sous le nom de Sandy Linux AMI lol). Je me suis demandé si je devais utiliser le systemd [rustysd] de Rust (https://github.com/KillingSpark/rustysd) pour init pour Rust's coreutils. penser. Est-ce que ssh est cool avec this? Vos rêves se répandront.
De plus, j'ai trouvé que la star sur github était vraiment sympa, alors j'ai décidé de jouer de plus en plus à partir de maintenant.
Merci d'avoir lu jusqu'au bout. Si vous avez des likes, des commentaires, des conseils, des problèmes, des pull requests, etc., n'hésitez pas à nous contacter.