Capire
Come si compone un sistema
Domini di protezione, capability e canali; la descrizione del sistema e il suo file delle concessioni; i messaggi tipizzati; l'input; i guasti, il supervisore e lo stato fail-safe; e come costruire i propri sistemi accanto a NoWayOS.
Ogni dispositivo NoWayOS è un sistema: un insieme fisso di parti, dichiarato prima della build, ciascuna isolata dalle altre da seL4. Nulla viene aggiunto in fase di esecuzione. Questa pagina spiega i pezzi e come si collegano.
Domini di protezione
Un dominio di protezione è una parte di un sistema: un programma con la propria memoria, che detiene solo le capability che gli sono concesse. In seL4 una capability è un token non falsificabile che indica un oggetto del kernel e i diritti che si hanno su di esso, come un badge che apre una sola porta. Un dominio di protezione non può toccare nulla che le sue capability non indichino, e seL4 lo fa rispettare.
Un sistema tipico ha un dominio di protezione per ogni funzione: un driver del display, il percorso dell'input, lo stack di rete, l'interfaccia utente, l'applicazione, un timer, un supervisore e un watchdog. Ognuno è un piccolo programma Rust, no_std, che dopo l'avvio non alloca nulla.
La descrizione del sistema
La descrizione di sistema Microkit (systems/NAME.system, un file XML) dichiara l'intero sistema: ogni dominio di protezione con la sua priorità, il suo budget e il suo stack; ogni regione di memoria e chi la mappa; ogni canale; ogni interrupt.
- Le descrizioni indicano i dispositivi per nome, mai per indirizzo:
phys_addr="@NAME". Gli indirizzi vengono dal file della scheda inplatforms/, ciascuno con la sua fonte, così la stessa descrizione si compila per QEMU e per il Raspberry Pi 5. - I canali collegano due domini di protezione, per le notifiche (segnali senza dati, tra cui gli interrupt) e le chiamate di procedura protette (chiamate sincrone verso una parte con priorità più alta).
La descrizione è il progetto di sicurezza del sistema, quindi ogni sua modifica viene revisionata come una modifica di sicurezza.
Il file delle concessioni
Accanto a ogni descrizione, systems/NAME.grants elenca tutto ciò che il sistema concede, un fatto per riga: priorità, budget, memoria, dispositivi, interrupt e canali. La build fallisce se i due non concordano, in un senso o nell'altro.
Ogni dominio di protezione ha anche un ruolo (ui, input, broker, display, driver, network, security, storage, update, app, supervisor, watchdog, clock o test), e la build rifiuta una concessione che il suo ruolo non deve mai avere. A un'interfaccia utente non si può consegnare per errore un dispositivo di rete, e uno strumento di test non può infilarsi in un prodotto.
Messaggi tipizzati
I domini di protezione comunicano tramite messaggi tipizzati: record versionati di dimensione fissa, con un layout esplicito, che viaggiano su ring buffer a produttore singolo e consumatore singolo in memoria condivisa.
- Copia, poi verifica. Chi riceve copia ogni record fuori dalla memoria condivisa prima di convalidarlo, così il mittente non può modificarlo tra il controllo e l'uso (il bug noto come double-fetch).
- I decoder non possono andare in panic. Ogni decoder restituisce un errore tipizzato per qualsiasi input malformato, e ha dimostrazioni Kani limitate e un target di fuzzing.
- Ogni lato ha la propria vista. Il produttore e il consumatore vedono il ring attraverso viste di memoria separate, e gli indici condivisi usano l'ordinamento acquire e release.
Input
Le applicazioni vedono eventi di input canonici, tasti, movimenti del puntatore e tocchi, verificati rispetto allo schermo e limitati nella frequenza. Non vedono mai USB né altri protocolli di dispositivo; il percorso dell'input li analizza in domini di protezione propri, trattando ogni byte come ostile.
Guasti, il supervisore e lo stato fail-safe
Quando un dominio di protezione fa qualcosa che seL4 non consente, oppure va in panic, seL4 lo ferma e segnala un guasto al suo genitore.
- Il supervisore è il genitore delle altre parti. Riceve ogni guasto, lo segnala e resetta la scheda, così il sistema riparte in uno stato pulito. È costruito e testato nei sistemi di test in QEMU.
- Lo stato fail-safe è dove va un dispositivo quando i guasti continuano: per impostazione predefinita, al quarto riavvio dall'ultimo avvio a freddo, oppure al secondo guasto in una parte sensibile per la sicurezza, come l'archiviazione o gli aggiornamenti. Restano in funzione solo il supervisore, il watchdog e, dove possibile, il display, e solo un avvio a freddo, da dispositivo spento, riporta in funzione il dispositivo.
- Il dominio di protezione watchdog rinfresca il watchdog hardware solo finché ogni altra parte risponde al suo heartbeat. Se una smette di rispondere, l'hardware resetta la scheda.
Memoria e tempo
- La memoria è fissata quando si costruisce l'immagine. Il codice del dispositivo non alloca nulla: ogni buffer e ogni coda ha una dimensione stabilita, da
nowayos-bounded. L'unica eccezione, il client TLS, lavora in un'arena fissa tutta sua. - Due tipi di tempo che non si mescolano mai. Il tempo monotono conta dall'avvio e non va mai all'indietro; ogni timeout lo usa. L'ora di calendario, cioè la data e l'ora del giorno, viene da una fonte autenticata. Sono tipi Rust diversi, quindi non si può passare l'uno dove è previsto l'altro.
Costruire i propri sistemi
I sistemi e i domini di protezione di un prodotto vivono in un repository a parte, accanto a nowayos, mai al suo interno. Il Makefile di quel repository imposta SYSTEM_REPO := $(CURDIR) e include i frammenti make di NoWayOS:
SYSTEM_REPO := $(CURDIR)
include ../nowayos/tools/make/container.mk
include ../nowayos/tools/make/system.mk
A quel punto make image, make qemu-test, make qemu-test-all e i controlli funzionano sulle sue cartelle systems/ e pds/, con il container di sviluppo, i file delle schede, le regole sulle concessioni e i controlli di NoWayOS. Le variabili sono documentate in cima a tools/make/system.mk. Lo Showcase è costruito esattamente così.