Table of Contents

Laboratorul 04. QEMU & Tools

Atenție! Pentru rezolvarea laboratorului, recomandăm folosirea sistemului de operare Ubuntu 22.04. Puteți descărca o mașina virtuală Ubuntu 22.04 de pe GitHub + torrent și apoi folosiți VmWare Player pentru a o rula.

Introducere

Până acum am interacționat cu sisteme embedded ce au avut la baza un sistem de operare Real Time numit NuttX, ce a fost configurat si compilat folosind Kconfig-uri si CMake.

În continuare, laboratorul își propune să vă familiarizeze cu sisteme embedded care rulează Linux, începând de la dezvoltare și configurare, până la mentenanță. Vom trata subiecte precum:

De ce Linux?

Sistemele Linux oferă o mulțime de avantaje dezvoltatorilor de produse, care micșorează timpul de dezvoltare, lucru care este din ce în ce mai important în zilele noastre:

De-a lungul anilor Linux a devenit cel mai folosit sistem de operare pentru aplicațiile embedded. Îl puteți găsi folosit în orice:

Sistemele embedded diferă foarte mult în dimensuni și putere de procesare, unele dintre ele apropiindu-se chiar de puterea de procesare a unui calculator obișnuit. De asemenea, aplicațiile pe care acestea le rulează pot fi foarte variate (ex: smartphone), amestecând diferențele dintre un calculator obișnuit și un sistem embedded. Un lucru care deosebește însă sistemele embedded este modul de interacțiune cu utilizatorii, care foarte rar se face printr-un ecran și o tastatură. Lipsa unui mod tradițional de interacțiune cu utilizatorul este și ceea ce face dezvoltarea unui sistem embedded mai grea, dar și mai interesantă.

Cele mai întâlnite două metode de interacțiune cu un sistem embedded în timpul dezvoltării sunt: consola serială și conexiunea SSH. Dintre acestea, conexiunea SSH este metoda mai robustă și mai simplu de utilizat, însă ea e disponibilă doar pe sistemele care dispun de o interfață de rețea. Consola serială, însă este de obicei prezentă pe orice sistem și permite interacțiunea cu sistemul chiar și înainte ca interfața de rețea să fie disponibilă (ex: în bootloader sau înainte de inițializarea driver-ului de rețea).

RaspberryPi

RaspberryPi Model B

Vom lucra în principal cu RaspberryPi 3, un sistem de calcul bazat pe un procesor “System on Chip” ARM de la Broadcom. Specificațiile complete sunt:

Schema perifericelor RaspberryPi

Schema bloc

Din punct de vedere hardware, RaspberryPi este un dispozitiv simplu, care expune diferitele periferice pe care le oferă SoC-ul Broadcom. Singura excepție o reprezintă Hub-ul USB, care dublează numărul de porturi USB disponibile și atașează și un dispozitiv Ethernet la SoC-ul Broadcom.

Diagrame bloc

Diagrame bloc

Diagrama bloc
Diagrama block a chip-ului de USB și Ethernet

Unelte de dezvoltare

Există două concepte importante folosite în dezvoltarea unui sistem embedded: target și host. Target-ul este reprezentat de sistemul embedded pe care îl dezvoltăm și la care ne conectăm (ex: RaspberryPi, Intel Galileo etc.). Host-ul este reprezentat de calculatorul pe care îl folosim pentru dezvoltare și prin care ne conectăm cu sistemul embedded. Pentru a elimina inconvenientele compilării pe sistemul embedded (target-ul) compilarea se face de obicei pe un sistem desktop (host-ul). Bineînțeles, acum pot apărea probleme dacă target-ul și host-ul folosesc procesoare cu arhitecturi diferite (executabilul generat de host nu va fi înțeles de procesorul target-ului). Aceste probleme apar deoarece compilarea va folosi în mod implicit compilatorul host-ului: host-compiler-ul (ex: gcc).

Rezolvarea constă în instalarea pe host a unui compilator care poate genera executabile înțelese de target. Acest compilator poartă denumirea de cross-compiler sau toolchain, el rulând pe arhitectura host-ului, dar generând cod pentru arhitectura target-ului. Procesul prin care un program este compilat pe un alt sistem diferit de sistemul target se numește cross-compiling.

Cross-compiler toolchain

Există patru componente de bază într-un lanț de instrumente de compilare încrucișată Linux. În plus față de acestea, sunt necesare câteva dependențe pentru a construi gcc în sine:

  1. gcc (contine compilatorul în sine, cc1 pentru C, cc1plus pentru C++ ce generează numai cod de asamblare în format text, apoi gcc, g++, care apeleaza compilatorul în sine, dar și asamblatorul și linkerul binutils, biblioteci precum libgcc (gcc runtime), libstdc++ (the C++ library), libgfortran etc și fișiere antet pentru biblioteca standard C++);
  2. binutils (contine ld, as, addr2line, ar, c++filt, gold, gprof, nm, objcopy, objdump, ranlib, readelf, size, strings, strip);
  3. kernel headers: antetele kernelului de Linux (definiții ale apelurilor de sistem, diferitelor tipuri de structuri și alte definiții similare);
  4. biblioteca standard C (e.g., glibc, newlib, uclibs, musl etc.), ce oferă implementarea funcțiilor standard POSIX, plus câteva alte standarde și extensii).

Versiunea kernelului de Linux folosită pentru anteturile kernelului trebuie să fie aceeași versiune sau mai vechi decât versiunea kernelului care rulează pe sistemul țintă. În caz contrar, biblioteca standard C ar putea folosi apeluri de sistem care nu sunt furnizate de kernel.

Diferențierea între host-compiler și cross-compiler se face prin prefixarea acestuia din urmă cu un string, denumit prefix de forma <arch>-<furnizor>-<os>-<libc/abi> (ex: aarch64-linux-gnu-), ce conține următoarele variabile (trăsături ale target-ului):

Prefixul unui cross compiler se termină întotdeaduna cu -. El va fi concatenat la numele utilitarelor (ex: gcc) pentru a obține numele complet (ex: aarch64-linux-gnu-gcc)

Make și Bash

După cum v-ați obișnuit, aceste două utilitare sunt de-facto standard în dezvoltarea de programe de sistem. Extindeți secțiunea de mai jos pentru mai multe detalii:

Make and Bash intro

Make and Bash intro

GNU Make

Un program important pentru dezvoltarea unui sistem embedded, și nu numai, îl reprezintă make. Acest utilitar ne permite automatizarea și eficientizarea procesului de compilare prin intermediul fișierelor Makefile. Pentru o reamintire a modului de scriere a unui Makefile revedeți urmatoarea resursa - makefiletuturial.

Pentru ușurarea dezvoltării pe multiple sisteme embedded, fiecare având toolchain-ul lui propriu, vom dori să scriem Makefile-uri generice, care pot fi refolosite atunci când prefixul cross-compiler-ului se schimbă. Pentru aceasta va trebui să parametrizăm numele utilitarelor apelate în Makefile. Putem folosi în acest caz variabile de mediu în cadrul Makefile-ului. Acestea pot fi configurate apoi din exterior în funcție de sistemul target pentru care compilăm la un moment dat, fără a mai fi necesară editarea Makefile-urilor.

Cel mai simplu mod de a face acest lucru este să urmăm convenția deja stabilită pentru variabila de mediu care conține prefixul cross-compiler-ului: CROSS_COMPILE. Putem folosi această variabilă de mediu în cadrul Makefile-ului nostru utilizând sintaxa de expandare unei variabile, $(<variabila>), și prefixând numele utilitarului cu variabila pentru prefix.

Makefile
hello: hello.c
	$(CROSS_COMPILE)gcc hello.c -o hello

Orice variabilă exportată în shell-ul curent va fi disponibilă și în fișierul Makefile. Putem de asemenea pasa variabile utilitarului make și sub formă de parametri, astfel:

$ make CROSS_COMPILE=aarch64-linux-gnu- hello

Bash

O mare parte din dezvoltarea unui sistem embedded se face prin intermediul terminalului. Shell-ul care rulează în terminal permite personalizarea unor aspecte utile pentru dezvoltare precum variabilele de mediu încărcate la fiecare rulare. Aceste personalizări se fac însă în fișiere de configurare specifice fiecărui shell.

Pentru bash aceste fișiere reprezintă niste script-uri care sunt rulate automat și se găsesc în /etc (afectează toți utilizatorii) și în $HOME (afectează un singur utilizator). Prin intermediul fișierelor din $HOME fiecare utilizator își poate personaliza shell-urile pentru propriile nevoi. Aceste fișiere sunt:

  • .bash_profile - este executat când se pornește un shell de login (ex: primul shell după logare);
  • .bashrc - este executat cand se pornește orice shell interactiv (ex: orice terminal deschis);
  • .bash_logout - este executat când shell-ul de login se închide.

Un alt fișier util folosit de bash este .bash_history, care memorează un istoric al comenzilor interactive rulate. Istoricul comenzilor este salvat în acest fișier la închiderea unui shell. Pentru o reamintire a unor comenzi utile în linia de comandă puteți revizita laboratorul de USO - Automatizare în linia de comandă.

În dezvoltarea unui sistem embedded este deseori utilă adăugarea în variabila $PATH a căilor către diferitele tool-uri folosite, pentru ca acestea să poată fi accesate direct prin numele executabilului. Modificarea variabilei $PATH pentru fiecare shell pornit se poate face ușor prin intermediul fișierelor de personalizare a shell-ului.

QEMU

QEMU este un emulator / hipervizor, care permite rularea unui sistem de operare complet ca un simplu program în cadrul unui alt sistem. A fost dezvoltat inițial de Fabrice Bellard și este disponibil gratuit, sub o licență open source. QEMU poate rula atât pe Linux, cât și pe Windows [1] [3] [4].

Este un hypervizor, deoarece poate virtualiza componentele fizice ale unui sistem de calcul, pentru a permite rularea unui sistem de operare, numit oaspete (guest), în cadrul altui sistem de operare, numit gazdă (host). În acest mod de funcționare, atât sistemul guest, cât și sistemul host, folosesc aceeași arhitectură (ex: x86). QEMU poate folosi un modul de nucleu, KVM, pentru a accelera rularea guest-ului, atunci când există suport pentru virtualizare în hardware. În acest caz QEMU poate atinge o performanță comparabilă cu sistemul nativ, deoarece lasă mare parte din cod să se execute direct pe procesorul host. Folosind KVM sunt suportate diferite arhitecturi, printre care x86, PowerPC și S390 [1].

Este un emulator deoarece poate rula sisteme de operare și programe compilate pentru o platformă (ex: o placă ARM) pe o altă platformă (ex: un PC x86). Acest lucru este făcut prin translatarea dinamică a intrucțiunilor architecturii guest în instrucțiuni pentru arhitectura host. Ca un emulator, QEMU poate rula în două moduri [2] [4]:

În dezvoltarea sistemelor embedded, QEMU este folosit deoarece poate emula un sistem de calcul complet, nefiind necesar ca sistemul țintă (target) pentru care se face dezvoltarea, și sistemul host, pe care se face dezvoltarea, să folosească aceeași arhitectură. Acest lucru permite ca dezvoltarea software-ului pentru un sistem embedded să poată fi făcută în paralel cu proiectarea hardware-ului, lucru crucial pentru obținerea unui timp de dezvoltare scurt. Un alt avantaj pe care il poate avea emularea, mai ales a sistemelor low-end, este o viteză superioară a emulării pe un sistem host performant, în comparație cu sistemul target.

Instalare

Instalare

Cel mai simplu mod de instalare pe o distribuție Linux este de a folosi package manager-ul. În majoritatea distribuțiilor pachetul principal se numește qemu și cuprinde de obicei toate executabilele aferente diferitelor moduri de funcționare ale QEMU. Dacă se dorește doar modul de virtualizare cu KVM poate fi instalat pachetul qemu-kvm, iar dacă se dorește modul de emulare a unui sistem ARM poate fi instalat pachetul qemu-system-arm.

Ubuntu 22.04
sudo apt update
sudo apt install qemu qemu-kvm qemu-system-arm qemu-utils

Pe VM-ul de laborator aveți gata instalat qemu!

Rulare

Pentru rularea unei mașini virtuale cu KVM se folosește comanda qemu-kvm împreună cu imaginea pentru hard disk. În acest caz imaginea hard disk-ului trebuie să conțină un sistem compatibil cu arhitectura host, accelerarea oferită de KVM putând fi folosită doar dacă guest-ul și host-ul folosesc arhitecturi compatibile (ex: x86_64).

Pentru rularea în user-mode emulation poate fi folosit unul din executabilele de forma qemu-<arch> împreună cu executabilul pe care vrem să-l rulăm [5]. Bineînțeles, acest executabil trebuie să fie compatibil cu arhitectura aleasă, <arch>, iar momentan QEMU oferă suport pentru user-mode emulation doar pe Linux și BSD. Dintre cele două, suportul pentru BSD nu este însă la fel de complet ca cel pentru Linux [6]

Exemplu de rulare in user-mode emulation:

qemu-arm -cpu <procesor> <executabil>

Pentru rularea în modul system emulation se folosește unul din executabilele de forma qemu-system-<arch> împreună cu imaginea pentru hard disk [7].

Exemplu de rulare in modul system emulation:

qemu-system-arm -machine <arhitectura> -drive file=... [+ multe alte argumente]

Configurare Qemu

În modul mașină virtuală sau system emulation QEMU simulează un întreg sistem de calcul. În lipsa unor alte argumente se folosește însă o configurație implicită de sistem, care este specifică fiecărei arhitecuri în parte. QEMU poate însă simula o gamă largă de configurații de sistem. În limbajul QEMU acestea se numesc mașini și pot fi selectate cu opțiunea -machine.

Nokia N800 tablet
qemu-system-arm -machine n800 <disk image>

QEMU oferă însă și un control mai fin asupra configurației sistemului simulat printr-o serie de alte opțiuni, precum [8]:

Configurații mai avansate pot fi obținute cu opțiunile -device, -drive, -net, -soundhw, -bt care adaugă dispozitive periferice, de stocare, plăci de rețea și de sunet și, respectiv, dispozitive bluetooth [8]. Documentația oferă informații despre toate aceste opțiuni, precum și multe altele.

O altă opțiune utilă este -kernel. Aceasta permite specificarea imaginii de kernel folosite de sistemul guest direct în comanda QEMU. Astfel, QEMU va încărca kernelul dintr-un fișier aflat pe sistemul host în loc de a-l cauta în imaginea de hard disk. Acest lucru poate reduce semnificativ timpul de iterație în momentul dezvoltării unui sistem embedded, deoarece nu mai este necesară recrearea imaginii de hard disk pentru fiecare modificare a kernel-ului.

Pe unele sisteme emulate este chiar obligatoriu ca opțiunea -kernel să fie prezentă, deoarece emularea sistemului nu include și un bootloader. Fără un bootloader, sistemul nu știe altfel cum să găsească imaginea de kernel.

De obicei, împreună cu specificarea imaginii de kernel este nevoie să specificăm și linia de comandă a kernel-ului. Pentru aceasta se folosește opțiunea -append împreună cu string-ul care vrem să fie pasat kernel-ului la bootare.

O ultimă opțiune, folositoare mai ales pentru debugging, o reprezintă redirectarea monitorului către consolă. Acest lucru se face cu opțiunea -monitor stdio. Monitorul oferă o interfață în linie de comandă care permite un control interactiv al modului în care se face emularea.

Networking

Pentru a emula o interfață de rețea, QEMU se bazează pe două componente: device-ul prezentat guest-ului, configurat cu opțiunea -device sau -net nic, și back-end-ul care leagă acest device de host, configurat cu opțiunea -netdev. Opțiunea -device nu este limitată la a emula doar interfețe de rețea, ea putând configura orice dispozitiv suportat de către QEMU însă, unele plăci de rețea sunt suportate doar de opțiunea -net nic.

Pentru back-end, QEMU suporta mai multe moduri, printre care:

În mod implicit QEMU emulează un sistem cu o interfață de rețea reprezentată de un device specificat de mașina selectată, în modul user-mode. Aceasta configurare implicită nu ne oferă însă toată flexibilitatea unui target real, conectat la o rețea fizică. Din acest motiv în cadrul laboratorului ne vom folosi de modul bridge.

Bridge-ul folosit de către back-end se configurează cu parametrul br=<nume bridge>, iar device-ul pentru opțiunea -net nic se specifică prin parametrul model=<device>. Legatura dintre cele două componente se face prin adăugarea parametrului netdev=<id> la device și a parametrului id=<id> la back-end. Valoarea <id> trebuie bineînteles să fie identică pentru ca cele două componente să fie legate. În final, cele două opțiuni arată astfel: -net nic,model=<device>,netdev=<id> -netdev bridge,br=<nume bridge>,id=<id>.

Configurare acces Internet în QEMU folosind modul bridge

Configurare acces Internet în QEMU folosind modul bridge

  • Pentru a crea și configura bridge-uri se folosește utilitarul brctl din pachetul bridge-utils. Crearea unui bridge care să ofere unui guest accesul la rețea fizică a host-ului se face astfel:
    sudo brctl addbr virbr0						# creăm bridge-ul
    sudo brctl addif virbr0 <interfata fizica>			# adăugam interfața fizică a host-ului la bridge
    sudo ip address flush dev <interfata fizica>	                # ștergem adresa IP de pe interfața fizică, doar dacă avem o adresă
                                                                    # IP pe interfață. Va șterge și ruta default automat
    sudo dhclient virbr0                                            # obținem adresa IP pentru bridge și ruta default prin DHCP
  • Dacă nu merge obținerea adreselor prin DHCP, se poate configura manual adresa și ruta default:
    ip address show							# notăm ip-ul și prefixul interfeței fizice
    ip route show							# notăm ruta implicită
    sudo brctl addbr virbr0						# creăm bridge-ul
    sudo brctl addif virbr0 <interfata fizica>			# adaugăm interfața fizică a host-ului la bridge
    sudo ip address del <ip>/<prefix> dev <interfata fizica>	# mutăm adresa interfeței fizice
    sudo ip address add <ip>/<prefix> dev virbr0			# pe bridge
    sudo ip link set dev virbr0 up
    sudo ip route add default via <gateway>				# readăugam ruta implicită
  • Pentru ca bridge-ul să fie acceptat de QEMU el trebuie configurat și în fișierul /etc/qemu/bridge.conf sub forma:
bridge.conf
allow virbr0
  • Connectati-va la ssh în sistemul emulat de qemu prin intermediul IP-ului VM-ului în bridge.
student@virtual-machine:~$ ssh root@192.168.122.<X>

Configurare acces Internet în QEMU folosind user networking (*recomandat*!)

Configurare acces Internet în QEMU folosind user networking (*recomandat*!)

  • Emulați interfața de rețea folosind un USB network adaptor virtual. Pentru a avea access la serviciul de SSH din QEMU o sa avem nevoie de port-forwarding pentru portul 22 (SSH default). Pentru a realiza acest lucru, adaugam parametrul hostfwd=tcp::5555-:22 in optiunea de -netdev din comanda de qemu-system-aarch64:
     ...
    -device usb-net,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22 \
  • Logarea pe target se face doar cu user-ul root.
  • Verificam interfetele disponibile
root@rpi3-20220807:~# ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enx405400123457: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 40:54:00:12:34:57 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::4254:ff:fe12:3457/64 scope link 
       valid_lft forever preferred_lft forever
  • Putem observa ca nu avem nicio adresa IP asociata interfetei. Pentru a realiza acest lucru, vom cere una prin intermediul protocolului DHCP.
dhclient <enx...>  # <-- numele interfeței ca parametru
  • Connectati-va la ssh în sistemul emulat de qemu prin intermediul port-ului forward-uit local.
student@virtual-machine:~$ ssh -p 5555 root@localhost

Exerciții

Pentru rezolvarea laboratorului recomandăm folosirea mașininii virtuale de laborator cu Ubuntu 22.04 descărcabilă de pe GitHub + torrent.

Ca sistem de operare pentru embedded / RPI (azi, emulat), vom folosi distribuția Debian Bookworm compilată pentru ARM64.

0. Setup

Dacă folosiți VM-ul de laborator, pașii de mai sus au fost deja efectuați, însă tot mai trebuie să descărcați imaginea de debian (ultimul subtask).

sudo apt install git vim wget bridge-utils
sudo apt install crossbuild-essential-arm64
sudo apt install qemu-user            # Pentru user-mode emulation
sudo apt install qemu-system-aarch64  # Pentru system-mode emulation
sudo apt install qemu-utils           # Pentru utilitare precum qemu-nbd

1. User-mode emulation

Compilați următorul program hello world pentru RaspberryPi și linkați static. Aflați setul de instrucțiuni folosit de executabilul generat și apoi rulați-l în QEMU folosind user-mode emulation și emulând procesorul Cortex-A53. Salvați comanda folosită pentru emulare.

#include <stdio.h>
int main(void)
{
    printf("hello world\n");
}

In mod normal executabilul astfel obtinut nu merge rulat si pe sistemul host. In cazul in care merge rulata aplicatia de hello world si in host explicatie pentru care se intampla asta este: qemu instaleaza un handler care permite aceasta translatia direct, doar ca acest lucru se intampla selectiv, deoarece nu pe toate sistemele de operare este instalat/configurat similar.

2. System-mode emulation

Rulați distribuția Debian folosind QEMU în modul system emulation. Veți avea nevoie de următoarele argumente pentru emulare.

qemu-system-aarch64 \
	-machine … \
	-kernel … \
	-initrd … \
	-dtb … \
	-sd … \
	-append "console=ttyS1 root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4" \
	-nographic \
	-serial null \
	-serial stdio \
	-monitor none

Extrageți imaginea de kernel, dtb-ul si initrd-ul din imaginea de disc de Debian downloadată

sudo losetup --show -fP 20220808_raspi_3_bookworm.img   # Notați numărul device-ului /dev/loop returnat de comanda losetup
sudo mkdir /mnt/debian
sudo mount /dev/loop16p1 /mnt/debian     # Înlocuiți valoarea 16 cu valoarea numărului vostru
cp /mnt/debian/vmlinuz-5.18.0-3-arm64 .
cp /mnt/debian/initrd.img-5.18.0-3-arm64 .
cp /mnt/debian/bcm2837-rpi-3-b.dtb .
# facem unmount și deconectăm imaginea din dispozitivul bloc
sudo umount /mnt/debian
sudo losetup -d /dev/loop16

Pentru montare, puteți folosi utilitarul qemu-nbd (care, în plus față de losetup, știe să deschidă mai multe formate de mașini virtuale precum vbox și vmdk):

sudo modprobe nbd max_part=8
sudo qemu-nbd -c /dev/nbd0 220121_raspi_3_bullseye.img
sudo mkdir /mnt/debian /mnt/debian/boot
sudo mount /dev/nbd0p2 /mnt/debian
sudo mount /dev/nbd0p1 /mnt/debian/boot
# acum putem explora partiția de boot din imagine
ls -l /mnt/debian/boot
# facem unmount și deconectăm imaginea din dispozitivul bloc
sudo qemu-nbd -d /dev/nbd0

Deoarece discul pasat către Qemu este de tip SD card, Qemu așteaptă ca dimensiunea discului să fie o putere a numărului 2 (ex. 512MB, 1024MB). Din acest motiv trebuie să redimensionăm discul, spre exemplu la 4GB. Qemu dispune, de asemenea, de utilitare pentru manipulat imagini (creare / redimensionare / conversie între formate):

qemu-img resize 20220808_raspi_3_bookworm.img 4G

  • Re-citiți despre modalitățile de rulare.
  • Revedeți parametrii de configurare ai QEMU și citiți pagina de manual sau documentația acestora.
  • Pentru a inchide mașina virtuală, folosiți, din cadrul ei, comanda sudo halt sau sudo poweroff. Dacă doriți să opriți din terminal de pe host, opriti procesul qemu cu ajutorul comenzii killall qemu-system-aarch64.
  • Dacă întâlniți probleme de rulare qemu, însă acesta se închide prea repede și nu puteți vedea eroarea, folosiți argumentele -no-reboot -no-shutdown.

3. Instalați serviciul Libvirt

Libvirt este un serviciu ce permite folosirea Qemu mult mai ușor. Împreună cu tool-uri precum virsh (virtual shell) sau virt-manager, utilizatorul poate crea, porni, opri, clona sau migra mașini virtuale foarte ușor si rapid. În acest laborator vom folosi tool-ul virt-install pentru a crea o noua mașină virtuală.

sudo apt install virtinst

folosiți kernel-ul, initrd-ul si imaginea discului de la ex.3 pentru a crea o mașină virtuală:

virt-install --name rpi3-qemu-si \
	--arch aarch64 \
	--machine virt \
       	--os-variant debian11 \
	--boot kernel=...,initrd=...,kernel_args="console=ttyAMA0 root=/dev/vda2 rw rootwait rootfstype=ext4" \
	--disk ... \
       	--vcpus 2 \
	--nographic \
       	--feature acpi=off

Pentru a ieși din consola virsh, folosiți combinația ctrl + ]. Avem la dispoziție următoarele comenzi:

Numele destroy poate induce putin în eroare, deoarece mașina este doar oprită și nu ștearsă.

4. Schimbați configurația VM-ului (CPU-ul si RAM)

Cu mașina virtuală de la ex. 4 oprită, editați configurația cu ajutorul următoarei comenzi și adăugați 8 procesoare si 32GB de RAM:

virsh edit NUME_VM

În mod implicit, kernel-ul va refuza alocarea de memorie virtuală pentru un process de user-space, mai mult decât are sistemul disponibil. Dar îl putem convinge cu următoarea comanda executată in VM-ul de Ubuntu 22:

sudo sysctl vm.overcommit_memory=1

Desigur, în momentul în care VM-ul începe să folosească multă memorie, VM-ul (procesul) va fi automat oprit (killed).

Porniți mașina virtuală, deschideți consola cu ajutorul comenzii “virsh console NUME_VM” și listați noua configurație:

cat /proc/cpuinfo
free -m

5. Accesul la Internet

Configurați și testați accesul guest-ului la Internet. Salvați comanda folosită pentru emulare.

Pentru aceasta, opriți VM-ul din virsh si reluați comanda de la ex. 2 (vom folosi qemu).

	-device usb-net,netdev=net0 \
	-netdev bridge,br=...,id=net0 

Pentru a avea Internet in interiorul VM-ului, putem urma pașii din secțiunea configurare a rețelei.

Dacă doriți să folosiți modul bridge, instalați daemon-ul libvirt care îl configurează ajutomat:

sudo apt install libvirt-daemon-system

Folosiți numele noului bridge pentru adaptorul de retea virtual, și permiteți folosirea lui în libvirt:

echo "allow virbr0" >> /etc/qemu/bridge.conf

După ce pornește mașina virtuală, listați interfețele de rețea si porniți clientul de DHCP:

ip addr
dhclient en<XYZ>

  • Înainte de realizarea configurațiilor de rețea, dezactivați conectarea automată din setările sistemului de operare (Settings → Network → Wired → Connect Automatically (off)) și puneți placa de rețea a mașinii virtuale Ubuntu in modul de NAT (Devices → Network → Network Settings). Dacă folosiți o rețea wired și nu vă merge cu NAT atunci setați pe modul Bridged Adapter. Atenție, NU setați pe modul “NAT Network”.

Setare rețea

6. BONUS

Creați un Makefile generic pentru programul hello world care poate compila pentru orice sistem target în funcție de variabilele primite (convenția CROSS_COMPILE). Compilați programul pentru host și pentru target-ul RaspberryPi, apoi salvați executabilele generate.

Copiati cu scp binarul necesar pe target, ce observati?

Ce puteți spune despre conținutul celor 2 fișiere executabile create la exercițiul anterior?

  • Dacă o variabilă nu este setată, construcția $(<variabilă>) într-un Makefile va fi echivalentă cu șirul vid.

  • Pentru informaţii legate de tipul fişierelor se poate folosi comanda file;
  • Conținutul unui fișier executabil poate fi inspectat cu utilitarul objdump (ptr target folosiţi utilitarul din toolchain: aarch64-linux-gnu-objdump)

Resurse

Referințe