Il kernel di os16 (ma così vale anche per gli applicativi) viene compilato senza un'intestazione predefinita, pertanto questa viene costruita nel primo file: crt0.s
. Questo file ha lo scopo di eseguire la funzione main() del kernel, in cui si sintetizza il funzionamento dello stesso.
Il file del kernel prodotto dagli strumenti di sviluppo è strutturato come sintetizza il disegno seguente:
La prima parte del file è utilizzata dal codice (text), quindi ci può essere un piccolissimo spazio inutilizzato, seguito dalla porzione che riguarda i dati, tenendo conto che nel file ci sono solo i dati inizializzati, mentre gli altri non hanno bisogno di essere rappresentati, ma in memoria occupano comunque il loro spazio.
Il kernel è organizzato per tenere separate l'area delle istruzioni da quella dei dati, pertanto il compilatore (precisamente il «collegatore», ovvero il linker) offre il simbolo __segoff, con il quale si conosce la distanza del segmento dei dati dall'inizio del file. Il valore di questo scostamento è espresso in «paragrafi», ovvero in multipli di 16; in pratica si tratta dello scostamento da utilizzare in un registro di segmento. Dal momento che lo scostamento effettivo è costituito dalla dimensione dell'area del codice, approssimata per eccesso ai 16 byte successivi, tra la fine dell'area codice e l'inizio di quella dei dati c'è quel piccolo spazio vuoto a cui già si è fatto riferimento.
Il kernel viene caricato in memoria, con l'ausilio di Bootblocks, all'indirizzo 1000016. Da lì il kernel si mette in funzione e, prima si copia all'indirizzo 3000016, quindi riprende a funzionare dal nuovo indirizzo, poi si copia mettendo i dati a partire dall'indirizzo 0050016 (dopo la tabella IVT e dopo l'area BDA) e il codice a partire dall'indirizzo 1050016. Alla fine, riprende a funzionare dall'indirizzo 1050016. La pila dei dati (stack) viene attivata solo quando il kernel ha trovato la sua collocazione definitiva.
Listato i188.7.2.
Dopo il preambolo in cui si dichiarano i simboli esterni e quelli interni da rendere pubblici, con l'istruzione entry startup si dichiara all'assemblatore che il punto di partenza è costituito dal simbolo startup, ma in ogni caso questo deve essere all'inizio del codice, mancando un'intestazione precostituita. In pratica, la primissima cosa che si ottiene nel file eseguibile finale è un'istruzione di salto a una posizione più avanzata del codice, dove si colloca il simbolo startup_code, e nello spazio intermedio (tra quell'istruzione di salto e il codice che si trova a partire da startup_code) si collocano le impronte di riconoscimento, oltre ai dati sulla dislocazione dell'eseguibile in memoria.
|
Tra la prima istruzione di salto e le impronte di riconoscimento, introdotte dal simbolo magic, c'è uno spazio vuoto (nullo), calcolato automaticamente in modo da garantire che la prima impronta inizi all'indirizzo relativo 000416. Di seguito vengono gli altri dati.
|
A partire da startup_code viene analizzato il valore effettivo del registro CS. Se questo è pari a 100016, significa che il kernel si trova in memoria a partire dall'indirizzo efficace 1000016, ma in tal caso si salta a una procedura che copia il kernel in un'altra posizione di memoria (3000016); se invece il valore di CS viene riconosciuto pari a quello della destinazione della prima copia, si passa a un'altra procedura che scompone l'area dati e l'area codice (testo) del kernel, in modo da collocare l'area dati a partire da 0050016 e l'area codice a partire da 1050016. Quando si riconosce che il valore di CS è quello finale, si salta al simbolo main_code e da lì inizia il lavoro vero e proprio.
|
Non si prevede che il kernel possa trovarsi in memoria in una collocazione differente da quelle stabilite nelle varie fasi di avvio, pertanto, in caso contrario, si crea semplicemente un circolo vizioso senza uscita.
Dal simbolo main_code inizia finalmente il lavoro e si procede con l'allineamento dei registri dei segmenti dei dati, in modo che siano tutti corrispondenti al valore previsto: 005016 (il segmento in cui inizia l'area dati, secondo la collocazione prevista). Viene poi posizionato il valore del registro SP a zero, in modo che al primo inserimento questo punti esattamente all'indirizzo più grande che si possa raggiungere nel segmento dati (FFFE16, considerato che gli inserimenti nella pila sono a 16 bit).
|
Appena la pila diventa operativa, si inizializza anche il registro FLAGS, verificando di disabilitare inizialmente le interruzioni.
|
A questo punto, si chiama la funzione main(), fornendo come argomenti tre valori a zero.
|
Nel caso la funzione dovesse terminare e restituire il controllo, si passerebbe al codice successivo al simbolo halt, con cui si crea un ciclo senza uscita, corrispondente alla conclusione del funzionamento del kernel.
|
Utilizzando il compilatore Bcc per compilare ciò che descrive la funzione main(), viene richiesta la presenza della funzione __mkargv() (il simbolo ___mkargv), che in questo caso può limitarsi a non fare alcunché.
|
Listato u0.7 e successivi.
Tutto il lavoro del kernel di os16 si sintetizza nella funzione main(), contenuta nel file kernel/main/main.c
. Per poter dare un significato a ciò che vi appare al suo interno, occorre conoscere tutto il resto del codice, ma inizialmente è utile avere un'idea di ciò che succede, se poi si vuole compilare ed eseguire il sistema operativo.
La funzione main() viene dichiarata secondo la forma tradizionale di un programma per sistemi POSIX, ma gli argomenti che riceve dalla chiamata contenuta nel file kernel/main/crt0.s
sono nulli, perché nessuna informazione gli viene passata effettivamente.
|
Dopo la dichiarazione delle variabili si inizializza la gestione del video della console con la funzione tty_init(), si mostra un messaggio iniziale, quindi si passa alla predisposizione di ciò che serve, prima di poter avviare dei processi. In particolare va osservata la funzione heap_clear(), la quale inizializza con il codice FFFF16 lo spazio di memoria libero, tra la fine delle variabili «statiche» e il livello che ha raggiunto in quel momento la pila dei dati. Successivamente, avendo marcato in questo modo quello spazio, diventa possibile riconoscere empiricamente quanto spazio di quella porzione di memoria avrebbe potuto essere utilizzato, senza essere sovrascritto dalla pila dei dati. Il messaggio iniziale contiene la data di compilazione e la memoria libera (la macro-variabile BUILD_DATE viene definita dallo script makeit, usato per la compilazione, creando il file kernel/main/build.h
che viene poi incluso dal file kernel/main/main.c
).
L'attivazione della gestione dei processi (e delle interruzioni) con la funzione proc_init(), comporta anche l'innesto del file system principale (chiamando da lì la funzione sb_mount()).
|
A questo punto il kernel ha concluso le sue attività preliminari e, per motivi diagnostici, mostra un menù, quindi inizia un ciclo in cui ogni volta esegue una chiamata di sistema nulla e poi legge un carattere dalla tastiera: se risulta premuto un tasto previsto, fa quanto richiesto e riprende il ciclo. La chiamata di sistema nulla serve a far sì che lo schedulatore ceda il controllo a un altro processo, ammesso che questo esista, consentendo l'avvio di processi ancor prima di avere messo in funzione quel processo che deve svolgere il ruolo di init.
In generale le chiamate di sistema sono fatte per essere usate solo dalle applicazioni; tuttavia, in pochi casi speciali il kernel le deve utilizzare come se fosse proprio un'applicazione. Qui si rende necessario l'uso della chiamata nulla, perché quando è in funzione il codice del kernel non ci possono essere interruzioni esterne e quindi nessun altro processo verrebbe messo in condizione di funzionare. |
Le funzioni principali disponibili in questa modalità diagnostica sono riassunte nella tabella successiva:
|
Premendo [x], il ciclo termina e il kernel avvia /bin/init
. Quindi si mette in un altro ciclo, dove si limita a passare ogni volta il controllo allo schedulatore, attraverso la chiamata di sistema nulla.
|
Figura u171.15. Aspetto di os16 in funzione, con il menù in evidenza. os16 build 20YY.MM.DD HH:MM:SS ram 639 Kibyte .-------------------------------------------------------------. | [h] show this menu | | [p] process status and memory map | | [1]..[9] kill process 1 to 9 | | [A]..[F] kill process 10 to 15 | | [l] send SIGCHLD to process 1 | | [a]..[c] run programs `/bin/aaa' to `/bin/ccc' in parallel | | [f] system file status | | [n], [N] list of active inodes | | [m], [M] mount/umount `/dev/dsk1' at `/usr/' | | [x] exit interaction with kernel and start `/bin/init' | | [q] quit kernel | `-------------------------------------------------------------' |
Figura u171.16. Aspetto di os16 in funzione mentre visualizza anche la tabella dei processi avviati (tasto [p]). http://www.youtube.com/watch?v=0gbgNpRrXBU ababaaababaaababaabbaaababaaababap pp p pg id id rp tty uid euid suid usage s iaddr isiz daddr dsiz sp name 0 0 0 0000 0 0 0 00.35 R 10500 eb7c 00500 0000 ffc8 os16 kernel 0 1 0 0000 0 0 0 00.33 r 2f100 0600 2f700 aa00 a8e8 /bin/ccc 0 2 0 0000 0 0 0 00.01 r 1f100 0600 84300 aa00 a8e8 /bin/ccc 2 3 0 0000 10 10 10 00.01 r 1f100 0600 44b00 aa00 a8e8 /bin/ccc 0 4 0 0000 0 0 0 00.17 r 21600 0600 3a100 aa00 a8e8 /bin/ccc 4 5 0 0000 10 10 10 00.02 r 21c00 2400 6f100 a900 a86c /bin/aaa 4 6 0 0000 11 11 11 00.02 s 24000 2500 59d00 a900 a8b6 /bin/bbb 0 7 0 0000 0 0 0 00.13 r 26500 0600 64600 aa00 a8e8 /bin/ccc 7 8 0 0000 10 10 10 00.02 r 26b00 2400 8ee00 a900 981e /bin/aaa 7 9 0 0000 11 11 11 00.02 s 2bf00 2500 79a00 a900 a8b6 /bin/bbb CS=1050 DS=0050 SS=0050 ES=0050 BP=ffe4 SP=ffe4 heap_min=878c etext=eb7c edata=1 b3c ebss=4c34 ksp=ffc8 clock=0000084b, time elapsed=00:01:57 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffe00000003ffffffffffffffffffffffffff fffe000000000001fffffffff0007fffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800 0000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffbfffffffffffffffffffffffffffffffffffffffffe0000000000000000000000000f abbaaaaababaaababaaabbaabbaabababbaabbbbbbbbbbbbbb |
Figura u171.17. Aspetto di os16 in funzione con il menù in evidenza, dopo aver premuto il tasto [x] per avviare init. http://www.youtube.com/watch?v=epql4EhgWPU os16 build 20YY.MM.DD HH:MM:SS ram 639 Kibyte .------------------------------------------------------------------. | [h] show this menu | | [p] process status and memory map | | [1]..[9] kill process 1 to 9 | | [A]..[F] kill process 10 to 15 | | [l] send SIGCHLD to process 1 | | [a]..[c] run programs `/bin/aaa' to `/bin/ccc' in parallel | | [f] system file status | | [n], [N] list of active inodes | | [m], [M] mount/umount `/dev/dsk1' at `/usr/' | | [x] exit interaction with kernel and start `/bin/init' | | [q] quit kernel | `------------------------------------------------------------------' init os16: a basic os. [Ctrl q], [Ctrl r], [Ctrl s], [Ctrl t] to change console. This is terminal /dev/console0 Log in as "root" or "user" with password "ciao" :-) login: |
«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net