Linux 0.01!

Questo è un articolo decisamente più tecnico di quelli a cui vi ho abituato, specie in questa categoria, tuttavia può essere interessante anche per i non addetti ai lavori più curiosi.

Si parla delle origini del kernel Linux, ma non come semplice e piacevole aneddoto: quello potete leggerlo pressoché su ogni articolo su Linux. Ho trovato da qualche parte su Internet i sorgenti del primissimo rilascio di Linux, la versione 0.01 e ho pensato che potrebbe essere interessante dargli un’occhiata e commentare i principi che hanno portato alla nascita del pinguino più famoso del mondo!
Visto che la licenza lo consente, anche se ancora non era la nostra amata GNU GPL, ho pubblicato l’archivio originale col kernel. Mancano i file a corredo, in primis la shell bash compilata, ma tanto difficilmente qualcuno si metterà a compilare i sorgenti così come sono! Sono invece intatte le note di rilascio originali che vi traduco in parte per la nostra piccola analisi!
Clicca qui per scaricare il file

Ecco, per l’appunto, la traduzione di alcuni punti salienti delle note di rilascio. Uso una sezione codice, così il testo si potrà scorrere senza creare un messaggio lunghissimo!
Perdonatemi se la traduzione non sarà perfetta (correzioni sono ben accette), ma il significato comunque sarà intatto.

              1. Breve introduzioneQuesto è un kernel minix-like gratuito per macchine AT basate su i386(+). È incluso il sorgente completo e questo sorgente è stato usato per produrre un kernel funzionante su due macchine differenti. Al momento non ci sono binari del kernel per una visualizzazione pubblica, dato che devono essere ricompilati per macchine differenti. Devi compilarli con gcc (io uso l’1.40, non so se l’1.37.1 gestirà tutte le direttive __asm__), dopo aver cambiato i file di configurazione rilevanti.Come il numero di versione (0.01) suggerisce, non è un progetto maturo.
Attualmente è supportato solo un sottoinsieme dell’hardware AT (hard disk, schermo, tastiera e linee seriali) ed alcune delle chiamate di sistema non sono ancora state pienamente implementate (in particolare mount/umount non sono ancora state implementate). Vedi i commenti o i readme nel codice.

Questa versione ha principalmente scopi di lettura – per esempio se sei interessato a come il sistema appare al momento.  Verrà compilato e produrrà un kernel funzionante, ma anche se potrò aiutare come potrò a farlo funzionare sulla tua tua macchina (scrivimi via mail), non è propriamente supportato. I cambiamenti sono frequenti e la prima versione “di produzione” forse differirà parecchio da questo rilascio pre-alpha.

Hardware richiesto per eseguire linux:
– AT 386
– Schermo VGA/EGA
– Controller per hard disk tipo AT (IDE va benissimo)
– Tastiera finlandese (oh, puoi usare una tastiera americana, ma non senza una certa pratica 🙂

La tastiera finlandese è fortemente cablata e dato che non ne ho una americana non posso cambiarla senza grossi problemi. Vedi kernel/keyboard.s per i dettagli. Se qualcuno fosse disposto a fare un port anche parziale, gli sarei grato. Non dovrebbe essere troppo difficile, dato che è guidato da tabelle (è assembler, comunque, quindi…)

Sebbene linux sia un kernel completo e non usi codice di minix o altri sorgenti, quasi nessuna delle routine di supporto è stata ancora codificata. Quindi al momento ti serve minix per avviare il sistema. Dovrebbe essere possibile usare il disco dimostrativo gratuito di minix per creare un filesystem e far girare linux senza avere minix, ma non so…

[…]

3. Breve panoramica tecnica del kernel

Il kernel linux è stato fatto sotto minix e la mia idea originale era dargli compatibilità binaria con minix. Quest’idea è stata abbandonata, dato che le differenze sono cresciute, ma il sistema ancora somiglia molto a minix. Alcuni dei punti chiave sono:

– Uso efficiente delle possibilità offerte dal chip 386.
Minix è stato scritto su un 8088 e successivamente portato su altre macchine – linux si avvantaggia totalmente del 386 (che una buona cosa se hai un 386, ma rende il porting molto difficile)

– Nessun passaggio di messaggi, questo è un approccio più tradizionale a unix. Le chiamate di sistema solo solo questo: chiamate. Questo può o no essere più veloce, ma significa che possiamo evitare molti dei problemi coi messaggi (code di messaggi, ecc). Certamente, perderemo anche le belle caratteristiche :-b.

– Filesystem multithread – una differenza diretta del non utilizzare messaggi. Questo rende il filesystem un po’ (molto) più complicato, ma più carino. Accoppiato con uno scheduler migliore, significa che puoi effettivamente eseguire processi in modo concorrente senza le perdite di performance indotte da minix.

– Cambio di task minimo. Anche questa è una conseguenza del non usare messaggi. Cambiamo task solo quando vogliamo farlo, diversamente da minix che cambia task qualsiasi cosa tu faccia. Questo significa che possiamo implementare più facilmente implementare il supporto a 387 (infatti questo è già in buona parte implementato).
[il 387 era un coprocessore progettato per lavorare in coppia col processore 386, ndt]

– Gli interrupt non sono nascosti. Molte persone (fra le quali Tanenbaum) pensano che gli interrupt siano brutti e dovrebbero essere nascosti. Non secondo me. In seguito a ragioni pratiche, gli interrupt devono essere gestiti da codice macchina, che è un peccato, ma sono una parte del codice come tutte le altre. Specialmente i driver sono per lo più routine degli interrupt – vedi kernel/hd.c, ecc.

– Non c’è distinzione fra kernel, filesystem e gestore della memoria, sono tutti linkati all’interno dello stesso codice. Questo ha lati positivi e negativi. Il codice non è modulare come quello di minix, ma d’altra parte alcune cose sono più semplici. Le differenti parti del kernel sono sotto differenti sottodirectory nell’albero dei sorgenti, ma quando vengono eseguite tutto avviene nello stesso spazio di codice e dati.

Le linee guida nell’implementazione di linux erano: renderlo presto funzionante. Volevo che il kernel fosse semplice, anche se potente abbastanza per eseguire molto del software unix. Riguardo al filesystem non ho potuto fare molto – doveva essere compatibile con minix per ragioni pratiche e il filesystem di minix era semplice abbastanza così com’era. Il kernel ed il gestore di memoria potevano essere semplificati, comunque:

– Una sola struttura dati per i task. I “veri” unix hanno le informazioni sui task in vari posti, io volevo tutto in un solo posto.

– Un algoritmo di gestione della memoria molto semplice che usasse sia la paginazione che la segmentazione offerte dal 386. Attualmente il gestore della memoria occupa solo due files: memory.c e page.s, solo un paio di centinaia di linee di codice.

Queste decisioni sembrano aver funzionato bene. I bug sono stati facili da individuare e le cose funzionano.

[…]

Ciò che è chiaro sin dall’inizio è che a Linus interessava esplorare le possibilità del suo nuovo processore 80386 e di sperimentarle scrivendo il suo kernel. Traspare anche una certa insoddisfazione verso MINIX, che allora era alla sua prima versione e aveva molte limitazioni imposte dalla retrocompatibilità con gli intel 8088, ma anche dall’esigenza di mantenersi semplice per i suoi utilizzi didattici. Inoltre possiamo notare come le idee di Torvalds differiscano da quelle di Tanenbaum (professore e principale autore di MINIX). Il secondo è un vivo sostenitore dell’approccio a microkernel per la scrittura dei sistemi operativi, nonché della stratificazione del software, mentre Linus segue l’approccio classico del kernel monolitico. Con un microkernel i servizi del sistema operativo vengono offerti da processi separati che comunicano fra loro attraverso messaggi, mentre in un kernel monolitico sono forniti da una serie di procedure compilate tutte insieme, senza una vera separazione, così che non c’è bisogno di scambio di messaggi.
Ciascuno dei due approcci ha vantaggi e svantaggi e ha generato vari dibattiti, fra cui quello più noto fra gli stessi Tanenbaum e Torvalds. Purtroppo questo dibatitto, che di per sé era interessante è iniziato col piede sbagliato, dal momento che Tanenbaum, persona molto competente e dotata di un certo senso dell’umorismo, definì Linux “obsoleto” in partenza, proprio per l’approccio più classico scelto da Linux. Vedi qui.

I principi che hanno portato alla nascita di Linux sono quindi, in sostanza, la creazione di un kernel che sfruttasse a pieno il processore 386 e che funzionasse subito!
Il rapido raggiungimento di questi obiettivi ha decretato il successo di Linux: i PC IBM, successori e cloni erano (e sono tutt’ora) la scelta ideale per chi voleva un computer in casa senza spendere una fortuna, ma BSD e Hurd sono nati su macchine diverse, inoltre il primo aveva problemi legali da affrontare ed il secondo era enormemente instabile. Linux era costruito intorno al 386 e non intorno all’8088 come MINIX o l’MS-DOS, era un kernel Unix-like, era libero e funzionava! Un hobbista non poteva chiedere di meglio.

Diamo ora uno sguardo al codice, senza addentrarci troppo nei dettagli.
Aprendo qualche file a caso nella directory kernel (nome fuorviante, dato che in realtà tutto fa parte del kernel. Qui ci sono le parti più centrali, come la gestione degli interrupt e lo scheduler) ci accorgiamo che il codice C è misto a codice assembly. Per chi non fosse pratico, il linguaggio assembly è quello più legato al linguaggio macchina, quindi diverso da un tipo di processore all’altro. Normalmente il codice dipendente dalla macchina viene messo in file separati, in modo da poterlo sostituire facilmente se si ricompila il sistema per un’architettura diversa (un esempio tipico dei giorni nostri è il passaggio dai 32 ai 64 bit delle CPU Intel), mentre in questo caso è tutto insieme, il che elimina l’overhead delle chiamate alle routine separate, ma rende il codice meno portabile, altro aspetto criticato da Tanembaum e comunque eliminato in seguito.
Un altro evidente esempio sia del legame stretto con l’architettura Intel 386 che della natura monolitica di Linux è nel file kernel/hd.c: questo file è di fatto un driver per dischi IDE, ma vi è anche parte della gestione degli interrupt del disco stesso e qualche riferimento al filesystem. Normalmente la gestione dell’I/O avviene in più strati per supportare diversi dispositivi e, nel caso dei dischi, diversi filesystem, fornendo all’utente un’interfaccia comune. Oggi come oggi difficilmente un driver è compilato insieme al kernel ed anche nei sistemi monolitici di solito è un modulo separato.. il kernel sarebbe enorme per gestire tutti i dispositivi possibili! Tutto questo in quel momento non era previsto per Linux: dobbiamo tener conto che si trattava di qualcosa scritto per divertimento e che, anche se il kernel resta monolitico, oggi le cose sono diverse ed organizzate meglio!

Si conclude questo nostro piccolo viaggio nella prima incarnazione di Linux, oggi divenuto uno dei sistemi più utilizzati al mondo (su varie architetture, nonostante le premesse!). Spero che i lettori abbiano gradito questa mia versione della nascita del nostro amato pinguino, se non altro perché la storia del ragazzo che scrive un kernel per divertimento e completa inconsapevolmente il sistema GNU l’abbiamo sentita e risentita in tutte le salse! Spero anche di aver reso le parti tecniche ragionevolmente comprensibili anche per persone poco pratiche. Di osservazioni se ne potrebbero fare altre mille, ma diventerebbe un post troppo tecnico, mentre la mia intenzione era regalarvi questa “curiosità”. Se qualcuno poi scaricasse il codice e volesse saperne di più, risponderò con piacere a richieste di chiarimenti.

Finisco con una simpatica citazione di un commento all’interno del codice sorgente:

/* vsprintf.c — Lars Wirzenius & Linus Torvalds. */
/*
* Wirzenius wrote this portably, Torvalds fucked it up 🙂
*/

Non è forse così che funziona il software libero?

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *