Salta al contenuto
Note per Studenti Sistemi operativi embedded e real-time per l'IoT

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:

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

  1. 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).
  2. 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).

μ\muClinux (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, μ\muClibc: per esempio libc da 1,2 MB di glibc a 284 KB di μ\muClibc. 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 →\to famiglia →\to tipo →\to 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:

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

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 += shell nel Makefile, #include "shell.h", shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE). I comandi sono un array di shell_command_t (nome, descrizione, funzione int f(int argc, char **argv)) terminato da {NULL, NULL, NULL}; le macro RIOT_BOARD e RIOT_CPU danno 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 globale static char stack[THREAD_STACKSIZE_MAIN]. Il comando ps (moduli shell_cmds_default e ps) elenca i thread. Un thread che termina sparisce da ps; 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_run non ritorna mai: i thread vanno creati prima.
  • Mutex (lab 12): mutex_t, mutex_lock, mutex_unlock, ztimer_sleep(ZTIMER_MSEC, ms) (moduli ztimer e ztimer_msec). Esercizio con scrittore e lettore: Esercizio - Thread e mutex in RIOT con un buffer condiviso (laboratorio 12).

Domande d'esame

  1. 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).
  2. 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.
  3. 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 →\to elabora →\to 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 →\to 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. μ\muClinux: senza MMU, un solo spazio di indirizzi, niente fork, libreria μ\muClibc (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 ⇒\Rightarrow niente memoria virtuale; differiscono per architetture, memoria, multithreading, scheduling, rete e I/O.

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

Esercizi su questo argomento

Teoria collegata