User Tools

Site Tools


lab:2015:kernel

Kernel

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 (figure 1). Î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.

 Architectura unui sistem de operare [By Bobbo (Own work) [CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0) or GFDL (http://www.gnu.org/copyleft/fdl.html)], via Wikimedia Commons]Fig. 1: Architectura unui sistem de operare

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

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.

Arhitecturile suportate de kernel-ul Linux se pot afla listând conținutul directorului 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.

Kernel-ul folosit pe RaspberryPi, care include suportul pentru SoC-ul Broadcom BCM2385 folosit de acesta, se găsește la adresa https://github.com/raspberrypi/linux.git.

Linux kernel build system

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.

Target-ul 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:

  • cleaning - conține target-uri pentru ștergerea fișierelor generate la compilare
    • spre deosebire de clean, mrproper șterge în plus toate fișierele generate, plus configurarea și diferite fișiere de backup
  • configuration - conține diferite target-uri pentru generarea unei configurări a kernelului; există target-uri care permit:
    • generarea unui fișier de configurare nou; ex: defconfig, allmodconfig, …
    • actualizarea unui fișier de configurare existent; ex: olddefconfig, localyesconfig, …
    • editarea unui fișier de configurare existent; ex: menuconfig, nconfig, …
  • generic - conține target-uri generice; există target-uri pentru:
    • compilare; ex: 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 selectate
    • instalare; ex: headers_install, module_install, …
    • informații; ex: kernelrelease, image_name, …
  • architecture dependent - conține target-uri dependente de architectură, care diferă în funcție de arhitectura selectată; există target-uri pentru;
    • generarea de imagini în diferite formate: compresate (zImage și bzImage), pentru U-Boot (uImage), …
    • generarea de configurări implicite pentru sisteme bazate pe arhitectura selectată; ex: *_defconfig

Arhitectura care va fi compilată este selectată de variabila de mediu ARCH.

$ ARCH=x86 make [<targets>]
sau
$ make ARCH=x86 [<targets>]

Dacă arhitectura nu este setată explicit, se folosește implicit arhitectura sistemului pe care se face compilarea.

Î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.

Testare

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.

Exerciții

1. (1p) 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.

  • Clonați repository-ul de git al kernel-ului RaspberryPi. Acesta este disponibil la adresa https://github.com/raspberrypi/linux.git sau la adresa ssh://student@<ip catedra>/opt/linux. Cereți asistentului IP-ul de la catedră.
  • Folosiți help-ul sistemului de build pentru a afla denumirile configurațiilor și pentru a genera fișierul de configurare.
  • Deschideți fișierul .config cu un editor de text pentru a vedea conținutul configurării.

  • Revedeți secțiunea despre git.
  • Revedeți modul de folosire a sistemului de build.
  • grep și operatorul de shell | (pipe) sunt prietenii voștri.
  • Nu uitați că arhitectura celor două platforme este ARM. Variabila de mediu ARCH trebuie setată pentru toate invocările make. Altfel veți utiliza arhitectura host-ului (x86), cu rezultate derutante.
  • Revedeți laboratorul de USO despre folosirea linei de comandă.

2. (2p) Compilați și rulați în QEMU ultima versiune a kernel-ului disponibil pe Raspbian Wheezy, urmărind instrucțiunile de mai jos:

După cum ați văzut în laboratorul 3, pentru a rula distribuția Raspbian Wheezy în QEMU vom folosi platforma Versatile PB cu un procesor ARM1176JZ-F, cu arhitectura ARMv6.

Click pentru explicația celor două patch-uri de mai jos

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ă:

  • pentru a configura arhitectura ARMv6 activați:
    • System Type →
      • Support ARM V6 processor
      • ARM errata: Invalidation of the Instruction Cache operation can fail
      • ARM errata: Possible cache data corruption with hit-under-miss enabled
  • pentru a include driver-ul pentru bus-ul PCI folosit de controller-ul de hard disk activați:
    • Bus support →
      • PCI support
  • pentru a include driver-ul pentru controller-ul de hard disk activați:
    • Device Drivers →
      • SCSI device support →
        • SCSI device support
        • SCSI disk support
        • SCSI low-level drivers →
          • SYM53C8XX Version 2 SCSI support
  • pentru a include driver-ul pentru sistemul de fișiere Ext 4 activați:
    • File systems →
      • The Extended 4 (ext4) filesystem
  • pentru a include pseudo-sistemul de fișiere tmpfs necesar funcționării distribuției activați:
    • File systems →
      • Pseudo filesystems →
        • Tmpfs virtual memory file system support (former shm fs)
  • pentru a suporta binarele din distribuția Raspbian activați:
    • Floating point emulation →
      • VFP-format floating point maths
  • pentru a repara o eroare de compilare în această configurație dezactivați:
    • Device Drivers →
      • MMC/SD/SDIO card support

  • Schimbați repository-ul pe branch-ul rpi-3.18.y pentru a obține sursele kernel-ului folosit în ultimul Raspbian Wheezy.
  • Configurați corespunzător kernel-ul.
    • Aplicați patch-ul pentru a permite arhitecturii ARMv6 să fie compilată într-un kernel pentru Versatile PB.
    • Generați fișierul de configurare implicită pentru platforma Versatile PB.
    • Aplicați patch-ul pentru a modifica automat configurarea, precum descrierii de mai sus.
  • Compilați kernel-ul.
  • Rulați kernel-ul în QEMU, folosind aceeași metodă utilizată și în laboratorul 3. Rootfs-ul necesar este disponibil aici sau la adresa ssh://student@<ip catedra>/opt/2015-05-05-raspbian-wheezy-qemu.zip. Cereți asistentului IP-ul de la catedră.
  • Afișați versiunea kernel-ului care rulează folosind comanda uname -a.

  • Revedeți secțiunea despre patch.
  • Folosiți variabila de mediu CROSS_COMPILE pentru a configura prefixul compilatorului folosit pentru compilare.
  • Folosiți parametrul -j 4 al make pentru a paraleliza compilarea pe 4 procese (host-urile au procesoare dual-core).
  • Compilarea durează aproximativ 6min.
  • Imaginea rezultată se găsește în arch/arm/boot/zImage

3. (1p) Boot-ați kernel-ul curent al Raspbian Wheezy pe RaspberryPi folosind U-Boot și TFTP.

  • Imaginea kernel-ului curent a fost extrasă din cadrul distribuției și este disponibilă aici.
  • Host-urile rulează câte un server TFTP care este configurat pentru a folosi directorul $HOME/export/tftp ca rădăcină pentru fișierele servite.
  • Lanțul de boot al RaspberryPi a fost modificat prin adăugarea U-Boot. Serverul interogat de fiecare target este de forma 10.0.0.10<x>, unde x este numărul scris pe cardul SD. Fișierul căutat pentru kernel se numește zImage.

  • Configurați host-ul astfel încât target-ul să găsească serverul TFTP și fișierul căutate.

  • Host-ul trebuie să dețină adresa IP căutată de target (pe lângă celelalte IP-uri deja folosite).
  • Revedeți laboratoarele de USO pentru configurarea rețelei.

4. (3p) Compilați și rulați pe RaspberryPi o versiune a kernel-ului, urmărind instrucțiunile de mai jos:

Pentru simplificarea execițiului vom folosi versiunea 3.12 a kernelului. Spre deosebire de ultimele versiuni, aceasta oferă o configurație minimală pentru platforma RaspberryPi, ceea ce duce la un timp de compilare rezonabil pe PC-urile disponibile în laborator. În plus, față de ultimele versiuni, compilarea acestei configurații implicite se face fără erori.

  • Schimbați repository-ul pe branch-ul rpi-3.12.y pentru a obține sursele versiunii 3.12 a kernel-ului pentru RaspberryPi.
    • Efectuați un clean pentru a curăța fișierele binare rămase de la compilarea unei alte versiuni.
  • Generați fișierul configurării implicite quick pentru RaspberryPi.
  • Compilați configurația și rulați noul kernel pe RaspberryPi folosind TFTP. Vom ignora instalarea modulelor pentru moment.
  • Afișați versiunea kernel-ului care rulează.

  • Nu uitați să curățați fișierele binare generate de compilarea unei alte versiuni a kernel-ului.
  • Compilarea durează aproximativ 8min.

5. (3p) Modificați configurația anterioară astfel încât să integreze toate driverele folosite de kernel-ul original direct în imagine. Compilați și rulați noua configurație, afișând totodată versiunea kernel-ului.

Driverele integrare în imagine vor fi încărcate o dată cu imaginea kernel-ului, de fiecare dată, încă de la bootare. Astfel nu se va pierde timp cu încărcarea dinamică a acestora în timpul rulării. Pentru o configurație hardware stabilă și o aplicație fixată (precum un sistem embedded) acest lucru poate constitui un avantaj.

  • Preluați lista de module folosite de kernel-ul original folosind comanda lsmod pe target în timp ce acest kernel rulează. Copiați lista de module într-un fișier pe host.
  • Folosiți target-ul de make localyesconfig, împreună cu variabila de mediu LSMOD setată la calea către fișierul care conține lista de module, pentru a transforma configurația curentă într-o configurație care integrează driverele necesare.
  • Editați manual configurația modificată anterior pentru a repara eventualele warning-uri apărute. Recomandăm rularea target-ului de make menuconfig pentru efectuarea de modificări manuale.
    • Căutați locația configurărilor care cauzează warning-uri și activați-le.
    • Verificați eliminarea warning-urilor prin rerularea target-ului de make localyesconfig.

  • menuconfig suportă tasta / pentru căutare.
  • Folosiți tasta y/Y pentru a selecta integrarea unei opțiuni în kernel.
  • Începeți căutarea cu configurările pentru modulele snd_soc_core, snd_seq_device și leds_gpio.
  • Activați suportul pentru I2C și SPI din meniul Device Drivers.
  • Puteți particulariza versiunea afișată a kernel-ului din meniul General setup → Local version.

Bonus. (1p) Comparați configurația kernel-ului original cu fișierul de configurare creat de voi la exercițiul 5. Ce diferență observați la opțiunile selectate de voi la pasul anterior?

  • Revedeți finalul secțiunii despre sistemul de build.
  • Dezarhivați configurația originală cu gunzip <fisier>.
  • Folosiți kdiff3 <path1> <path2> pentru a compara două fișiere sau două directoare.

Bonus. (bragging rights) Compilați și rulați ultima versiune a kernel-ului disponibil pe Raspbian Wheezy.

Configurațiile pentru RaspberryPi existente în branch-ul rpi-3.18.y sunt voluminoase, ceea ce duce la un timp de compilare foarte mare pe PC-urile din laborator. Din acest motiv vom transplanta configurația bcmrpi_quick de pe un alt branch. Ultimul commit care conțiune acea configurație a fost găsit folosind git bisect între un branch (rpi-3.17.y) care conține configurația și branch-ul nostru. Pentru mai multe informații despre utilizarea git bisect studiați această pagină.

  • Folosiți git cherry-pick eff92148ee1b5a1ff07e5817179fefb4a0562b17 pentru a transplanta commit-ul eff92148ee1b5a1ff07e5817179fefb4a0562b17 care este cel care conține configurația dorită.
  • Generați configurarea.
    • Actualizați fișierul de configurație conform versiunii 3.18 folosind target-ul de make silentoldconfig. Opțiunile noi adăugate în kernel de la commit-ul eff92148 încoace vor trebui configurate manual. Folosiți valoarea 0 pentru opțiunea PHYS_OFFSET.
  • Compilați noul kernel. Vom ignora din nou instalarea modulelor.
    • Apariția unor erori de simboluri negăsite (unresolved symbol) cu o configurație transplantată astfel nu este un lucru neobișnuit.
    • Notați funcția sau variabila care nu poate fi găsită.
    • Căutați această funcție/variabilă în surse folosind grep -nHR <string> și notați fișierul în care apare.
    • Porniți configurarea manuală și căutați printre opțiunile oferite pe cea/cele care conțin bucăți din calea fișierului notat anterior. Activați-le cu tasta y/Y.
    • Reporniți compilarea. Fișierele deja compilate vor fi refolosite pe cât posibil de make, astfel încât procesul de compilare se reia în mare parte de unde a rămas.
    • Dacă mai apar erori de compilare reluați procesul (nu ar trebui să mai apară).
  • Porniți RaspberyPi-ul folosind noul kernel și afișati versiunea kernelul folosit.

Resurse

Referințe

lab/2015/kernel.txt · Last modified: 2016/10/13 20:43 by Dan Dragomir