Kernel-ul reprezintă o parte a sistemului de operare responsabilă cu accesul la hardware și managementul dispozitivelor dintr-un sistem de calcul (ex: procesoul, memoria, dispozitivele de I/O). De asemenea, el are rolul de a simplifica accesul la diferitele dispozitive hardware, oferind o interfață generică pentru aplicații prin intermediul system-call-urilor (<imgref diagram>). În spatele interfeței generice se află porțiuni din kernel, numite drivere, care implementeză comunicația cu dispozitivele hardware. Un alt rol al kernel-ului este de a izola aplicațiile între ele, atât pentru stabilitatea sistemului, cât și din considerente de securitate.
<imgcaption diagram center| Architectura unui sistem de operare> </imgcaption>
Pentru a îndeplini toate aceste sarcini, codul kernel-ului rulează într-un mod special de lucru al procesorului, fapt care îi permite să execute o serie de instrucțiuni privilegiate. Acest mod privilegiat de lucru nu este accesibil aplicațiilor obișnuite. Spunem că aplicațiile rulează în user-space (modul neprivilegiat), iar kernel-ul rulează în kernel-space (modul privilegiat).
Linux este numele unui kernel creat de către Linus Torvalds, care stă la baza tuturor distribuțiilor GNU/Linux. Inițial, el a fost scris pentru procesorul Intel 80386 însă, datorită licenței permisibile, a cunoscut o dezvoltare extraordinară, în ziua de astăzi el rulând pe o gamă largă de dispozitive, de la ceasuri de mână până la super-calculatoare. Această versatilitate, cât și numărul mare de arhitecturi și de periferice suportate, îl face ideal ca bază pentru un sistem embedded.
arch
din cadrul surselor.
Linux este un kernel cu o arhitectură monolitică, acest lucru însemnând că toate serviciile oferite de kernel rulează în același spațiu de adresă și cu aceleași privilegii. Linux permite însă și încărcarea dinamică de cod (în timpul execuției) în kernel prin intermediul modulelor. Astfel, putem avea disponibile o multime de drivere, însă cele care nu sunt folosite des nu vor fi încarcate și nu vor rula. Spre deosebire de aplicații însă, care rulează în modul neprivilegiat (user-space) și nu pot afecta funcționarea kernel-ului, un modul are acces la toată memoria kernel-ului și se execută în kernel-space (poate executa orice instrucțiune privilegiată). Un bug într-un modul sau un modul malițios poate compromite întregul sistem.
Dezvoltarea kernel-ului Linux se face în mod distribuit, folosind sistemul de versionare Git. Versiunea oficială a kernel-ului, denumită mainline sau vanilla este disponibilă în repository-ul lui Linus Torvalds, la adresa https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git.
Versiunea oficială este însă rar folosită într-un sistem embedded nemodificată. Este foarte comun ca fiecare sistem să folosească o versiune proprie a kernelului (numită un tree) bazată mai mult sau mai puțin pe versiunea oficială. Datorită licenței GPLv2 a kernelului, însă, orice producător care folosește o versiune modificată a kernelului este obligat să pună la dispoziție modificările aduse.
Aceste modificări sunt puse la dispoziție sub formă de patch-uri care trebuie aplicate unei anumite versiuni de kernel. O altă modalitate, care este folosită și de către fundația RaspberryPi, este de a publica un repository de Git cu versiunea modificată (un tree alternativ). Datorită modelului distribuit de dezvoltare suportat de Git, această a doua metodă are avantajul că permite dezvoltarea ușoară în paralel a celor două versiuni. Modificările făcute într-una pot fi portate și în cealaltă, iar Git va ține minte ce diferențe există în fiecare versiune. Cele două versiuni sunt de fapt două branch-uri de dezvoltare, care se întâmplă să fie găzduite pe servere diferite.
Pentru compilare și generarea tuturor componentelor kernel-ului (ex: imaginea principală - vmlinux, module, firmware) Linux folosește un sistem de build dezvoltat o dată cu kernel-ul, bazat pe utilitarul make. Acest sistem de build însă nu seamănă cu clasicul config/make/make install, deși ambele sunt bazate pe utilitarul make.
Toți pașii de compilare sunt implementați ca target-uri pentru make. De exemplu, pentru configurare se poate folosi target-ul config
. Acestă metodă de configurare însă nu este recomandată deoarece oferă o interfață foarte greoaie de configurare a kernel-ului.
help
oferă informații despre aproape toate operațiile suportate de către sistemul de build.
$ make help
Operațiile oferite sunt grupate în diferite categorii:
clean
, mrproper
șterge în plus toate fișierele generate, plus configurarea și diferite fișiere de backupdefconfig
, allmodconfig
, …olddefconfig
, localyesconfig
, …menuconfig
, nconfig
, …all
- target-ul implicit care este executat la o invocare simplă a lui make
, iar vmlinux
și modules
compilează imaginea kernel-ului și, respectiv, modulele selectateheaders_install
, module_install
, …kernelrelease
, image_name
, …zImage
și bzImage
), pentru U-Boot (uImage
), …*_defconfig
ARCH
.
$ ARCH=x86 make [<targets>] sau $ make ARCH=x86 [<targets>]
În cele mai multe situații se dorește compilarea unui kernel pentru un sistem deja existent, fie pentru a adăuga sau elimina funcționalități sau pentru a actualiza versiunea de kernel folosită. În aceste cazuri folosirea target-urilor de generare a unei configurații noi, chiar și a celor care generează o configurație implicită pentru arhitectura noastră nu sunt neapărat utile. Este posibil ca kernel-ul existent să aibă deja o configurație personalizată care se dorește doar a fi actualizată/modificată folosind target-urile de editare.
Un kernel care rulează poate conține fișierul de configurare (de obicei în format comprimat gzip) din care a fost compilat, dacă această funcționalitatea a fost selectată la build. Acest fișier se regăsește în /proc/config.gz
. Tot ce rămâne este să extragem acest fișier și să-l modificăm conform dorințelor.
Pentru a testa un nou kernel acesta trebuie instalat pe target. Această procedură diferă de la sistem la sistem, iar pe RaspberryPi constă în copierea acestuia pe card-ul SD în partiția de boot sub numele de kernel.img
. În momentul dezvoltării și testării unui nou kernel, instalarea fiecărei versiuni a acestuia pe target reprezintă un bottleneck major.
O alternativă la instalarea kernel-ului pe target o reprezintă încărcarea acestuia prin rețea direct pe de host-ul folosit la dezvoltare, dacă există suport din partea bootloader-ului. Din păcate, bootloader-ul implicit de pe RaspberryPi nu are suport pentru a încărca o imagine de kernel de pe rețea. Un bootloader care oferă însă acestă facilitate este U-Boot [2], el folosind protocolul TFTP pentru a boota o imagine de kernel prin rețea.
1. Listați denumirile configurărilor implicite (defconfig) disponibile în ultima versiune a kernel-ului RaspberryPi pentru platformele Versatile PB și BCM. Generați fișierul de configurare pentru una dintre aceste configurații. Studiați 2min formatul acestui fișier si reflectați asupra acestuia îndelung.
bison
și flex
..config
cu un editor de text pentru a vedea conținutul configurării.
|
(pipe) sunt prietenii voștri.ARCH
trebuie setată pentru toate invocările make. Altfel veți utiliza arhitectura host-ului (x86), cu rezultate derutante.
2. Compilați și rulați în QEMU ultima versiune a kernel-ului disponibil pe Raspbian Wheezy, urmărind instrucțiunile de mai jos:
Daca ați clonat repositoryul de pe un calculator local, este posibil să nu găsiți anumite branchuri. Puteti reseta originea pentru git folosind urmatoarele comenzi:
git remote remove origin
git remote add origin https://github.com/raspberrypi/linux.git
git fetch
<hidden Click pentru explicația celor două patch-uri de mai jos>
Pentru a putea rula însă kernel-ul 3.18 pentru RaspberryPi pe această configurație va trebui să-i aducem câteva modificări. Prima este de a activa suportul pentru arhitectura ARMv6 pe platforma Versatile PB, kernel-ul original nesuportând această arthitectură pe platforma noastră. În continuare vom porni de la configurația implicită a kernel-ului pentru platforma Versatile PB și vom adăuga câteva feature-uri și drivere (opțiunile nu trebuie compilate sub formă de module; dorim <*>
, nu <M>
) pentru a putea boota rootfs-ul Raspbian Wheezy, după cum urmează:
</hidden>
rpi-3.18.y
pentru a obține sursele kernel-ului folosit în ultimul Raspbian Wheezy.uname -a
.
CROSS_COMPILE
pentru a configura prefixul compilatorului folosit pentru compilare (compilatorul este arm-linux-gnueabi-gcc
). !ATENŢIE: verificaţi versiunea compilatorului, noi vom utiliza arm-linux-gnueabi-gcc-5
sau o versiune mai veche; în cazul în care este o versiune mai nouă descărcaţi compilatorul mai vechi şi setaţi-l pe acesta ca fiind compilatorul implicit (exemplu pentru versiunea 5: sudo update-alternatives --install /usr/bin/arm-linux-gnueabi-gcc arm-linux-gnueabi-gcc /usr/bin/arm-linux-gnueabi-gcc-5 1
)-j 4
al make pentru a paraleliza compilarea pe 4 procese (host-urile au procesoare dual-core).arch/arm/boot/zImage