Un sistem embedded poate funcționa doar cu perifericele pe care le-am folosit deja (rețea, card SD, USB), însă va fi strict limitat la hardware-ul pentru care exista deja suport. Ce se întâmplă atunci când dorim să folosim un hardware nou sau diferit de cel pentru care există suport?
În sistemele care nu au MMU scrierea unui modul este directă: fie că modulul este foarte strâns cuplat cu întreg sistemul, fie că este sub forma unei biblioteci de funcții, totul se află în același spațiu de memorie cu programul care rulează (sau sistemul de operare, în cazurile mai complexe). Dezavantajele principale al acestui tip de sistem sunt lipsa de securitate și de stabilitate. Orice vulnerabilitate sau bug afectează și periclitează întreg sistemul, putând duce chiar la compromiterea acestuia.
Mai mult, două programe (de exemplu într-un sistem de operare cu multi-tasking cooperativ) pot concura pentru aceeași resursă, chiar dacă nu rulează concomitent. Dacă luăm exemplul aplicațiilor de la PM, dacă o funcție configura seriala cu un anumit baud rate, apoi ceda controlul altei funcții care seta baud rate la altă valoarea, valoarea finală va fi a doua, iar codul asociat cu prima funcție nu va mai rula corect.
Deși pare greu de ajuns la o asemenea situație, în realitate este foarte ușor: unul din modurile de a implementa sisteme multitasking colaborative este folosind corutine, funcții cu mai multe puncte de intrare care cedează controlul. În astfel de sisteme, o corutină joacă rolul unui proces. Astfel, având mai mult de un dezvoltator putem fi siguri că, mai devreme sau mai târziu, va apărea o situație ca cea menționată în scenariul anterior.
Sistemele cu MMU rezolvă această problemă. Accesul la hardware poate fi făcut doar de aplicațiile ce rulează într-un mod special, numit și privilegiat. Astfel, codul care interfațează hardware-ul va fi pus într-o zonă protejată de memorie, accesibilă doar din modul privilegiat. Un layer adițional va face comunicația între acest cod și codul neprivilegiat care îl folosește. Astfel, modulul va fi protejat de vulnerabilitățile programelor care îl utilizează, nu va avea probleme în urma crash-urilor lor și va putea face o arbitrare a accesului la resursa hardware pe care o gestionează.
Din punct de vedere al modului în care este organizată, arhitectura kernelului poate fi de două tipuri: monolitică și microkernel, însă pot exista și implementări hibride.
Arhitectura monolitică a unui kernel însemnă că toate serviciile oferite de acesta rulează într-un singur proces, în același spațiu de adrese (kernel-space) și cu aceleași privilegii. Toate facilitățile pe care le pune la dispoziție sunt înglobate într-un singur binar.
Spre deosebire de cel monolitic, microkernel-ul implementează un pachet minimal de servicii și mecanisme necesare pentru implementarea unui sistem de operare precum: alocarea memoriei, planificarea proceselor, mecanismele de comunicare între procese (IPC). Restul serviciilor (networking, filesystem, etc.) rulează ca daemoni în user-space (modul neprivilegiat).
După cum se poate observa, un mare dezavantaj al kernel-ului de tip monolitic îl reprezintă lipsa de modularitate și de extensibilitate, lucruri care au fost adăugate ulterior. Astfel, actual, kernel-urile monolitice au posibilitatea de a fi extinse la runtime cu noi funcționalități, prin inserarea de module. Aceste module vor rula în spațiu de adrese al kernelului.
În general, kernel-ul Linux rulează pe arhitecturi cu suport de MMU, implementarea tuturor funcționalităților bazându-se în mare măsură pe existența acestei componente hardware. Există însă un fork al kernel-ului existent de Linux pentru arhitecuri fără MMU, numit uClinux. Mai multe detalii despre acest subiect găsiți aici.
De altfel, din punct de vedere al arhitecurii kernel-ului, Linux este monolitic cu posibilitatea de extindere la runtime prin module.
Având în vedere că modulele rulează în kernel-space, în mod privilegiat, codul trebuie scris cu mare grijă. Astfel, programarea modulelor de kernel este guvernată de anumite reguli:
Orice modul de kernel are nevoie de o funcție de inițializare și o funcție de cleanup. Prima se apelează atunci când modulul este încărcat, a doua este apelată când modulul este descărcat. De asemenea, modulele au nevoie de autor, licență și descriere, utilizarea acestor macro-uri fiind obligatorie.
#include <linux/init.h> /* for __init and __exit */ #include <linux/module.h> /* for module_init and module_exit */ #include <linux/printk.h> /* for printk call */ MODULE_AUTHOR("SI"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Hello world module"); static int __init my_init(void) { printk(KERN_DEBUG "Hello, world!\n"); return 0; } static void __exit my_exit(void) { printk(KERN_DEBUG "Goodbye, world!\n"); } module_init(my_init); module_exit(my_exit);
Modulul Hello World conține:
KDIR = ? kbuild: make -C $(KDIR) M=`pwd` clean: make -C $(KDIR) M=`pwd` clean rm -f *~ Module.symvers Module.markers modules.order
EXTRA_CFLAGS = -Wall -g obj-m = hello.o
insmod
inserează module în kernel:
~#insmod hello_mod.ko
Modprobe face același lucru, dar cu modulele puse deja în sistemul de fișiere în locul corespunzător (/lib/modules/`uname -r`/… - unde uname -r este versiunea nucleului). În plus, modprobe va citi și lista de dependențe ale modulului de încarcat și o va rezolva (va insera și alte module, dacă e nevoie).
Observați că doar insmod necesită calea modulului (cu tot cu extensie)
~#modprobe hello_mod
Pentru descărcare, se folosește fie:
~#modprobe -r hello_mod
fie:
~#rmmod hello_mod
Afișarea modulelor încărcate se face cu lsmod
~#lsmod
Afișarea mesajelor date cu printk din modul se găsesc în /var/log/messages, se afișează cu:
~#dmesg | tail
sau cu
~#tail -f /var/log/messages
Pe raspberry-pi-uri rulam kernelul 4.9.80-v7+, sursele provenind de pe branchul rpi-4.9.y-stable din repository https://github.com/raspberrypi/linux. Acestea sunt sursele de care aveti nevoie pentru a compila modulele urmatoare. Descarcati sursele de la rpi-4.9.y-stable.zip
Folositi toolchain-ul gcc-arm-linux-gnueabihf.
Dupa ce ati descarcat sursele de kernel si v-ati setat variabilele de environment mentionate mai sus, mergeti in directorul descarcat si configurati-l folosind:“make bcm2709_defconfig”, apoi, “make prepare” iar apoi, “make modules_prepare”. Veti avea nevoie de acest director in exercitiile urmatoare (Hint: KDIR).