Sistemi operativi embedded e real-time per l'IoT
In questa pagina 7
Questa nota riguarda i sistemi operativi per i dispositivi embedded, i microcontrollori e l'Internet of Things (IoT). Hanno requisiti molto diversi da quelli di un PC: pochi kilobyte di memoria, consumo minimo, vincoli di tempo. Le tecniche di scheduling in tempo reale sono in Scheduling multiprocessore e real-timeNei sistemi real-time conta la scadenza: si misurano latenza di interrupt e latenza di dispatch; i sistemi soft real-time danno priorità ai compiti critici senza garanzie, quelli hard devono rispettare le scadenze; per compiti periodici (burst t, scadenza d, periodo p, con 0 <= t <= d <= p) l'utilizzo è la somma di t/p: Rate Monotonic (priorità fissa, minore il periodo maggiore la priorità) garantisce le scadenze se l'utilizzo non supera N(2^(1/N)-1) (83% per due compiti), Earliest Deadline First (priorità dinamica, scadenza più vicina) le garantisce se l'utilizzo è sotto il 100%; la ripartizione proporzionale assegna T quote con controllo di ammissione; con più processori si hanno code comuni o private (SMP), core con più thread hardware, bilanciamento del carico (push e pull migration), affinità al processore (soft e hard), scheduling NUMA-aware; Linux usa CFS/EEVDF con domini di scheduling, Windows 32 livelli di priorità.Scheduling multiprocessore e real-time →; l'hardware dei microcontrollori in Linguaggio C per i microcontrollori - puntatori, tipi e operatoriNei µC a 32 bit (ARM) un char è 1 byte, short 2, int e long 4, i puntatori 4 (sizeof(pa)=4). x++ restituisce il valore prima dell'incremento, ++x dopo; (p++) legge e poi sposta il puntatore di sizeof(p) byte. Una macro senza parentesi interne si espande come testo (#define square(x) (xx): 64/square(4) = 64/(44) = 4). Il prodotto di due uint16_t è un int a 32 bit con segno: per un uint16_t*uint16_t grande serve il cast a uint32_t prima del prodotto. Divisione intera e resto: $-13/4=-3$, $-13%4=-1$. Per i registri periferici: volatile.Linguaggio C per i microcontrollori - puntatori, tipi e operatori →, Organizzazione del processore - Von Neumann, Harvard, bus, ALU e registriOgni processore ha ALU, memoria e dispositivi di I/O. Nell'organizzazione Von Neumann dati e istruzioni stanno nella stessa memoria e viaggiano su un solo sistema di bus (semplice ed economica, usata nei µC più semplici); nell'organizzazione Harvard memorie e bus sono separati (più accessi per ciclo: tipica di DSP e µC veloci). I bus sono pilotati da porte tri-state; l'ALU lavora su registri (accumulatore o molti registri); il controllo è cablato o microprogrammato; la gerarchia di memoria va dai registri alla RAM interna e a quella esterna.Organizzazione del processore - Von Neumann, Harvard, bus, ALU e registri → e Bus I2C, SPI e CANI2C: bus seriale sincrono a 2 fili (SDA dati, SCL clock, con resistenze di pull-up), un master (il µC) e molti slave con indirizzo a 7 bit; byte da 9 bit (8 + ACK dato dal ricevitore abbassando SDA); start: SDA scende con SCL alto; stop: SDA sale con SCL alto; 100 kbit/s (normale), 400 kbit/s (fast); half-duplex. SPISerial Peripheral Interface: bus seriale sincrono a quattro fili: 4 fili (MOSI, MISO, SCK, CS), full duplex, master e slave selezionati da CS, 4 modi (CPOL, CPHA). CAN: bus differenziale (120 Ω) asincrono, messaggi etichettati da un identificatore (11 o 29 bit), arbitraggio automatico (vince l'ID più basso), bit stuffing dopo 5 bit uguali, CRC a 15 bit, contatori di errori (127, 255).Bus I2C, SPI e CAN →.
Sistemi embedded
Definizione (sistema embedded). Un sistema embedded è l'uso di elettronica e software dentro un prodotto che ha una funzione, o un insieme di funzioni, specifiche, a differenza di un calcolatore di uso generale. Equivale a qualunque dispositivo con un chip di calcolo che non è una workstation, un desktop o un portatile.
Esempi: telefoni, fotocamere, smart TV, calcolatrici, lavatrici, forni a microonde, sistemi di sicurezza domestica, automotive, sensori e attuatori nei sistemi automatici. Spesso sono strettamente accoppiati all'ambiente, e questo dà vincoli di tempo reale: velocità di movimento, precisione di misura e durata decidono i tempi delle operazioni software; se più attività sono gestite insieme, i vincoli sono più complessi.
Processori applicativi e dedicati
| processore applicativo | processore dedicato |
|---|---|
| sistema progettato per molte applicazioni e funzioni, con sistemi operativi complessi (Linux, Android, Chrome OS) | dedicato a uno o pochi compiti richiesti dal dispositivo ospite |
| uso generale (smartphone, Raspberry Pi) | processore e componenti progettati per ridurre dimensioni e costo |
Microprocessori e microcontrollori
Un microprocessore è un processore miniaturizzato in uno o pochi circuiti integrati, con registri, ALU e unità di controllo; i moderni hanno più core e molta cache, ma contengono solo alcuni degli elementi di un calcolatore: serve una scheda madre, memoria, chip di I/O, schede di espansione, memoria di massa.
Un microcontrollore (MCU) integra processore, memoria e periferiche:
- architetture a 8, 16 o 32 bit;
- RAM limitata (circa KB) ed EEPROM o flash (da KB a MB);
- di solito senza MMU (quindi niente memoria virtuale: vedi Memoria virtuale - paginazione su richiesta e sostituzione delle pagineLa memoria virtuale separa la memoria logica da quella fisica: basta che alcune pagine siano in RAM; con la paginazione su richiesta (demand paging) una pagina si carica solo al primo riferimento, e un riferimento a una pagina non residente (bit di validità = i) genera un page fault gestito dal sistema operativo in sei passi; EAT = (1 - p) * accesso + p * servizio del fault, con p minuscolo; copy-on-write rende economica la fork; se non ci sono frame liberi si sceglie una vittima con FIFO (anomalia di Belady), OPT (ottimo, irrealizzabile), LRU (algoritmo a stack), second chance (bit di riferimento, orologio) o la versione con bit di modifica; i frame si assegnano in modo uguale o proporzionale, con sostituzione globale o locale; se un processo non ha abbastanza frame va in thrashing; il modello del working set e la frequenza dei page fault lo evitano; considerazioni su prepaging, dimensione della pagina, TLB reach e struttura dei programmi; memoria del kernel con buddy system e slab.Memoria virtuale - paginazione su richiesta e sostituzione delle pagine →); alcuni (ARM Cortex-M3 e M4) hanno una MPU (memory protection unit) per la protezione della memoria;
- velocità di circa MHz;
- molte interfacce di I/O e di comunicazione, e temporizzatori;
- consumo di circa mW.
Un sistema profondamente embedded (deeply embedded) ha un processore il cui comportamento è difficile da osservare sia per il programmatore sia per l'utente: usa un microcontrollore, non è riprogrammabile una volta masterizzata la logica nella ROM, e ha poca o nessuna interazione con l'utente. Sono dispositivi dedicati e con un solo scopo: 1) rilevano qualcosa nell'ambiente, 2) fanno un'elaborazione di base, 3) fanno qualcosa con il risultato. Spesso hanno capacità wireless e stanno in reti di sensori su ampie aree; l'IoT dipende molto da essi. Hanno vincoli estremi di dimensione, memoria, tempo e consumo.
Caratteristiche di un OS embedded
Un OS embedded ha: funzionamento in tempo reale; funzionamento reattivo; uso diretto delle interruzioni (InterruptClassi di interruzioni; I/O guidato da interrupt; sequenza hardware e software di gestione con salvataggio del contesto sullo stack; identificazione della sorgente (linee multiple, polling, daisy chain, vettore); priorità e interruzioni annidate; eccezioni e modi in ARM.Interrupt →); configurabilità (si include solo ciò che serve); flessibilità con i dispositivi di I/O; meccanismi di protezione semplificati (Sistemi di I-O e memory-mapped I-OIl sistema operativo nasconde la varietà dei dispositivi con driver e un'interfaccia uniforme (dispositivi a blocchi, a caratteri, di rete, timer, ioctl); l'hardware è fatto di porte, bus e controller con registri di dato, stato e controllo, indirizzati con istruzioni di I/O dedicate o con il memory-mapped I/O; la CPU comunica con il dispositivo con il polling (busy wait, adatto a dispositivi veloci), con le interruzioni (linea IRQ, vettore, priorità, mascherabili) o con il DMA (trasferimenti di blocchi senza CPU, un'interruzione alla fine); le chiamate possono essere bloccanti, non bloccanti o asincrone; le istruzioni di I/O sono privilegiate e si passa dalle system call; il kernel gestisce errori, buffer e protezione; mmap() mappa un file nello spazio di indirizzi di un processo: accesso con load e store, caricamento pigro con il demand paging, meno chiamate di sistema e meno copie, condivisione con MAP_SHARED.Sistemi di I-O e memory-mapped I-O →).
Ambiente host e target
L'OS embedded è configurato e costruito su un calcolatore (host) e poi trasferito sul sistema embedded (target). Ha tre parti:
- il kernel, con alcuni di questi moduli: gestione di processi e thread, della memoria, IPC e timer, driver per I/O, rete, audio, memoria di massa e grafica, file system, rete, gestione dell'energia;
- il boot loader: l'avvio iniziale del sistema e il caricamento del kernel in memoria;
- il root file system: tutti i file necessari al funzionamento (come in un PC, ma solo un insieme minimo di programmi e librerie).
Il kernel si compila con la cross-compilazione: la produzione di codice eseguibile per una piattaforma diversa da quella su cui gira il compilatore.
Due strade
- Adattare un OS esistente (Linux, FreeBSD, Windows): si aggiungono capacità di tempo reale (soft), si snellisce il funzionamento, si aggiunge ciò che serve. Vantaggio: interfaccia nota. Svantaggio: non ottimizzato per applicazioni in tempo reale stretto (hard).
- OS costruito per lo scopo (FreeRTOS, RIOT, Zephyr): cambio rapido e leggero di processo o thread, scheduling real-time hard, dimensione piccola, gestione veloce delle interruzioni, partizioni di memoria fisse o variabili, file speciali sequenziali che accumulano dati velocemente.
Embedded Linux
Una versione di Linux per un sistema embedded: deve supportare un insieme specifico di dispositivi, periferiche e protocolli, secondo l'hardware presente e lo scopo. Una distribuzione embedded è personalizzata sulle limitazioni di dimensione e hardware; il kernel è molto più piccolo di quello normale (di un ordine di grandezza o più). Vantaggi: indipendenza dal fornitore, supporto di molte architetture e periferiche, basso costo, vantaggi del software libero. Svantaggi: di solito non adatto a microcontrollori e sistemi profondamente embedded, né a vincoli di tempo reale stretto.
File system embedded (devono essere piccoli): cramfs (sola lettura, compresso a unità pari alla pagina), squashfs (compresso, sola lettura, poca memoria), jffs2 (a log per flash NOR e NAND, con il wear leveling: vedi Memoria di massa e scheduling del discoLa memoria di massa è fatta di dischi rigidi (HDD: piatti, tracce, cilindri, settori) e memorie non volatili NAND (SSD); tempo di accesso HDD = seek + latenza rotazionale (mezzo giro, 30/RPM secondi), tempo di I/O = accesso + trasferimento + overhead del controller; i blocchi sono indirizzati con un LBA lineare; per l'HDD si minimizza il seek con FCFS, SSTF (rischio di starvation), SCAN (ascensore) e C-SCAN (più uniforme), con le varianti LOOK e C-LOOK e il deadline di Linux; per le NVM contano FTL, garbage collection, overprovisioning e wear leveling; il sistema operativo formatta (fisico e logico), partiziona, gestisce il boot e lo spazio di swap; parità ed ECC rilevano e correggono errori; il RAID usa striping, mirroring e parità (livelli 0, 1, 4, 5, 6, 10) per più velocità e affidabilità, con MTTDL = 1/PDL.Memoria di massa e scheduling del disco →), ubifs (migliori prestazioni su flash grandi, con cache di scrittura), yaffs2 (veloce e robusto per flash grandi riscritte spesso).
Clinux (microcontroller Linux) è la variante di Linux per microcontrollori e sistemi molto piccoli, oggi integrata nel kernel principale. La filosofia: snellire l'ambiente togliendo utilità e servizi non necessari. La differenza più importante è la gestione della memoria: non usa la MMU, quindi niente indirizzi virtuali ma solo assoluti; c'è un solo spazio di indirizzi condiviso da tutti i processi (Linux ne ha uno per processo); non c'è la fork standard, ma si possono avere più processi. Usa una libreria C più piccola, Clibc: per esempio libc da 1,2 MB di glibc a 284 KB di Clibc. Le alternative più recenti sono uClibc-ng e musl.
Per costruire un sistema embedded completo: Buildroot automatizza la costruzione, con cross-compilazione, di toolchain, root file system, immagine del kernel Linux e bootloader per il target (con configurazioni per molte schede). Il progetto Yocto (con il riferimento Poky e il sistema OpenEmbedded) personalizza sistemi Linux per ARM, MIPS, PowerPC e x86, tipicamente per sistemi dedicati senza schermo o input.
OS per microcontrollori
Non c'è la MMU, quindi niente memoria virtuale. I sistemi si differenziano per architetture e schede supportate (8, 16, 32, 64 bit), impronta di memoria (memory footprint), multithreading e scheduling, rete e dispositivi di I/O.
TinyOS
Un OS ridotto al minimo per i sistemi embedded, popolare per le reti di sensori wireless (WSN). La parte base richiede circa 400 byte di codice e dati insieme. Non è un vero OS: nessun kernel, nessun processo concorrente, nessuna allocazione di memoria; ma ha uno scheduler. Non bloccante, con poche primitive di sincronizzazione.
- Un'applicazione è un insieme di componenti, ognuno con uno o più semplici task, che comunicano tra loro e con l'hardware in modi limitati e ben definiti.
- Non c'è un kernel ma un modulo scheduler, presente in ogni sistema TinyOS, che esegue un solo task alla volta; lo scheduler predefinito è una coda FIFO, ma si può scegliere uno schema non preemptive a priorità; è consapevole dell'energia: mette in sleep il processore quando non ci sono task in coda.
- Un task è atomico: una volta partito, finisce. Non può essere prelazionato da un altro task dello stesso componente né attendere in attesa bloccante; può calcolare, essere interrotto da un evento esterno, segnalare eventi di livello più alto, chiamare comandi di componenti di livello più basso, schedulare altri task.
- Un comando è una richiesta non bloccante a un componente di livello inferiore (per esempio una lettura del sensore); un evento può essere legato direttamente o no a eventi hardware: i componenti di livello più basso sono collegati alle interruzioni hardware.
RIOT
Un piccolo OS per sistemi di rete a bassa potenza e con poca memoria: pochi kB, non servono MMU né MPU. Sviluppato da FU Berlin, INRIA e HAW Hamburg (dal 2013, derivato da FireKernel). Gira su processori a 8, 16 e 32 bit (anche 64 in modo nativo), con molte architetture (Arm, STM32, RISC-V, AVR, MIPS) e oltre 250 schede. Ha un insieme completo di funzioni (kernel, astrazione hardware, librerie, strumenti), è software libero (LGPL), programmabile in C, C++ e Rust, ed è modulare: si costruisce solo ciò che serve. Reti: IPv6, 6LoWPAN, UDP, TCP, CoAP, e stack come GNRC, lwIP, OpenThread. Configurazione minima (core + cpu + board su Arm Cortex-M): 3,2 kB di ROM e 2,8 kB di RAM.
Struttura: core (kernel e strutture dati di base), astrazione hardware (cpu, boards, periph per le periferiche, drivers), sys (librerie di sistema, rete), pkg (librerie di terzi), application. La CPU è organizzata come architettura famiglia tipo modello; l'API delle periferiche (GPIO, UART, SPI, PWM) è la stessa su tutte le architetture; i driver sono indipendenti da scheda e CPU (con strati generici come netdev, SAUL per sensori e attuatori, MTD per le flash).
Kernel a microkernel, con solo le funzioni essenziali:
- multithreading (opzionale, per risparmiare memoria; senza si comporta come Arduino);
- cambio di contesto;
- scheduler preemptive a priorità fisse, in tempo reale, senza tick (tickless), con operazioni ;
- gestione delle IRQ;
- mailbox e messaggi per la comunicazione tra processi (IPC);
- mutex e semafori (anche opzionali) per la sincronizzazione (vedi Sincronizzazione - sezione critica, race condition e mutexQuando più processi o thread accedono agli stessi dati e il risultato dipende dall'ordine degli accessi si ha una race condition (esempio: counter++ e counter-- sono tre istruzioni macchina ciascuna, e interleaving diversi danno 4, 5 o 6 partendo da 5); il problema della sezione critica chiede un protocollo (entry section, sezione critica, exit section, remainder section) che garantisca mutua esclusione, progresso e attesa limitata; disabilitare gli interrupt non scala sui multiprocessori, l'alternanza stretta con turn viola il progresso, la soluzione di Peterson (flag[2] e turn) soddisfa i tre requisiti ma può fallire su hardware moderno per il riordino delle istruzioni, per cui servono barriere di memoria; l'hardware offre test_and_set e compare_and_swap (atomiche) e variabili atomiche; il mutex lock (acquire/release, in POSIX pthread_mutex_t) è lo strumento di alto livello, con attesa attiva se è uno spinlock oppure con sospensione del thread se è un mutex di sistema; bloccare due volte lo stesso mutex senza sbloccarlo causa deadlock.Sincronizzazione - sezione critica, race condition e mutex →);
- gestione dell'energia (solo le interruzioni risvegliano il sistema).
Scheduler. Basato su priorità fisse e prelazione; permette lo scheduling (soft) real-time. Un thread di priorità più alta può essere interrotto solo da una ISR (routine di servizio dell'interruzione); è disponibile round robin come opzione. Non c'è cambio di contesto per simulare il parallelismo tra task di pari priorità: ognuno gira fino al completamento o finché cede volontariamente. È tickless: non controlla di continuo se è successo qualcosa; quando nessun altro task gira, il thread idle (priorità minima) mette il sistema in sleep, con il massimo risparmio di energia, risvegliato solo da eventi esterni. Per Scheduling della CPU - FCFS, SJF, priorità e round robinLo scheduler della CPU sceglie tra i processi pronti quello a cui dare il core (il dispatcher esegue il cambio, con latenza di dispatch); lo scheduling è non preemptive (il processo tiene la CPU finché termina o si blocca) o preemptive (la CPU gli può essere tolta, con rischio di race condition); i criteri sono utilizzo della CPU e throughput (da massimizzare), tempo di attesa, di turnaround (completamento meno arrivo) e di risposta (da minimizzare); FCFS è semplice ma soffre dell'effetto convoglio (esempio 24, 3, 3: attesa media 17 contro 3); SJF è ottimo per l'attesa media ma richiede la lunghezza del prossimo burst, stimata con la media esponenziale tau(n+1) = alfa t(n) + (1-alfa) tau(n); SRTF è la versione preemptive (esempio con attesa media 6,5 contro 7,75); round robin dà a ciascuno un quanto q e nessuno attende più di (n-1)q; la priorità causa starvation, risolta con l'aging; code multilivello e a feedback (MLFQ) combinano le politiche.Scheduling della CPU - FCFS, SJF, priorità e round robin → e la priorità in generale vedi la parte sullo scheduling.
Thread. Due di default: il main (esegue main()) e l'idle (priorità minima, si esegue quando gli altri sono bloccati o terminati; porta il sistema in modo a basso consumo). Ogni thread ha stack proprio e un blocco di controllo (TCB) minimo. Eventi esterni: il contesto ISR li gestisce e avvisa i thread con messaggi IPC. In RIOT un numero di priorità più basso significa priorità più alta: THREAD_PRIORITY_MAIN + 1 è meno prioritario del thread principale.
Librerie utili: xtimer (poi ztimer: astrazione dei timer hardware, callback, sleep), shell (riga di comando interattiva), crittografia e altre; pacchetti esterni (lwIP, OpenThread, lvgl, LoRaMAC). Supporto di rete: GNRC (802.15.4, 6LoWPAN, IPv6), CAN, BLE (NimBLE), LoRaWAN, SigFox.
FreeRTOS
Creato nel 2003, passato ad Amazon Web Services nel 2017; piccolo e semplice, scritto soprattutto in C; è lo standard de facto fra i RTOS. Offre task (thread), mutex, semafori, timer software, funzionamento a basso consumo; supporta priorità dei thread (variabili a runtime) e allocazione statica o dinamica con 5 schemi di gestione della memoria. Lo scheduler supporta di default il time slicing (round robin) preemptive e la priorità fissa preemptive; il tempo di CPU è assegnato a tick (intervallo di solito 1-10 ms da un timer hardware); le ISR hanno la priorità più alta e possono interrompere i task in ogni momento.
Zephyr
Parte da Virtuoso RTOS per DSP; acquisito da Wind River nel 2001, chiamato Rocket nel 2015, progetto open source della Linux Foundation con il nome Zephyr dal 2016. Per applicazioni embedded su MCU: piccolo kernel monolitico, spazio di indirizzi unico con supporto MPU, più algoritmi di scheduling, multiprocessing asimmetrico (AMP) e simmetrico (SMP), stack di protocolli (IPv4/6, BLE, 802.15.4, CAN), interfaccia con diversi file system (FATFS, LittleFS, NVS). Oltre 350 schede, ma solo MCU a 32 bit.
Contiki-NG
Evoluzione di Contiki (nato per le WSN). Segue un modello a eventi con scheduling cooperativo basato su protothread (pseudo-thread molto leggeri): i processi sono definiti staticamente, senza priorità; ogni processo è una funzione che a un certo punto cede l'esecuzione; se non cede (per esempio un ciclo infinito) si blocca l'intero sistema. Stack di rete uIP con IPv6, RPL, 6LoWPAN e CoAP.
Confronto
Un confronto sperimentale tra Contiki-NG, RIOT e Zephyr: con tutti i servizi e lo stack di rete la RAM richiesta è circa 29,8 KB, 33,0 KB e 52,9 KB e la flash 50,6 KB, 59,9 KB e 130,5 KB: Contiki-NG e RIOT stanno in un dispositivo di classe 1, Zephyr richiede la classe 2.
| Contiki-NG | RIOT | Zephyr | FreeRTOS | |
|---|---|---|---|---|
| architettura | monolitica a eventi | microkernel | monolitica | piccolo kernel |
| scheduling | cooperativo (protothread) | preemptive a priorità fissa, tickless | preemptive, a priorità | preemptive a priorità + round robin, a tick |
| MCU | 32 bit | 8, 16, 32 bit | solo 32 bit | molte |
Internet of Things
L'IoT è la crescente interconnessione (a corto raggio) di dispositivi embedded intelligenti, dagli elettrodomestici ai piccoli sensori. Forniscono informazioni dai sensori, agiscono sull'ambiente e a volte si modificano da soli, per gestire un sistema più grande (una fabbrica, una città). Sono soprattutto dispositivi profondamente embedded, a basso consumo, bassa banda e pochi dati, che comunicano tra loro; ci sono anche videocamere di sicurezza, telefoni VoIP e altri con streaming a banda alta.
Quattro generazioni di dispiegamento: 1) IT (PC, server, router, firewall; cablate); 2) OT (operational technology: macchine con IT incorporato da aziende non informatiche, per esempio macchinari medici, chioschi; cablate); 3) tecnologia personale (smartphone, tablet, e-reader; wireless di vari tipi); 4) tecnologia di sensori e attuatori: dispositivi con un solo scopo, wireless, parte di sistemi più grandi: è l'IoT vero e proprio.
Livelli di architettura: edge (rete di dispositivi con sensori e attuatori, collegata da un gateway alla rete di comunicazione superiore), fog (pre-elaborazione vicino ai sensori della massa di dati prodotti), core (rete dorsale che collega le reti locali a server ad alte prestazioni e database), cloud (memoria ed elaborazione dei dati dell'IoT e applicazioni che gestiscono i dispositivi). Per i protocolli di rete e le architetture vedi Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribeIl modello ISO/OSI (7 livelli) e la pila TCP/IP (4 livelli) dividono la comunicazione in strati; ogni livello riceve una SDU dal livello superiore, aggiunge la sua intestazione e produce una PDU; il confine fra due livelli adiacenti e' il SAP, identificato da un indirizzo (EtherType per Ethernet, numero di protocollo per IP, numero di porta per TCP e UDP) e per un programma C il SAP del trasporto e' l'API delle socket; il data plane inoltra i pacchetti seguendo le tabelle, il control plane le costruisce con i protocolli di instradamento; client/server funziona come una chiamata di funzione (richiesta e risposta), peer-to-peer mette tutti i nodi alla pari, publish/subscribe funziona come un interrupt: un broker inoltra gli eventi agli iscritti, con disaccoppiamento nello spazio, nel tempo e nella sincronizzazione.Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribe →.
Dispositivi vincolati
La maggior parte dei dispositivi IoT ha poche risorse (memoria, calcolo, rete), niente MMU e bassi consumi: sono dispositivi vincolati. Molti hanno microcontrollori a 8 o 16 bit con poca RAM e memoria, e connettività wireless a bassa velocità. I dispositivi diventano più economici, piccoli ed efficienti, ma non necessariamente più potenti. Classi (IETF):
| classe | risorse | esempio |
|---|---|---|
| 0 | meno di 10 KB di RAM e 100 KB di flash | sensori "mote" o smart dust sparsi in una regione, che raccolgono dati e li passano fino a un punto centrale (sensori di temperatura in una fattoria) |
| 1 | circa 10 KB di RAM e 100 KB di flash | vincolati nel codice e nel calcolo, non parlano facilmente con i nodi di Internet ma usano protocolli dedicati (CoAP) senza gateway; con funzioni di instradamento e sicurezza |
| 2 | più risorse | piccoli gateway, che comunicano con i calcolatori ma richiedono protocolli leggeri e a basso consumo |
Componenti di un dispositivo IoT: microcontrollore (la parte "intelligente"), sensore (misura un parametro fisico, chimico o biologico e produce un segnale analogico o digitale, vedi ADC e DAC - risoluzione, SNR, ENOB ed erroriUn convertitore a $n$ bit con fondo scala $FSR$ ha risoluzione $LSB=FSR/2^n$; il codice vale $C=\mathrm{round}(V/LSB)$ e la tensione corrispondente è $V=C\cdot LSB$ con incertezza $\pm\frac12LSB$ (errore di quantizzazione). Con un modello a rumore uniforme di potenza $LSB^2/12$ il SNR di una sinusoide a fondo scala è $6{,}02,n+1{,}76$ dB; dal SNR misurato si ricava il numero di bit efficaci $ENOB=\frac{SNR-1{,}76}{6{,}02}$. Gli errori reali (offset, guadagno, INL, DNL, missing code) abbassano l'accuratezza. Accuratezza ≠ precisione: alta accuratezza implica alta precisione, non viceversa.ADC e DAC - risoluzione, SNR, ENOB ed errori →), attuatore (riceve un segnale e agisce sull'ambiente), trasmettitore-ricevitore (transceiver, di solito wireless; con RFID: tag e lettori). Per questi dispositivi sono più adatti OS piccoli (RIOT, FreeRTOS) di altri che richiedono molte risorse (embedded Linux).
Requisiti di un OS per l'IoT
- Poca memoria: molti ordini di grandezza meno di smartphone, tablet e grandi dispositivi embedded.
- Hardware eterogeneo: ARM a 32 bit domina, ma i dispositivi vincolati sono ancora a 8 e 16 bit.
- Connessione di rete: uno stack con molte tecniche e protocolli a basso consumo (LoRaWAN, BLE).
- Efficienza energetica: spesso devono funzionare anni con una sola carica; modi a basso consumo (deep sleep), perché molti compiti sono periodici.
- Tempo reale: flussi di dati dai sensori in tempo reale (sorveglianza), dispositivi che si parlano (robot in un magazzino), risposta immediata a eventi di sicurezza (freni di un'auto).
- Sicurezza: meccanismi e aggiornamenti a livello di applicazione (antivirus), di rete (autenticazione) e di dispositivo (validazione dell'integrità); vedi Sicurezza e protezione nei sistemi operativiLe minacce all'accesso sono gli intrusi (masquerader, misfeasor, clandestine user) e il software malevolo (parassita: virus, logic bomb, backdoor; indipendente: worm, bot); contromisure: sistemi di rilevamento delle intrusioni (IDS, host o network, con sensori, analizzatori e interfaccia), autenticazione (identificazione + verifica con qualcosa che si sa, si ha, si è, si fa) e controllo degli accessi (permessi, sudo, SELinux, ACL, auditing, firewall); il buffer overflow scrive oltre un buffer nello stack e può sovrascrivere variabili o l'indirizzo di ritorno (code injection, shellcode); difese a compilazione (linguaggi sicuri, codice sicuro, librerie, stack protection con canarino) e a esecuzione (NX bit, ASLR, guard pages); sulla rete ci sono sniffing, spoofing e man-in-the-middle, e la crittografia (simmetrica e asimmetrica, RSA) limita mittenti e destinatari; la tolleranza ai guasti usa ridondanza (spaziale, temporale, informativa) e si misura con R(t), MTTF, MTTR e disponibilità.Sicurezza e protezione nei sistemi operativi →.
Architettura tipica di un OS per l'IoT: librerie di sistema e di supporto (shell, log), driver e file system minimi, stack di rete a basso consumo (anche IP), kernel (scheduler, sincronizzazione, mutua esclusione, timer), HAL (hardware abstraction layer) con API coerente per GPIO, UART, SPI, I2C.
Laboratori 11 e 12: RIOT
I laboratori usano RIOT 2023.04 su una macchina virtuale, con la scheda Arduino Mega 2560 (microcontrollore ATmega2560 a 8 bit, 16 MHz, 54 pin digitali, 16 ingressi analogici, 4 UART) e il simulatore online Wokwi. Procedimento: installare gcc-avr e avr-libc; scaricare RIOT; entrare in examples/hello-world; compilare con make BOARD=arduino-mega2560 (produce bin/arduino-mega2560/hello-world.elf); caricare l'.elf su Wokwi ("Upload Firmware and Start Simulation"). I percorsi senza spazi, altrimenti make fallisce.
- Shell (lab 11):
USEMODULE += shellnelMakefile,#include "shell.h",shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE). I comandi sono un array dishell_command_t(nome,descrizione, funzioneint f(int argc, char **argv)) terminato da{NULL, NULL, NULL}; le macroRIOT_BOARDeRIOT_CPUdanno scheda e CPU. Esercizio: Esercizio - Shell di RIOT con i comandi board, cpu ed echo (laboratorio 11). - Thread (lab 12):
thread_create(stack, sizeof(stack), priorità, flags, funzione, arg, "nome"); lo stack è una variabile globalestatic char stack[THREAD_STACKSIZE_MAIN]. Il comandops(modulishell_cmds_defaulteps) elenca i thread. Un thread che termina sparisce daps; un thread con prioritàTHREAD_PRIORITY_MAIN + 1(meno prioritaria) e busy loop resta in attesa perché il thread principale ha priorità maggiore e non cede.shell_runnon ritorna mai: i thread vanno creati prima. - Mutex (lab 12):
mutex_t,mutex_lock,mutex_unlock,ztimer_sleep(ZTIMER_MSEC, ms)(moduliztimereztimer_msec). Esercizio con scrittore e lettore: Esercizio - Thread e mutex in RIOT con un buffer condiviso (laboratorio 12).
Domande d'esame
- Perché Linux di solito non è adatto a un dispositivo IoT di classe 0 o 1? Quale alternativa? Richiede MMU, molta memoria e flash, non garantisce il tempo reale stretto e consuma troppo. Si usa un RTOS (RIOT, FreeRTOS, Zephyr) o uClinux (senza MMU, spazio di indirizzi unico, niente
fork). - Descrivere lo scheduler di RIOT e il thread idle. Priorità fisse e prelazione, tickless; un thread prioritario è interrotto solo da una ISR; a pari priorità non si cambia contesto; l'idle (priorità minima) porta il sistema in sleep finché un evento esterno lo risveglia.
- In TinyOS cosa sono task, comandi ed eventi? Un task è atomico e non prelazionabile da altri task; un comando è una richiesta non bloccante verso il livello inferiore; un evento arriva dal basso (hardware) verso l'alto; lo scheduler FIFO esegue un task alla volta e dorme quando la coda è vuota.
Versione ripasso
Sistemi embedded. Elettronica e software dentro un prodotto con funzione specifica (non un calcolatore generale): telefoni, fotocamere, lavatrici, automotive, sensori e attuatori. Spesso accoppiati all'ambiente: vincoli di tempo reale (velocità, precisione, durata). Processori applicativi (molte funzioni, Linux, Android, Chrome OS: smartphone, Raspberry Pi) o dedicati (pochi compiti, riducono dimensione e costo).
Microprocessore e microcontrollore. Microprocessore: registri, ALU, unità di controllo (più core, cache) ma serve il resto del calcolatore. Microcontrollore (MCU): 8/16/32 bit; RAM in KB, EEPROM/flash da KB a MB; di solito senza MMU (alcuni con MPU, ARM Cortex-M3/M4); MHz; mW; molte interfacce e timer. Deeply embedded: MCU, non riprogrammabile (logica nella ROM), poca interazione; rileva elabora agisce; base dell'IoT; vincoli estremi di dimensione, memoria, tempo, consumo (Linguaggio C per i microcontrollori - puntatori, tipi e operatoriNei µC a 32 bit (ARM) un char è 1 byte, short 2, int e long 4, i puntatori 4 (sizeof(pa)=4). x++ restituisce il valore prima dell'incremento, ++x dopo; (p++) legge e poi sposta il puntatore di sizeof(p) byte. Una macro senza parentesi interne si espande come testo (#define square(x) (xx): 64/square(4) = 64/(44) = 4). Il prodotto di due uint16_t è un int a 32 bit con segno: per un uint16_t*uint16_t grande serve il cast a uint32_t prima del prodotto. Divisione intera e resto: $-13/4=-3$, $-13%4=-1$. Per i registri periferici: volatile.Linguaggio C per i microcontrollori - puntatori, tipi e operatori →, Organizzazione del processore - Von Neumann, Harvard, bus, ALU e registriOgni processore ha ALU, memoria e dispositivi di I/O. Nell'organizzazione Von Neumann dati e istruzioni stanno nella stessa memoria e viaggiano su un solo sistema di bus (semplice ed economica, usata nei µC più semplici); nell'organizzazione Harvard memorie e bus sono separati (più accessi per ciclo: tipica di DSP e µC veloci). I bus sono pilotati da porte tri-state; l'ALU lavora su registri (accumulatore o molti registri); il controllo è cablato o microprogrammato; la gerarchia di memoria va dai registri alla RAM interna e a quella esterna.Organizzazione del processore - Von Neumann, Harvard, bus, ALU e registri →).
OS embedded. Tempo reale, reattivo, interruzioni dirette (InterruptClassi di interruzioni; I/O guidato da interrupt; sequenza hardware e software di gestione con salvataggio del contesto sullo stack; identificazione della sorgente (linee multiple, polling, daisy chain, vettore); priorità e interruzioni annidate; eccezioni e modi in ARM.Interrupt →), configurabile, I/O flessibile, protezione semplificata. Host target: kernel (processi e thread, memoria, IPC, timer, driver, file system, rete, energia), boot loader, root file system minimo; cross-compilazione. Due strade: adattare un OS esistente (Linux, FreeBSD, Windows: interfaccia nota, ma non hard real-time) o un OS dedicato (FreeRTOS, RIOT, Zephyr: cambio di thread rapido, hard real-time, piccolo, interruzioni veloci, partizioni fisse o variabili).
Embedded Linux. Kernel di un ordine di grandezza più piccolo; vantaggi: indipendenza dal fornitore, hardware vario, basso costo, open source; svantaggio: non per MCU né per hard real-time. File system: cramfs (sola lettura, compresso a pagine), squashfs, jffs2 (log, flash NOR e NAND, wear leveling), ubifs (flash grandi, cache di scrittura), yaffs2. Clinux: senza MMU, un solo spazio di indirizzi, niente fork, libreria Clibc (284 KB contro 1,2 MB di glibc; poi uClibc-ng e musl). Buildroot: toolchain, root file system, kernel, bootloader con cross-compilazione; Yocto (Poky, OpenEmbedded) per ARM, MIPS, PowerPC, x86.
OS per MCU. Niente MMU niente memoria virtuale; differiscono per architetture, memoria, multithreading, scheduling, rete e I/O.
- TinyOS (WSN): circa 400 byte; non è un vero OS (niente kernel, processi concorrenti, allocazione) ma ha uno scheduler. Componenti con task atomici (non prelazionabili da altri task dello stesso componente, né attesa bloccante), comandi (richieste non bloccanti verso il basso), eventi (dall'hardware verso l'alto); scheduler FIFO (o priorità non preemptive), un task alla volta, sleep a coda vuota.
- RIOT: microkernel, pochi kB, senza MMU/MPU, 8/16/32 bit, oltre 250 schede, LGPL, C/C++/Rust, modulare; min. 3,2 kB ROM e 2,8 kB RAM (core, cpu, board su Cortex-M); struttura
core,cpu,boards,periph,drivers,sys,pkg,application. Scheduler a priorità fisse, preemptive, tickless, ; a pari priorità niente cambio di contesto; ISR interrompono; round robin opzionale; idle (priorità minima) manda il sistema in sleep. Thread main e idle, stack proprio, TCB minimo; mailbox, messaggi, mutex, semafori (Sincronizzazione - sezione critica, race condition e mutexQuando più processi o thread accedono agli stessi dati e il risultato dipende dall'ordine degli accessi si ha una race condition (esempio: counter++ e counter-- sono tre istruzioni macchina ciascuna, e interleaving diversi danno 4, 5 o 6 partendo da 5); il problema della sezione critica chiede un protocollo (entry section, sezione critica, exit section, remainder section) che garantisca mutua esclusione, progresso e attesa limitata; disabilitare gli interrupt non scala sui multiprocessori, l'alternanza stretta con turn viola il progresso, la soluzione di Peterson (flag[2] e turn) soddisfa i tre requisiti ma può fallire su hardware moderno per il riordino delle istruzioni, per cui servono barriere di memoria; l'hardware offre test_and_set e compare_and_swap (atomiche) e variabili atomiche; il mutex lock (acquire/release, in POSIX pthread_mutex_t) è lo strumento di alto livello, con attesa attiva se è uno spinlock oppure con sospensione del thread se è un mutex di sistema; bloccare due volte lo stesso mutex senza sbloccarlo causa deadlock.Sincronizzazione - sezione critica, race condition e mutex →); priorità numerica bassa alta.xtimer/ztimer,shell, GNRC, lwIP, NimBLE, LoRaWAN. - FreeRTOS (2003, AWS dal 2017): task, mutex, semafori, timer software, priorità variabili, 5 schemi di memoria; tick da 1 a 10 ms, time slicing (round robin) e priorità preemptive; ISR a priorità massima. Zephyr (Linux Foundation): kernel monolitico piccolo, spazio unico con MPU, più scheduler, AMP e SMP, solo 32 bit, oltre 350 schede. Contiki-NG: eventi, protothread cooperativi, processi statici senza priorità; un processo che non cede blocca tutto.
- Confronto (RAM, flash): Contiki-NG 29,8 KB e 50,6 KB; RIOT 33,0 KB e 59,9 KB; Zephyr 52,9 KB e 130,5 KB: i primi due in classe 1, Zephyr in classe 2.
IoT. Interconnessione a corto raggio di dispositivi embedded. Generazioni: IT, OT, personale, sensori e attuatori (l'IoT vero). Livelli: edge (sensori e attuatori, gateway), fog (pre-elaborazione), core (dorsale), cloud (storage, applicazioni). Dispositivi vincolati (IETF): classe 0 (meno di 10 KB RAM e 100 KB flash, smart dust), classe 1 (circa 10 KB e 100 KB, CoAP senza gateway), classe 2 (gateway). Componenti: microcontrollore, sensore, attuatore, transceiver (RFID: tag e lettori) (ADC e DAC - risoluzione, SNR, ENOB ed erroriUn convertitore a $n$ bit con fondo scala $FSR$ ha risoluzione $LSB=FSR/2^n$; il codice vale $C=\mathrm{round}(V/LSB)$ e la tensione corrispondente è $V=C\cdot LSB$ con incertezza $\pm\frac12LSB$ (errore di quantizzazione). Con un modello a rumore uniforme di potenza $LSB^2/12$ il SNR di una sinusoide a fondo scala è $6{,}02,n+1{,}76$ dB; dal SNR misurato si ricava il numero di bit efficaci $ENOB=\frac{SNR-1{,}76}{6{,}02}$. Gli errori reali (offset, guadagno, INL, DNL, missing code) abbassano l'accuratezza. Accuratezza ≠ precisione: alta accuratezza implica alta precisione, non viceversa.ADC e DAC - risoluzione, SNR, ENOB ed errori →). Requisiti dell'OS: poca memoria, hardware eterogeneo, rete a basso consumo (LoRaWAN, BLE), energia (deep sleep), tempo reale, sicurezza (Sicurezza e protezione nei sistemi operativiLe minacce all'accesso sono gli intrusi (masquerader, misfeasor, clandestine user) e il software malevolo (parassita: virus, logic bomb, backdoor; indipendente: worm, bot); contromisure: sistemi di rilevamento delle intrusioni (IDS, host o network, con sensori, analizzatori e interfaccia), autenticazione (identificazione + verifica con qualcosa che si sa, si ha, si è, si fa) e controllo degli accessi (permessi, sudo, SELinux, ACL, auditing, firewall); il buffer overflow scrive oltre un buffer nello stack e può sovrascrivere variabili o l'indirizzo di ritorno (code injection, shellcode); difese a compilazione (linguaggi sicuri, codice sicuro, librerie, stack protection con canarino) e a esecuzione (NX bit, ASLR, guard pages); sulla rete ci sono sniffing, spoofing e man-in-the-middle, e la crittografia (simmetrica e asimmetrica, RSA) limita mittenti e destinatari; la tolleranza ai guasti usa ridondanza (spaziale, temporale, informativa) e si misura con R(t), MTTF, MTTR e disponibilità.Sicurezza e protezione nei sistemi operativi →). HAL: GPIO, UART, SPI, I2C (Bus I2C, SPI e CANI2C: bus seriale sincrono a 2 fili (SDA dati, SCL clock, con resistenze di pull-up), un master (il µC) e molti slave con indirizzo a 7 bit; byte da 9 bit (8 + ACK dato dal ricevitore abbassando SDA); start: SDA scende con SCL alto; stop: SDA sale con SCL alto; 100 kbit/s (normale), 400 kbit/s (fast); half-duplex. SPISerial Peripheral Interface: bus seriale sincrono a quattro fili: 4 fili (MOSI, MISO, SCK, CS), full duplex, master e slave selezionati da CS, 4 modi (CPOL, CPHA). CAN: bus differenziale (120 Ω) asincrono, messaggi etichettati da un identificatore (11 o 29 bit), arbitraggio automatico (vince l'ID più basso), bit stuffing dopo 5 bit uguali, CRC a 15 bit, contatori di errori (127, 255).Bus I2C, SPI e CAN →).
RIOT in laboratorio. gcc-avr, avr-libc, RIOT 2023.04; make BOARD=arduino-mega2560 [RIOTBASE=...] (percorsi senza spazi); Wokwi con l'.elf. Shell: USEMODULE += shell, shell_command_t {nome, descrizione, funzione} terminato da {NULL, NULL, NULL}, shell_run; RIOT_BOARD, RIOT_CPU; echo stampa argv[1..]. Thread: stack globale, thread_create(stack, sizeof(stack), priorità, 0, funzione, NULL, "nome"), prima di shell_run; ps; MAIN - 1 parte subito, MAIN + 1 resta in attesa. Mutex: mutex_lock, mutex_unlock, ztimer_sleep(ZTIMER_MSEC, ms); scrittore (50 ms di scrittura lenta) e lettore (250 ms): con il mutex il lettore legge sempre one two.
Domande tipiche. Perché Linux non va bene per un dispositivo vincolato; scheduler di RIOT e thread idle; task, comandi ed eventi in TinyOS; requisiti di un OS per l'IoT.
Errori tipici. Priorità capovolte in RIOT; thread creato dopo shell_run; stack non globale; TinyOS scambiato per thread preemptive; fork in uClinux.
Collegamenti: Scheduling multiprocessore e real-timeNei sistemi real-time conta la scadenza: si misurano latenza di interrupt e latenza di dispatch; i sistemi soft real-time danno priorità ai compiti critici senza garanzie, quelli hard devono rispettare le scadenze; per compiti periodici (burst t, scadenza d, periodo p, con 0 <= t <= d <= p) l'utilizzo è la somma di t/p: Rate Monotonic (priorità fissa, minore il periodo maggiore la priorità) garantisce le scadenze se l'utilizzo non supera N(2^(1/N)-1) (83% per due compiti), Earliest Deadline First (priorità dinamica, scadenza più vicina) le garantisce se l'utilizzo è sotto il 100%; la ripartizione proporzionale assegna T quote con controllo di ammissione; con più processori si hanno code comuni o private (SMP), core con più thread hardware, bilanciamento del carico (push e pull migration), affinità al processore (soft e hard), scheduling NUMA-aware; Linux usa CFS/EEVDF con domini di scheduling, Windows 32 livelli di priorità.Scheduling multiprocessore e real-time →, Sicurezza e protezione nei sistemi operativiLe minacce all'accesso sono gli intrusi (masquerader, misfeasor, clandestine user) e il software malevolo (parassita: virus, logic bomb, backdoor; indipendente: worm, bot); contromisure: sistemi di rilevamento delle intrusioni (IDS, host o network, con sensori, analizzatori e interfaccia), autenticazione (identificazione + verifica con qualcosa che si sa, si ha, si è, si fa) e controllo degli accessi (permessi, sudo, SELinux, ACL, auditing, firewall); il buffer overflow scrive oltre un buffer nello stack e può sovrascrivere variabili o l'indirizzo di ritorno (code injection, shellcode); difese a compilazione (linguaggi sicuri, codice sicuro, librerie, stack protection con canarino) e a esecuzione (NX bit, ASLR, guard pages); sulla rete ci sono sniffing, spoofing e man-in-the-middle, e la crittografia (simmetrica e asimmetrica, RSA) limita mittenti e destinatari; la tolleranza ai guasti usa ridondanza (spaziale, temporale, informativa) e si misura con R(t), MTTF, MTTR e disponibilità.Sicurezza e protezione nei sistemi operativi →, InterruptClassi di interruzioni; I/O guidato da interrupt; sequenza hardware e software di gestione con salvataggio del contesto sullo stack; identificazione della sorgente (linee multiple, polling, daisy chain, vettore); priorità e interruzioni annidate; eccezioni e modi in ARM.Interrupt →.