Anatomia del kernel Linux

tux.jpg

L’IBM è da diversi anni una delle aziende che più attivamente supporta lo sviluppo di Gnu/Linux. La partecipazione in tale sistema va dalle quote azionarie che IBM ha in RedHat, al personale coinvolto nello sviluppo di applicativi per tali sistemi all’ottima manualistica presente sul sito dell’IBM.

Oramai quasi un mese fa è uscito un interessante articolo firmato Tim Jones sul kernel del pinguino. Mi ero riproposto di tradurlo e proprorlo qui su Pista.

Per quasi tre settimane questo lavoro è rimasto tra le bozze, sia per la complessità degli argomenti trattati sia per il fatto che ultimamente ho decisamente molto meno tempo da dedicare al blog. Per un esaustiva bibliografia vi rimando al sito dell’IBM, qui sotto segue la traduzione dell’articolo.

Il kernel Linux® è il nucleo di un sistema operativo vasto e complesso, il quale, nonostante le sue dimensioni, risulta ottimamente organizzato in sottosistemi e layers. In questo articolo si esplora la struttura generale del kernel Linux per poi trattare i sottosistemi più rilevanti e le interfacce principali. Dove è possibile verranno inseriti collegamenti ad altri articoli IBM per ulteriori approfondimenti.

Lo scopo di questo articolo è presentare il kernel Linux, esplorare la sua architettura e suoi principali componenti: inizieremo con un breve resoconto della storia di Linux, in seguito esploreremo l’architettura del kernel da un alto livello e infine esamineremo i suoi maggiori sottosistemi.

Il kernel Linux conta più di sei milioni di linee di codice e, per questo motivo questa introduzione non potrà essere esaustiva. E’ tuttavia possibile approfondire i dettagli grazie alla bibliografia.

Premessa: Linux o GNU/Linux?

Avrete probabilmente notato che parlando di un sistema operativo Linux talvolta si usa la parola “Linux” mentre altre volte si usa “GNU/Linux”. Il motivo è che Linux è solo il kernel del sistema operativo. La vastità delle applicazioni che consentono a questo kernel di far parte di un sistema operativo fa parte del progetto GNU. Ad esempio, l’interfaccia grafiza, il compilatore, le shell, gli strumenti di sviluppo, gli editor di testo, le utility e tante altre applicazioni esistono a prescindere dal kernel e sono software GNU. Per questo motivo molti ritengono che “GNU/Linux” sia più appropriato per definire tale sistema operativo, mentre “Linux” è il termine giusto quando si parla del kernel.

Breve storia di Linux

Nonostante Linux sia indubbiamente il più famoso tra i sistemi opensource, la sua storia è piuttosto breve considerando l’età media dei sistemi operativi. All’inizio dell’era informatica i programmatori programmavano direttamente sull’hardware in linguaggio macchina. La mancanza di un sistema operativo significava che solo un applicativo (e solo un utente) poteva utilizzare un elaboratore alla volta.

I primi sistemi operativi furono sviluppati negli anni 50 al fine di semplificare complessi calcoli. Tra gli esempi si può citare il GMOS, il sistema operativo della General Motors sviluppato per l’IBM 701, oppure il FORTAN Monitor System (FMS) sviluppato dall’aviazione americana per l’IBM 709.

Negli anni 60 il MIT (Massachusetts Institute of Technology) assieme ad un gruppo di compagnie sviluppò per il GE-645 un sistema operativo che chiamò Multics (Multiplexed Information and Computing Service). Negli anni 70 AT&T uscì dal gruppo degli sviluppatori di Multics per far vita ad un progetto autonomo che chiamò Unics. All’interno di questo nuovo sistema operativo compariva il linguaggio di programmazione C, sviluppato con il preciso scopo di rendere portabile lo sviluppo del sistema.

Venti anni dopo (siamo quindi nel 90), Andrew Tanenbaum scrive una versione di UNIX®, chiamata MINIX (MInimal uNIX) basata su un microkernel, che aveva la capacità di girare sui pc. Questo OS ispirò Linus Torvalds, il creatore di Linux, nei primi anni 90 (figura 1).

Figura 1: numero delle linee di codice in funzione della release

Linux si è rapidamente diffuso da progetto di una singola persona a progetto di sviluppo mondiale coinvolgendo migliaia di programmatori. Una delle decisioni fondamentali per Linux fu l’adozione della licenza: la GPL (GNU General Public License). Sotto la GPL, il kernel Linux è stato protetto da sfruttamento commerciale ed ha anche tratto benefici dai programmi disponibili in user-space che facevano parte del progetto GNU (di Richard Stallman, che ha scritto tanto di quel codice che in confronto il sorgente del kernel Linux appare minuscolo). Questo ha permesso il supporto di utili applicativi come il compilatore GCC (GNU Compiler Collection) e varie shell.

Introduzione al kernel Linux

Si può pensare che l’architettura di un sistema operativo GNU/Linux sia basato su due diversi livelli, come mostrato in figura 2.

The fundamental architecture of the GNU/Linux operating system

Figura 2: Architettura fondamentale di un sistema operativo GNU/Linux *

In cima sta lo spazio dell’utente o dell’applicazione (in futuro user space). Questo è il livello in cui vengono eseguiti i programmi. Sotto lo spazio utente c’è quello del kernel (in futuro kernel space). In questo spazio risiede il kernel Linux.

Esiste anche la libreria GNU C (glibc). Questa libreria gestisce l’interfaccia per le chiamate al sistema relative al kernel e i meccanismi di transizione tra kernel e user space. Tale libreria è molto importante dal momento che il kernel e le applicazioni utente occupano spazi protetti diversi. Mentre ogni processo user space occupa il suo proprio indirizzo virtuale, il kernel utilizza un singolo indirizzo. Per ulteriori dettagli fate riferimento alla bibliografia.

Il kernel Linux può esser suddiviso ulteriormente in tre sottolivelli. Sopra tutto c’è la system call interface (interfaccia per le chiamate al sistema), essa è responsabile delle funzioni di base come la lettura (read) e la scrittura (write). Sotto la system call interface c’è il codice del kernel, o per meglio dire quella parte del codice del kernel che non dipende dall’architettura della macchina. Questo sorgente è presente su tutte le macchine che hanno un qualsiasi processore supportato da Linux. Ancora più sotto trovemo quella parte del codice del kernel che invece dipende dall’architettura presente sulla macchina. Tale porzione di kernel notoriamente forma quello che viene più comunemente chiamato un BSP (Board Support Package). Un BSP varia a seconda del processore e della specifica piattaforma per una data architettura.

Metodi per la system call interface (SCI)
In realtà le cose non sono così semplici come mostrate in figura 2. Ad esempio il meccanismo mediante il quale le chiamate sono gestite (le transizioni dallo user space al kernel space) possono variare a seconda dell’architettura. I nuovi processori x86 che prevedono un supporto per le istruzioni di virtualizzazione sono più decisamente più efficienti in questo processo dei loro antenati che invece fanno un uso tradizionale dell’interrupt int 80h.

Proprietà del kernel Linux

Si può osservare l’architettura di un sistema vasto e complesso da più prospettive. Uno degli scopi di tale scomposizione architettonica è quello permettere una maggior comprensione della struttura ed è quello che faremo qui.

Il kernel Linux implementa diversi importanti attributi architettonici. Sia ad alto livello che a basto livello il kernel è strutturato in un diverso numero di sottosistemi distinti. Linux può anche esser considerato monolitico perché reindirizza tutti i servizi basilari al kernel. In questo è molto diverso da una struttura a microkernel dove invece il kernel si occupa strettamente solo dei servizi base come le comunicazioni, l’I/O, e la gestione della memoria e dei processi; tutti gli altri servizi più specifici sono reindirizzati al layer del microkernel. Ognuno dei due approcci ha i suoi vantaggi, ma non entriamo ora in tale dibattito.

Con il passare del tempo, il kernel Linux ha migliorato la sua efficienza sia in termini di utilizzo della memoria che della CPU diventando estremamente stabile. L’aspetto più interessante di Linux, a dispetto della sua complessità e delle sue dimensioni, rimane la portabilità. Linux può esser compilato e girare su una grandissima collezione di processori e piattaforme completemante diverse. Un esempio è l’abilità da parte di Linux di funzionare sia con MMU che senza. Potete far riferimento alla bibliografia per ulteriori dettagli.

Principali sottosistemi del kernel Linux

Diamo ora un’occhiata ai principali componenti del kernel Linux utilizzando la figura 3 come guida.
One architectural perspective of the Linux kernel

Figura 3: Una prospettiva architettonica del kernel Linux

Che cos’è un kernel?

Come si può osservare nella figura 3 un kernel non è nient’altro che un gestore di risorse. A seconda che la risorsa gestita sia un processo, la memoria o una periferica hardware, il kernel gestisce e consente gli accessi a tale risorsa tra gli utenti di competenza (sia nel kernel che in user space).

System call interface

La SCI è una struttura sottile che si occupa di gestire le chiamate da parte dello user space al kernel. Come abbiamo già visto prima tale interfaccia può dipendere dall’architettura e differire tra processori della stessa famiglia. La SCI è un servizio di protocollo di multiplazione e demultiplazione (multiplexing/demultiplexing). L’implementazione della SCI si trova in ./linux/kernel, mentre le parti della SCI che dipendono dall’architettura in ./linux/arch. Ulteriori dettagli in bibliografia.

Gestione dei processi

La gestione dei processi entra in gioco nella loro esecuzione. Nel kernel tali processi vengono chiamati threads e rappresentano una virtualizzazione del processore. Nello user space il termine processo viene utilizzato molto spesso, nonostante l’implementazione di Linux non separa i due diversi concetti (processi e threads). Il kernel fornisce un’interfaccia per gli applicativi (comunemente chiamata API) attraverso la SCI al fine di creare nuovi processi (siano essi fork, exec, funzioni POSIX, termine di un processo (con kill e exit), comunicazione e sincronizzazione dall’uno all’altro).

Nella gestione dei processi fa parte anche la necessità di condividere la CPU tra i thread attivi. Il kernel prevede un algoritmo di scheduling che opera in tempi prefissati a prescindere dal numero dei thread presenti. Questo scheduler (pianificatore) viene chiamato scheduler O(1), che indica lo stesso intervallo di tempo che viene utilizzato sia per gestire uno che più thread. Lo scheduler O(1) è in grado di gestire anche un’architettura multiprocessore (SMP). I sorgenti per la gestione dei processi si trova dentro ./linux/kernel mentre quelle che dipendono dalla architettura in ./linux/arch. Si possono trovare ulteriori informazioni su questo algoritmo nella bibliografia.

Gestione della memoria

Un’altra importante risorsa gestita dal kernel è la memoria. Dal momento che l’hardware gestisce memoria virtuale, per una questione di efficienza la memoria viene suddivisa in quelle che vengono chiamate pages (pagine di 4KB per la maggior parte delle architetture). Linux prevede sia mezzi per gestire la memoria disponibile che meccanismi hardware per la mappatura della memoria fisica e virtuale.

La gestione della memoria va ben oltre la gestione di 4KB di buffer. Per questo motivo è prevista un’astrazione per i buffer che superano la soglia dei 4KB: lo slab allocator (letteralmente sarebbe “ripartitore di lastre”). Questo schema di gestione della memoria usa come base buffer da 4KB, prevedendo però delle strutture che permettano di tener traccia delle pagine che sono piene, parzialmente usate o vuote. In questo modo lo schema può crescere e ridursi dinamicamente a seconda delle necessità del sistema in oggetto.

Se molti utenti fanno uso di memoria può capitare che la memoria totale venga esaurita. Per questo motivo le pagine possono essere rimosse dalla memoria e riversate sul disco. Questo processo viene chiamato swapping dall’imglese swap (scambio) perché le informazioni vengono scambiate dalla memoria al disco. Il codice che riguarda la gestione della memoria è contenuto dentro ./linux/mm.

Il Virtual file system

Il virtual file system (VFS) rappresenta un’aspetto interessante del kernel Linux perché fornisce una comune interfaccia per l’astrazione dei filesystem. Il VFS consiste in un layer che si inserisce tra la SCI e i filesystem supportati dal kernel come in figura 4)

The VFS provides a switching fabric between users and file systems
Figura 4: Il VFS si inserisce tra gli utenti e i filesystem

Sopra al VFS c’è un’API che prevede le comuni funzioni come open, close, read e write (apertura, chiusura, lettura e scrittura). Sotto il VFS vi sono le astrazioni dei filesystem che definiscono l’implementazione delle stesse funzioni a basso livello. Queste sono le connessioni per i filesystem dati (ne esistono più di 50). I sorgenti si trovano in ./linux/fs.

Sotto il layer dei filesystem c’è la cache per il buffer che consiste in un layer per un insieme comune di funzioni (indipendenti dal filesystem). Il layer di caching ottimizza l’accesso alle periferiche fisiche mantenendo i dati in giro per un tempo relativamente piccolo (nel dettaglio legge in modo casuale i dati che seguono in modo che siano accessibili una volta che ve ne sia necessità). Sotto la cache per il buffer ci sono direttamente i driver per le periferiche fisiche che si occupano di gestire direttamente i dispositivi hardware.

Lo stack di rete

Lo stack di rete (letteralmente la pila di rete) segue un preciso modello di architettura oltre a supportare i protocolli. Ricordiamoci che il protocollo internet (IP) rappresenta la struttura base che giace sotto il protocollo di trasporto (più comunemente chiamato protocollo del controllo della trasmissione o TCP dall’inglese Transmission Control Protocol). Sopra il TCP c’è il sockets layer, che viene invocato tramite la SCI.

Il sockets layer non è nient’altro che lo standard API al sottosistema di rete che fornisce un’interfaccia utente per una varietà di protocolli di rete: sopra l’ IP gestisce il PDU, il TCP e l’UDP (User Datagram Protocol); in questo modo le connessioni e il movimento dei dati viene standardizzato, i sorgenti sono dentro ./linux/net.

Drivers per le periferiche

La parte più consistente del codice sorgente del kernel Linux riguarda proprio i drivers per le periferiche: quella porzione di software che rende le periferiche utilizzabili dal sistema. All’interno dell’albero delle directory del kernel si trovano sottodirectory suddivise a seconda della periferica supportata dal Bluetooth, I2C, seriale, e così via. Questi sorgenti sono dentro ./linux/drivers.

Codice dipendente dall’architettura

Mentre gran parte del kernel risulta indipendente dall’architettura del sistema su cui gira, ci sono parti di Linux che devono tenere in considerazione l’architettura sia per motivi di funzionalità che per questioni di efficienza. La directory ./linux/arch racchiude tutto il codice che dipende dall’architettura, in ogni sottodirectory troviamo codice pertinente alla singola architettura (la collezione di queste porzioni di codice viene comunemente chiamata BSP). Nel caso si tratti di un comune desktop verrà utilizzata la sottodirectory i386. Ogni sottodirectory specifica dell’architettura a sua volta conterrà un sottoalbero suddiviso in particolari aspetti del kernel che vanno dall’avvio, alla gestione della memoria e a tante altre cose.

Caratteristiche interessanti del kernel Linux

Se la portabilità e l’efficienza del kernel Linux non fossero sufficienti, vi sono caratteristiche ulteriori a quelle di cui abbiamo parlato che non è possibile classificare utilizzando il precedente schema.

Dal momento che Linux è un sistema operativo opensource rappresenta un’ottima piattaforma su cui testare protocolli sperimentali o miglioramenti di protocolli esistenti; Linux infatti oltre a supportare il tipico TCP/IP e un largo numero di altri protocolli di rete, prevede un’estensione per gestire reti ultra-veloci (che superano 1 Gigabit Ethernet [GbE] e 10 GbE). Sotto Linux è anche possibile utilizzare lo Stream Control Transmission Protocol (SCTP), che prevede molte caratteristiche avanzate basate sul TCP.

Linux è anche un kernel dinamico che prevede l’aggiunta o la rimozione di componenti software al volo. Questi componentin vengono chiamati moduli del kernel dinamicamente caricabili e possono venire inseriti all’avvio se necessari (nel caso in cui venga trovata una determinata periferica che richieda quel driver) o in qualsiasi altro momento grazie ad un comando dell’utente.

Una caratteristica di recente introdotta in Linux è il suo utilizzo come sistema operativo per altri sistemi operativi (chiamato un hypervisor). Proprio poco tempo è stata introdotta la Kernel-based Virtual Machine (KVM). Questa modifica ha permesso una nuova interfaccia allo user space dando la possibilità ad altri sistemi di girare sopra il KVM-enabled kernel. Oltre a lanciare una nuova istanza di Linux anche Microsoft Windows può esser virtualizzato. L’unica limitazione è che il processore deve supportare le nuove istruzioni di virtualizzazione; si faccia riferimento alla bibliografia per ulteriori dettagli.

Annunci

No comments yet

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

%d blogger hanno fatto clic su Mi Piace per questo: