Laboratorul 08. RootFS Bootstrapping

Crearea unei distribuții Linux

Până acum am folosit Root Filesystem de-a gata pre-instalat.

La acest laborator, dorim să învățăm procesul de generare a unui sistem de bază și modalitățile de personalizare a acestuia, proces numit rootfs bootstrapping.

Avem 2 variante:

  • plecarea de la o distribuție existentă și alegerea pachetelor pe care să le includem + configurații specifice;
  • compilarea (manuală sau automată) a tuturor componentelor unui astfel de sistem;

Prima soluție, deși mult mai facilă (atât rapidă, cât și ușor de înțeles), nu ne permite o optimizare a spațiului utilizat (sisteme bazate pe Debian ocupă de ordinul ~1GB) și nici fine-tuningul ulterior pentru îmbunătățirea performanțelor în cazul rulării pe anumite dispozitive (e.g., ARMv8 cu extensii NEON pentru SIMD).

Pe de altă parte, dacă am dori să facem asta de la zero, ar trebui să plecăm de la un toolchain (pe care, de cele mai multe ori, trebuie să-l compilăm tot noi), pe care îl vom folosi să compilăm din cod sursă toate componentele necesare într-un sistem Linux: biblioteci standard (LibC), sistem de init (SystemV / SystemD / OpenRC etc.), suita de programe de bază Unix (e.g., mount, cat, ls, cp, sh, awk și multe altele!), device manager (udev) precum și multe alte servicii (e.g., de networking) și aplicații necesare scopului nostru final (e.g., Python, X11 + QT Toolkit pentru interfețe grafice etc.).

Din fericire, există suite software (Yocto Linux, Buildroot) care pot automatiza repetabil acest proces. Însă un dezavantaj tot rămâne: timpul de compilare este de ordinul orelor, iar spațiul pe disk necesar între 20GB – 50GB.

Exerciții

Pentru acest laborator, vom încerca ambele abordări!

1. Crearea de distribuții bazate pe Debian (debootstrap)

Pentru a genera distribuții personalizate bazate pe Debian (și derivate, e.g., Ubuntu), se poate folosi utilitarul debootstrap.

Acesta descarcă automat din repository-uri pachetele .dpkg, pe care le instalează într-un prefix (director) precizat de utilizator, e.g.:

# Sfat: încă nu rulați (veți aștepta mult și vă va da o eroare la final). Vedeți mai jos cauza + soluția!
$ sudo debootstrap --verbose --arch="arm64" --include="netbase,vim,bash,curl" \
        --variant=minbase "bookworm" "/root/mydebian/" "http://ftp.de.debian.org/debian"

Puteți alege alt mirror din lista oficială de aici (recomandat!). Alegeți la întâmplare o țară din Europa (să nu fiți toți pe același server că s-ar putea să facă throttling!).

Aproape toate distribuțiile de Linux au câte un utilitar (de multe ori, chiar package managerul distribuției) care permite bootstrappingul RootFS-urilor din pachete (tot așa sunt generate și imaginile ISO live!). Spre exemplu, Arch Linux are pacstrap, Alpine Linux are apk.static and so on…

De menționat ar fi că instalarea unui pachet are loc în 2 etape:

  • decompresia arhivei (extragerea fișierelor din pachet în directorul rădăcină destinație);
  • rularea unor scripturi post-install pentru crearea configurațiilor implicite / upgrade fișiere / integrarea cu alte aplicații / instalarea serviciilor etc.;

Acest aspect devine important când încercăm să generăm un rootfs pentru o arhitectură străină celei gazdă (cross-bootstrapping)! Pentru a realiza acest lucru, va trebui să distingem cele două etape: decompresia va putea avea loc direct pe host (avem utilitarele necesare instalate cu scriptul de bootstrapping), pe când etapa de execuție a programelor va trebui să fie virtualizată cumva (chroot + qemu-user-static FTW).

Astfel, pentru a realiza debootstrap de pe o arhitectură Intel/AMD x86_64 pentru o țintă AArch64 (ARM 64-bit):

# a fost adăugat flag-ul `--foreign` în plus la comanda de debootstrap normală:
$ sudo debootstrap --foreign --verbose --arch="arm64" ... /root/mydebian ... # restul de argumente normale
# apoi copiem emulatorul în noua rădăcină (va fi rulat de binfmt_misc ca în Lab 04)
$ sudo cp -af "$(which qemu-aarch64-static)" /root/mydebian/usr/bin/
# Acum va trebui să rulăm stagiul 2 al debootstrap, însă într-un container / chroot!
# Folosim systemd-nspawn pentru a rula binarul de second-stage instalat pe rootfs de la primul:
$ sudo systemd-nspawn --as-pid2 --resolv-conf=copy-host -D /root/mydebian \
    /debootstrap/debootstrap --second-stage --verbose

Este posibil ca descărcarea să dureze ceva, în funcție de viteza de conectare la Internet + mirror-ul ales.

Tehnica cu systemd-nspawn + qemu-user-static (și binfmt_misc) este utilă pentru a intra într-un chroot pe rootfs-ul nostru pentru a-l personaliza (putem chiar și instala programe noi sau configura servicii etc.). Însă va trebui configurat programul de gestionat de pachetele, apt: de adăugat mirror-uri la repo-urile de bază (și cele extinse, de preferat) în /etc/apt/sources.listgăsiți exemplu aici.

Atenție: momentan, rootfs-ul NU ARE parolă la contul root (și nu are nici un alt cont) și nu vă veți putea autentifica din niciun tty! Se recomandă accesarea unui chroot (rulați /usr/bin/bash ca ultim argument al lui systemd-nspawn) și setarea uneia prin passwd:

host$ sudo systemd-nspawn --as-pid2 --resolv-conf=copy-host -D /root/mydebian bash
root@debian$ passwd root
root@debian$ apt update && apt install -y wget curl # nu le vom folosi, dar exemplu :P

O dată generat rootfs-ul, acesta trebuie copiat (atenție să nu se piardă permisiunile și atributele unor fișiere speciale – mai ales symlink-urile) pe o partiție și bootat! Se recomandă arhivarea acestuia folosind utilitarul tar (acesta salvând automat metadatele necesare în arhivă):

$ tar czf mydebian.tar.gz -C /root/mydebian .

În final, dorim să vedem cât ocupă rootfs-ul nostru:

$ du -hs /root/mydebian

Vom trece să instalăm + rulăm sistemul pe un Raspberry PI 4 fizic imediat ce pornim compilarea unui sistem Buildroot ;)

2. Compilarea programelor de bază (buildroot)

Mai departe, descriem procesul de utilizare al unui utilitar destul de popular (cel mai important fiind ușurința de învățare față de Yocto Linux).

Din păcate, vom avea nevoie de aproximativ ~20-30GB de spațiu disponibil (în mașina virtuală).

Cel mai simplu este să creați un Virtual HDD nou de 30GB (nu îi pre-alocați spațiul, deoarece nu va ajunge să ocupe atât azi – poate la temă :P ) și atașați-l proiectului VirtualBox / VMWare ca dispozitiv de stocare suplimentar (din setări / storage, add [hard] disk pe ambele hipervizoare). Apoi va trebui formatat + montat din VM: mkdir /media/big && mkfs.ext4 /dev/sd<X> && mount /dev/sd<X> /media/big. O idee bună ar mai fă să creați director de lucru pentru student: mkdir /media/big/work && chown student:student /media/big/work -R.

De asemenea, tot acum ar fi momentul să alocați memorie RAM mai multă (3-6GB, dacă aveți de unde) aproape câte nuclee aveți procesorului virtualizat (să se paralelizeze procesul). Și să conectați laptop-ul la 230V ;)

Spre deosebire de debootstrap, care avea nevoie de drepturi de root, aceste utilitare folosesc principiul de fake root (metadatele sistemului de fișiere sunt virtualizate într-o bază de date externă kernelului), fiind utilizabile (uneori obligatoriu) și de către conturi non-superuser. Aproape toți pașii va trebui să-i urmați autentificați ca student!

Primul pas, ar fi, desigur descărcarea lui buildroot. Alegeți o versiune de la https://buildroot.org/downloads/ și descărcați-o + dezarhivați-o (sub contul student și pe HDD-ul mare!)

$ wget https://buildroot.org/downloads/buildroot-2023.08.tar.xz
$ tar xf ...
$ cd buildroot-*

Utilitarul Buildroot folosește mecanismul Kconfig (la fel ca U-Boot și kernel-ul Linux), deci: make menuconfig. Navigați prin meniuri și schimbați următoarele opțiuni:

  • Target OptionsTarget ArchitectureAArch64 (little endian);
  • Target OptionsTarget Architecture Variantcortex-A53 (pentru compatibilitate sporită cu mai multe modele Raspberry PI + Qemu);
  • Target OptionsFloating point strategyVFPv4 (la fel, compatibilitate sporită);
  • ToolchainEnable compatibility shims …;
  • System ConfigurationRoot passwordstudent – pentru a ne putea autentifica în shell!
  • Filesystem imagesCompression methodgzip – opțional, imaginea ocupă extrem de puțin oricum.

Apoi rulați make! Procesul poate dura între 10 minute și 1h, în funcție de numărul de nuclee, frecvența lor (ce poate fi scăzută dacă sunteți pe baterie) și memoria RAM disponibilă.

Distribuția de bază Buildroot include doar utilitarele ce vin cu busybox. Aveți, însă, o mulțime de pachete de aplicații pe care le puteți bifa la secțiunea Target packages!

Însă acest lucru va duce la o mărire drastică a timpului de compilare + consum de spațiu pe disk (în funcție de ce / câte pachete + câte dependințe au)!

Nu așteptați! lăsați-l să ruleze și treceți la task-ul următor ;)

La final, să inspectați sistemul de fișiere rezultat, îl găsiți la calea <buildroot-dir>/output/images/. Cât ocupă?

3. Instalarea rootfs-ului în imaginea RPI 4

0. Descărcați de aici imaginea rpi-full.img.tar.xz.

1. Trebuie să montăm imaginea și să copiem noul RootFs pe cea de a doua partiție (pe care o vom formata):

  • Dezarhivați fișierul imaginii rpi-full.img; ar trebui să obțineți imaginea de 2GB;
  • Verificați că există ambele partiții: parted rpi-full.img print;
  • Conectați imaginea la un dispozitiv de tip bloc folosind losetup /dev/loop0 rpi-full.img și partprobe /dev/loop0 (verificați cu losetup -L ce aveți conectat dacă întâmpinați probleme);
  • Formatați sistemul de fișiere a celei de-a doua partiții în ext4: mkfs.ext4 -L RPI_ROOTFS /dev/loop0p2 (confirmați!);
  • Montați noua partiție goală ext4 (puteți folosi /mnt ca mountpoint) și copiați conținutul arhivei rootfs generate anterior (ori cea de Debian, ori cea obținută prin Buildroot):
    tar xf <ARCHIVE_FILE> -C <DEST_MOUNTPOINT>
    # inspectați calea dezarhivată:
    ls -l <DEST_MOUNTPOINT>
  • Demontați partiția ext4 și deconectați dispozitivul loop folosite!

2. Porniți Raspberry PI-ul în u-boot, apoi intrați în modul ums mmc X (vedeți laboratorul 5); folosiți ori dd, ori un utilitar de Raw Disk Imager pentru Windows (recomandat: Raspberry PI Imager) ca la laboratoarele anterioare pentru a scrie noua imagine pe RPI.

  • Atenție: Va trebui, mai întâi, să aduceți imaginea pe Windows (folosiți scp cu username@<ip_sursa>:/cale/catre/fisier.img ca prim argument, iar, ca destinație, o cale de pe PC-ul fizic, e.g., C:\Users\..etc)!
  • Notă pentru VirtualBox: va trebui să folosiți argumentul -P 2023 și student@localhost:<cale> ca sursă.
  • Notă pentru cei cu gazda pe Linux: dacă utilizați dd, va trebui să rulați comanda sync și să așteptați să se termine! Asta e important deoarece kernelul modern cache-uiește în memoria RAM și scrierea se termină, aproape instant (însă rămâne să se copieze pe SD în background, această operațiune fiind mult mai lentă).

3. Reporniți Raspberry PI-ul. Lăsați-l să booteze normal, ar trebui să pornească kernel-ul implicit cu rootfs-ul vostru!

  • Inspectați-l! Cât ocupă pe disk (df)?
  • Dacă vă printează mesajul cu waiting for device /dev/mmcblk0p2 și nu se termină procesul, verificați dacă partiția a doua există din u-boot (part list mmc 0).

4. [BONUS] Rulați imaginea voastră în QEmu (system)

Observăm că procesul de testare a unei imagini embedded consumă ceva timp (mai ales la scrierea pe cardul SD). Dorim să optimizăm!

Rulați imaginea obținută anterior (va trebui să exrageți kernel-ul din imagine!) prin qemu-system-aarch64. Re-vedeți instrucțiunile din laboratorul 4.

Resurse

si/laboratoare/08.txt · Last modified: 2023/12/06 10:24 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0