Università Politecnica delle Marche Facoltà di Ingegneria Corso di Laurea in Ingegneria Informatica e dell’Automazione Porting su architettura Cris AXIS ETRAX 100LX del sistema operativo Xenomai Tesi di Laurea di: Lorenzo RUGGERI Relatore: Prof. Aldo Franco DRAGONI Anno Accademico 2010/2011 Alla mia famiglia Indice Introduzione vi 1 I sistemi operativi real-time e Linux 1.1 1.2 1.3 2 Caratteristiche generali di un RTOS . . . . . . . . . . . . . . 2 1.1.1 Task periodici/aperiodici e hard/soft real-time . . . . 2 1.1.2 Sistemi Hard e Soft real-time . . . . . . . . . . . . . . 4 1.1.3 Schedulazione dei sistemi real-time . . . . . . . . . . . 6 1.1.4 Caratteristiche di un sistema real-time . . . . . . . . 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.2.1 Interruzioni hardware e software . . . . . . . . . . . . 12 1.2.2 Schedulazione a code multiple . . . . . . . . . . . . . . 15 1.2.3 Schedulazione CFS . . . . . . . . . . . . . . . . . . . . 16 1.2.4 Architettura modulare e licenza Open Source . . . . . 18 1.2.5 Il livello applicativo . . . . . . . . . . . . . . . . . . . 19 1.2.6 Portabilità . . . . . . . . . . . . . . . . . . . . . . . . . 20 1.2.7 Compilazione, debugging e test . . . . . . . . . . . . . 20 1.2.8 I problemi della schedulazione real time sotto Linux . 21 1.2.9 Strumenti di supporto . . . . . . . . . . . . . . . . . . 22 Soluzioni real-time per il kernel Linux . . . . . . . . . . . . . 23 1.3.1 Linux e le applicazioni real-time . . . . . . . . . . . . . 23 1.3.2 Caratteristiche RT del Kernel Linux . . . . . . . . . . 24 1.3.3 Approccio RT basato sullo sviluppo del kernel Linux . 26 1.3.4 Approccio RT basato sull’utilizzo di un kernel RT di GNU/Linux basso livello . . . . . . . . . . . . . . . . . . . . . . . 2 Xenomai 2.1 28 30 Struttura del progetto . . . . . . . . . . . . . . . . . . . . . . iii 30 iv INDICE 2.2 Adeos pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.2.1 Adeos e Xenomai . . . . . . . . . . . . . . . . . . . . . 35 2.2.2 I dominii di Xenomai . . . . . . . . . . . . . . . . . . . 36 2.2.3 Intercettare le chiamate di sistema . . . . . . . . . . . 38 2.2.4 Propagazione degli interrupt . . . . . . . . . . . . . . . 39 2.3 Installazione di Xenomai su x86 . . . . . . . . . . . . . . . . . 40 2.4 Testing della skin nativa . . . . . . . . . . . . . . . . . . . . . 45 2.5 Sviluppo di script per il testing . . . . . . . . . . . . . . . . . 46 3 Cris AXIS ETRAX 100LX 3.1 3.2 La FOX Board . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.1.1 Panoramica . . . . . . . . . . . . . . . . . . . . . . . . 52 3.1.2 Accensione . . . . . . . . . . . . . . . . . . . . . . . . 54 3.1.3 Modalità di accesso alla FOX Board . . . . . . . . . . 55 3.1.3.1 Web access . . . . . . . . . . . . . . . . . . . 55 3.1.3.2 FTP access . . . . . . . . . . . . . . . . . . . 56 3.1.3.3 TELNET access . . . . . . . . . . . . . . . . 57 Cris ETRAX . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.2.1 CPU RISC . . . . . . . . . . . . . . . . . . . . . . . . 58 3.2.1.1 Registri . . . . . . . . . . . . . . . . . . . . . 58 3.2.1.2 Flag e Condition Code . . . . . . . . . . . . . 59 3.2.1.3 Organizzazione dei dati in memoria . . . . . 59 3.2.1.4 Formato delle istruzioni . . . . . . . . . . . . 60 Gestione degli interrupt su Linux/CRIS . . . . . . . . 61 3.2.2.1 Interrupt Paths . . . . . . . . . . . . . . . . . 61 3.2.2.2 Casi speciali . . . . . . . . . . . . . . . . . . 63 Software Development Kit (SDK) . . . . . . . . . . . . . . . . 64 3.3.1 I sistemi embedded e la cross compilazione . . . . . . . 64 3.3.2 Installazione e test del cross compilatore gcc-cris . . . 66 3.3.3 Distribuzione software . . . . . . . . . . . . . . . . . . 69 3.3.4 Configurazione e compilazione del firmware . . . . . . 72 3.3.5 Scrittura del firmware sulla board . . . . . . . . . . . . 73 3.2.2 3.3 52 4 Porting 76 4.1 Approccio al problema del porting . . . . . . . . . . . . . . . 76 4.2 Adeos patch per architettura CRIS . . . . . . . . . . . . . . . 77 INDICE v 5 Conclusioni 83 Ringraziamenti 85 Bibliografia 85 Introduzione I sistemi operativi in tempo reale (comunemente abbreviati con RTOS) rivestono un ruolo fondamentale nella nostra società coprendo diversi settori applicativi, quali il controllo di impianti industriali, i sistemi di regolazione di volo, il controllo del traffico aereo, navale e ferroviario, i sistemi di difesa militari, le missioni spaziali, i sistemi di telecomunicazione, i sistemi multimediali, l’automazione industriale e la robotica. La loro presenza è fondamentale quando si vuole ottenere una risposta dal sistema entro un tempo prefissato. Un sistema operativo real-time non deve essere necessariamente veloce rispetto al tempo medio di risposta: tuttavia il tempo di reazione di tali sistemi è importante e proprio per questo si impongono limiti massimi su di esso (deadlines), in modo tale che le applicazioni possano essere eseguite rispetto a vincoli temporali determinati a priori. Nonostante la vastità degli scenari coinvolti, la maggior parte dei sistemi real-time viene ancora progettata con tecniche empiriche, senza l’ausilio di una metodologia scientifica consolidata. La conseguenza di ciò è una scarsa affidabilità del software che, in applicazioni critiche, può causare gravi danni a cose o persone. Negli ultimi anni, è cresciuto un notevole interesse nell’utilizzo del sistema operativo Linux per scenari real-time, specialmente nei sistemi di controllo. L’architettura semplice e ordinata fornita da Linux garantisce robustezza e ottime prestazioni, mentre la licenza GPLv2 permette di modificare e di cambiare il codice sorgente in relazione alle necessità dell’utente. Tuttavia Linux è stato progettato per essere un sistema operativo general purpose; pertanto presenta alcune questioni che (come per esempio le latenze impredicibili a priori, supporto limitato per la schedulazione a tempo reale del task-set, risoluzione temporale dei timer insufficiente) possono vi Introduzione 1 rappresentare un problema per applicazioni real-time. Per questi motivi sono state proposte alcune modifiche con lo scopo di aggiungere le indispensabili caratteristiche «real-time» al kernel Linux sia per piattaforme hardware di utilizzo generico (x86), sia per soluzioni embedded (come per esempio ARM) utilizzate per le applicazioni di controllo. Il lavoro svolto in questa tesi, ha come obiettivo quello di analizzare il funzionamento del sistema Xenomai, in particolare della patch Adeos, al fine di effettuarne il porting sul sistema embedded a nostra disposizione, ovvero una FOX Board ETRAX 100LX, la cui architettura CRIS non è supportata direttamente da Xenomai, a differenza di altre architetture più utilizzate (tipo ARM). Nel Capitolo 1 di questa tesi vengono illustrati i concetti basilari che caratterizzano un sistema operativo real-time, le tecniche più recenti per la schedulazione in tempo reale e la schedulazione utilizzata dal kernel Linux. Nel Capitolo 2 viene descritta, in dettaglio, l’architettura di Xenomai e la sua struttura modulare, con una particolare attenzione al funzionamento dello strato introdotto dalla patch Adeos e alla gestione degli interrupt. In questo capitolo viene illustrata anche la procedura di installazione di Xenomai su architettura x86. Inoltre, vengono presentati alcuni test creati appositamente per verificare il funzionamento del sistema e prendere confidenza con le interfacce di programmazione. Nel Capitolo 3 viene descritta dettagliatamente la piattaforma embedded utilizzata per il porting, insieme all’ambiente di sviluppo fornito dalla Axis. Per una maggiore dimestichezza con il sistema embedded, viene esposta la procedura per la cross compilazione del kernel Linux 2.6.26 e successivamente la procedura per il porting di quest’ultimo sulla piattaforma in uso. Nel Capitolo 4 viene affrontato il problema del porting del sistema Xenomai per l’architettura a nostra disposizione, basato sulla patch Adeos per ARM. Per finire, nel Capitolo 5 vengono illustrate le conclusione di questo lavoro di tesi. Capitolo 1 I sistemi operativi real-time e Linux 1.1 Caratteristiche generali di un RTOS Un sistema operativo real-time o in tempo reale è un sistema operativo specializzato per il supporto di applicazioni software real-time. Questi sistemi vengono utilizzati tipicamente in ambito industriale (controllo di processo, pilotaggio di robot, trasferimento dati nelle telecomunicazioni) o, comunque, dove sia necessario ottenere una risposta dal sistema entro un tempo prefissato. Un sistema operativo real-time non deve essere necessariamente veloce: non è importante l’intervallo di tempo in cui il sistema operativo/applicativo deve reagire; l’importante è che risponda entro un tempo massimo predeterminato. In altre parole il sistema deve essere prevedibile. In pratica un sistema real-time deve garantire che una elaborazione (o task) termini entro un dato vincolo temporale o scadenza (detta in gergo deadline). Per garantire questo, è richiesto che la schedulazione delle operazioni sia fattibile. Il concetto di fattibilità di schedulazione è alla base della teoria dei sistemi real-time ed è quello che ci permette di dire se un insieme di task sia eseguibile o meno, in funzione dei vincoli temporali dati. 1.1.1 Task periodici/aperiodici e hard/soft real-time I task di un sistema real-time possono essere: 2 CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 3 • periodici: quando un task consiste in una sequenza di attività avviate con cadenza regolare; • aperiodici: quando un task consiste in una sequenza di attività avviate a intervalli irregolari. I task periodici sono propri di un sistema di controllo a tempo discreto. Quando si ha a che fare con task di tipo periodico, si parla anche di periodo di esecuzione con il quale si intende il lasso di tempo che intercorre tra due attivazioni del task. Solitamente si fa coincidere la deadline con la fine del periodo, poiché questo è il limite massimo di esecuzione di un task. I task di un sistema real-time possono essere ulteriormente distinti in: • soft real-time: un task che non rispetta la sua scadenza (in gergo si dice sfondare la deadline) provoca un danno non irreparabile al sistema. Il superamento della deadline produce un degrado delle prestazioni proporzionale al tempo di superamento; • hard real-time: un task che superi temporalmente la sua deadline provoca un danno irreparabile al sistema. Sostanzialmente questa distinzione si traduce nella diversa quantificazione dei costi di una possibile inesattezza temporale del sistema. Un esempio di task soft real-time può essere un riproduttore DVD, in cui il mancato rispetto dei vincoli si traduce in un degrado della qualità del filmato, ma non pregiudica il proseguimento della riproduzione; mentre un task hard realtime può essere il controllore della temperatura del nocciolo di una centrale nucleare, dove il mancato rispetto dei vincoli temporali può provocare un evidente disastro. Oltre alla sua criticità (hard o soft), un task real-time Ji può essere caratterizzato dai seguenti parametri, (vedi Fig. 1.1): • Arrival time ai : (anche detto Release time ri ) è il tempo in cui il task diventa pronto per l’esecuzione; • Computation time Ci : è il tempo massimo necessario al processore per eseguire completamnte il task senza interruzioni; • Absolute Deadline di : è il tempo entro cui il task deve essere completato per evitare danni al sistema (se hard) o un degrado delle prestazioni (se soft); • Relative Deadline Di : è l’intervallo entro cui il task deve essere completato a partire dal tempo di arrivo (Di = di − ri ); CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 4 Figura 1.1: Parametri tipici di un processo real-time • Start time si : è il tempo in cui viene eseguita la prima istruzione del task; • Finishing time fi : è il tempo in cui il task termina la sua esecuzione. È anche detto Completition time; • Response time Ri : è la differenza tra il finishing time e il release time:Ri = fi − ri ; • Lateness Li : Li = fi − di rappresenta il ritardo di completamento del task rispetto alla deadline. Si noti che se un task termina prima della sua deadline, la sua lateness è negativa; • Tardiness o Exceeding time Ei = max(0, Li ): rappresenta il tempo in cui un processo è rimasto attivo oltre la propria deadline; • Laxity o Slack time Xi : Xi = di − ai − Ci rappresenta il ritardo di attivazione massimo che un task attivo può subire in coda pronti per non eccedere la sua deadline. 1.1.2 Sistemi Hard e Soft real-time I sistemi real-time si dividono in due categorie: • i sistemi «hard» sono quelli che possono garantire la fattibilità di schedulazione di un insieme di task hard e soft real-time; • i sistemi «soft» sono quelli che possono garantire la fattibilità di schedulazione di un insieme di soli task soft real-time. Per chiarire meglio il concetto si pensi a un sistema real-time come a un sistema che, dato un insieme di n task, ognuno con i propri vincoli temporali (deadline del task i-esimo), è in grado di minimizzare la funzione di costo definita come: Ks = n X Ki (t) i dove Ki (t) è la funzione costo del task i-esimo definita come: CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 5 Figura 1.2: Esempi di funzioni di costo per diverse tipologie di task 0 Ki (t) = ∞ se t ≤ di se t > di se il task i-esimo è di tipo hard real-time, o come: 0 Ki (t) = f (t) se t ≤ di se t > di se il task i-esimo è di tipo soft real-time, dove si è indicato con f (t) una funzione monotona crescente all’aumentare del tempo che scorre. Così come mostrato nella Fig. 1.2 i task di tipo hard real-time dovranno essere schedulati in modo da terminare a un istante t0 minore della deadline, in modo da far valere la funzione di costo 0 e non ∞; mentre i task soft real-time dovranno anche loro far valere 0 la funzione di costo relativa ma, in questo caso, uno sfondamento della deadline t0 > di non manderà il costo globale del sistema all’infinito (equivalente al disastro). In entrambi i casi il comportamento real time è ottenuto dividendo un programma in processi, il cui comportamento è prevedibile e noto in anticipo. Questi processi hanno normalmente una vita breve e possono essere eseguiti nella loro completezza in un tempo inferiore al secondo. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 6 Nel momento in cui viene rilevato un evento esterno, lo scheduler dei processi ha il compito di determinare una opportuna sequenza di esecuzione dei processi, tale da garantire il rispetto di tutte le scadenze. Come descritto nella sezione precedente, gli eventi a cui un sistema real time deve poter reagire sono classificati in task periodici e aperiodici. Nel caso di eventi periodici, se esistono m task e se il task i-esimo arriva con periodo Pi e richiede Ci secondi di tempo di CPU per essere gestito, il carico può essere gestito solo se: M X Ci i=1 Pi ≤1 Un sistema real time che rispetta questo vincolo è detto schedulabile. Per esempio, consideriamo un sistema soft real time con tre eventi periodici rispettivamente di 100, 200 e 500 millisecondi. Se questi eventi richiedono 50, 30 e 100ms di tempo di CPU per la loro esecuzione, il sistema è schedulabile in quanto: 50/100+30/200+100/500 = 0, 5 + 0, 15 + 0, 2 ≤ 1. Se un quarto evento, con periodo di 1 secondo, si aggiunge al sistema, l’insieme dei processi rimarrà schedulabile fino a quando a questo evento non verranno richiesti più di 150ms di tempo di CPU per occorrenza. Notare che, nel calcolo precedente, si suppone implicitamente che l’overhead per il cambio di contesto sia così piccolo da poter essere trascurato. I sistemi operativi general purpose non supportano la funzionalità hard real-time, ma possono supportare quelle di tipo soft (per esempio Linux è un sistema soft real-time). 1.1.3 Schedulazione dei sistemi real-time Ciò che differenzia un sistema non real-time da un RTOS è principalmente lo schedulatore. Lo schedulatore è un algoritmo che controlla l’accesso alla CPU da parte dei vari processi del computer. La schedulazione dei processi è fondamentale per garantire l’efficienza computazionale del computer, garantendo un utilizzo ottimizzato della CPU tra processi che evolvono parallelamente, tenendo conto degli interrupt prodotti dai controller delle varie unità I/O, dalle memorie e dagli hard-disk. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 7 Figura 1.3: Task Concetti generali I possibili stati di un task sono: • ready : pronto all’esecuzione, in attesa della CPU; • pended : in attesa di una risorsa richiesta dal task per procedere nell’esecuzione; • delayed : in attesa della fine di un timeout pressato; • suspended : lo stato iniziale di ogni task appena creato. Tutti i task in stato ready sono pronti per essere eseguiti. L’accesso alla CPU di un task rispetto a un altro, è funzione dell’algoritmo implementato nello schedulatore. Gli aspetti più importanti di uno schedulatore sono: PRIORITÀ: la priorità è un indice che definisce l’importanza che un processo ha nei confronti degli altri. Si possono distinguere priorità interna (definita dal sistema in base a diversi requisiti tipo memoria, I/O, tempo di esecuzione) ed esterna (definita dall’utente). In aggiunta si definisce una priorità dinamica (aging). CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 8 Se un processo ha una priorità bassa verrà sempre scavalcato da processi a priorità più alta. Il rischio è che tale processo non venga eseguito mai (starvation). Perciò quando un processo è pronto per essere eseguito da troppo tempo, la sua priorità viene aumentata dinamicamente dallo scheduler; PREEMPTION: se nella coda esiste un processo pronto a essere eseguito, con priorità maggiore di quello in esecuzione nella CPU, lo scheduler forza il rilascio della CPU e passa a eseguire il processo a più alta priorità. Un esempio può essere l’I/O. Se un processo ad alta priorità subisce un interrupt per la gestione di un I/O, lo scheduler passa la CPU al processo successivo. Se lo scheduler non fosse dotato di diritto di prelazione (preemption), il processo ad alta priorità dovrebbe aspettare che il processo a bassa priorità finisca prima di completare il suo ciclo. Con la preemption, invece, la CPU viene liberata dal processo a bassa priorità nell’istante in cui il processo ad alta priorità ritorna pronto a essere eseguibile (cioè quando ha finito l’I/O). Algoritmi di schedulazione real-time Gli algoritmi di scheduling real time sono distinti in: • statici: la decisione di schedulazione è presa prima che il sistema inizi l’esecuzione dei processi. Questi metodi richiedono che le informazioni complete circa il lavoro da fare e le scadenze da rispettare, siano disponibili in anticipo rispetto all’esecuzione dei processi; • dinamici: la decisione di schedulazione è presa durante l’esecuzione dei processi. Non hanno restrizioni circa la conoscenza anticipata sui tempi di esecuzione e le scadenze da rispettare. Successivamente verranno analizzate alcune politiche di scheduling realtime, facendo riferimento al particolare contesto delle applicazioni multimediali. Infatti i sistemi operativi che supportano applicazioni multimediali differiscono da quelli tradizionali per tre aspetti principali: la schedulazione dei processi, il file system e la schedulazione del disco. Schedulazione a frequenza monotona Il classico algoritmo statico di schedulazione in tempo reale per processi periodici e prelazionabili è RMS (Rate Monotonic Scheduling, schedulazione a frequenza monotona). È utilizzabile per processi che soddisfino le seguenti condizioni: CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 9 1. ogni processo periodico deve essere completato entro il suo periodo di tempo; 2. nessun processo è dipendente dagli altri; 3. ogni processo necessita della stessa quantità di tempo di CPU per ogni periodo di esecuzione; 4. i processi non periodici non hanno scadenze temporali; 5. la prelazione dei processi avviene istantaneamente e senza sovraccarico di lavoro per il sistema. Le prime quattro condizioni sono ragionevoli, mentre l’ultima rende più semplice la modellazione del sistema. RMS assegna a ciascun processo una priorità prefissata uguale alla frequenza con cui deve essere eseguito. Un processo che debba essere eseguito ogni 30ms (33 volte/s) acquisisce priorità 33; un processo da eseguire ogni 40ms (25 volte/s) acquisisce priorità 25, mentre un processo da eseguire ogni 50ms (20 volte/s) acquisisce priorità 20. Dato che le priorità variano linearmente con la frequenza (numero di volte al secondo in cui il processo è eseguito), il metodo è detto a frequenza monotona. Durante l’esecuzione, lo schedulatore esegue sempre il processo pronto a più alta priorità, prelazionando, se necessario, il processo in esecuzione. È stato dimostrato che RMS è ottimale rispetto alla classe di algoritmi di schedulazione statici. Schedulazione con priorità alla scadenza più vicina L’algoritmo EDF (Earliest Deadline First, schedulazione con priorità alla scadenza più vicina), è un algoritmo dinamico e pertanto non richiede nè che i processi siano periodici, nè che abbiano lo stesso tempo di esecuzione per periodo e di CPU. Con questo approccio, è sufficiente che un processo che ha bisogno della CPU annunci la sua presenza e la scadenza temporale. Lo schedulatore mantiene una lista dei processi eseguibili, ordinata rispetto alla scadenza temporale; l’algoritmo esegue il primo processo della lista, cioè quello con scadenza temporale più vicina. Quando un nuovo processo è pronto, il sistema controlla se la sua scadenza preceda quella del processo correntemente in esecuzione: in caso affermativo il nuovo processo prelaziona quello corrente. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 10 È interessante notare che, utilizzando priorità statiche, l’algoritmo RMS funziona solo se l’utilizzo della CPU non è troppo elevato. È possibile dimostrare per ogni sistema di m processi periodici che il funzionamento di RMS è garantito (condizione sufficiente) se: m X Ci i=1 Pi ≤ m · (21/m − 1) Per m che tende all’infinito, l’utilizzo massimo della CPU tende in modo asintotico a ln2 = 0.69. Questo significa che per m = 3, RMS funziona sempre se l’utilizzazione della CPU è uguale e o minore di 0.780. Al contrario, EDF funziona sempre per qualunque insieme di processi schedulabile e può raggiungere il 100% di utilizzo della CPU. Il prezzo di tutto ciò è pagato in termini di una maggiore complessità dell’algoritmo EDF rispetto a RMS. 1.1.4 Caratteristiche di un sistema real-time Un sistema real-time dovrebbe possedere le seguenti caratteristiche: • schedulazione ottima: tutti i task sono noti a priori così come i vincoli temporali. Dovrebbe essere possibile avere uno schedulatore che minimizzi la funzione di costo Ks presentata prima; • condivisione delle risorse: i task sono entità separate che concorrono a uno stesso scopo: pertanto non è necessario avere spazi di indirizzamento separati; • garanzia di esecuzione: tutti i task di tipo hard real-time devono terminare entro le proprie deadline. Nel caso in cui arrivi un nuovo task, o un task non possa completare entro la deadline, una notifica anticipata del sistema può essere utilizzata per impedire l’esecuzione del nuovo task o per recuperare l’esecuzione del task che sta per sfondare; • prevedibilità delle chiamate di sistema: il sistema deve essere in grado di valutare i tempi di calcolo di ogni task per determinare la schedulazione fattibile. Ogni chiamata di sistema deve avere un tempo di esecuzione massimo ben definito in modo da non introdurre ritardi indefiniti. I prodotti delle famiglie Windows e Unix non soddisfano le caratteristiche tipiche di un sistema real-time: per esempio, pur gestendo l’esecuzione di più CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 11 processi con pre-rilascio, non è possibile prevedere in alcun modo quale sarà il tempo di esecuzione di un singolo processo. Inoltre l’utilizzo di hard disk per la conservazione dei dati, dispositivi USB o altri dispositivi che introducono forti latenze di esecuzione da parte della CPU, rende impossibile stabilire con certezza quanto tempo sarà necessario per reperire l’informazione utile alla corretta esecuzione del codice. Anche il sistema operativo Windows CE1 viene erroneamente considerato da alcuni un sistema operativo real-time, quando in effetti non esiste nessuna prerogativa architetturale a livello hardware e software che dia modo di prestabilire il tempo di esecuzione di una serie di operazioni. Esistono diversi fattori che causano la non prevedibilità nella risposta del sistema operativo, i principali sono: • il dma: può rubare il bus alla CPU ritardando l’esecuzione di un task critico. In un sistema real-time si preferisce disattivarlo o usarlo in modalità timeslice, dove si assegna in maniera costante e fissa il bus al DMA, anche se non ci sono operazioni da fare; • la cache: può causare non prevedibilità poiché esistono casi in cui essa fallisce, causando ritardi nell’accesso alla memoria da parte della CPU. Ipotizzando il peggiore dei casi si preferisce non usarla affatto; • meccanismi di gestione della memoria: queste tecniche non devono introdurre ritardi non prevedibili durante l’esecuzione di task critici: la paginazione può causare dei page fault intollerabili per un sistema hard real-time. Tipicamente si usa la segmentazione o la partizione statica della memoria; • interrupt: sono generati da dispositivi periferici quando devono scambiare delle informazione con la CPU. Se queste interruzioni si verifcano durante l’esecuzione di un task critico, generano ritardi non prevedibili ed è quindi necessario trattarle in maniera opportuna; • i sistemi di power management: sono meccanismi hardware che possono rallentare la CPU o far eseguire a essa del codice utile a dissipare minor energia. Essi vengono disattivati in quanto è chiaro che, in un sistema real-time, è importante non sfondare una deadline piuttosto che consumare poca energia. Tra gli RTOS commerciali troviamo il POSIX-conformant (per esempio LynxOS che è Unix compatibile) e non POSIX-conformant come per esempio VxWorks (che supporta in parte gli standard POSIX). 1 sistema operativo sviluppato da Microsoft per dispositivi portatili e sistemi embedded CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 12 Per quanto riguarda i sistemi Open Source è possibile l’uso di Linux, con necessarie precauzioni, opportunatamente applicate in soluzioni come Xenomai, RTAI, RT-Preempt e altre ancora. 1.2 GNU/Linux In questa sezione si darà una panoramica generale sulle opportunità, i vantaggi e i limiti di utilizzo del sistema operativo GNU/Linux per i sistemi embedded. Verranno considerati sia gli aspetti più strettamente tecnici, sia quelli legati genericamente al modello di sviluppo software. Si noti che parliamo qui di GNU/Linux (e non semplicemente di Linux) per sottolineare il fatto che trattiamo non solo del nucleo del sistema operativo (Linux appunto) ma anche delle applicazioni nonché dei sistemi di sviluppo: editor, sistemi di controllo della configurazione, compilatori, debugger, programmi di test. Buona parte di questi programmi sono il frutto diretto del progetto GNU (www.gnu.org), mentre la totalità di essi (compreso ovviamente Linux stesso) è, o meglio può essere, a seconda delle scelte degli utenti, software libero, distribuito secondo la licenza GNU GPL o altre licenze libere. Alcune riflessioni saranno dedicate ai vantaggi dell’uso di software libero nello sviluppo di applicazioni embedded. Non ci occuperemo, invece, specificamente di distribuzioni Linux embedded commerciali, ma molti dei concetti qui esposti si applicano ugualmente anche a esse. Il nucleo (kernel) Linux è funzionalmente equivalente a un kernel Unix, e per questo motivo è molto diverso, nonché molto più complesso, della maggior parte dei tradizionali sistemi operativi per applicazioni specifiche (con esigenze di tempo reale o meno), nati per CPU con limitate risorse di calcolo e sistemi con piccole quantità di memoria (sia RAM sia ROM). Qui di seguito si riportano alcune delle caratteristiche più significative, con particolare riferimento agli aspetti che di norma risultano meno familiari per gli utenti provenienti da altre esperienze. 1.2.1 Interruzioni hardware e software Le interruzioni («interrupt» o IRQ per «interrupt request») sono alla base del funzionamento di un sistema multiprocesso. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 13 Normalmente l’esecuzione del codice da parte del processore («CPU», «central processing unit») è sequenziale, poichè viene seguito il flusso logico del programma in esecuzione senza distrazioni. Infatti, le prime macchine adottavano esclusivamente questo modo di funzionamento. Poco dopo, però, si è pensato di permettere l’interruzione del normale flusso di istruzioni eseguito dal processore da parte di eventi esterni. Oggi questo meccanismo è ampiamente usato e gli eventi che interrompono il processore sono normalmente associati a una periferica che richiede attenzione: per esempio la pressione di un tasto, l’invio di un pacchetto dalla rete o lo scattare del tempo sull’orologio di sistema. Il meccanismo usato per riportare le interruzioni al processore può essere dei più vari. Nel caso più semplice si tratta di un unico filo collegato con il mondo esterno, attraverso il quale un circuito dedicato, chiamato PIC («programmable interrupt controller»), comunica la sua richiesta di attenzione. La CPU interromperà il suo lavoro e interrogherà il PIC per sapere quale periferica ha richiesto attenzione. In certi sistemi il processore viene raggiunto da vari segnali, corrispondenti a richieste di interruzioni con priorità diversa e potrebbe non essere necessario un PIC esterno. In altri casi molte periferiche risiedono fisicamente all’interno del processore e viene quindi realizzata un’architettura con più livelli di priorità con un solo segnale di IRQ proveniente dall’esterno. A tale segnale può essere associato o meno un controllore programmabile. Altre variazioni sul tema sono le cosiddette trap (spiegate più avanti), le interruzioni non mascherabili (NMI, «non maskable interrupt») e tutte le complicazioni introdotte nel mondo PC negli ultimi anni (APIC, IOAPIC, MSI, MSI-X). Queste ultime possono essere tranquillamente ignorate, in quanto l’interfaccia offerta dal kernel verso i suoi moduli è indipendente dalla gestione di basso livello implementata nel caso specifico. Anche la gestione dei livelli di priorità, quando presente, può essere ignorata dal codice dei driver. Questi possono usare il semplice modello a due livelli in cui il driver può chiedere la disabilitazione temporanea delle interruzioni per poi riabilitarle, oppure non toccare niente del tutto. Le cosiddette trap (trappole), sono interruzioni generate dal processore quando non riesce a eseguire un’istruzione macchina, perché si tratta di una divisione per zero, ovvero l’indirizzo di memoria cui deve accedere non è valido, oppure l’istruzione non è definita nel set di istruzioni della CPU. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 14 In tutti questi casi l’esecuzione passa al sistema operativo con un meccanismo simile o identico (a seconda delle scelte dei progettisti hardware) a quello utilizzato nella gestione di interruzioni esterne. Il sistema operativo può analizzare la situazione e correggere il problema (per esempio recuperando una pagina di dati dallo spazio di swap) oppure «punire» il programma che si è comportato male. In un sistema Unix la punizione consiste nell’invio al processo di un segnale; nei tre casi elencati si tratta: di SIGFPE (floating point exception), SIGSEGV (segmentation violation) e SIGILL (illegal intruction). Il processo può essere predisposto per intercettare questi segnali e cercare di recuperare la situazione, altrimenti verrà terminato. Anche le interruzioni software si avvalgono del meccanismo hardware delle interruzioni (ancora una volta, un meccanismo simile o identico) al fine di trasferire il controllo al sistema operativo. Nel set di istruzioni del processore è in genere definita una istruzione INT (o SWI – software interrupt – o equivalente) che trasferisce il controllo al sistema operativo proprio come una trap o una interruzione esterna. Il sistema operativo, analizzando lo stato del processore, estrae gli argomenti passati dal programma e provvede a eseguire la richiesta o a ritornare un codice di errore. Per esempio, su piattaforma x86 le chiamate di sistema per il kernel Linux sono implementate dall’interruzione numero 0x80. Il registro EAX contiene il numero della chiamata di sistema e, all’uscita, il valore di ritorno; gli altri registri contengono gli argomenti della chiamata di sistema. Per i dettagli implementativi è interessante leggere <asm/unistd.h> per la propria architettura. Rispetto ai requisiti imposti dallo scenario real-time, una prima differenza rilevante riguarda il fatto che il nucleo non è compilato assieme all’applicazione, ma vive in maniera indipendente. Il meccanismo di accesso ai servizi del nucleo è costituito dalle chiamate di sistema, implementate attraverso interruzioni software. Inoltre il codice del nucleo funziona sempre in modalità «supervisore»: ha quindi accesso a tutte le risorse hardware della macchina, gestisce direttamente interruzioni ed eccezioni e si occupa di tutte le funzionalità di basso livello (vedi Fig. 1.4). Al contrario, il codice dell’applicazione viene eseguito in modalità utente, ed è quindi limitato per quanto riguarda l’accesso fisico al sistema, oltre a non poter direttamente gestire interruzioni ed eccezioni. Dal punto di vista CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 15 Figura 1.4: Linux e la sua gestione interrupt hardware e software della scalabilità Linux ha lo svantaggio di richiedere sempre un file system per poter funzionare. Si noti tuttavia che ciò non implica necessariamente che il sistema sia dotato di un dispositivo di memorizzazione di massa. È infatti possibile usare svariati supporti fisici per memorizzare un file system: RAM, ROM, memoria FLASH, il file system di un’altra macchina con cui si è collegati via rete, e così via. 1.2.2 Schedulazione a code multiple La parte del nucleo del sistema operativo che decide istante per istante quale sia il processo che debba avere il controllo della CPU prende il nome di schedulatore. A ogni processo vengono assegnati due attributi fondamentali: una priorità e una politica di schedulazione. In Linux esistono due code di processo: i processi soft real-time (scheduler RR o FCFS con priorità statica) e i processi utente (scheduler RR, non realtime con priorità dinamiche). La gestione avviene tramite tick (crediti). A ogni processo sono assegnati un certo numero di crediti che equivalgono a unità di tempo di utilizzo della CPU. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 16 Figura 1.5: Modello dello scheduler Linux Quando un processo diventa pronto o in attesa gli si sottrae il numero di tick utilizzati. Al momento del dispatch (rimozione di un processo dalla CPU e sostituzione con un altro) viene eseguito il processo con maggiori crediti. Quando tutti i processi pronti hanno esaurito i crediti, questi vengono riassegnati. ∀ processoi CREDIT Ii = CREDIT Ii + P RIORIT A0 2 Pertanto gli obiettivi che Linux si propone, per quanto riguarda lo scheduling, sono un’equa distribuzione della CPU tra i diversi processi e l’evitare che un solo processo possa impadronirsi di tutta la banda del processore per evidenti ragioni di sicurezza. Per ulteriori approfondimenti si rimanda alla pagina di manuale [1] relativa alla chiamata di sistema sched_setscheduler(2). 1.2.3 Schedulazione CFS Dalla versione 2.6.23 in poi del kernel Linux, viene utilizzato l’algoritmo di schedulazione CFS (Completely Fair Scheduler). Questo scheduler invece di basarsi sul meccanismo delle code multiple, utilizza un albero RB [2] per la gestione del task-set. L’80% dell’implementazione dell’algoritmo CFS (Completely Fair Scheduler) può essere riassunta in una singola frase: il CFS fondamentalmente CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 17 modella una CPU ideale e accurata multi-tasking sull’hardware reale. Una CPU multi-tasking «ideale» è una CPU che ha il 100% dell’occupazione di banda e che può eseguire ogni task alla stessa velocità, in parallelo, ognuno alla velocità di 1 nrrunning (dove nrrunning è il numero di task attivi). Per esempio se ci sono due processi attivi, li esegue ognuno al 50% delle proprie possibilità, completamente in parallelo. Nell’hardware reale possiamo eseguire un solo processo alla volta: mentre un task viene eseguito, gli altri, che rimangono in attesa della CPU, sono in svantaggio, perchè il task corrente si appropria ingiustamente del tempo di CPU. Nel CFS, questo sbilanciamento è espresso e tracciato tramite un valore, associato a ogni task, p->wait_runtime espresso in nanosecondi, dove wait_runtime è il tempo totale di CPU che il processo dovrebbe ancora ottenere affinchè il sistema ritorni perfettamente «giusto» e bilanciato. Su una piattaforma hardware «ideale», il valore p->wait_runtime dovrebbe essere sempre pari a zero; nessun task dovrebbe trovarsi «sbilanciato» rispetto al tempo ideale di suddivisione del tempo di CPU totale 1 nrrunning . La politica di scelta di attivazione del task sucessivo è basata proprio sul valore p->wait_runtime ed è veramente semplice: il processo che ha il valore più alto di p->wait_runtime sarà quello scelto. In altre parole, il CFS prova ad attivare il task che ha bisogno urgente di altro tempo di CPU. Quindi il CFS cerca di dividere, quanto più possibile, il tempo totale di CPU tra tutti i processi attivi, come accadrebbe idealmente in un hardware multitasking. La parte restante dell’implementazione del CFS, che non rientra in questa semplificazione, è relativa a poche cose aggiunte: come i livelli di priorità, la gestione dei sistemi multiprocessore e alcune variazioni per il riconoscimento dei processi non attivi (vedi Sez. 1.1.3). In pratica funziona così: il sistema esegue il processo per un po’. Quando questo si sospende (o viene eseguito lo scheduler) l’uso della CPU viene accreditato al task: il tempo in cui ha usato la CPU fisica viene sotratto dal tempo totale che gli spettava p->wait_runtime, corretto del tempo ulteriore che comunque gli sarebbe spettato. Appena il valore di p->wait_runtime diventa così basso che un altro processo diventa «il prossimo task» tra quelli conservati nell’albero RN ordinato in base al tempo (viene introdotta anche una certa distanza temporale CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 18 in modo da non sovra-schedulare i task e non sporcare la cache); al processo corrente viene sotratta la CPU e concessa al nuovo task. Il valore di rq->fair_clock descrive il tempo di CPU in cui il processo è stato in esecuzione rispetto al tempo che avrebbe dovuto ottenere dallo scheduler. Perciò usando questo valore si può misurare il tempo di CPU che un task si aspetta di ottenere. Tutti i processi attivi sono ordinati nell’albero RN secondo la chiave rq->fair_clock- p->wait_runtime; il CFS concede la CPU sempre al task collocato nell’albero più a sinistra. Con il procedere del sistema, i processi che diventano avviabili vengono inseriti nell’albero da «destra» lentamente e, a mano a mano che viene effettuata la schedulazione, a ogni processo viene data la possibilità di spostarsi sempre più a sinistra in modo tale che possa giungere alla CPU in un periodo di tempo deterministico. 1.2.4 Architettura modulare e licenza Open Source Una caratteristica del kernel Linux è quella di poter essere esteso mentre il sistema funziona (mediante il meccanismo dei moduli). Questo consente il supporto di piattaforme differenti dal punto di vista delle periferiche hardware, senza dover ricompilare il nucleo. I driver di periferica fanno di norma parte del codice del kernel e sono accessibili come voci del file system. Uno dei grossi vantaggi dell’uso di software libero nei sistemi embedded è che lo sviluppo dei driver, e in generale di porzioni/moduli del kernel, è ampiamente facilitato rispetto ai sistemi tradizionali, vista la disponibilità totale dei sorgenti del kernel e quindi di un numero notevole di esempi funzionanti sul campo, nonché di un’ampia documentazione. Nel caso di utilizzo di macchine con MMU (Memory Management Unit), sempre più frequente in virtù dei costi via via più bassi del silicio, è possibile sfruttare funzioni molto avanzate sia dal punto di vista della protezione della memoria, e quindi dell’affidabilità del sistema, sia da quello delle funzionalità. In particolare la disponibilità di chiamate come mmap(), che consente di mappare i contenuti di un file o genericamente di un dispositivo su un intervallo di indirizzi virtuali, rende il kernel Linux particolarmente attraente anche se non è abilitato per la gestione degli interrupt. Il modo Unix/Linux di creare nuovi processi risulta ostico ai programmatori embedded tradizionali. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 19 Quello che Linux fa è «clonare» un processo per poi sostituirne l’immagine con quella del programma da eseguire, contrariamente a quanto accade con i sistemi operativi basati su thread, in cui la creazione di un nuovo thread comporta una sola chiamata. Uno dei parametri necessari è l’indirizzo del punto di ingresso del thread stesso. Si tenga tuttavia presente che anche sotto Linux è possibile la programmazione a thread. Esistono infatti diverse implementazioni dei cosiddetti «thread POSIX», definiti dalla norma POSIX 1003.1c. Infine Linux dispone di una grande quantità di meccanismi di comunicazione interprocesso, eredità dei suoi predecessori: si va dai segnali, la forma più arcaica, ad altre più sofisticate (pipe, fifo, semafori, code di messaggi, memoria condivisa, socket). La varietà di possibili soluzioni impone all’utente una buona conoscenza di tutte le alternative, al fine di scegliere la più adatta alle esigenze dell’applicazione. 1.2.5 Il livello applicativo Per ciò che concerne la rete, il sistema GNU/Linux fornisce una grande quantità di applicazioni già scritte e collaudate da tempo, ma l’aspetto più interessante consiste nel cambio radicale di ottica rispetto all’approccio tradizionale. La novità consiste nel fatto che la filosofia Unix (da cui Linux discende da un punto di vista tecnico) privilegia la modularità. Di conseguenza, ciò che verrebbe visto da un programmatore embedded tradizionale come un’unica applicazione da scrivere in C, può essere spezzato in una serie di programmi tra loro interagenti, alcuni dei quali già esistenti e altri da scrivere in linguaggi diversi a seconda dell’opportunità tecnica. È inutile rimarcare tutti i vantaggi dell’approccio modulare. E’ importante, invece, sottolineare che, con l’approccio presentato, la fase di test di modulo risulta semplificata perchè il modulo stesso è già un programma eseguibile, rendendo quindi inutile la costosa scrittura di programmi di test ad hoc. Infine evidenziamo il fatto che l’uso di linguaggi di scripting (per esempio script della shell, tcl e le sue varianti, perl) può risolvere un gran numero di problemi i quali risultano ostici in C, come per esempio lavorare con stringhe ed espressioni regolari. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 1.2.6 20 Portabilità Sebbene in origine sia nato come un sistema operativo esclusivamente per piattaforma x86 (e in particolare PC), il kernel Linux è stato portato su un grande numero di architetture differenti, e anche su macchine non dotate di MMU. A livello di applicazioni, sono disponibili distribuzioni Linux libere che coprono diverse architetture. Per esempio la distribuzione Debian, pur non specifica per l’embedded, dispone di pacchetti per differenti piattaforme comunemente impiegate su macchine a impiego specifico (x86, ARM, PowerPC, SH e altre). A livello di librerie C, esistono progetti liberi nati per dare origine a librerie poco esigenti dal punto di vista delle risorse richieste: bisogna tenere presente che Linux non è un sistema operativo utilizzabile per sistemi con risorse limitatissime. L’aspetto più delicato è costituito dalla quantità di memoria a disposizione: di norma si prende come limite inferiore quello dei 2MB di memoria RAM + 2MB di memoria ROM (o FLASH), anche se il limite effettivo varia a seconda della piattaforma. La potenza di calcolo richiesta alla CPU dal kernel non rappresenta un fattore particolarmente critico. Nonostante a volte venga considerato «scomodo» e/o «poco usabile», Linux rappresenta una piattaforma ideale anche per ciò che concerne la macchina host, cioè quella in cui si sviluppa il codice che girerà sul campo. Questo è vero anche per quello che riguarda i compilatori, i sistemi di controllo della configurazione, il test e il debugging. 1.2.7 Compilazione, debugging e test L’insieme di binutils (assembler, linker e altri programmi di utilità) e di gcc (GNU compiler collection, il compilatore GNU) costituisce la toolchain standard sotto Linux. Si tratta di strumenti di sviluppo di livello professionale, in grado di produrre codice binario per un numero vastissimo di CPU e sistemi operativi. Il debugger standard sotto Linux fa parte del progetto GNU e si chiama gdb. La versione «base» di gdb prevede l’uso da linea di comando, ma esistono anche versioni grafiche (come per esempio Insight e ddd). Inoltre sotto CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 21 Linux il debugging delle applicazioni può essere effettuato sia mediante il debugger vero e proprio, sia attraverso strumenti di tracing come per esempio strace(1), che evidenzia la sequenza delle chiamate di sistema effettuate dall’applicazione sotto esame. Per ciò che consente il testing, soprattutto se si parla di applicazioni di rete, la disponibilità di linguaggi di scripting e di programmi come netcat(1) o tcpdump(1) rende possibile la scrittura rapida di programmi di test anche relativamente complessi. Il debugging del kernel e dei suoi moduli è invece più problematico. Per scelta progettuale, infatti, il kernel non dispone (in modo nativo) di un modulo per il debugging. Si ricorre quindi di norma ad altri metodi, tutti sostanzialmente riconducibili ad attività di tracciamento. Esistono però anche patch del kernel che consentono il debugging vero e proprio. 1.2.8 I problemi della schedulazione real time sotto Linux Essendo un kernel multitasking e multiutente, Linux, come altri Unix, non è stato scritto pensando al tempo reale «stretto», ossia ad applicazioni in cui il mancato rispetto dei tempi di esecuzione specificati porti a un malfunzionamento grave. La conseguenza di quanto si è detto in precedenza è il fatto che lo schedulatore Linux non ha il concetto di «deadline», ovvero il tempo entro il quale una certa attività andrà svolta. Non è quindi adatto ad applicazioni in tempo reale perchè non può conoscere i requisiti temporali dei vari processi real-time. Si potrebbe a questo punto pensare che, sostituendo lo schedulatore tradizionale con uno opportunamente riscritto, sia possibile aggirare il problema: questo non è vero a causa di un secondo ordine di motivi. Bisogna, infatti, chiedersi quando possa avvenire un cambio di contesto, ossia quando effettivamente il controllo della CPU possa passare da un processo a un altro. La risposta è che i cambi di contesto, sotto Linux, possono avvenire solo al passaggio dallo spazio del kernel (kernel space) allo spazio delle applicazioni (user space), ossia per esempio al ritorno da una chiamata di sistema oppure al ritorno da un interrupt handler, quando l’interruzione avviene mentre la CPU sta eseguendo codice dell’utente. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 22 Motivo di ciò è l’esigenza di preservare l’integrità delle strutture dati interne del nucleo, integrità che verrebbe compromessa se il cambio di contesto potesse avvenire in qualsiasi momento. A causa di questo fatto, non è possibile calcolare un limite superiore per il tempo che intercorre tra un cambio di contesto e il successivo. Prendiamo per esempio il caso in cui avvenga un grande numero di interruzioni annidate mentre la CPU si trova già all’interno del codice del kernel. Se si verifica una condizione del genere, non si può prevedere il ritorno al codice utente e quindi procedere a un eventuale cambio di contesto. Se il controller IDE continua a interrompere senza che sia possibile uscire dallo spazio del kernel, il processo utente di elaborazione (anche se a priorità più alta possibile) non può riprendere il controllo perché lo scheduler non può girare. Per maggiori informazioni si può consultare l’articolo «Linux e real time (prima parte)» su [4]. 1.2.9 Strumenti di supporto Linux fornisce strumenti validi anche per ciò che concerne l’editing (per esempio emacs), il controllo della configurazione dei moduli (cvs), la stesura della documentazione (texinfdeto, sgmtools), e in generale tutto ciò che riguarda lo sviluppo di software. Il modello di sviluppo da adottare quando si utilizza software libero, deve essere diverso rispetto a quello che si impiega tradizionalmente. Salvo infatti che non ci si affidi a una distribuzione commerciale o si stipuli un contratto di consulenza, il sistema GNU/Linux è distribuito senza garanzia di funzionamento. Questo viene spesso considerato da molti utenti, o meglio dai loro manager, come un difetto. In realtà, non si riesce ad afferrare che il software libero fornisce anche dei diritti e di conseguenza delle opportunità in più rispetto al software proprietario: la disponibilità del codice sorgente per la lettura, lo studio e le modifiche consente di correggere malfunzionamenti, effettuare personalizzazioni, nonché imparare dal codice e dagli errori altrui e in qualche caso contribuire attivamente allo sviluppo. Infine è bene tener presente che sia il kernel sia gli altri pacchetti liberi di un sistema GNU/Linux sono opera di comunità di sviluppatori, con i quali si può stabilire un contatto per ricevere aiuto, scambiare esperienze e consigli. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 23 Pertanto, qualora si volesse realizzare un sistema operativo real-time basato su Linux, occorrerebbe tenere in considerazione queste problematiche. Nel prossimo capitolo viene presentata la soluzione «ibrida» del sistema Xenomai, il quale permette di ottenere un sistema operativo GNU/Linux di tipo real-time. Un recente studio sui sistemi operativi utilizzati nel mercato dei sistemi embedded [3] ha dimostrato come la percentuale di utenti che ha scelto di non utilizzare GNU/Linux Embedded, a causa di lacune nelle caratteristiche di tipo real time, sia scesa dal 25% al 18% negli ultimi tre anni. Da questi dati si può dedurre come il sistema operativo GNU/Linux, sebbene nato non nell’ottica di un utilizzo specifico per i sistemi embedded, stia sviluppando quelle capacità tali da renderlo adatto anche al mondo real time, in cui la velocità di risposta, ma soprattutto il determinismo (sia degli eventi, sia delle tempistiche), giocano un ruolo fondamentale. 1.3 1.3.1 Soluzioni real-time per il kernel Linux Linux e le applicazioni real-time Lo sviluppo e la diffusione del sistema operativo Linux e delle applicazioni real-time, soprattutto in contesti embedded, ha portato alla ricerca di soluzioni congiunte in cui sistemi con caratteristiche funzionali di tipo real-time fossero basati sul sistema operativo Linux. L’utilizzo di software Open Source offre notevoli vantaggi in termini di costi, flessibilità, sviluppo e supporto. I sistemi embedded ricoprono invece un ruolo sempre più importante in settori come l’automazione, l’automotive, le telecomunicazioni e il digital entertainment. Come già anticipato Linux rappresenta una grande opportunità: esso è un sistema operativo stabile, libero, in grado di fornire il supporto per moltissime architetture hardware e sviluppato costantemente. Esiste però anche un grande problema: Linux non è stato concepito per essere un sistema operativo real-time. Come noto, Linux è nato ispirandosi a Unix e il suo target principale sono sempre stati i server e recentemente i computer desktop. Linux è stato sempre utilizzato in ambito scientifico anche per sistemi embedded, ma non è mai stato sviluppato con la finalità di essere un sistema operativo veramente real-time. Recentemente si è cercato di trovare soluzioni CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 24 finalizzate per avvicinare il sistema operativo Linux al mondo real-time, in modo da rendere questo connubio conveniente. Consideriamo ora le motivazioni tecniche che hanno reso necessario uno sviluppo mirato di Linux in direzione delle applicazioni real-time. La considerazione chiave da fare è la seguente: scelte progettuali che risultano ottime per sistemi operativi general-purpose sono spesso pessime per i sistemi operativi real-time. Un sistema operativo general-purpose non può operare correttamente in ambito real-time, in quanto è stato sviluppato per raggiungere buone prestazioni in contesti con diverse applicazioni in esecuzione contemporanea, ma senza la possibilità di ottimizzare le prestazioni di alcuna di esse in particolare. Inoltre alcune delle principali caratteristiche dei sistemi operativi generalpurpose risultano essere, di fatto, delle limitazioni per le prestazioni in ambito real-time. Per i sistemi multiprocesso è molto importante minimizzare l’overhead dovuto al context-switch: se però da una parte uno scheduler che opera con una risoluzione temporale piuttosto bassa (ovvero effettua lo scheduling non troppo frequentemente) riesce a limitare questo overhead, dall’altra esso rende molto difficile, se non impossibile, mandare in esecuzione un processo time-critical nei tempi corretti. Consideriamo ora lo sviluppo di un sistema operativo dal punto di vista opposto. Un modo per migliorare le prestazioni real-time è quello di aggiungere dei nuovi punti di preemption, dove il sistema operativo possa fermare l’esecuzione di un processo e fornire le risorse a un processo critico. Tuttavia questo approccio, aumentando l’overhead dovuto ai contextswitch, riduce le prestazioni generali di un sistema multiprocesso. Normalmente, invece, i sistemi operativi general-purpose vengono sviluppati cercando di ottimizzare le prestazioni medie, lasciando che il comportamento worst-case non sia deterministico. 1.3.2 Caratteristiche RT del Kernel Linux Il kernel Linux non è stato sviluppato con l’intenzione di ottenere un sistema operativo strettamente real-time. Tuttavia, visto il crescente interesse per l’utilizzo di Linux in applicazioni embedded con stringenti vincoli temporali, recentemente si è in parte orien- CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 25 tato lo sviluppo del kernel Linux nella direzione dei sistemi real-time, tanto che alcuni definiscono il kernel 2.6 (l’ultima versione stabile disponibile del kernel Linux) come «Born to be embedded». Il kernel 2.6 presenta infatti alcune caratteristiche fortemente orientate al real-time, come il nuovo scheduler O(1) scritto da Ingo Molnar e alcune patch per la gestione dell’interruzione dell’esecuzione dei processi. I dettagli delle caratteristiche real-time per le diverse versioni di kernel Linux saranno approfondite in seguito. L’interesse per l’utilizzo di Linux in sistemi realtime ha portato alla nascita di diversi progetti finalizzati a trovare soluzioni in grado di rendere il sistema operativo Open Source adatto ad applicazioni di tipo real-time. Questi progetti, sviluppati inizialmente sul kernel 2.4, possono essere suddivisi in due categorie in base all’approccio adottato nella soluzione del problema: 1. sviluppo del kernel Linux per inserire funzionalità e caratteristiche tipiche degli RTOS; 2. inserimento di un nuovo strato software costituito da un kernel strettamente real-time; tale strato viene posizionato tra l’hardware e il kernel Linux. Quest’ultimo sarà visto dal kernel real-time come un semplice processo. Il primo approccio (mostrato nella Fig. 1.6), cerca quindi di rendere il kernel Linux maggiormente orientato ad applicazioni real-time, andando a modificare il kernel stesso negli aspetti caratteristici dei sistemi operativi real-time. In particolare si cerca di modificare il comportamento dello scheduler e le caratteristiche di preemption sui processi. Il secondo approccio vuole invece astrarre il kernel Linux dall’hardware del sistema frapponendovi un kernel real-time, diverso e indipendente dal kernel Linux, in grado di gestire adeguatamente i processi real-time e di trattare Linux come un processo a bassa priorità. In questo modo si riesce a garantire il corretto trattamento dei processi real-time direttamente con il kernel di basso livello, e a mantenere la compatibilità con le applicazioni Linux che vengono eseguite quando il sistema non è impegnato da applicazioni real-time. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 26 Figura 1.6: Struttura di un sistema operativo basato sul primo approccio (sinistra) e sul secondo approccio (destra) 1.3.3 Approccio RT basato sullo sviluppo del kernel Linux Questo tipo di approccio si pone l’obiettivo di rendere Linux maggiormente adatto ad applicazioni in tempo reale apportando delle modifiche sostanziali in alcune funzionalità del kernel, strategiche dal punto di vista dell’utilizzo in contesti real-time. I due punti fondamentali su cui si agisce sul kernel sono: 1. scheduler; 2. gestione della preemption. Lo scheduler deve essere in grado di svolgere il suo compito considerando le richieste dei processi RT e deve eseguirlo in un tempo predicibile. Lo scheduler del kernel Linux 2.4 non fornisce le garanzie necessarie per un suo utilizzo in un sistema real-time. Il tempo di scheduling dipende infatti linearmente dal numero di task che deve gestire, caratteristica che rende non predicibile il suo tempo di esecuzione. Questo scheduler viene quindi considerato O(N). Per rimediare a tale problema Ingo Molnar ha scritto uno scheduler completamente nuovo che, grazie a una diversa gestione del processo di scheduling, ottempera ai suoi compiti in un tempo indipendente dal numero di task che deve gestire e pertanto viene indicato come O(1). Le principali caratteristiche dei due scheduler sono: CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 27 1. Scheduler O(N): questo scheduler suddivide il tempo in «epoche», periodi in cui ogni task può utilizzare il proprio timeslice. I timeslice di ogni task vengono calcolati insieme all’inizio di ogni epoca (la durata di tale operazione dipende linearmente dal numero di task in esecuzione nel sistema). Al termine di un’epoca i task potrebbero non aver utilizzato tutto il loro timeslice: in questo caso il timeslice inutilizzato sarà considerato nel calcolo del timeslice per l’epoca successiva; 2. Scheduler O(1): questo scheduler (ideato da Ingo Molnar) permette di definire un tempo massimo di esecuzione indipendente dal numero di task in esecuzione nel sistema. Esso è incentrato su una struttura chiamata runqueue che tiene traccia di tutti i task assegnati a una certa CPU (quindi ci sarà una runqueue per ogni CPU presente nel sistema). Ogni runqueue contiene due array: l’active array e l’expired array. Tutti i task vengono inizialmente posti nell’active array, per poi essere trasferiti uno alla volta nell’expired array quando hanno utilizzato tutto il timeslice che gli era stato assegnato. Durante il trasferimento da un array all’altro viene calcolato il nuovo timeslice. Quando l’active array è vuoto si torna alla situazione iniziale con un semplice swap dei puntatori dei due array. Questo permette di realizzare la transizione tra epoche diverse in un tempo costante. L’ordine in cui i task vengono schedulati dipende ovviamente dalla loro priorità. Per individuare il task a massima priorità in un tempo limite predeterminabile, si è adottata una struttura particolare per i due array: essi sono infatti array di linked-list in cui è presente una lista per ogni livello di priorità. Quando un task viene inserito nell’array, esso viene in realtà appeso alla linked-list corrispondente al livello di priorità che gli è stato assegnato. Per trovare il task a maggiore priorità è così sufficiente individuare la prima lista di task ed estrarne il processo da mandare in esecuzione. La preemption, ovvero la possibilità di interrompere l’esecuzione di un processo per passare il controllo delle risorse a un altro, deve essere gestita in modo da garantire che un processo RT riesca a ottenere la disponibilità delle risorse in un tempo utile a eseguire correttamente (sia dal punto di vista logico sia, soprattutto, temporale) la propria elaborazione. Nel kernel Linux 2.4 i problemi principali che si riscontrano nella gestione della preemption sono dovuti all’impossibilità di interrompere l’esecuzione dei processi eseguiti in kernel space, al limitato numero di chiamate allo scheduler (che comportano l’esecuzione monolitica di grandi parti di codice) e alla mancanza di timer ad alta risoluzione, necessari per gestire uno scheduling molto frequente. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 28 Per ovviare a questi problemi sono state preparate delle patch per il kernel, in parte poi adottate anche nel kernel Linux 2.6 ufficiale. Le principali patch realizzate per agevolare la preemption nel kernel Linux sono: • Preemption Patch: consiste nel permettere che lo scheduler venga eseguito e che un task perda il controllo mentre si trova a eseguire del codice appartenente al kernel. Ovviamente un task può essere interrotto solo quando esegue le sezioni del kernel in corrispondenza delle quali la preemption può effettivamente avere luogo senza traumi; • Low-Latency Patch: consiste semplicemente nel modificare il kernel, spezzando i loop nel codice e inserendo delle chiamate allo scheduler per aumentare il numero di punti di preemption. Questa patch richiede cambiamenti piuttosto pesanti e diffusi; inoltre raggiunge migliori risultati sui tempi di latenza del kernel rispetto alla Preemption Patch. La funzionalità di questa patch è anche chiamata Voluntary Preemption; • High Resolution Timers Patch: introduce la possibilità di utilizzare timer ad alta risoluzione (fino all’ordine dei microsecondi). 1.3.4 Approccio RT basato sull’utilizzo di un kernel RT di basso livello Questo tipo di approccio si pone l’obiettivo di rendere Linux adatto ad applicazioni in tempo reale inserendo un nuovo strato software, costituito da un kernel realmente real- time, tra l’hardware e il kernel Linux. Il kernel RT di basso livello gestisce direttamente i processi real-time e tratta il kernel Linux come un processo a bassa priorità. Lo scheduler del kernel real-time tratta il kernel Linux come se fosse il task di idle. Linux viene eseguito solo quando non ci sono task RT da lanciare e il kernel RT è inattivo. I task Linux non possono mai bloccare interrupt o proteggere sé stessi dalla preemption del kernel RT. Questo comportamento è reso possibile dall’emulazione software del controllo hardware degli interrupt. I due principali progetti che adottano questo genere di approccio sono Xenomai e RTAI, sviluppati rispettivamente dal progetto DENX e dal DIAPM (Dipartimento di Ingegneria Aerospaziale del Politecnico di Milano [9]). Considerando il maggior supporto dei forum, della mailing list e la licenza di utilizzo GPL/LGPL si è deciso di approfondire Xenomai. CAPITOLO 1. I SISTEMI OPERATIVI REAL-TIME E LINUX 29 Figura 1.7: Struttura di una soluzione real-time Linux con l’uso di un kernel RT Capitolo 2 Xenomai In questo capitolo si descriveranno i dettagli della soluzione adottata per il porting. Si porteranno in luce tutti gli aspetti che hanno fatto sì che la nostra attenzione si focalizzasse su questo sistema, a partire dalla struttura del sistema, in particolar modo sulla pipeline Adeos, per finire con la descrizione dei servizi offerti dalle interfacce di programmazione e con la loro modalità di utilizzo. 2.1 Struttura del progetto Il progetto Xenomai iniziato nel 2001, finì col fondersi, due anni più tardi, nel progetto RTAI allo scopo di creare una piattaforma software gratuita, con caratteristiche real-time, chiamata RTAI/fusion che si appoggiava al kernel real-time di Xenomai. Nel 2005 il progetto tornò indipendente da RTAI per continuare lo sviluppo autonomamente. La caratteristica principale di Xenomai è la presenza di API (Application Programming Interface) indipendenti tra loro. Accanto alle interfacce (chiamate anche skin) che emulano i principali sistemi operativi real-time proprietari, Xenomai permette di sviluppare delle applicazioni, sfruttando le potenzialità del kernel real-time e della sua perfetta integrazione con l’ambiente Linux. A questo proposito, Xenomai mette a disposizione due alternative per lo sviluppo del software: una estensione real-time dello standard POSIX e una 30 CAPITOLO 2. XENOMAI 31 Figura 2.1: Schema dell’architettura di Xenomai skin nativa, semanticamente vicina alle interfacce degli altri sistemi operativi in tempo reale (VxWorks, pSOS+, uITRON, VRTX). Al contrario di RTAI, Xenomai predilige principalmente la portabilità, l’estensibilità e la mantenibilità. I comportamenti richiesti dai tradizionali RTOS, relativi alla gestione dei thread e alla loro sincronizzazione, agli interrupt e alla risoluzione dei tempi, sono stati implementati nel nucleo esportando alcune classi di servizio. Tali servizi rappresentano lo strato fondamentale su cui sviluppare ogni API secondo le proprie esigenze. Per rendere tale strato indipendente dall’architettura utilizzata, viene fornito il supporto necessario per il controllo dell’hardware, attraverso un sottostante layer software (Hardware Abstraction Layer) a cui si accede tramite una interfaccia standardizzata. In questo modo, il porting del kernel real-time su un’altra architettura, consta solamente dell’implementazione di questa interfaccia a basso livello per la piattaforma che si vuole utilizzare. Questo strato software deve fornire le funzionalità di un RTOS ovvero deve: • inviare su richiesta gli interrupt esterni all’handler specifico; CAPITOLO 2. XENOMAI 32 • fornire un modo per nascondere o meno gli interrupt; • garantire la possibilità di creare nuovi thread di controllo nella forma più semplice; • fornire un supporto per interrupt periodici e aperiodici utilizzati nella gestione dei timer; • supportare l’allocazione di memoria non pageable. In figura 2.1 risultano evidenti le posizioni dei vari componenti a partire dalla pipeline Adeos, di cui si parlerà dettagliatamente nella sezione seguente, per continuare col livello di astrazione dell’hardware e con il kernel real-time fino ad arrivare alle varie interfacce di programmazione. 2.2 Adeos pipeline Xenomai basa il suo funzionamento sul layer di virtualizzazione Adeos, che è disponibile come patch del kernel. Adeos abilita entità multiple, chiamate anche dominii, che permettono di poter avere sullo stesso hardware un ambiente GNU/Linux e un RTOS perfettamente funzionanti. Tutti i dominii esistenti non sono necessariamente consapevoli dell’esistenza degli altri, ma ognuno di essi può e deve interagire con Adeos. Ogni entità compete con l’altra per il processamento di eventi esterni, come gli interrupt, e di eventi interni, come trap ed exception, a seconda della priorità che è stata loro fissata. Oltre alla virtualizzazione, Adeos consente di esportare ai dominii un’API generica indipendente dall’architettura della CPU. La struttura di Adeos può essere vista come una catena di dominii che hanno il compito di gestire il controllo di eventi. Un dominio è una componente software kernel-based che può chiedere al layer Adeos di essere notificato quando si verificano determinati eventi: • l’arrivo di un interrupt esterno o di un interrupt virtuale autogenerato; • ogni chiamata di sistema richiesta dalle applicazioni Linux; • tutte le altre chiamate di sistema generate dal codice del kernel, come per esempio lo switch o l’uscita dei task di Linux e la notifica di segnali. CAPITOLO 2. XENOMAI 33 Figura 2.2: Schema dell’astrazione introdotta dalla pipeline Adeos Adeos assicura che gli eventi siano inviati in maniera ordinata ai vari dominii secondo il rispettivo ordine di priorità nel sistema, in modo da assicurare consegne tempestive e predicibili di tali eventi. Quanto detto si ottiene, appunto, tramite l’assegnazione di una priorità statica a ogni dominio per mezzo della quale viene determinato l’ordine di consegna degli eventi a tutti i dominii. I dominii attivi si trovano accodati secondo la loro priorità, realizzando così l’astrazione della pipeline utilizzata da Adeos per ottenere un flusso di eventi, ordinato dal dominio a priorità maggiore sino a quello a priorità minore. Tutti gli eventi in arrivo sono inviati all’inizio della pipeline, ovvero al dominio a più alta priorità, e inoltrati progressivamente fino all’ultimo dominio esistente, quello a priorità più bassa. Nella figura 2.2 viene illustrata una rappresentazione generica di un sistema basato su Adeos, in cui si possono osservare più dominii che condividono trap e interrupt attraverso l’astrazione della pipeline. Il kernel Linux può ricoprire qualunque posizione nella pipeline ed è marcato come dominio root, in quanto tutti gli altri dominii hanno bisogno di Linux per poter essere installati, spesso come moduli del kernel dello stesso. Uno stadio della pipeline occupato da un dominio può trovarsi in stallo, il che significa che il successivo interrupt in arrivo non sarà consegnato all’handler del dominio e, allo stesso tempo, gli sarà impedito di scorrere verso i dominii di priorità inferiore. Quando uno stadio si trova in stallo, gli interrupt pendenti sono accu- CAPITOLO 2. XENOMAI 34 Figura 2.3: Gestione di Adeos su dominii multipli con n CPU mulati nel log degli interrupt del dominio ed eventualmente elaborati, una volta rimosso lo stato di stallo dall’operazione interna di sincronizzazione. I dominii utilizzano questa funzione, per proteggere le proprie sezioni critiche da prelazioni indesiderate da parte dei propri interrupt handler. In ogni caso, grazie alla virtualizzazione del controllo degli interrupt introdotto da Adeos, un dominio di priorità più alta può comunque ricevere interrupt ed eventualmente prelazionare un dominio a priorità più bassa. Questo significa che se si utilizza Adeos, si può avere il kernel Linux che svolge le proprie operazioni critiche e, allo stesso tempo, un sistema real-time in cima alla pipeline, che sarà in grado di ricevere gli interrupt ogni volta senza alcun ritardo. Quando un dominio finisce di processare gli interrupt pendenti che ha ricevuto, richiama un servizio di Adeos che porta la CPU al dominio successivo della pipeline, cosicché quest’ultimo possa processare i propri interrupt e così via sino al dominio che si trova nell’ultima posizione della pipeline. Nella figura 2.3 viene illustrato il modo in cui diversi dominii, eseguiti su CPU multiple, condividono gli interrupt in arrivo attraverso l’astrazione della pipeline introdotta da Adeos. Risulta evidente che si deve mantenere una corretta memorizzazione de- CAPITOLO 2. XENOMAI 35 gli interrupt pendenti quando uno stadio si trova in stallo. Questo viene realizzato tramite un log che tiene conto del dominio i-esimo e della n-esima CPU. Gli interrupt non sono l’unico genere di eventi che può fluire attraverso l’astrazione della pipeline. Gli eventi interni, innescati dal kernel Linux stesso o dalle sue applicazioni, possono generare quelli che sono chiamati eventi di sistema. Gli eventi di sistema sono notifiche sincrone di trap, exception o di alcune azioni del kernel Linux che sono notificate attraverso la pipeline. Dal momento in cui questi eventi sono sincroni per definizione, non c’è alcun modo di ritardare la loro notifica tramite l’operazione di stallo, non essendo possibile posticipare la loro gestione. Un codice che fa partire un evento di sistema, potrebbe non essere in grado di continuare senza l’intervento immediato dell’handler corrispondente. Per chiarire meglio quanto detto, consideriamo un errore di paginazione: l’handler relativo deve attivarsi immediatamente al momento dell’exception relativa all’indirizzamento di memoria. L’operazione di stallo su un dato dominio ha significato solo per quanto riguarda gli interrupt reali o virtuali. 2.2.1 Adeos e Xenomai Tutte le caratteristiche di Adeos elencate sino a ora permettono l’implementazione di Xenomai accanto al kernel Linux. Come sistema in tempo reale, è facile intuire che l’esigenza di Xenomai è quella di riuscire a gestire tutti gli interrupt in arrivo, prima che il kernel Linux sia in grado di notarli. Inoltre, Xenomai deve assicurarsi di poterli elaborare immediatamente anche in presenza dei tentativi di blocco del kernel Linux. Xenomai deve assicurarsi di rafforzare la priorità di gestione dei propri thread, anche quando questi si trovano in esecuzione in altri dominii. Mettendo a disposizione queste caratteristiche, Adeos consente a Xenomai di avere latenze di interrupt predicibili nell’ordine dei micro-secondi, con qualunque attività di Linux contemporaneamente in esecuzione. Tutto ciò, abbinato alla tecnica di co-scheduling dei task di Linux, fornisce latenze di scheduling dei thread real-time deterministiche. In figura 2.1 si può notare la posizione di Adeos che risulta direttamente a contatto con l’Hardware Abstraction Layer (HAL), posto al di sotto del CAPITOLO 2. XENOMAI 36 nucleo di Xenomai. Molte delle richieste per i servizi Adeos sono diramate proprio da quest’ultimo. 2.2.2 I dominii di Xenomai Xenomai permette l’esecuzione di thread real-time sia in kernel-space, sia entro lo spazio di indirizzi di un processo Linux. Di seguito ci si riferirà a quest’ultimi come thread di Xenomai che non devono essere confusi con i regolari task di Linux. Tutti i thread gestiti da Xenomai sono conosciuti dal suo nucleo. Xenomai introduce quindi un vero supporto user-space per il real-time, che determina la vera novità rispetto ai tradizionali sistemi a doppio kernel, in cui le applicazioni RT potevano essere eseguite solo se incorpate in moduli del kernel. L’approccio di Xenomai nei confronti di Linux, a differenza di quello realizzato dal sistema RTAI/LXRT, si può definire simbiotico. Infatti, i thread di Xenomai possono andare in esecuzione non solo nel dominio a maggior priorità della pipeline (dominio primario), ma anche nel dominio di Linux (dominio secondario) che viene considerato ancora real-time anche se soggetto a latenze di scheduling più elevate. Affinché sia garantito un pieno supporto real-time ai thread che si trovano in esecuzione nel dominio secondario, è necessario che siano rispettati i seguenti punti: • schema di priorità comune: si deve fare in modo che il kernel real-time e il kernel Linux condividano lo stesso schema di priorità. In altre parole, un thread di Xenomai dovrebbe veder rispettata la sua priorità qualunque sia il dominio di esecuzione. Il kernel Linux eredita automaticamente la priorità del thread di Xenomai che è passato in modalità secondaria: ciò significa che i thread di Xenomai, in esecuzione in modalità primaria, non prelazioneranno necessariamente quelli che si trovano in modalità secondaria, a meno che la loro effettiva priorità sia più alta. CAPITOLO 2. XENOMAI 37 Figura 2.4: Migrazione tra dominii Quanto descritto è esattamente l’opposto di quello che accade su RTAI, dove i thread che migrano nel kernel Linux perdono allo stesso tempo la loro priorità real-time. I task di Linux saranno sempre prelazionati dai thread di Xenomai, in esecuzione in modalità primaria, mentre i thread di Xenomai, passati in modalità secondaria, continueranno a competere con questi ultimi tramite il sistema di priorità; • predicibilità dei tempi di esecuzione del programma: i tempi di esecuzione di un thread di Xenomai, in modalità secondaria, non dovrebbero essere condizionati dalle attività di gestione degli interrupt di Linux o, in termini più generici, da qualunque attività asincrona che si verifica a livello del kernel. Il modo più semplice per evitare che questo accada, è mandare il kernel Linux in starvation quando un thread di Xenomai è in esecuzione nel dominio di Linux, cosicché non vengano introdotti ritardi dovuti agli interrupt handler. Per mandare in starvation il kernel Linux, si introduce un dominio intermedio di Adeos in cui bloccare gli interrupt quando necessario. Tale dominio è chiamato interrupt shield e va a occupare nella pipeline la posizione tra il kernel real-time e il kernel Linux (Figura 2.5). L’interrupt shield viene attivato ogni volta che un thread di Xenomai viene schedulato dal kernel Linux e disattivato in tutti gli altri casi. L’abilitazione di tale schermo può essere effettuata per una determinata applicazione o in fase di compilazione di Xenomai marcando l’opportuna flag di configurazione; CAPITOLO 2. XENOMAI 38 Figura 2.5: Interrupt shield • granularità del kernel Linux: per ottenere delle buone prestazioni in modalità secondaria, è necessario che il kernel Linux esponga la sua sezione non prelazionabile per il più breve tempo possibile, cosicché si possa schedulare in tempi rapidi un thread di Xenomai che ha effettuato lo switch di modalità. Inoltre, questo assicura che i thread di Xenomai possono realizzare la migrazione dal dominio primario al secondario entro un ridotto periodo di tempo, poichè questa operazione deve attendere un punto di rescheduling. Xenomai beneficia dei continui miglioramenti riguardo alla prelazione del kernel, come per esempio l’estensione PREEMPT_RT di Ingo Molnar [Rif]. Ovviamente i thread di Xenomai che vanno in esecuzione in modalità primaria non sono affetti dal livello di granularità del kernel e beneficiano sempre delle basse latenze garantite dal nano-kernel real-time, poichè non devono sincronizzarsi in alcun modo con le operazioni di Linux, le quali vengono prelazionate incondizionatamente; • gestione dell’inversione di priorità: sia il kernel real-time, sia il kernel Linux devono gestire il caso in cui l’esecuzione di un thread ad alta priorità viene impedita da un thread a priorità inferiore, che tiene occupata una risorsa condivisa per un certo tempo. Xenomai fornisce questo supporto, mentre il kernel Linux vi riesce solo nella sua variante PREEMPT_RT. Per questa ragione lo sviluppo di Xenomai segue con attenzione lo sviluppo della patch PREEMPT_RT, nonostante la release principale del kernel rimanga il sistema di riferimento. In conclusione, quando il kernel di Xenomai viene caricato, la pipeline Adeos contiene tre stadi attraverso i quali fluiscono tutti gli interrupt secondo l’ordine di priorità: il dominio primario di Xenomai, il dominio dell’interrupt shield e il dominio di Linux. 2.2.3 Intercettare le chiamate di sistema Dal momento in cui le skin, le quali sono allocate nello stack del nano-kernel real-time, possono esportare i propri set di servizi ai thread user-space di CAPITOLO 2. XENOMAI 39 Xenomai, si deve gestire un modo in cui le corrispondenti chiamate di sistema e le chiamate regolari del kernel Linux vengono spedite ai rispettivi handler. Xenomai intercetta ogni chiamata di sistema relativa a exception o trap inviata a tutti i dominii da parte dei thread real-time tramite il servizio di Adeos adeos_catch_event(). In questo modo Xenomai riesce a: • inviare le richieste per i servizi real-time dalle applicazioni ai rispettivi handler, i quali sono implementati dalle varie interfacce di programmazione che sono in esecuzione sul kernel real-time; • assicurarsi che ogni chiamata di sistema sia effettuata sotto il controllo del dominio appropriato, effettuando se necessario una migrazione del chiamante al dominio richiesto senza nessuna interruzione. Per esempio una chiamata di sistema avanzata da un thread di Xenomai, che si trova in esecuzione nel dominio real-time, provocherà una migrazione automatica nel dominio di Linux prima che la richiesta venga trasmessa all’handler. Al contrario, supponendo di avere un thread di Xenomai che invoca una chiamata di sistema per Xenomai potenzialmente bloccante, questo viene spostato nel dominio real-time, prima che il servizio sia assolto, cosicché il thread possa andare in sleep sotto il controllo del kernel real-time. In questo modo si riesce a ottenere una ottima integrazione dei thread Xenomai in Linux. 2.2.4 Propagazione degli interrupt Data la sua posizione in cima alla pipeline, il kernel real-time di Xenomai è il primo a essere avvisato di un interrupt in arrivo, lo processa e lo segnala in modo che possa essere inoltrato agli altri dominii della pipeline. Ricevuta la notifica dell’arrivo di un interrupt, il kernel real-time effettua un rescheduling e fa partire il thread a più alta priorità che lo gestisce. Adeos ha due modalità di propagazione per gli interrupt attraverso la pipeline: • modalità implicita: ogni interrupt in arrivo è marcato automaticamente come pendente in ogni log di dominio che accetta tale interrupt; • modalità esplicita: un interrupt, se necessario, viene propagato manualmente al dominio confinante nella pipeline. CAPITOLO 2. XENOMAI 40 La scelta della modalità di propagazione è settata all’interno di ciascun dominio. Per quel che riguarda Xenomai, viene sempre utilizzata la modalità esplicita per tutti gli interrupt che vengono intercettati. Ogni handler deve richiamare il servizio di propagazione esplicito per inoltrare un interrupt in arrivo lungo la pipeline. Nel caso in cui non sia definito l’handler per un dato interrupt, questo viene propagato incondizionatamente al kernel Linux mantenendo quindi il sistema funzionante. 2.3 Installazione di Xenomai su x86 Il primo passo da intraprendere per l’installazione di Xenomai sul kernel vanilla è il recupero dei sorgenti dal sito ufficiale del progetto. Una volta recuperata l’ultima release stabile, disponibile al momento, si è passati all’estrazione dell’archivio nella cartella /usr/src tramite il comando tar: tar -xjvf xenomai-2.5.x.tar.bz2 La versione utilizzata in questa tesi è la 2.5.5.2. Terminata l’estrazione dell’archivio, il passo successivo è controllare le versioni del kernel Linux, supportate dalla patch Adeos, disponibili nella sottocartella ksrc/arch/x86/patches e scaricare il relativo kernel vanilla [10]. Per l’installazione su x86 è stato utilizzato il kernel 2.6.35.7. Sempre con il comando tar si effettua l’estrazione dei sorgenti del kernel nella directory /usr/src. A quel punto resta solo da avviare lo script opportuno dell’archivio di Xenomai per applicare le patch e compilare quindi il sottosistema real-time integrato nel kernel Linux. Preparazione del kernel Nella cartella scripts è presente lo script prepare-kernel.sh, che va utilizzato con la seguente sintassi: prepare-kernel.sh --linux=<linux-srctree> [--adeos=<adeos-patch>] [--arch=<target-arch>] –linux specifica il percorso della cartella dove sono stati estratti i sorgenti del kernel vanilla, nel nostro caso la cartella /usr/src/linux-2.6.35.7. CAPITOLO 2. XENOMAI 41 –adeos indica il percorso in cui si trova la patch Adeos da applicare sulla cartella del kernel. –arch specifica l’architettura del target. Configurazione e preparazione del kernel Terminata la fase di preparazione dei sorgenti del kernel, è necessario creare il file di configurazione. Questo può essere realizzato attraverso una configurazione grafica tramite il comando make menuconfig o in alternativa make xconfig/gconfig. Utilizzando il primo dei comandi appena elencati, ci si trova di fronte alla seguente schermata: Figura 2.6: Menu di configurazione del kernel Per chi abbia già compilato un kernel Linux, è facile notare immediatamente una nuova voce nel menu di configurazione ovvero la sezione Real-time sub-system, che comprende tutte le funzionalità relative al nano-kernel di Xenomai. Esplorando il contenuto del sottomenu, si trovano varie opzioni di configurazione come l’inclusione delle nuove classi di scheduling (es. Sporadic Ser- CAPITOLO 2. XENOMAI 42 ver ), supporto per il debug, abilitazione delle varie interfacce di programmazione (POSIX, VxWorks, pSOS), settaggio dei watchdog e altre impostazioni da valutare secondo le proprie necessità. Da questa schermata di configurazione, si possono disattivare le parti relative all’hardware che non verranno utilizzate, come per esempio i moduli realtivi a Joystick, Touchscreen, etc. Inoltre, è necessario controllare che determinate voci siano attive, mentre altre funzionalità devono essere disabilitate poiché possono compromettere le prestazioni, in termini di latenza, del kernel real-time. Esaminando nel dettaglio, si deve fare attenzione alle seguenti voci del menu di configurazione: • in Processor type and features: – Controllare che la voce relativa alla Interrupt pipeline sia abilitata. Questo garantisce che l’implementazione della I-pipe aggiunta con la patch Adeos sia attiva; – Verificare che le voci Local APIC support on uniprocessors e IOAPIC support on uniprocessors siano abilitate. • in Power management and ACPI options: – Disabilitare tutto il sotto menu ACPI ; – Disattivare il controllo di potenza avanzato APM ; – Nella menu CPU frequency scaling disabilitare la voce riferita alla CPU. Oltre alle opzioni appena elencate, si è disattivata la voce SMP perchè si è compilato il kernel con il supporto per un solo processore. Terminata la fase di configurazione si deve uscire dall’interfaccia grafica e salvare le modifiche. A questo punto nella source tree del kernel sarà disponibile il file .config, con tutte le informazioni relative alla configurazione appena effettuata. Il passo successivo è la compilazione del kernel. Compilazione e avvio del nuovo kernel Tramite il comando make si può procedere alla compilazione del kernel. Tuttavia si è scelto di adottare un accorgimento molto pratico, cioè quello di CAPITOLO 2. XENOMAI 43 creare dei pacchetti .deb, così da avere disponibile l’installazione del kernel patchato per qualunque altra macchina con architettura x86. Per lanciare la compilazione e creare i pacchetti .deb, i repository di Debian mettono a disposizione il pacchetto kernel-package che contiene lo script per la compilazione e fornisce il modo di creare un’immagine del kernel Linux. Il comando per eseguire quanto detto è il seguente: make-kpkg --initrd --append-to-version -xenomai --revision 1.0 kernel_image kernel_headers Le opzioni –append-to-version e –revision sono facoltative, ma utili per distinguere diverse versioni di kernel customizzati. La fase di compilazione è piuttosto lunga in quanto i sorgenti da compilare sono molto corposi e numerosi. In un sistema multi-core si può velocizzare questa operazione utilizzando l’opzione CONCURRENCY_LEVEL=p, dove p indica il numero di processori che si vogliono dedicare alla compilazione del kernel. Terminata la compilazione, si ha a disposizione il file relativo all’immagine della source tree del kernel e il file contenente i vari header. Nel nostro caso particolare avremo a che fare con i seguenti file: • linux-image-2.6.35.7-xenomai.deb; • linux-headers-2.6.35.7-xenomai.deb. Per installare i pacchetti, si utilizza il comando dpkg specificando l’opzione -i. Attraverso questa procedura l’immagine del kernel con i relativi headers viene installata, inoltre viene anche aggiornato il bootloader (nel nostro caso il grub). Riavviando il sistema, troveremo visualizzata anche la voce del kernel 2.6.35.7-xenomai. Selezionandola si potrà avviare il kernel modificato con l’inclusione del dominio real-time. CAPITOLO 2. XENOMAI 44 Figura 2.7: Controllo sul buffer del kernel della corretta attivazione di Xenomai Per controllare che l’installazione sia andata a buon fine, si può filtrare la voce Xenomai dal contenuto dei messaggi del buffer del kernel: dmesg | grep -i xenomai Il risultato prodotto in output sulla shell è visualizzato in figura 2.7 Dalla prima riga è possibile vedere che Adeos è stata attivata e si trova regolarmente in esecuzione e che il dominio relativo al kernel real-time di Xenomai è stato attivato. Di seguito vengono elencati tutti i servizi di Xenomai attivati in fase di configurazione. L’ultimo step da eseguire è l’installazione del supporto user-space di Xenomai. Questo si realizza tramite l’esecuzione dello script configure presente nella cartella /usr/src/xenomai. Una verifica veloce della buona riuscita dell’installazione, si può riscontrare mandando in esecuzione alcuni programmi forniti con Xenomai. Infatti, nella cartella /usr/xenomai/bin si possono trovare una serie di binari da avviare che realizzano una serie di test sul sistema appena installato. CAPITOLO 2. XENOMAI 2.4 45 Testing della skin nativa L’interfaccia di programmazione nativa segue determinate linee guida. In primo luogo si tratta di un’API compatta, che fornisce un numero contenuto di servizi, senza rinunciare ad avere un sistema stabile e di facile programmazione nell’ambiente GNU/Linux. Tali servizi vengono resi disponibili in maniera non ambigua, in modo da non poter essere semanticamente confusi e lasciare allo sviluppatore il compito di combinarli tra loro per ottenere il risultato desiderato. Sebbene sia preferibile eseguire le applicazioni di Xenomai in user-space, possono presentarsi dei casi in cui bisogna eseguire del codice in moduli del kernel come per esempio nel caso di hardware poco performanti. Per questo motivo la skin nativa fornisce lo stesso set di servizi real-time alle applicazioni indipendentemente dal loro spazio di esecuzione. Inoltre, è possibile che attività real-time che si trovano in spazi di esecuzione differenti, debbano cooperare e questo è possibile tramite la condivisione degli stessi oggetti messi a disposizione dalla skin. Utilizzando la skin nativa si può inoltrare la stessa chiamata di sistema per eseguire un’azione senza preoccuparsi dello spazio di esecuzione. Ciò significa che un task sarà sempre rappresentato da un descrittore RT_TASK, una coda da un RT_QUEUE e un semaforo da un RT_SEM qualunque sia lo spazio in cui vengono utilizzati. Tali descrittori possono essere condivisi tra gli spazi di esecuzione cosicché un servizio invochi un oggetto che è stato creato nello spazio opposto. Per creare una facile corrispondenza per la ricerca di questi descrittori sul sistema Xenomai, esiste un registro unificato: esso permette a tutte le categorie di servizi real-time di indicizzare ogni oggetto che creano in una unica chiave simbolica definita dall’applicazione. Come tutte le altre, la skin nativa è una parte opzionale del sistema Xenomai ed è basata sullo stesso kernel real-time che fornisce una serie di servizi. I servizi dell’API nativa sono implementati dal modulo del kernel chiamato xeno_native e, come già detto, possono essere invocati sia in kernelspace, utilizzando le funzioni di chiamata inter-moduli, sia dal contesto userspace attraverso la libreria libnative.so invocando le opportune chiamate di sistema. CAPITOLO 2. XENOMAI 46 La skin nativa fornisce sei categorie di servizio e ognuna di esse definisce un set di chiamate di sistema disponibili per entrambi gli space. Tali categorie sono: • gestione dei task: definisce il set di servizi relativi allo scheduling e al controllo in senso lato dei task. Un’applicazione necessita di questa chiamate di sistema per creare task e controllarne il comportamento. Tutti questi servizi sono inclusi di base nel modulo dell’API nativa indipendentemente dalle opzioni di configurazione adottate. Essa utilizza una scala di priorità per i task, compatibile con la scala POSIX per la classe di schedulazione SCHED_FIFO, supportata da Linux (dove 99 rappresenta il livello di priorità più alto); • servizi di timing: raggruppa tutte le chiamate di sistema relative alle interrogazioni e alla gestione del timer di sistema. Questa categoria comprende anche la creazione di timer chiamati Alarms; • supporto di sincronizzazione: fornisce supporto ai task che necessitano di sincronizzare le proprie operazioni. Gli oggetti di sincronizzazione utilizzabili per questo scopo sono semafori, mutex, variabili condizionali e event flag; • messaggi e comunicazione: questa categoria implementa diversi modi per lo scambio di dati tra i task real-time o tra attività real-time in kernel-space e processi di Linux in user-space; • gestione di dispositivi i/o: la skin nativa fornisce un supporto per l’interazione con interrupt e per accedere alla memoria di dispositivi di input/output dal contesto user-space. Per quel che riguarda la completa creazione di driver per dispositivi è disponibile l’interfaccia RTDM; • supporto di registri: consente l’accesso alle chiamate di sistema da contesti di esecuzione differenti. 2.5 Sviluppo di script per il testing Una volta installato il sistema Xenomai, si è scelto di sviluppare alcuni semplici, ma pragmatici script sulla skin nativa, per verificare il funzionamento dello scheduler RT. Uno script interessante prevede la creazione di due task real-time aventi lo stesso periodo ognuno dei quali va a occupare la CPU rispettivamente per CAPITOLO 2. XENOMAI 1 3 e per 2 3 47 del periodo, in modo da avere in totale una occupazione della CPU pari al 100%. Una possibile implementazione di questo test consiste nel seguente set di istruzioni: #i n c l u d e <s t d i o . h> #i n c l u d e <s i g n a l . h> #i n c l u d e <u n i s t d . h> #i n c l u d e <s y s /mman. h> #i n c l u d e <time . h> #i n c l u d e <n a t i v e / t a s k . h> #i n c l u d e <n a t i v e / t i m e r . h> // uso d e l l a r t _ p r i n t f ( ) #i n c l u d e <r t d k . h> RT_TASK task_desc_1 ; RT_TASK task_desc_2 ; v o i d task_body_1 ( v o i d ∗ a r g ) { // t a s k 1 p e r i o d i c o , con p e r i o d o t 1=3s r t _ t a s k _ s e t _ p e r i o d i c (NULL, TM_NOW, 3 0 0 0 0 0 0 0 0 0 ) ; int i ; f o r ( i =0; i <10; i ++) { // c1=1s r t _ p r i n t f ( " Busy w a i t 1 s . . . \ n " ) ; rt_timer_spin ( 1 0 0 0 0 0 0 0 0 0 ) ; rt_task_wait_period (NULL ) ; } } v o i d task_body_2 ( v o i d ∗ a r g ) { // t a s k 2 p e r i o d i c o , con p e r i o d o t 2=3s r t _ t a s k _ s e t _ p e r i o d i c (NULL, TM_NOW, 3 0 0 0 0 0 0 0 0 0 ) ; CAPITOLO 2. XENOMAI 48 int i ; f o r ( i =0; i <10; i ++) { // c2=2s r t _ p r i n t f ( " Busy w a i t 2 s . . . \ n " ) ; rt_timer_spin ( 2 0 0 0 0 0 0 0 0 0 ) ; rt_task_wait_period (NULL ) ; } } i n t main ( i n t argc , c h a r ∗ argv [ ] ) { s i g n a l (SIGTERM, c a t c h _ s i g n a l ) ; s i g n a l ( SIGINT , c a t c h _ s i g n a l ) ; /∗ Avoids memory swapping f o r t h i s program ∗/ m l o c k a l l (MCL_CURRENT|MCL_FUTURE) ; /∗ Perform auto−i n i t o f r t _ p r i n t b u f f e r s ∗/ /∗ i f t h e t a s k doesn ’ t do s o ∗/ rt_print_auto_init ( 1 ) ; /∗ c r e a z i o n e t a s k RT ∗/ r t _ p r i n t f ( " c r e a z i o n e t a s k 1 c1 / t 1 =1/3\n " ) ; r t _ t a s k _ c r e a t e (&task_desc_1 , " Task1 " , 0 , 9 9 , 0 ) ; r t _ p r i n t f ( " c r e a z i o n e t a s k 2 c2 / t 2 =2/3\n " ) ; r t _ t a s k _ c r e a t e (&task_desc_2 , " Task2 " , 0 , 9 9 , 0 ) ; /∗ a v v i o t a s k RT ∗/ r t _ p r i n t f ( " t a s k 1 p r o n t o \n " ) ; r t _ t a s k _ s t a r t (&task_desc_1 , &task_body_1 , NULL ) ; r t _ p r i n t f ( " t a s k 2 p r o n t o \n " ) ; r t _ t a s k _ s t a r t (&task_desc_2 , &task_body_2 , NULL ) ; pause ( ) ; r t _ p r i n t f ( " c a n c e l l a z i o n e t a s k \n " ) ; r t _ t a s k _ d e l e t e (&task_desc_1 ) ; CAPITOLO 2. XENOMAI 49 r t _ t a s k _ d e l e t e (&task_desc_2 ) ; } Come prima cosa si creano i due task RT con la funzione: int rt_task_create (RT_TASK * task, const char * name, | int stksize, int prio, int mode) dove: • task è l’indirizzo del descrittore di task Xenomai usato per memorizzare i dati del rispettivo task; • name è una stringa ASCII e rappresenta il nome simbolico del task; • stksize rappresenta la dimensione (in byte) dello stack del nuovo task, passandogli zero come valore verrà utilizzata una dimensione predefinita; • prio è un intero che va da 0 a 99 e costituisce la priorità che si vuole assegnare al task (0 indica la priorità minima, 99 la priorità massima); • mode indica la modalità di creazione del task. Nel caso in cui la creazione del task vada a buon fine, la routine restituirà il valore intero zero. E’ importante sottolineare che la funzione esplicitata può essere chiamata sia in un contesto user-space, per esempio all’interno della funzione principale di uno script, sia in un contesto kernel-space, per esempio all’interno della funzione di inizializzazione del modulo (init_module). Un’altra osservazione da tener presente è che dopo aver chiamato questa procedura, è possibile il rescheduling dei task da parte del sistema Xenomai. Inoltre, creato un processo, questo rimane inattivo fin tanto che non si chiama la funzione: int rt_task_start (RT_TASK * task, void(*)(void *cookie) | entry, void * cookie) dove: task, come già detto, è l’indirizzo del descrittore del task Xenomai e entry è l’indirizzo del corpo della routine del task contenente le istruzione. Anche in questo caso, se la routine ha avuto successo viene restituito il valore intero zero. Dopo aver chiamato la procedura in questione, il task può essere schedulato per la prima volta. CAPITOLO 2. XENOMAI 50 Essendo queste due funzioni strettamente correlate è possibile ottenere lo stesso risultato in un solo step, utilizzando la funzione rt_task_spawn() la quale richiede, ovviamente, un numero maggiore di parametri. Come accennato le procedure rt_task_start() e rt_task_spawn() vogliono come parametro anche l’indirizzo della routine contenente le istruzioni che il processo deve svolgere. Quindi, in base alle ipotesi fatte per il nostro test, dovremo far sì che i due task creati abbiano lo stesso periodo e, inoltre, che un task occupi la CPU per il 33, 3% e l’altro per il 66, 6%. E’ possibile rendere periodico un task richiamando la funzione: int rt_task_set_periodic (RT_TASK * task, RTIME idate, | RTIME period) dove: • task è l’indirizzo del descrittore del task di cui si vuole settare il periodo; • idate è la data (assoluta) di esecuzione della prima istruzione, in altre parole costrituisce il ritardo affinchè la prima istruzione del processo venga eseguita. Nel nostro caso abbiamo impostato tale valore a TM_NOW in modo da non avere alcun ritardo iniziale; • period rappresenta il periodo del task, espressa in nanosecondi o in clock ticks. E’ possibile eseguire le azioni in più periodi utilizzando, per esempio, il ciclo iterativo for(;;), all’interno del quale deve essere richiamata, come ultima istruzione la funzione: int rt_task_wait_period(NULL) la quale mette il task nello stato di attesa del prossimo periodo, a discrezione dello scheduler di Xenomai. All’interno del ciclo iterativo for(;;), per simulare l’uso della CPU da parte del task, abbiamo utilizzato la semplice funzione: void rt_timer_spin (RTIME ns) che tiene occupata la CPU per un certo numero di cicli non compiendo alcuna azione, ossia «brucia» cicli di CPU. Infatti, l’unico parametro richiesto dalla routine è proprio il tempo di impiego della CPU espresso in nanosecondi o in numero di cicli della cpu (clock ticks). CAPITOLO 2. XENOMAI 51 E’ possibile cancellare un task, in modo tale che tutte le risorse del kernel assegnategli al momento della creazione vengano rilasciate chiamando la funzione: int rt_task_delete (RT_TASK * task) E’ importante sottolineare l’uso della funzione rt_printf() al posto della printf() [16], Infatti, tutti i thread Xenomai vengono avviati in modalità primaria (nucleo RT) ma, invocando una primitiva non RT, in questo caso la printf(), il thread viene spostato nella coda dei processi di Linux, mantenendo la stessa priorità, cioè si ha un passaggio in modalità secondaria. Così facendo però, il thread entra in competizione con i task di Linux, di conseguenza l’ordine di esecuzione dei processi potrebbe essere diverso da quello che ci aspettiamo. Come test successivo abbiamo pensato di rendere meno uniforme e più dinamica la creazione dei vari task, generando casualmente il numero di processi creati (al massimo 10), il tempo computazionale (ci ) e il periodo (ti ) di ogni task. Per far questo abbiamo utilizzato la coppia di funzioni note srand() e rand(). Lo sviluppo di questi test è stato fatto sia in user-space sia in kernel-space, evidenziando le seguenti differenze: • nel contesto di esecuzione user-space non è possibile settare una C/T globale pari a 1, poiché bisogna considerare delle latenze dovute al cambio di contesto dei due task. L’occupazione massima della CPU che si è raggiunta empiricamente è del 95%; • in kernel-space ci si può spingere a un limite del 99%, oltre il quale si provoca un crash del nano-kernel di Xenomai. Risulta interessante notare che utilizzando lo script in user-space, l’utente resta in grado di controllare i dispositivi di input come touchpad e tastiera, sebbene con notevoli rallentamenti. Nel caso del modulo kernel-space, questo è impossibile poichè, come già visto, ci si può spingere verso limiti di computazione più elevati, eliminando la possibilità di entrare in esecuzione alle routine di controllo di tali dispositivi. Capitolo 3 Cris AXIS ETRAX 100LX 3.1 3.1.1 La FOX Board Panoramica La FOX Board [12] utilizzata per il porting del sistema RT Xenomai, (Fig. 3.1) è una scheda madre prodotta dalla Acme Systems di ridotte dimensioni e bassi consumi con un microprocessore ETRAX 100LX, a 100MIPS RISC CPU progettato dalla Axis Communications. La scheda possiede le seguenti caratteristiche tecniche: Caratteristiche Hardware • 1 microprocessore RISC con architettura Axis ETRAX 100LX MCM 4+16 (32 bit, 100 MHz di frequenza di clock) prodotto da Axis Communications; • 4 MB di memoria flash e 16 MB di memoria RAM (per questo è anche denominata "LX416"); • 1 porta Ethernet a 10/100 Mb/s; • 2 porte USB 1.1; • 1 porta seriale TTL a 3,3 Volt per consolle; • 2 connettori di espansione con segnali per interfacce IDE, SCSI, porte seriali, linee di I/O e bus I2C; • alimentazione singola a 5 Volt, 280 mA (equivalente a circa 1 W di potenza); 52 CAPITOLO 3. CRIS AXIS ETRAX 100LX 53 Figura 3.1: FOX Board • dimensioni: 66x72x19 mm; • peso: 37g; • range temperature: 0 – 70 °C. Caratteristiche Software • Linux kernel 2.4 (upgrade al 2.6 disponibile); • WEB server, FTP server, SSH, TELNET e PPP preinstallati; • supporto ai convertitori usb/seriali basati su chip FTDI; • supporto alle chiavi di memoria USB; • ambiente di sviluppo (SDK) Open Source per sistemi Linux; • compilatore GNU C1 con interfaccia web disponibile gratuitamente su Internet per la compilazione di piccole applicazioni senza dover installare l’SDK. 1 il gcc (GNU Compiler Collection, in origine GNU C Compiler) è un compilatore multitarget creato inizialmente dal fondatore della Free Software Foundation, Richard Matthew Stallman, come parte del Progetto GNU. Le versioni recenti sono incluse nelle principali distribuzioni del sistema operativo GNU/Linux, e di molti altri sistemi. CAPITOLO 3. CRIS AXIS ETRAX 100LX 54 Questa scheda è adattabile a moltissimi utilizzi, che spaziano dal semplice lettore MP3 a veri e propri web server, ed è espandibile grazie ai componenti hardware aggiuntivi (quali per esempio display, espansioni di memoria, antenne GSM/GPRS) messi in commercio da Acme Systems e da altre ditte. La scheda è progettata espressamente per ospitare un sistema operativo Linux. Infatti, viene fornita con una immagine del kernel Linux pronta per essere eseguita. Come è noto, il kernel di questo sistema operativo offre un elevato grado di adattabilità. 3.1.2 Accensione Per funzionare la scheda necessita di una tensione continua pari a 5 Volt, che gli può essere fornita collegando un alimentatore commerciale di tipo PS5V1A al connettore J14 o collegando un alimentatore tipo quello dei floppy disk al connettore J2 (Fig. 3.2). Figura 3.2: Alimentazione attraverso il connettore J14 o J2 Una volta alimentata, i tre led presenti, DL1, DL2 e DL3 si illuminano per un breve periodo di tempo, dopo di che rimane acceso solo il led verde. Il significato dei tre led è il seguente: • Led rosso DL1, può essere usato dall’utente per controllare le applicazioni software. Normalmente è spento. All’avvio viene utilizzato dal kernel per indicare un errore sulla board. Tipicamente lampeggia quando l’indirizzo MAC della scheda Ethernet non è configurato (que- CAPITOLO 3. CRIS AXIS ETRAX 100LX 55 sta condizione si verifica solo dopo la riprogrammazione della memoria flash con il comando boot_linux -F, cioè quando il kernel è stato riprogrammato cancellando tutte le informazioni del «boot block»); • Led giallo DL2, mostra il traffico LAN sul connettore ethernet; • Led verde DL3, è collegato direttamente alla rete di alimentazione e di conseguenza mostra lo stato acceso/spento della board. 3.1.3 Modalità di accesso alla FOX Board La FOX Board ha un connettore femmina LAN RJ45 per la connessione alla rete Ethernet 10/100 Mbit (J11). E’ possibile collegare direttamente un PC attraverso un cavo Ethernet incrociato. La configurazione di default della rete sulla FOX Board è: • indirizzo IP: 192.168.0.90 • netmask: 255.255.255.0 • default gateway: 192.168.0.1 Quindi per vedere la scheda tramite TCP/IP dal nostro PC, bisogna che questo sia configurato sullo stesso pool di indirizzi LAN, cioè l’indirizzo IP del PC deve appartenere o al range da 192.168.0.2 a 192.168.0.89 o al range da 192.168.0.91 a 192.168.0.254. Vi sono principalmente tre modalità per utilizzare il sistema Linux presente sul sistema embedded. 3.1.3.1 Web access La FOX Board ha preinstallato di default il web server BOA2 . Se il PC si trova sulla stessa LAN della piattaforma embedded, come descritto sopra, digitando il seguente URL: http://192.168.0.90 all’interno di un browser, comparirà la schermata in figura (Fig. 3.3). La home page predefinita si trova su /usr/html/index.html. Questo file viene salvato in una cartella di sola lettura, quindi non è possibile modificarne il contenuto. Per cambiare la home page predefinita o per aggiungerne altre, è necessario utilizzare la cartella di lettura/scrittura /usr/html/local. 2 è un webserv http elementare ma efficace, in grado di offrire le funzionalità tipiche di questo servizio. Si compone fondamentalmente di un demone, boa, e di un file di configurazione, /etc/boa/boa.config. CAPITOLO 3. CRIS AXIS ETRAX 100LX 56 Figura 3.3: Home page FOX Board 3.1.3.2 FTP access Il sistema embedded ha un server FTP sempre attivo che opera sulla porta 21. Se la board è correttamente visibile sulla LAN, digitando il seguente comando all’interno della shell: ftp 192.168.0.90 verrà chiesto di inserire l’username e la password, che sono rispettivamente root e pass (vedi Fig. 3.4). Figura 3.4: ftp access Una volta effettuato il login è possibile per esempio navigare all’interno CAPITOLO 3. CRIS AXIS ETRAX 100LX 57 del file system della board o impostare la modalità binaria per il traferimento dei file. 3.1.3.3 TELNET access La FOX board ha un server Telnet sempre attivo e in ascolto sulla porta 23, che consente l’accesso remoto da PC. Per l’accesso Telnet è possibile utilizzare il software gratuito PuTTY, il quale supporta anche sessioni SSH, che a differenza del Telnet sono protette. Al fine di utilizzare Putty è necessario deselezionare l’opzione «Return key sends Telnet New Line instead of M » nel pannello di configurazione di PuTTY, come mostrato in Fig. 3.5. Figura 3.5: Pannello di configurazione Telnet di PuTTY 3.2 Cris ETRAX Il cuore della FOX Board è una CPU Axis ETRAX 100LX. Questo chip è stato progettato e realizzato dalla Axis Communications per l’impiego in sistemi embedded del sistema operativo Linux (LX sta per Linux). CAPITOLO 3. CRIS AXIS ETRAX 100LX 58 La sua architettura è nota negli ambienti Linux come un’architettura CRIS che è l’acronimo di Code Reduced Instruction Set. Mentre ETRAX è un acronimo delle caratteristiche del chip: Ethernet, Token Ring, AXis. Le informazioni seguenti (sottosezione 3.2.1) rappresentano una piccolissima parte del manuale «AXIS ETRAX 100LX Designer’s Reference», per maggiori dettagli e chiarimenti si consiglia di consultare direttamente il documento [14]. 3.2.1 CPU RISC La CPU della ETRAX 100LX è una CPU RISC a 32-bit con istruzioni a 16-bit, compatibile con l’architettura CRIS. Funziona con una frequenza di clock di 100 MHz e può raggiungere una velocità di picco pari a 100 MIPS. 3.2.1.1 Registri Il processore contiene 14 General Registers a 32-bit (R0-R13), uno Stack Pointer a 32-bit (R14 o SP) e un Program Counter a 32-bit (R15 o PC) (Fig. 3.6). L’architettura del processore comprende anche 16 Special Registers (P0P15), 10 dei quali sono definiti (Fig. 3.7). Figura 3.6: General Registers CAPITOLO 3. CRIS AXIS ETRAX 100LX 59 Figura 3.7: Special Registers 3.2.1.2 Flag e Condition Code Il Condition Code Register (CCR) e la sua estensione a 32 bit, il Dword Condition Code Register (DCCR), contengono 11 diversi flag, mentre i bit rimanenti sono sempre a zero (vedi Fig. 3.8). 3.2.1.3 Organizzazione dei dati in memoria I tipi di dati supportati dalla CRIS sono: • byte, intero 8 bit; • word, intero 16 bit; • dword, intero o indirizzo 32 bit. Ogni locazione indirizzabile contiene dati di un byte. Questi sono archiviati in memoria con il byte meno significativo all’indirizzo più basso (little indian). La CPU CRIS nella ETRAX 100LX ha una bus dati a 32-bit. La conversione da 32 bit a 16 bit, se necessaria, viene eseguita mediante l’interfaccia del bus. Se i dati superano i 32-bit, la CPU dividerà l’accesso ai dati in due accessi separati. Perciò, l’uso di parole non allineate e dword possono degradare le prestazioni. CAPITOLO 3. CRIS AXIS ETRAX 100LX 60 Figura 3.8: CCR e DCCR Le figure seguenti mostrano un esempio di organizzazione dei dati con un bus a 16 bit Fig. 3.9 e con un bus a 32 bit Fig. 3.10. Figura 3.9: Esempio di organizzazione dei dai con un bus a 16 bit 3.2.1.4 Formato delle istruzioni L’istruzione base ha la lunghezza di una dato word, cioè 16-bit. Le istruzioni devono essere 16 bit allineati. Quando la CPU recupera 32 bit, contenenti due istruzioni allineate a 16 bit, salva i due byte superiori in un registro interno di prefetch. Dopodiché, la CAPITOLO 3. CRIS AXIS ETRAX 100LX 61 Figura 3.10: Esempio di organizzazione dei dati con un bus a 32 bit CPU effettuerà una lettura per ogni seconda istruzione durante l’esecuzione del codice consecutivo. La maggior parte delle istruzioni hanno la fisionomia generale in Figura 3.11. Figura 3.11: Fisionomia generale di un’istruzione 3.2.2 Gestione degli interrupt su Linux/CRIS E’ molto importante ai fini del porting della patch Adeos, capire come il kernel Linux sull’architettura CRIS gestisca le interruzioni [17]. Si ricordi che la funzione principale del livello di astrazione hardware (HAL) è quella di prendere gli interrupt dall’hardware e se necessario inoltrarli a Linux o al gestore real-time. 3.2.2.1 Interrupt Paths Nella Fig. 3.12, vengono mostrati i percorsi di gestione dei vari interrupt del kernel Linux su architettura CRIS. Nella maggior parte dei casi un interrupt causa l’esecuzione della routine IRQxx_interrupt (queste routine vengono inserite nel vettore degli interrupt ogni volta che un gestore dell’interrupt «xx» viene installato, per esempio, da un driver hardware). La prima istruzione della routine consiste nel salvare i registri e disabilitare gli interrupt (poichè non vengono disabilitati automaticamente). Do- CAPITOLO 3. CRIS AXIS ETRAX 100LX Figura 3.12: Interrupt handling paths in the Linux kernel on CRIS 62 CAPITOLO 3. CRIS AXIS ETRAX 100LX 63 podiché l’interrupt xx viene mascherato. Se questo non fosse mascherato e il sistema potesse ricevere nuovi interrupt. prima che quello gestito venga riconosciuto, si entrerebbe in un loop infinito. Viene chiamata poi la routine-C, do_IRQ, passandogli come parametro il numero dell’interrupt. Questa funzione esegue il gestore dell’interruzione, inoltre dovrebbe anche riconoscere l’interrupt. Normalmente le interruzioni vengono disattivate durante l’esecuzione dell’handler, ma questo dipende da come il gestore è stato installato. La ricezione degli interrupt potrebbe rimanere attiva mentre si esegue l’handler. Questa soluzione è possibile se l’interrupt è stato precedentemente mascherato. Quando il gestore ha terminato, la routine IRQxx_interrupt continua e infine smaschera l’interrupt. Il percorso appena descritto è il più importante da capire, ma è anche necessario vedere cosa accade dopo. Alcune routine meritano una spiegazione: • ret_from_intr, controlla se il processore era in user o in kernel (supervisor) mode quando si è verificato l’interrupt. User-mode significa che era in esecuzione un’applicazione in user-space e kernel-mode che era in esecuzione qualche codice del kernel, come un driver. In user-mode si ha un percorso più lungo; • _ret_with_reschedule, controlla se il processo corrente ha qualche signal pendente o è necessaria una schedulazione. In modo tale che venga inoltrata la routine appropriata; • _Rexit, è responsabile del ripristino dei registri precedentemente salvati e del ritorno al codice interrotto (dal processore quando si è verificato l’interrupt). Attraverso questa routine, tutti i gestori delle interruzioni si concludono «con il ritorno». 3.2.2.2 Casi speciali • IRQ 0: Interruzioni hardware (hwbreakpoint) usate solo per il debug; • IRQ 1: Interruzioni dal watchdog. Se il watchdog è abilitato nella configurazione del kernel, la routine IRQ1_interrupt visualizza le informazioni di debug e in seguito il chip viene resettato. La routine può essere chiamata anche per il reset del chip da software, dato che il watchdog è usato per questo; CAPITOLO 3. CRIS AXIS ETRAX 100LX 64 • IRQ 2: Per gli interrupt del timer viene usata la routine speciale IRQ2_interrupt. Questa funziona è simile alla routine IRQxx_interrupt, solamente che gli interrupt non vengono mascherati. Questo perchè do_IRQ gestisce anche le interruzioni software pendenti (chiamando do_softirq) dopo l’attuale gestore di interrupt. do_softirq consente gli interrupt durante l’esecuzione dei gestori: un nuovo interrupt del timer può essere processato solo se non è mascherato. L’interrupt del timer è cruciale e dovrebbe essere sempre processato il più presto possibile, in quanto il watchdog deve essere resettato e l’orologio di sistema aggiornato; • IRQ 14: il bus-fault della MMU (Memory Management Unit) si verifica per esempio quando un’istruzione cerca di accedere a un indirizzo di memoria per il quale la traduzione da indirizzo virtuale a fisico non è memorizzata nella TLB. Quando si verifica un bus-fault viene chiamata la ruotine mmu_bus_fault. Questa salva i registri e disabilità gli interrupt come la procedura generale. Viene poi chiamata la routine-C handle_mmu_bus_fault, che si suppone risolva il bus-fault, qualunque esso sia. Al ritorno viene inoltrata ret_from_intr, ma invece di _Rexit è usata la funzione speciale _RBFexit. Essa riprista i registri e riavvia l’istruzione che ha causato il fault; • IRQ 15: Le interruzioni multiple si verificano quando ci sono diversi interrupt attivi allo stesso tempo. La routine multiple_interrupt, inizialmente, salva i registri e disabilita gli interrupt; in seguito processa il primo interrupt in attesa, chiamando la routine IRQxx_interrupt con una scorciatoia. La scorciatoia può essere percorsa dato che i registri sono stati salvati. L’interrupt viene poi trattato in modo normale. Se vi sono altri interrupt attivi, verrà chiamata di nuovo la routine multiple_interrupt. 3.3 3.3.1 Software Development Kit (SDK) I sistemi embedded e la cross compilazione I sistemi embedded, a causa delle limitate risorse di cui dispongono, non possono ospitare ambienti di sviluppo. Non è possibile eseguire su di essi nessun IDE, editor di testo, compilatori e debugger. Tuttavia è necessario scrivere applicativi per questi sistemi. Il problema viene risolto utilizzando una macchina host opportunamente configurata per generare il codice oggetto, che sarà poi trasferito ed eseguito sulla macchina target, il sistema embedded. CAPITOLO 3. CRIS AXIS ETRAX 100LX 65 Il processo relativo alla costruzione di un programma su di un sistema host, per poter poi essere eseguito su di un sistema target, è chiamato cross compilazione, il cui elemento fondamentale è il cross compilatore. Il gcc è stato portato praticamente su tutti i principali sistemi: per ognuno di essi è stato configurato per produrre dei binari ottimizzati per quella particolare architettura. Il gcc è il cross compilatore ottimale da utilizzare per costruire una cross toolchain. La costruzione di un cross compilatore e di una cross toolchain non sono operazioni semplici. Per effettuare una cross compilazione non è sufficiente disporre di un cross compilatore, configurato e ottimizzato per una particolare architettura hardware. Sono necessarie anche una serie di utility, che a loro volta devono essere costruite, ottimizzate e configurate per poter contribuire alla cross compilazione per quella particolare architettura. Il cross compilatore richiede il supporto delle librerie C e di altri eseguibili, come il linker, l’assembler e il debugger. L’insieme dei tool, delle librerie e dei programmi usati per la cross compilazione si chiama cross platform toolchain, o toolchain in breve. Tutte le macchine atte alla compilazione di codice dispongono di una toolchain. Se gli eseguibili prodotti sulla macchina host dovessero girare su una macchina target, dotata di una architettura simile all’host, allora la toolchain è detta nativa. Se macchina host e macchina target hanno differenti architetture, allora la toolchain è detta cross platform. La cross toolchain può essere costruita a mano o si possono utilizzare dei tool (crosstoll, buildroot, crossdev) che cercano di automatizzare tutto il processo di costruzione della toolchain. Il principale problema che si può incontrare impiegando questi tool è che potrebbero non funzionare e fallire la costruzione della cross toolchain. Questa eventualità può accadere soprattutto se si cerca di utilizzare una versione del compilatore gcc, dei binutils, delle librerie C o del kernel che ancora non è supportata dal tool. In questi casi l’unica alternativa è quella di costruire a mano la toolchain. La costruzione della toolchain può rivelarsi un compito più o meno complicato a seconda dei pacchetti software che si utilizzeranno. CAPITOLO 3. CRIS AXIS ETRAX 100LX 66 Per quanto riguarda la CPU a nostra disposizione sulla FOX Board, è disponibile gratuitamente, sul sito della Axis, un Software Development Kit (SDK) ufficiale. Inoltre, sono fruibili una serie di ambienti di sviluppo alternativi completamente open source e scaricabili dal sito della Acme Systems realizzati da utenti appassionati. Per esempio: • phrozen sdk fatto da John Crispin. Questo SDK storico è stato usato dalla Acme Systems per costruire immagini di default del kernel. Quando Acme Systems ha sviluppato la prima versione della sua FOX Board LX, Axis Communications ha rilasciato il suo Axis SDK versione 2.01 basato sulle versioni del kernel 2.4.31 e 2.6.15. A partire da questa versione, John Crispin, un utente appassionato di FOX Board, ha rilasciato la sua Phrozen SDK basata su quella dell’Axis SDK, ma con un sacco di nuovi driver, utility, altri miglioramenti è un menuconfig più facile da usare. Successivamente Crispin non ha proseguito lo sviluppo di questo ambiente. Linux kernel 2.6.15 o 2.4.31. Librerie glibc o uclibc. • cris OS fatto da Claudio Mignani. Questo ambiente si basa sul porting di OpenWRT, originariamente realizzato da John Crispin e ora fortemente migliorato da Claudio Mignanti. I principali vantaggi di questo ambiente sono che l’intero file system è sia leggibile sia scrivibile, e l’installazione del pacchetto è molto facile grazie al repository software on-line in formato binario fatto da Mignani. Linux kernel 2.6.25.16. Librerie uclibc. • foXServe SDK realizzato da Davide Cantaluppi. Questo ambiente si basa sul phrozen sdk migliorato con web server Apache, PHP, WebDAV e SQLite. Linux kernel 2.6.15. Libreria glibc. FoXServe funziona solo sulla FOX Board LX832. Per quanto riguarda il nostro lavoro di tesi abbiamo scelto il Software Development Kit ufficiale della Axis [13]. Questo, è costituito da tre parti principali: il compilatore, il debugger e la distribuzione software (vedi Fig. 3.13). 3.3.2 Installazione e test del cross compilatore gcc-cris Il primo passo per poter utilizzare l’ambiente di sviluppo per applicazioni è quello di installare il cross compilatore. L’architettura CRIS, utilizzata dall’Axis ETRAX 100LX, è supportata dal GNU Compiler Collection (GCC) CAPITOLO 3. CRIS AXIS ETRAX 100LX 67 Figura 3.13: Schema a blocchi SDK distribuito dalla Free Software Foundation (FSF). L’assembler, il linker e altri strumenti CRIS sono inclusi come parte del sorgente GNU Binutils. Analogamente per la libreria Newlib. Vi sono due modi per installare il compilatore. Il primo, da noi adottato, è quello di scaricarare direttamente, dal sito della Axis, il pacchetto binario pre-compilato cris-dist-1.64-1.i386.deb e installarlo. Mentre la seconda soluzione, più complicata, consiste nel compilare prima il codice sorgente e poi installare i file binari ottenuti. Il compilatore viene installato di default nella directory: /usr/local/cris. E’ importante sottolineare il fatto che l’installazione presenta due compilatori, uno per crisv10 (relativo alla ETRAX 100LX) e uno per crisv32 (relativo alla ETRAX FX). Di conseguenza è necessario selezionare il compilatore giusto in fase di cross-compilazione. Per essere certi di utilizzare quello appropriato, lanciando il comando: /<path to compiler>/bin/gcc-cris --version dove <path to compiler> di default corrisponde a: /usr/local/cris. Si dovrebbe ottenere la schermata in figura 3.14, per quanto riguarda il compilatore crisv10. Figura 3.14: Versione gcc-cris Al fine di verificare il funzionamento del compilatore gcc-cris, abbiamo scritto una semplicissima applicazione in C, prima compilata ed eseguita sul CAPITOLO 3. CRIS AXIS ETRAX 100LX 68 sistema host e poi cross-compilata sul sistema host ed eseguita sul sistema target. In pratica, abbiamo scritto il codice sorgente (Algoritmo 3.1) in un file denominato hello.c salvato all’interno della directory v/hello. Algoritmo 3.1 Codice sorgente applicazione hello.c #i n c l u d e <s t d i o . h> i n t main ( i n t argc , c h a r ∗ argv [ ] ) { p r i n t f ( " H e l l o World ! \ n " ) ; return 0; } Poi abbiamo compilato l’algoritmo per il sistema host con il seguente comando: gcc -o hello_host hello.c Il quale ha prodotto, all’interno della stessa directory del file oggetto, il file binario, hello_host, chiaramente eseguibile sul sistema host. Mandando in esecuzione il file ottenuto, sarà possibile visualizzare sullo schermo la semplice stringa «Hello World!». Quanto detto finora non è niente più che la classica compilazione di un’applicazione in C. Successivamente abbiamo poi cross-compilato lo stesso file sorgente, hello.c per il sistema target. Il comando che ci permette di ottenere il file eseguibile sul sistema embedded è il seguente: gcc-cris -mlinux -o hello_target v/hello/hello.c Ciò è fattibile nel caso in cui si abbia precedentemente aggiunto il percorso del compilatore gcc-cris alla variabile d’ambiente PATH con il comando: export PATH=$PATH:/usr/local/cris/bin Dal momento che il file binario, hello_target, non può essere eseguito sul sistema host, come ci aspettavamo, lo abbiamo caricato sul sistema embedded per verificare che l’esecuzione produca gli stessi risultati. Ciò può essere ottenuto tramite FTP o SCP. Naturalmente, il sistema embedded deve supportare il metodo di trasferimento scelto. Inoltre, il file eseguibile può essere scritto solo su un file system scrivibile. Utilizzando il caricamento sul sistema embedded tramite FTP bisogna eseguire i seguenti comandi. Per prima cosa occorre connettersi alla board CAPITOLO 3. CRIS AXIS ETRAX 100LX 69 digitando il comando: ftp <ip address> che come abbiamo visto richiedererà l’username e la password. Successivamente bisogna assicurarsi di trasferire il file in modalità binaria e non in modalità ASCII. Per far questo basta lanciare il comando: ftp> binary L’eseguibile può essere posizionato sia nella partizione flash riscrivibile (/mnt/flash) sia nella partizione tmpfs (/tmp) nella RAM. Ovviamente, se si inserisce l’eseguibile nella RAM, esso non sarà disponibile al riavvio del sistema. Quindi, portiamoci nella directory /mnt/flash con il comando: ftp> cd /mnt/flash e carichiamo il file con: put <file name> Dal momento che il trasferimento di un file utilizzando FTP non rende eseguibile il file, è necessario impostare manualmente il bit execute. E’ possibile utilizzare il comando chmod dal client FTP: ftp> chmod 755 <file name> Infine ci disconnettiamo: ftp> quit Poichè la FOX Board possiede due porte USB 1.1, per il trasferimento del file eseguibile, hello_target, possiamo utilizzare anche una semplice chiavetta usb. Una volta caricato il file sulla piattaforma di destinazione, per verificare se funziona come previsto, ci connettiamo alla board tramite Telnet o ssh con PuTTY e, dopo aver effettuato il login, ci spostiamo dentro la directory /mnt/flash con il comando: cd /mnt/flash e lanciamo l’applicazione come visto per il sistema host: ./hello_target Ciò che otterremo sarà di nuovo la stringa «Hello World!». 3.3.3 Distribuzione software La distribuzione software è il sottoinsieme dell’ambiente di sviluppo utilizzato dalla Axis, che comprende tutto il codice sorgente e i file del sistema di compilazione, necessari a produrre un’immagine del firmware di Linux per l’Axis ETRAX. CAPITOLO 3. CRIS AXIS ETRAX 100LX 70 Il sistema di compilazione permette una configurazione semplice e flessibile delle opzioni hardware e delle applicazioni software. Inoltre, è disponibile un certo numero di configurazioni, predefinite per i sistemi più comunemente usati. Le caratteristiche del firmware di destinazione (Linux 2.6) sono: pieno supporto al MMU, libreria standard glibc o uClibc, librerie condivise, thread Linux e POSIX e varie applicazioni. Il kernel Linux include, inoltre, driver per le interfacce della ETRAX come Ethernet, porte seriali e parallele, general purpose I/O, USB e IDE. Un elenco completo delle librerie e delle applicazioni incluse nel Software Development Kit è reperibile all’indirizzo Internet http://developer.axis. com/wiki/doku.php?id=axis:sw-list. Il passo successivo, dopo aver installato il cross compilatore, per avere un SDK «completo» (senza Debugger) è quello di installare la distribuzione software (la versione più recente è la 2.20). Prima di far questo però bisogna verificare che tutti i requisiti richiesti dalla distribuzione siano soddisfatti. Elenco requisiti: • OS: qualsiasi distribuzione Linux abbastanza nuova dovrebbe funzionare; • interfaccia di rete Ethernet; • accesso root; • compilatore C gcc; • GNU make versione 3.80 o 3.81; • GNU wget; • CRISS cross-compilatore; • awk (o gawk); • bc; • byacc (o yacc se byacc è un link a esso); • lex o flex; • perl; • sed; • tar; CAPITOLO 3. CRIS AXIS ETRAX 100LX 71 • zlib (per esempio zlib1g e zlib1g-dev); • md5sum; • curses o ncurses (per esempio libncurses5 e libncurses5-dev); • bison; • which. La distribuzione software è composta da un pacchetto di installazione e da un insieme di pacchetti distribuiti. Può essere installata in due modi: installazione normale e installazione tarball. L’unica differenza tra le due procedure riguarda la modalità di accesso ai pacchetti distribuiti, in quanto entrambe richiedono il pacchetto di installazione. L’installazione normale necessita di una connessione Internet sul computer dove si installa la distribuzione software. Mentre, con l’installazione tarball si scaricano tutti i tarball supplementari (pacchetti .tar) che riguardano i pacchetti distribuiti per essere poi utilizzati su un sistema target. Noi abbiamo optato per l’installazione normale. In pratica, durante il processo di installazione vengono scaricati tutti i pacchetti software richiesti. Questo riduce la quntità di spazio occupato dalla distribuzione software installata, nonché la quantità di contenuto scaricato. Ovviamente, sul computer è necessaria una connessiona a Internet. Come prima cosa, bisogna scaricare il pacchetto di installazione devboardR2_20.tar.gz dal sito della Axis. Il pacchetto contiene, tra le altre cose, lo script di configurazione per l’installazione della distribuzione. Dopo aver scaricato il file è necessario estrarlo con il comando: tar xvfz devboard-R2_20.tar.gz L’estrazione crea una directory devboard-R2_20. A questo punto bisogna entrare nella directory appena creata ed eseguire lo script di configurazione. Questo scaricherà e istallerà gli strumenti e il codice sorgente necessario per configurare la distribuzione software. cd devboard-R2_20 ./configure Lo script di configurazione chiederà la tipologia del prodotto per il quale si vuole costruire il firmware. La selezione che si effettua determina i file di configurazione che verranno utilizzati. Nel nostro caso abbiamo selezionato la tipologia di prodotto «fox_416». In alternativa, è possibile impostare il CAPITOLO 3. CRIS AXIS ETRAX 100LX 72 tipo di prodotto settando la variabile DEV_BOARD_PRODUCT=<product name> prima di eseguire lo script di configurazione. Successivamente viene chiesto se si desidera personalizzare il firmware o mantenere la configurazione predefinita per la tipologia di prodotto selezionata. E’ consigliabile configurare la distribuzione del software, dal momento che la configurazione predefinita include solo un sottoinsieme di tutti i pacchetti disponibili. Immettendo y, la selezione dei pacchetti può essere configurata prima che questi vengano scaricati, risparmiando tempo e spazio sul disco. Questa domanda viene fatta solo la prima volta che si installa la distribuzione software, anche se questa può essere configurata successivamente. 3.3.4 Configurazione e compilazione del firmware Per selezionare quali pacchetti vogliamo includere o togliere nel firmware, una volta recati all’interno della directory devboard-r2_20, bisogna eseguire il comando: make menuconfig Ciò che otteniamo nella shell è un menù grafico suddiviso per categorie, in modo da rendere la selezione dei pacchetti abbastanza semplice e logica. Nel nostro caso, oltre alla configurazione di default, abbiamo abilitato il supporto dropbear ssh 3 e il supporto a utelnetd 4 . In alternativa a questo comando possono essere eseguiti make xconfig o make config. E’ importante salvare la configurazione prima di uscire dall’utility. Fatto ciò, bisogna eseguire lo script ./configure per installare i pacchetti richiesti dalla configurazione. Questi genererà il Makefile di primo livello che viene utilizzato per compilare l’immagine del firmware. Ora basta lanciare il comando make dopodiché occorre attendere (richiede abbastanza tempo) che il processo di creazione dell’immagine del firmware termini. 3 Secure SHell, è un protocollo di rete che permette di stabilire una sessione remota cifrata tramite interfaccie a riga di comando con un altro host di una rete informatica. E’ il protocollo che ha sostituito l’analogo ma insicuro Telnet. 4 è un protocollo di rete utilizzato su Internet. L’obiettivo del procollo Telnet è fornire un supporto per le comunicazioni sufficientemente generalizzato, bidirezionale e orientato ai byte (8 bit). E’ solitamente utilizzato per fornire all’utente sessioni di login remoto di tipo riga di comando tra host su Internet. CAPITOLO 3. CRIS AXIS ETRAX 100LX 73 Per quanto riguarda la versione 2.20 della distribuzione software, questa produrrà una immagine basata sulla versione 2.6.26 del kernel Linux. 3.3.5 Scrittura del firmware sulla board Poichè sulla piattaforma embedded a nostra disposizione vi era un kernel Linux 2.4, mentre con la distribuzione software 2.20 si ottiene un’immagine del kernel Linux 2.6.26, abbiamo aggiornato la board con questo firmware compilato precedentemente. Come si può notare dalle caratteristiche tecniche del sistema embedded, sulla board vi è una memoria FLASH in cui è possibile archiviare tutti i file di programma necessari per il corretto funzionamento del sistema stesso. In questa memoria permanente viene salvato il sistema operativo Linux e tutte le applicazioni e i dati di cui necessita la board. In pratica corrisponde al disco rigido di un PC. E’ importante ricordare che non c’è bisogno di ricompilare una nuova immagine del firmware per avere la FOX Board con le nostre applicazioni. E’ sufficiente, invece, nella maggior parte dei casi, trasferire semplicemente le nostre applicazioni tramite FTP o SSH direttamente nel file system della scheda in una zona riscrivibile della memoria flash (/mnt/flash). Uno dei metodi di reflashing della scheda, chiamato «local network flashing», si basa su un piccolo codice ROM all’interno del processore AXIS che viene eseguito all’accensione quando il jumper J8 è chiuso. Questo codice carica nella memoria cache interna i pacchetti che gli vengono inviati dal PC attraverso la LAN. L’Axis eseguirà progressivamente i frammenti di codice contenuti in questi pacchetti e di conseguenza inizierà a interagire con il PC per scaricare la procedura di programmazione scelta per la memoria flash. Tale metodo funziona solo su una rete locale e non supporta alcun routing IP. Inoltre richiede che il PC e la FOX Board siano sulla stessa rete locale (vedi 3.1.3). All’interno della directory principale SDK ( v/devboard-R2_01/ ) occorre digitare: . init_env uno script che viene creato quando si lancia il comando make. Lo scopo di questo file è quello di settare tutte le variabili d’ambiente necessarie alla di- CAPITOLO 3. CRIS AXIS ETRAX 100LX 74 stribuzione software. Come regola si dovrebbe sempre richiamare il seguente script prima di iniziare a lavorare con l’SDK in una nuova shell. Dopo init_env, bisogna lanciare il comando: boot_linux -F -i <image_filename> ... In seguito otterremo un messaggio come quello in Figura 3.15. Figura 3.15: Network flashing In questo momento si deve spegnere la FOX Board, chiudere il jumper J8 (Ethernet flashing) sulla scheda (vedi Fig. 3.16) Figura 3.16: Ethernet flashing e riaccenderla di nuovo (così facendo la FOX Board dovrebbe essere sulla stessa LAN del PC avendo lanciato il comando boot_linux). Se la procedura risulta corretta, vedremo il processo di trasferimento del file immagine nella memoria FLASH della scheda con messaggi come quelli in Figura 3.17. Durante la programmazione il LED rosso (DL1) della scheda rimane sempre acceso. Al termine della fase di programmazione della FOX Board essa si riavvia automaticamente. Bisogna tuttavia ricordarsi di rimuovere il jumper J8 altrimenti la scheda carica ancora i pacchetti dalla rete e non si avvia. CAPITOLO 3. CRIS AXIS ETRAX 100LX 75 Figura 3.17: Ethernet flashing Alcune opzioni utilizzabili con il comando boot_linux: • -d «dispositivo», interfaccia di reta da utilizzare, eth0 di default; • -f, salva l’intera immagine del firmware sulla memoria flash tranne che la partizione di recupero; • -F, salva l’intera immagine del firmware sulla flash. Inoltre questa operazione sovrascrive i parametri memorizzati nella partizione di recupero, come il numero di serie e l’indirizzo MAC; • -h, visualizza informazioni e opzioni aggiuntive non mostrate in questo elenco; • -i «image», il percorso e il nome dell’immagine da usare; • -i, visualizza i risultati del comando «etraxboot» invece di eseguirlo. Capitolo 4 Porting 4.1 Approccio al problema del porting In linea di massima, per effettuare il porting di Linux con Xenomai basta scaricare il file della patch Adeos pipeline relativo all’architettura di destinazione e applicarlo a un kernel Linux vanilla scaricabile da Internet, la cui versione sia compatibile con quella della patch. A volte è necessario effettuare qualche ulteriore «piccolo» adattamento. Dopodiché bisogna cross compilare opportunamente il kernel patchato e installarlo sul rispettivo sistema embedded. Quanto al sistema target utilizzato nella tesi, le cose si complicano notevolmente poiché l’architettura CRIS della CPU contenuta nella ETRAX 100LX non è supportata dalla patch Adeos, vale a dire che la patch della pipeline non esiste per la nostra architettura. Consapevoli di questo abbiamo optato per la seguente soluzione: abbiamo scaricato una patch Adeos relativa a un’architettura supportata, come la ARM cercando di modificarla e adattarla all’architettura Cris. Tenuto conto che la distribuzione software dell’ambiente di sviluppo da noi utilizzato e descritto nel capitolo precedente produce un’immagine del kernel Linux relativo alla versione 2.6.26, abbiamo scaricato dal sito http: //download.gna.org/adeos/patches/v2.6/arm/older/ la patch per ARM adeos-ipipe-2.6.26-arm-1.12-00.patch rilasciata il 13 Gennaio 2009. 76 CAPITOLO 4. PORTING 4.2 77 Adeos patch per architettura CRIS Da una prima analisi generale della patch Adeos per ARM «scaricata», abbiamo osservato che questa interessava, tra modifiche e file completamente nuovi, più o meno 145 file. Di conseguenza, al fine di rendere il lavoro più comprensibile, abbiamo diviso la patch in parti più piccole, ognuna riferita a una categoria di file. Inizialmente abbiamo isolato in una patch indipendente tutte le modifiche che andavano ad agire solamente sui file del kernel Linux 2.6.26. Questo sottoinsieme di modifiche della patch originale lo abbiamo chiamato: adeos-ipipe-2.6.26-generic-1.12.patch (per l’elenco completo dei file modificati vedi sezione 4.2), mentre la parte restante è stata rinominata: adeos-ipipe-2.6.26-cris-specific-1.0.patch, per distinguerla dalla patch completa originale . Abbiamo poi testato se le modifiche della patch generic venivano applicate regolarmente sui file del kernel 2.6.26 presente all’interno dell’ambiente di sviluppo fornito dalla Axis. A tal fine, attraverso la shell, ci siamo spostati all’interno della directory contenente i sorgenti del kernel (v/devboardr2_20/os/ ), utilizzando il terminale, e abbiamo applicato la patch con il comando «omonimo». Prima di applicare veramente le modifiche ai file conviene eseguire il comando patch con l’opzione --dry-run, il quale permette di «simulare» l’applicazione della patch in modo da rendersi conto se le modifiche saranno applicate o no e, in caso negativo, conoscere quali file creano problemi. La sequenza di comandi per fare quanto detto è: cd <linux_dir> patch -p1 --dry-run < v/adeos-ipipe-2.6.26-generic-1.12.patch Poiché nel nostro caso la simulazione è andata a buon fine, come del resto ci aspettavamo essendo la versione della patch Adeos e la versione del kernel compatibili, abbiamo applicato la patch ai file del kernel, rilanciando il comando patch senza l’opzione --dry-run dal comando precedente: patch -p1 < v/adeos-ipipe-2.6.26-generic-1.12.patch Abbiamo quindi racchiuso tutti i file della patch specific, le cui modifiche riguardavano istruzioni assembly1 , in una patch denominata adeos-ipipe1 o linguaggio assemblatore è, tra i linguaggi di programmazione, quello più vicino al linguaggio macchina vero e proprio (pur essendo differente rispetto a quest’ultimo). Erroneamente viene spesso chiamato «assembler» anche se quest’ultimo identifica il programma CAPITOLO 4. PORTING 78 2.6.26-cris-asm-1.0.patch. L’elenco dei file modificati da quest’ultima patch è: • /arch/arm/boot/compressed/head.S • /arch/arm/kernel/entry-armv.S • /arch/arm/kernel/entry-common.S • /arch/arm/kernel/entry-header.S • /arch/arm/kernel/ipipe-mcount.S • /arch/arm/mm/proc-arm920.S • /arch/arm/mm/proc-arm926.S • /arch/arm/mm/proc-xscale.S • /arch/arm/vfp/vfphw.S • /include/asm-arm/arch-integrator/entry-macro.S Quest’ultima patch, chiamata per intenderci «asm», non è stata adattata all’architettura CRIS in quanto il tempo a nostra disposizione è risultato insufficiente al fine di acquisire l’esperienza necessaria per interpretare e modificare il codice assemly in essa contenuto. Abbiamo infine «scremato» ulteriormente la patch adeos-ipipe-2.6.26cris-specific-1.0.patch, poiché vi erano alcune modifiche su file riguardanti particolari architetture della famiglia ARM, per esempio mach-at91, machimx, mach-integrator, mach-pxa, mach-sa1100 che non influenzavano l’architettura CRIS. La parte della patch che compredeva queste modifiche è stata archiviata, per un eventuale consultazione futura, anche se poteva benissimo essere cancellata. L’elenco dei file modificati è riportato nella sezione 4.2. Ricapitolando, la patch Adeos scaricata inizialmente per architettura ARM, adeos-ipipe-2.6.26-arm-1.12-00.patch, è stata scomposta in tre parti fondamentali più una quarta patch non considerata. Le tre patch principali sono: 1. adeos-ipipe-2.6.26-generic-1.12.patch che converte il linguaggio assembly in linguaggio macchina. CAPITOLO 4. PORTING 79 2. adeos-ipipe-2.6.26-cris-asm-1.0.patch 3. adeos-ipipe-2.6.26-cris-specific-1.0.patch Da un’ultima analisi della patch specific, abbiamo visto che c’erano dei file completamente nuovi, come per esempio /arch/arm/kernel/ipipe.c, il quale implementa la pipeline della patch Adeos su cui si basa tutto il sistema Xenomai, utilizzando le funzioni definite nel file /include/asm-arm/ipipe.c. Il file /include/asm-arm/irqflags.h sostituisce la funzione raw_local_irq_save(x) che salva lo stato corrente dell’interrupt attivo e disabilita le richieste di altri interrupt, con local_irq_save_hw_notrace(x). Le funzioni raw_local_irq_enable() e raw_local_irq_disable() che attivano e disattivano le richieste di interrupt, vengono sostituite rispettivamente con local_irq_enable_hw_notrace() e local_irq_disable_hw_notrace(), la funzione raw_local_save_flags(x) con local_save_flags_hw(x). Tutte le funzioni sovrascritte sono anche state definite. Una serie di modifiche va a influenzare file di gestione della memoria, come per esempio /arch/arm/mm/fault.c, /arch/arm/mm/flush.c, /include/asmarm/cacheflush.h e /include/asm-arm/memory.h. Infine c’erano ancora alcuni file specifici di ARM non riguardanti la nostra architettura CRIS e sono stati dunque tralasciati, tipo: /arch/arm/mm/alignment.c, /arch/arm/mm/context.c, /arch/arm/mm/copypage-v4mc.c, /arch/arm/mm/copypagexscale.c e b/arch/arm/mm/fault-armv.c. File modificati dalla patch generic • /Makefile • /include/asm-generic/cmpxchg-local.h • /include/linux/hardirq.h • /include/linux/ipipe.h • /include/linux/ipipe_base.h • /include/linux/ipipe_compat.h • /include/linux/ipipe_percpu.h • /include/linux/ipipe_tickdev.h CAPITOLO 4. PORTING • /include/linux/ipipe_trace.h • /include/linux/irq.h • /include/linux/kernel.h • /include/linux/linkage.h • /include/linux/mm.h • /include/linux/preempt.h • /include/linux/sched.h • /include/linux/spinlock.h • /include/linux/spinlock_types.h • /init/Kconfig • /init/main.c • /kernel/Makefile • /kernel/exit.c • /kernel/fork.c • /kernel/ipipe/Kconfig • /kernel/ipipe/Kconfig.debug • /kernel/ipipe/Makefile • /kernel/ipipe/core.c • /kernel/ipipe/tracer.c • /kernel/irq/chip.c • /kernel/irq/handle.c • /kernel/lockdep.c • /kernel/panic.c • /kernel/power/disk.c • /kernel/printk.c • /kernel/sched.c • /kernel/signal.c 80 CAPITOLO 4. PORTING • /kernel/spinlock.c • /kernel/time/tick-common.c • /kernel/time/tick-sched.c • /kernel/timer.c • /incluce/linux/Kconfig.debug • /incluce/linux/bust_spinlocks.c • /incluce/linux/ioremap.c • /incluce/linux/smp_processor_id.c • /incluce/linux/spinlock_debug.c • /mm/memory.c • /mm/mlock.c • /mm/vmalloc.c File cancellati dalla patch specific • /arch/arm/mach-at91/Kconfig • /arch/arm/mach-at91/Makefile • /arch/arm/mach-at91/at91_ipipe_time.c • /arch/arm/mach-at91/at91rm9200.c • /arch/arm/mach-at91/at91sam9260.c • /arch/arm/mach-at91/at91sam9261.c • /arch/arm/mach-at91/at91sam9263.c • /arch/arm/mach-at91/at91sam9rl.c • /arch/arm/mach-at91/gpio.c • /arch/arm/mach-at91/irq.c • /arch/arm/mach-imx/irq.c • /arch/arm/mach-imx/time.c 81 CAPITOLO 4. PORTING • /arch/arm/mach-integrator/core.c • /arch/arm/mach-integrator/integrator_cp.c • /arch/arm/mach-ixp4xx/common.c • /arch/arm/mach-pxa/gpio.c • /arch/arm/mach-pxa/irq.c • /arch/arm/mach-pxa/leds-idp.c • /arch/arm/mach-pxa/leds-lubbock.c • /arch/arm/mach-pxa/leds-mainstone.c • /arch/arm/mach-pxa/leds-trizeps4.c • /arch/arm/mach-pxa/time.c • /arch/arm/mach-s3c2440/irq.c • /arch/arm/mach-sa1100/irq.c • /arch/arm/mach-sa1100/leds-assabet.c • /arch/arm/mach-sa1100/leds-badge4.c • /arch/arm/mach-sa1100/leds-cerf.c • /arch/arm/mach-sa1100/leds-hackkit.c • /arch/arm/mach-sa1100/leds-lart.c • /arch/arm/mach-sa1100/leds-simpad.c • /arch/arm/mach-sa1100/time.c 82 Capitolo 5 Conclusioni Il lavoro svolto in questa tesi ha messo in evidenza diversi aspetti importanti riguardo il funzionamento del sistema «ibrido» Xenomai, il quale non costituisce un sistema operativo real-time vero e proprio, ma cerca di combinare le caratteristiche funzionali di tipo real-time con i vantaggi, in termini di costi, flessibilità, sviluppo e supporto, offerti dal sistema operativo Linux, anche nei sistemi embedded. Dai test svolti sul sistema Xenomai abbiamo potuto verificare come si comporta lo scheduler RT del sistema per quanto riguarda la gestione dei processi e i vari passaggi tra il dominio RT e il dominio Linux. Ciò costituisce una nozione indispensabile per lo sviluppo di eventuali applicazioni, i cui processi debbono essere eseguiti con stringenti vincoli temporali. In merito al porting della patch Adeos, «la quale costituisce il nucleo fondamentale del sistema Xenomai», su architettura CRIS, abbiamo realizzato tre patch. La prima chiamata generic, dato che le modifiche agivano solo sui file del kernel Linux 2.6.26. La seconda definita asm poichè interessava solo i file assembly e l’ultima denominata specific, in quanto apportava le modifiche necessarie della Adeos sui file specifici dell’architettura. La realizzazione di queste tre patch si è basata sullo studio della patch Adeos per architettura ARM, poichè la nostra non è supportata da Xenomai. Come già accennato precedentemente non siamo riusciti a terminare il porting del sistema Xenomai sulla FOX Board ETRAX 100LX, in quanto l’implementazione della parte in assembly della patch Adeos richiede molta esperienza e una cospicua quantità di tempo. Il lavoro svolto costituisce comunque un buon punto di partenza per 83 CAPITOLO 5. CONCLUSIONI 84 chiunque si affaccia al mondo dei sistemi real-time e più in particolare al sistema ibrido Xenomai. Ringraziamenti Giunto al termine di questo percorso universitario, desidero ringraziare ed esprimere la mia riconoscenza nei confronti di tutte le persone che, direttamente o indirettamente, mi sono state vicine e mi hanno incoraggiato nei miei studi e nella stesura di questa tesi. Il primo pensiero va, ovviamente, a tutta la mia famiglia: i miei genitori, mio fratello Alberto e la compagna Michela, zia Maria e zio Pino, mio cugino Giorgio e i miei nonni, in particolare nonno Claudio. Senza il loro aiuto morale ed economico non avrei mai raggiunto questa meta. Ringrazio vivamente il Prof. Dragoni che ha reso possibile questo lavoro di tesi e Andrea Claudi che mi ha seguito e consigliato durante il tirocinio. Inoltre desidero ringraziare gli amici che ho conosciuto e con cui ho trascorso quest’ultimo periodo universitario, Andrea, Francesco, Nicola e Marco e tutti gli altri all’interno del DIIGA. Voglio ringraziare il Gotha (F. Bertin e G. Quaresima) per avermi aiutato nella revisione sintattica e grammaticale della tesi. Infine ringrazio tutti gli amici con cui ho trascorso parte della mia vita, che hanno reso più sereni e spensierati i momenti di sconforto. Bibliografia [1] Linux Manual schedsetscheduler Page, [2] Albero rosso-nero, http://it.wikipedia.org/wiki/RB-Albero [3] Embedded Market Study 2010, http://www.techonline.com/ learning/webinar/224000228 [4] Barbanov M., Yodaiken V., “RealTime Linux”, 1996 [5] M. Galassi, J. Davies, J. Theiler, B. Gough, G. Jungman, M. Booth, F. Rossi, “GNU Scientific Library Reference Manual”, Network Theory Ltd, 2nd edition, 2003, disponibile online: http: //www.gnu.org/software/gsl/manual [6] Bovet D. P., Cesati M., «Understanding the Linux Kernel», http://linux.die.net/man/2/ O’Reilly 3rd Edition [7] Corbet J., Rubini A., Kroah-Hartman G., «Linux Device Drivers», O’Reilly 3rd Edition [8] Xenomai Webpage, http://www.xenomai.org [9] A. Sambi, RTAI. disponibile su: http://www-lia.deis.unibo.it/ Courses/SistRT/1b-RTAI.pdf [10] Linux Kernel Archives, http://www.kernel.org/pub/linux/ kernel [11] Kevin Dankwardt, 2002 «Real-Time and Linux, part 1», Embedded Linux Journal [12] FOX Board LX832, http://foxlx.acmesystems.it/?id=14 [13] Documentazione SDK Axis Communications, http://www. axis.com/products/dev_sdk/index.htm 86 [14] AXIS ETRAX 100LX «Designer’s Reference», http://www. axis.com/products/dev_sdk/index.htm [15] AXIS ETRAX 100LX «Programmer’s Manual», http://www. axis.com/products/dev_sdk/index.htm [16] Giuseppe Lipari, «Real Time Linux and The Xenomai system» [17] Martin P. Andersson, Jens-Henrik Lindskov, «Real-Time Linux in an Embedded Environment», A Port and Evaluation of RTAI on the CRIS Architecture [18] Lucconi F., «Porting su architettura ARM Marvell 88F6281 ed analisi comparativa delle patch real-time RTAI e Xenomai per il kernel Linux» [19] Baldini A., «Progettazione, sviluppo e time analysis di una applicazione real-time per image-processing in ambiente Xenomai»