CURSUL 08

Sincronizare, Arhitecturi de Kernel și RTOS-uri pentru Sisteme Embedded

Durată: 120 min de predare Nivel: licență, anul III - recomandat după Cursul 07 Disciplină: Sisteme Încorporate Laborator asociat: Laboratorul 03 PDF: descarcă suportul EN English version

Cursul trecut a arătat cum planifică un RTOS task-urile în timp; acesta arată ce se întâmplă când acele task-uri trebuie să coopereze - să partajeze date fără să se strice reciproc. Pornim de la condiția de cursă și mecanismele clasice de sincronizare, trecem prin capcana inversiunii de prioritate și cele două protocoale care o rezolvă (Priority Inheritance și Priority Ceiling), apoi urcăm un nivel: cum se organizează kernelul însuși (monolitic vs. microkernel), cum izolează memoria task-urile unele de altele, ce înseamnă multicore în timp real, și cum se alege, practic, un RTOS pentru un proiect concret.

1Obiectul și structura cursului6 min

Un task planificat corect, dar care corupe datele altui task, nu e un sistem funcțional - e un sistem care eșuează imprevizibil. Sincronizarea corectă a accesului la resurse partajate e la fel de esențială ca planificarea temporală studiată în Cursul 07, iar cele două probleme sunt legate mai strâns decât pare la prima vedere: un mecanism de sincronizare prost ales poate distruge garanțiile de schedulabilitate demonstrate anterior.

Recapitulare din Cursul 07
  • Un task trece prin stările Running, Ready, Blocked, Suspended
  • RMS oferă priorități fixe, cu testul suficient Liu-Layland; EDF oferă priorități dinamice, cu testul U ≤ 1
  • Timpul de blocare al unui task de prioritate mare, produs de altele cu prioritate mai mică, trebuie inclus în analiza temporală
Astăzi analizăm exact acel timp de blocare: de unde vine, cum poate exploda necontrolat, și cum se ține sub control.

Rezultate ale învățării

  • Să identificați o condiție de cursă și să explicați de ce apare la o operație aparent simplă
  • Să alegeți între mutex, semafor și grup de evenimente pentru un scenariu dat
  • Să explicați inversiunea de prioritate și să aplicați Priority Inheritance sau Priority Ceiling
  • Să comparați arhitectura monolitică cu microkernel din perspectiva izolării erorilor
  • Să descrieți rolul MPU/MMU și diferența dintre AMP și SMP pe platforme multicore
  • Să enumerați criteriile practice pentru alegerea unui RTOS într-un proiect real

2Condiții de cursă și secțiuni critice8 min

Condiție de cursă (race condition)
Apare atunci când rezultatul programului depinde de ordinea temporală imprevizibilă a două sau mai multe accesări concurente la aceeași resursă partajată.

Chiar și o operație care arată atomică în cod poate fi, la nivel de procesor, o secvență de trei pași separați:

uint32_t event_count = 0;
void TaskA() { ++event_count; }
void TaskB() { ++event_count; }

event_count++ nu e neapărat o singură instrucțiune: se descompune în citirea valorii din memorie, incrementarea ei și scrierea rezultatului înapoi. Dacă TaskA și TaskB execută această secvență aproape simultan, se poate întâmpla:

Exemplu - o actualizare pierdută

τA citește 10 → τB citește 10 → τA scrie 11 → τB scrie 11

Deși ambele task-uri au incrementat variabila, valoarea finală e 11, nu 12 - a doua scriere suprascrie rezultatul primei, fără să știe că prima a avut loc. Actualizarea lui τA a dispărut complet, silențios, fără nicio eroare semnalată.

Secțiune critică
O regiune de cod care accesează o resursă partajată și trebuie executată în mod indivizibil. Pentru o resursă R: N_task-uri_în_secțiunea_critică(R) ≤ 1

O secțiune critică trebuie să fie cât mai scurtă: fără operații blocante, fără întârzieri voluntare, fără calcule care pot fi mutate în afara ei.

Exercițiu rezolvat - cât de lungă poate fi o secțiune critică?

Varianta A ține un mutex în timpul filtrării, scrierii pe Flash și trimiterii pe rețea. Varianta B copiază datele sub protecția mutex-ului, apoi procesează în afara lui. Care e mai bună și de ce?

Vezi rezolvarea
// Varianta A - secțiune critică excesiv de lungă
lock(data_mutex);
read_shared_data();
perform_complex_filtering();
write_external_flash();
send_network_packet();
unlock(data_mutex);

// Varianta B - copiere rapidă, procesare în afara mutex-ului
SensorData local_copy{};
lock(data_mutex);
local_copy = shared_sensor_data;
unlock(data_mutex);
perform_complex_filtering(local_copy);
write_external_flash(local_copy);
send_network_packet(local_copy);

Varianta B e corectă: doar copierea propriu-zisă necesită protecție, restul operațiilor pot rula fără mutex-ul blocat. Durata maximă a secțiunii critice mărginește direct timpul de blocare al altor task-uri care cer aceeași resursă: B_i ≥ C_critical,max - o secțiune critică lungă în Varianta A ar propaga întârzieri mari către orice task blocat pe acel mutex.

Mascarea întreruperilor și volatile - nu sunt soluții generale

Dezactivarea temporară a întreruperilor poate proteja secvențe foarte scurte între un task și un ISR (L_IRQ,max ≥ T_întreruperi_dezactivate,max), dar nu funcționează pe multicore - mascarea pe un nucleu nu împiedică alt nucleu să acceseze aceeași memorie. Calificatorul volatile influențează doar optimizările compilatorului; el nu oferă excludere mutuală și nu transformă o secvență citire-modificare-scriere într-o operație atomică.

3Mutex-uri și semafoare10 min

Trei primitive de sincronizare acoperă majoritatea nevoilor unui sistem embedded: mutex-ul, semaforul și grupul de evenimente.

PrimitivăModelUtilizare tipică
Mutexare proprietar; doar cine îl obține îl poate eliberaprotejarea unei resurse partajate (excludere mutuală)
Semaforcontor 0 ≤ S ≤ S_max, fără proprietarsemnalizare între task-uri sau între ISR și task
Grup de evenimenteset de biți, fiecare marcând o condițieașteptarea combinată a mai multor condiții
De ce distincția mutex/semafor contează Termenul „mutex" vine de la mutual exclusion - un mutex are conceptul de proprietar: task-ul care îl obține trebuie să fie același care îl eliberează, iar un mutex nu ar trebui eliberat dintr-un ISR. Un semafor nu are proprietar - de aceea e potrivit exact pentru semnalizare (un ISR poate „da" un semafor pe care un task îl „ia"), dar nepotrivit ca înlocuitor automat al unui mutex pentru protejarea unei resurse.
// Mutex - protejarea unei resurse partajate, cu timeout
if (lock_mutex(configuration_mutex, mutex_timeout)) {
    shared_configuration = new_configuration;
    unlock_mutex(configuration_mutex);
} else {
    handle_lock_timeout();
}
Exemplu - semnalizare ISR → task printr-un semafor binar

Un ADC termină o conversie și generează o întrerupere; task-ul de procesare așteaptă acel eveniment fără să consume procesor în așteptare activă.

// în ISR
void ADC_IRQHandler() {
    read_conversion_result();
    give_semaphore_from_isr(adc_ready_semaphore);
}

// în task
void ProcessingTask() {
    while (true) {
        take_semaphore(adc_ready_semaphore, portMAX_DELAY);
        process_adc_sample();
    }
}

Semaforul binar (S ∈ {0,1}) e potrivit aici: ISR-ul „dă" semaforul, iar task-ul „ia" - blocându-se eficient, fără polling, până la sosirea evenimentului.

Timeout, nu blocare nelimitată

Ca și în Cursul 07: aproape orice așteptare pe un mutex sau semafor ar trebui să aibă un timeout - resursa așteptată poate să nu mai sosească (mesaj pierdut, periferic defect), iar un task blocat nelimitat pe o resursă care nu mai vine e echivalent, din perspectiva sistemului, cu un task mort.

Alegeți primitiva potrivită pentru fiecare scenariu

4Grupuri de evenimente și cozi de mesaje6 min

Grupurile de evenimente permit unui task să aștepte o combinație logică de condiții, nu doar una singură:

Grup de evenimente
E = e₀ ∨ e₁ ∨ ... ∨ eₘ₋₁ - fiecare bit e setat independent de o sursă diferită (task sau ISR), iar un task poate aștepta orice combinație AND/OR a acestor biți.
Limitarea grupurilor de evenimente Un grup de evenimente poate pierde informație de numărare: dacă evenimentul e setat de două ori înainte ca vreun task să-l verifice, rămâne doar informația „a avut loc", nu „a avut loc de două ori" - pentru situații unde numărul de ocurențe contează, un semafor cu contor sau o coadă de mesaje sunt alegerea corectă, nu un grup de evenimente.

Cozile de mesaje transportă date, nu doar semnalizare - un task „pune" un element, altul „scoate", cu blocare (și timeout) la coadă plină sau goală. Un aspect ușor de neglijat: proprietatea datelor transmise printr-o coadă trebuie definită explicit - odată ce un buffer a fost transmis, task-ul emițător nu ar trebui să-l mai modifice, altfel apare, din nou, o condiție de cursă, doar mutată de la o variabilă globală la un obiect transmis prin canal.

  • „Am folosit o coadă de mesaje, deci sunt protejat de condiții de cursă." Doar dacă și proprietatea datelor e respectată strict - dacă emițătorul continuă să scrie în bufferul transmis după ce l-a pus în coadă, problema revine sub altă formă. Transferați proprietatea complet: după trimitere, emițătorul nu mai atinge acel buffer.

5Inversiunea de prioritate8 min

Cele trei primitive de mai sus rezolvă corectitudinea accesului la date - dar introduc o problemă nouă, pur temporală, când task-uri cu priorități diferite partajează aceeași resursă.

Inversiunea de prioritate
Un task cu prioritate mare ajunge să aștepte, indirect, după un task cu prioritate mică - inversând, efectiv, ordinea de prioritate pe care planificarea ar trebui să o garanteze.

Trei task-uri: τH (prioritate mare), τM (prioritate medie), τL (prioritate mică). τL deține un mutex M. Scenariul problematic:

  1. τL obține mutex-ul M și intră în secțiunea critică.
  2. τH devine Ready, preemptează pe τL, dar apoi solicită și el mutex-ul M → τH se blochează, așteptând ca τL să-l elibereze.
  3. τM devine Ready. Cum τL are prioritate mai mică decât τM, τM preemptează pe τL.
  4. τM rulează cât are nevoie, fără să folosească deloc mutex-ul M - dar, cât timp τM rulează, τL nu poate finaliza secțiunea critică, deci nu poate elibera M, deci τH rămâne blocat.
De ce e periculoasă, nu doar incomodă τH, cea mai importantă prioritate din sistem, așteaptă acum indirect după τM - o prioritate pe care ar fi trebuit s-o domine complet. Fără o limită superioară a acestei situații, timpul de blocare al lui τH devine practic nemărginit: depinde de câte task-uri de prioritate intermediară apar, nu doar de durata secțiunii critice a lui τL. Acest scenariu a fost documentat celebru pe misiunea Mars Pathfinder (1997), unde inversiunea de prioritate producea resetări repetate ale sistemului de bord.

Soluția nu e „evitați mutex-urile" - e să limitați explicit cât poate dura blocarea, prin protocoale dedicate de control al priorității, subiectul următoarelor două secțiuni.

6Priority Inheritance Protocol8 min

Priority Inheritance Protocol (PIP) reduce inversarea de prioritate prin ridicarea temporară a priorității task-ului care deține resursa.

Regula de bază
Dacă τH e blocat de τL: p_efectiv_L = max(p_bază_L, p_H). După eliberarea resursei: p_efectiv_L → p_bază_L.

Aplicat scenariului anterior:

  1. τL deține mutex-ul.
  2. τH solicită mutex-ul și se blochează.
  3. τL moștenește prioritatea lui τH.
  4. τM nu mai poate preempta pe τL, pentru că prioritatea efectivă a lui τL e acum mai mare decât a lui τM.
  5. τL termină rapid secțiunea critică și eliberează mutex-ul.
  6. τH e deblocat și rulează.
  7. τL revine la prioritatea sa de bază.

Blocarea lui τH e acum mărginită de durata secțiunii critice a lui τL, nu de interferența unui număr nedeterminat de task-uri intermediare.

Moștenirea tranzitivă și blocarea în lanț Dacă τH → M1 → τL, iar τL așteaptă la rândul lui un alt mutex deținut de τX, prioritatea ridicată trebuie propagată și către τX - această situație se numește blocare în lanț și e una dintre limitările PIP: protocolul nu o elimină, doar o gestionează corect atunci când apare.
AvantajLimitare
Elimină interferența task-urilor cu prioritate intermediarăNu previne automat deadlock-ul
Integrat în numeroase mutex-uri RTOS, fără modificarea modelului de programarePoate permite blocări în lanț
Păstrează modelul obișnuit de obținere/eliberare a resurseiFuncționează doar dacă se folosește mutex-ul corespunzător, nu un semafor binar
PIP nu compensează o secțiune critică proiectată prost

Task-ul care deține mutex-ul nu trebuie suspendat, întârziat voluntar sau blocat pe o operație lentă în interiorul secțiunii critice - dacă τL, chiar cu prioritate moștenită, execută o operație lentă și necontrolată în secțiunea critică (de exemplu o scriere Flash blocantă), blocarea rămâne lungă indiferent de protocol. PIP mărginește cine poate întârzia τH, nu cât durează secțiunea critică în sine - acea disciplină rămâne responsabilitatea proiectantului.

De asemenea, în multe RTOS-uri, Priority Inheritance e implementat pentru mutex-uri, nu pentru semafoare binare - înlocuirea unui mutex cu un semafor, într-un cod care pare echivalent, poate elimina complet protecția împotriva inversiunii de prioritate, fără niciun avertisment.

Inversiunea de prioritate nemarginita: puneti evenimentele in ordinea producerii

7Priority Ceiling și reguli de proiectare10 min

Inversiunea de prioritate, cu si fara protocol

Protocoalele de tip Priority Ceiling atribuie fiecărei resurse o valoare numită plafon de prioritate (priority ceiling).

Plafonul unei resurse
Pentru resursa Rₖ: Π(Rₖ) = max(pᵢ), peste toate task-urile τᵢ care pot utiliza Rₖ - adică cea mai mare prioritate dintre task-urile care pot cere resursa.

Există mai multe variante. În Immediate Ceiling Priority Protocol (varianta cea mai folosită practic), task-ul care obține o resursă primește imediat prioritatea plafonului acelei resurse:

Regula plafonului imediat
p_efectiv_i = max(p_bază_i, Π(Rₖ))
Exemplu - protejarea unei resurse prin plafon imediat

Resursa R e folosită de τH, τM și τL; plafonul ei e Π(R) = pH (cea mai mare prioritate dintre utilizatori). Când τL obține R: p_efectiv_L = max(pL, Π(R)) = pH - τL rulează imediat la prioritatea maximă posibilă pentru acea resursă, chiar înainte ca vreun task cu prioritate mai mare s-o solicite efectiv. τM nu poate preempta secțiunea critică, iar analiza temporală devine mai simplă decât la PIP, pentru că blocarea nu mai depinde de când apare cererea concurentă.

Ce garantează, în plus față de PIP În ipotezele modelului clasic, protocoalele Priority Ceiling oferă: prevenirea deadlock-ului produs de resursele gestionate, limitarea blocării în lanț, o limită mai simplă pentru timpul de blocare, și predictibilitate temporală mai bună. Regula practică rezultată: un task poate fi blocat de cel mult o singură secțiune critică a unui task cu prioritate mai mică - Bᵢ ≤ max(Cⱼ,critic), peste task-urile τⱼ cu prioritate mai mică ce ating o resursă relevantă pentru τᵢ.
Timpul de blocare si testul de schedulabilitate cu resurse partajate
ProtocolAvantaj principalLimitare principală
Fără protocolImplementare minimalăInversare de prioritate potențial nemărginită
Priority InheritanceElimină interferența priorităților intermediareNu previne automat deadlock-ul și blocarea în lanț
Priority Ceiling (original)Previne deadlock-ul și limitează blocareaNecesită administrarea plafonului sistemului
Immediate CeilingComportament predictibil, implementare eficientăNecesită stabilirea corectă a plafonului fiecărei resurse

Alegerea între Priority Inheritance și Priority Ceiling depinde de serviciile oferite de RTOS, posibilitatea cunoașterii anticipate a tuturor utilizatorilor unei resurse, cerințele de analiză formală și nivelul de criticitate al sistemului - Priority Ceiling e mai potrivit pentru sisteme statice, bine analizate dinainte; poate fi dificil de aplicat în arhitecturi foarte dinamice, unde nu se cunosc dinainte toți utilizatorii posibili ai unei resurse.

8Arhitecturi de kernel: monolitic vs. microkernel10 min

Arhitectura kernelului stabilește distribuția responsabilităților între codul privilegiat și componentele aplicației - o decizie care influențează latența serviciilor, costul comunicației între componente, izolarea erorilor, suprafața de cod cu privilegii, și posibilitatea certificării.

Clasificarea e conceptuală, nu strictă Monolitic și microkernel descriu principii arhitecturale; implementările concrete pot combina elemente din ambele modele. Un sistem poate produce o singură imagine executabilă, dar poate folosi un MPU pentru a izola anumite task-uri; un microkernel poate rula servicii complexe în componente separate, deși întregul produs e distribuit ca o singură imagine firmware.

Arhitectura cu spațiu unic (monolitică)

Kernelul, driverele și aplicația accesează aceeași memorie, legate frecvent într-o singură imagine executabilă: Aplicație + servicii + kernel + drivere → imagine firmware. Un apel de serviciu e, de regulă, direct: C_serviciu ≈ C_apel + C_kernel, fără schimbare de spațiu de adrese și fără copiere de mesaje.

Avantaje: latență redusă, overhead mic, utilizare eficientă a memoriei, integrare simplă pe microcontrolere fără MMU. Limitare principală: propagarea erorilor - un pointer invalid dintr-un driver poate modifica memoria kernelului sau stiva altui task, pentru că nu există o graniță hardware între ele.

Microkernel și servicii separate

Un microkernel păstrează în modul privilegiat numai mecanismele esențiale: planificarea, gestionarea firelor, comunicația inter-proces (IPC), spațiile de adrese, întreruperile și controlul accesului la resurse. Driverele, sistemele de fișiere și stivele de rețea rulează în componente separate, accesate prin schimb de mesaje: client → IPC → server, cu un cost suplimentar față de apelul direct:

Costul unui apel prin microkernel
C_microkernel = C_apel + C_IPC + C_schedule + C_serviciu + C_retur - mai mare decât apelul direct monolitic, dar cu un beneficiu structural important.

Beneficiul: dacă un driver neprivilegiat eșuează, kernelul poate împiedica accesul lui la memoria altor componente, iar sistemul poate opri driverul, reporni serviciul sau trece într-un mod degradat - fără să compromită restul sistemului.

Microkernelul nu garantează, singur, siguranța sau securitatea

Proprietățile depind de corectitudinea kernelului, configurarea drepturilor, serviciile executate deasupra lui, protocolul IPC și gestionarea erorilor. Principiul relevant e cel al privilegiului minim: Permisiuni(Cᵢ) = minimum necesar - un microkernel configurat greșit poate acorda unui serviciu acces excesiv la memorie sau periferice, anulând practic avantajul arhitectural.

CriteriuMonolitic / spațiu unicMicrokernel
Latență a serviciilormicămai mare (cost IPC)
Izolarea erorilorslabă, fără hardware dedicatbună, prin granițe IPC
Potriviremicrocontrolere mici, cerințe stricte de latențăsisteme cu criticitate mixtă, izolare importantă
Complexitateredusămai mare (design + comunicare)

9Protecția memoriei: MPU și MMU8 min

Izolarea software descrisă mai sus necesită suport hardware. Două componente centrale sunt MPU (Memory Protection Unit) și MMU (Memory Management Unit).

MPU - regiuni de memorie cu drepturi
Pentru regiunea Rₖ: P(Rₖ) = (rₖ, wₖ, xₖ, qₖ), unde rₖ permite citirea, wₖ scrierea, xₖ execuția, iar qₖ e nivelul de privilegiu necesar.

MMU-ul adaugă, suplimentar, traducerea adreselor virtuale în adrese fizice: A_fizic = M(A_virtual) - permite spații de adrese separate și paginare, cu un cost hardware și software mai mare decât un simplu MPU.

Într-un sistem cu MPU, un task neprivilegiat poate avea acces la propria stivă, datele proprii, un buffer partajat explicit și anumite periferice - dar nu la memoria altui task sau a kernelului. O încercare de acces în afara regiunilor permise generează o excepție, iar handlerul de eroare poate identifica task-ul vinovat, adresa accesată și tipul operației.

Ce detectează protecția memoriei - și ce nu Poate detecta: depășirea stivei, scrierea în memorie read-only, execuția dintr-o regiune de date, accesul neautorizat la periferice. Nu detectează: erori logice în interiorul regiunii permise, o valoare validă dar greșită, deadline-uri ratate, protocoale incorecte sau date eronate transmise printr-o interfață autorizată. Izolarea trebuie combinată cu validarea parametrilor, timeout-uri, supraveghere și verificarea integrității - nu o înlocuiește pe niciuna dintre ele.

10Multicore și sisteme cu criticitate mixtă10 min

Procesoarele embedded moderne conțin, tot mai frecvent, mai multe nuclee. Două modele principale de organizare: AMP (Asymmetric Multiprocessing) și SMP (Symmetric Multiprocessing).

ModelPrincipiuExemplu de organizare
AMPCore0 → RTOS0, Core1 → RTOS1 - fiecare nucleu rulează o instanță separată de softwareun nucleu pentru control, altul pentru comunicații, altul pentru funcții necritice
SMPRTOS → {Core0, Core1, ..., Coreₘ₋₁} - o singură instanță de kernel planifică task-urile pe toate nucleeleo singură coadă Ready, un singur scheduler global

AMP oferă o separare arhitecturală mai clară, cu comunicare explicită între nuclee prin memorie partajată, mailbox-uri hardware sau întreruperi inter-core. SMP permite utilizarea flexibilă a nucleelor, dar introduce complexități noi: execuție paralelă reală, migrarea task-urilor între nuclee, lock-uri între nuclee, interferență asupra cache-ului comun și dificultăți suplimentare în analiza WCET.

U ≤ m nu mai e suficient pe multicore Pe un sistem cu m nuclee, utilizarea totală nu mai e descrisă simplu prin U ≤ m - task-urile pot avea afinitate restrânsă la o submulțime de nuclee (Aᵢ ⊆ {0,1,...,m-1}), secțiuni seriale care nu se pot paraleliza, resurse comune care introduc blocare între nuclee, și interferență hardware (cache, magistrale) greu de mărginit analitic. Analiza de schedulabilitate pentru multicore e semnificativ mai complexă decât cea pentru un singur procesor studiată în Cursul 07.

Sisteme cu criticitate mixtă

Într-un sistem cu criticitate mixtă, funcții cu niveluri diferite de importanță (de exemplu control critic și interfață grafică; funcție de siguranță și conectivitate) folosesc aceeași platformă hardware. Separarea trebuie realizată atât spațial, cât și temporal:

Izolare spațială și temporală
Spațial: Mᵢ ∩ Mⱼ = ∅ pentru regiunile private ale componentelor i și j. Temporal: Cᵢ ≤ Qᵢ în fiecare interval Pᵢ, unde Qᵢ e bugetul alocat componentei, iar Pᵢ perioada de reîncărcare a bugetului.

Un sistem cu criticitate mixtă poate trece într-un mod de criticitate ridicată atunci când timpul de execuție al unei funcții critice depășește estimarea normală - în acel mod, activitățile necritice pot fi reduse sau suspendate pentru a păstra funcțiile esențiale, exact ca o formă de degradare controlată la suprasarcină.

11RTOS-uri moderne: o privire comparativă10 min

Ecosistemul de sisteme de operare embedded acoperă un spectru larg: kerneluri compacte pentru microcontrolere, sisteme apropiate de POSIX, platforme Linux cu preempție în timp real și microkerneluri cu izolare formal verificată.

Numele produsului nu determină singur performanța

Comparația trebuie făcută la nivelul unei versiuni și configurații concrete - opțiunile de compilare, driverele, BSP-ul și configurația aplicației pot modifica semnificativ latența maximă, memoria ocupată, certificabilitatea și securitatea reale ale unui produs.

PlatformăOrientare dominantăAspect de evaluat
FreeRTOSkernel compact, integrare directă pe microcontrolere miciserviciile suplimentare necesare produsului (rețea, fișiere) nu vin incluse
Zephyrecosistem integrat și configurabil (Devicetree, Kconfig), pentru dispozitive conectatecomplexitatea configurației și a actualizării
Apache NuttXinterfețe apropiate de POSIX (fire, socket-uri, sistem de fișiere)costul serviciilor activate
Linux PREEMPT_RTfuncționalitate generală și latență redusă, pe procesoare cu MMUconfigurația completă hardware-software trebuie măsurată, nu presupusă
Microkernel comercial (VxWorks, QNX)izolare, suport profesional, pachete de certificarecostul licențierii și dependența de furnizor
seL4microkernel bazat pe capabilități, verificat formal pentru anumite configurațiiconstrucția serviciilor și integrarea aplicației rămân responsabilitatea echipei

FreeRTOS oferă task-uri, priorități, planificare preemptivă sau cooperativă, cozi, semafoare, mutex-uri și grupuri de evenimente - exact primitivele studiate în această lecție - într-o imagine statică, legată direct cu aplicația. Zephyr separă descrierea hardware-ului (Devicetree) de logica aplicației, permițând aceleiași aplicații să ruleze pe platforme diferite. NuttX apropie dezvoltarea embedded de modelul POSIX clasic (pthread_create, descriptori de fișiere, socket-uri). Linux cu PREEMPT_RT reduce regiunile nepreemptibile din kernel, dar rămâne potrivit doar pentru platforme cu procesor performant, MMU și memorie suficientă - și, ca peste tot în acest domeniu, utilizarea PREEMPT_RT nu demonstrează, singură, respectarea unui deadline; platforma trebuie măsurată în configurația reală a produsului.

O arhitectură eterogenă e adesea cea mai practică soluție Multe produse combină Linux (funcționalitate complexă, interfață, conectivitate) cu un RTOS mic pe un nucleu sau microcontroler separat, dedicat buclelor de control cu deadline strict: Linux ↔ RTOS. Nu trebuie ales un singur „câștigător" pentru întregul sistem - alegerea se face per componentă, în funcție de cerințele ei temporale.

12Criterii de alegere a unui RTOS8 min

Nu există un RTOS universal optim. Selecția trebuie să pornească de la cerințele sistemului, nu de la popularitatea unei tehnologii, și trebuie să răspundă la o singură întrebare centrală:

Întrebarea de bază

„Care platformă permite demonstrarea cerințelor produsului, cu un cost și un risc acceptabile pe întreaga durată de viață?" - nu „care platformă e cea mai populară" sau „care are cele mai multe funcționalități".

Criteriile trebuie ponderate diferit în funcție de context: pentru un dispozitiv simplu, memoria și costul pot domina decizia; pentru un sistem critic, izolarea, certificarea și predictibilitatea temporală au, de regulă, prioritate.

DimensiuneCe trebuie evaluat
Cerințe funcționale și temporaletask-uri, IPC, rețea, latență maximă a întreruperilor, jitter, politica de planificare - fiecare marcată obligatorie/recomandată/opțională
ResurseM_RAM = M_kernel + ΣM_stack,i + M_heap + M_buffer + M_driver, măsurat cu driverele și protocoalele reale, nu doar kernelul gol
Siguranță, securitate, izolarestandard aplicabil, documentație de certificare, calificarea toolchain-ului, modelul de amenințări, secure boot
Ecosistem și cost totalC_total = C_licență + C_integrare + C_dezvoltare + C_validare + C_mentenanță + C_migrare
O cerință temporală trebuie să fie verificabilă „Sistemul trebuie să răspundă rapid" nu poate fi verificată. „În configurația de producție, task-ul de control trebuie să înceapă execuția în maximum 50 µs de la activarea întreruperii, cu toate funcțiile de comunicație active" poate fi testată direct - la fel cum am cerut, în Cursul 07, ca fiecare deadline să fie exprimat cantitativ, nu ca intenție vagă.

Un certificat de siguranță e relevant numai dacă versiunea folosită e cea acoperită de el, configurația e permisă, iar procesorul și compilatorul sunt cele suportate - un pachet de certificare pentru o versiune diferită de RTOS nu se transferă automat.

Procesul recomandat parcurge șase etape: definirea cerințelor măsurabile, filtrarea eliminatorie a candidaților care nu satisfac cerințele obligatorii, evaluarea ponderată a celor rămași, construirea unui prototip pe platforma țintă reală, măsurători efective (latență, jitter, RAM/Flash, consum, comportament la suprasarcină) și, în final, decizia documentată - cu candidații eliminați, ipotezele folosite și condițiile care ar impune o reevaluare ulterioară.

13Erori frecvente5 min

  • „Am protejat variabila cu volatile, deci e thread-safe." Fals - volatile influențează doar optimizările compilatorului, nu oferă excludere mutuală și nu transformă o secvență citire-modificare-scriere într-o operație atomică. Folosiți un mutex sau o operație atomică dedicată pentru accesul concurent real.
  • „Un semafor binar poate înlocui întotdeauna un mutex." Nu - semafoarele nu au conceptul de proprietar, iar Priority Inheritance, în multe RTOS-uri, e implementat doar pentru mutex-uri. Înlocuirea unui mutex cu un semafor poate elimina, tăcut, protecția împotriva inversiunii de prioritate. Folosiți mutex pentru protejarea resurselor, semafor pentru semnalizare.
  • „Priority Inheritance rezolvă complet inversiunea de prioritate, indiferent de cât durează secțiunea critică." Nu - PIP mărginește cine poate întârzia task-ul de prioritate mare, dar nu scurtează secțiunea critică în sine; o secțiune critică prost proiectată (operații lente, I/O blocant) rămâne o problemă indiferent de protocol. Păstrați secțiunile critice scurte, indiferent de protocolul de control al priorității folosit.
  • „Un microkernel garantează, singur, un sistem mai sigur decât unul monolitic." Nu automat - microkernelul reduce codul privilegiat și izolează componentele, dar siguranța reală depinde de configurarea corectă a permisiunilor și de aplicarea principiului privilegiului minim. Evaluați configurația concretă, nu doar clasificarea arhitecturală generică.

14Rezumat și glosar5 min

Sincronizarea corectă a task-urilor concurente cere excludere mutuală pentru resursele partajate (mutex) și mecanisme dedicate de semnalizare (semafor, grup de evenimente, coadă de mesaje) - alegerea greșită între ele poate reintroduce, tăcut, condiții de cursă sau poate elimina protecții de care aplicația depinde fără să știe. Când task-uri cu priorități diferite partajează o resursă, poate apărea inversiunea de prioritate; Priority Inheritance și Priority Ceiling sunt cele două protocoale clasice care o mărginesc, cu compromisuri diferite între simplitate și predictibilitate. La nivelul kernelului, arhitectura monolitică oferă latență minimă dar izolare slabă, microkernelul oferă izolare mai bună cu un cost de comunicare; MPU/MMU aduc suportul hardware necesar izolării, iar sistemele multicore (AMP/SMP) și cele cu criticitate mixtă complică semnificativ analiza temporală. Selecția unui RTOS concret trebuie ghidată de cerințele verificabile ale produsului, nu de popularitatea unei platforme.

Condiție de cursă
rezultat dependent de ordinea temporală imprevizibilă a accesărilor concurente.
Secțiune critică
regiune de cod care trebuie executată cu excludere mutuală.
Inversiune de prioritate
un task de prioritate mare ajunge să aștepte, indirect, după unul de prioritate mică.
PIP
Priority Inheritance Protocol - deținătorul unei resurse moștenește temporar prioritatea celui mai prioritar task blocat pe ea.
PCP
Priority Ceiling Protocol - fiecare resursă are un plafon de prioritate, stabilit dinainte.
MPU/MMU
unități hardware pentru protecția, respectiv gestionarea memoriei virtuale.
AMP/SMP
Asymmetric/Symmetric Multiprocessing - două modele de organizare multicore.
TCB (aici)
Trusted Computing Base - componentele de încredere ale unui sistem (nu Task Control Block).

15Întrebări de verificare6 min

  1. De ce operația counter++ nu e neapărat atomică, la nivel de procesor?
  2. Care e diferența fundamentală dintre un mutex și un semafor?
  3. Descrieți scenariul minimal de inversiune de prioritate, cu trei task-uri.
  4. Cum reduce Priority Inheritance timpul de blocare al unui task de prioritate mare?
  5. Ce garantează în plus Priority Ceiling față de Priority Inheritance?
  6. Care e diferența principală dintre un kernel monolitic și un microkernel?
  7. Ce poate, și ce nu poate, detecta protecția memoriei prin MPU?
  8. Care e diferența dintre AMP și SMP pe un procesor multicore?

16Direcții de aprofundare2 min

Cursul următor trece de la sincronizarea și structura RTOS-ului la o direcție diferită: sistemele reconfigurabile și accelerarea hardware - de ce și când merită mutat un calcul din software în hardware dedicat (FPGA, accelerator), și ce compromisuri aduce această decizie.

Mutex-urile, semafoarele, grupurile de evenimente și protocoalele de control al priorității discutate aici devin cod real, verificabil pe hardware, în Laboratorul 03, alături de conceptele din Cursul 07.