Processi e thread
Nella pagina sui sistemi operativi abbiamo imparato a chiamare padrone di casa il programma che governa la macchina, quel gestore discreto e onnipotente che assegna il processore, la memoria e i dispositivi a chi ne ha bisogno, e adesso scendiamo nella sua stanza più segreta, quella in cui custodisce il trucco che rende possibile ogni altra cosa, perché il modo in cui il padrone tiene in ordine i programmi che chiedono di essere eseguiti è una delle idee più eleganti che l'informatica abbia mai messo per iscritto. Il nome di quell'idea è processo, e tutta questa pagina gli è dedicata, insieme al suo cugino leggero chiamato thread, che in inglese significa filo, perché capire i processi significa capire come un computer con un solo processore riesca a svolgere tanti lavori nello stesso istante, e significa padroneggiare il concetto senza il quale nessun programma moderno potrebbe nemmeno cominciare a girare.
Che il computer compia tante azioni contemporaneamente lo vediamo ogni giorno senza accorgercene, e il libro apre il capitolo proprio da qui, invitandoci a osservare un server web che risponde a richieste che arrivano da ogni dove, controlla se la pagina desiderata giace nella sua memoria veloce, la spedisce subito oppure va a pescarla dal disco, e mentre aspetta che il disco finisca il suo giro altre richieste continuano a bussare, così che il gestore è costretto a tenere in piedi parecchi lavori nello stesso momento per non lasciare il processore con le mani in mano. La stessa scena si ripete sul computer di casa a ogni accensione, perché nascono processi che ascoltano la posta in arrivo, processi che controllano gli aggiornamenti, processi che indicizzano i file, e tutti continuano a lavorare mentre noi scriviamo un documento e ascoltiamo musica, e il sistema operativo coordina questo mercato con la calma di un direttore d'orchestra che sa esattamente quando far entrare ogni strumento.
Per spiegare che cos'è un processo il libro ci regala una storia da cucina, e vale la pena di raccontarla con calma perché dentro di essa vive tutto il capitolo, quindi immagina un informatico con la passione della pasticceria che prepara una torta di compleanno per la figlia piccola, e osserva con attenzione i ruoli dei protagonisti, perché la ricetta scritta sul quaderno è il programma, cioè un algoritmo espresso in una notazione precisa, il pasticciere con le mani in pasta è il processore che esegue, la farina, le uova e lo zucchero sono i dati in ingresso, e il processo è l'attività nel suo insieme, cioè il pasticciere che legge la ricetta, prende gli ingredienti, mescola e inforna. La ricetta da sola non profuma di nulla, così come il programma custodito sul disco resta carta morta finché qualcuno lo esegue, e questo è il primo insegnamento, che il processo è un'attività viva, fatta di passi compiuti uno dopo l'altro nel tempo, mentre il programma è la sua partitura silenziosa.
La storia continua con un imprevisto che è in realtà una piccola lezione di architettura, perché mentre il pasticciere lavora il figlio entra in cucina piangendo dopo essere stato punto da un'ape, e allora il padre segna con una matita il punto esatto della ricetta dove si era fermato, ripone il grembiule, prende il manuale di primo soccorso e comincia a seguire le istruzioni di quello, e quando la puntura è stata curata torna alla torta riprendendo esattamente dal segno lasciato sulla pagina. In quel gesto banale dimorano tre idee fondamentali, cioè lo stato del processo interrotto che viene salvato, il passaggio del processore da un lavoro all'altro in base a una gerarchia di urgenze, e la ripresa dal punto esatto in cui tutto si era fermato, e lo stesso programma eseguito due volte conta come due processi distinti, esattamente come puoi aprire due volte il tuo elaboratore di testi e scrivere due lettere diverse, perché contano i dati, il punto raggiunto e il contesto, ben oltre il nome del programma.
Vediamo ora com'è organizzato il quartiere in cui abita un processo, e cioè come il sistema operativo dispone il programma in esecuzione dentro la memoria, perché l'immagine di un processo si compone di quattro zone ben distinte che conviene imparare a riconoscere fin da subito. La prima zona è il codice, la traduzione in linguaggio macchina delle istruzioni che hai imparato a conoscere nella pagina sull'architettura di Neumann, e resta immutabile durante tutta l'esecuzione, come una ricetta cucinata non si riscrive da sola. La seconda zona sono i dati globali, le variabili dichiarate una volta e visibili dall'inizio alla fine del programma, la terza è lo stack, la pila ordinata dove ogni chiamata di funzione depone il proprio indirizzo di ritorno e le proprie variabili locali, e la quarta è lo heap, il mucchio libero da cui il programma attinge spazio aggiuntivo quando ne ha bisogno, e ritroverai queste stesse quattro zone rivisitate nei minimi dettagli nella pagina sulla memoria virtuale, dove scopriremo come il padrone di casa dia a ogni processo una città tutta sua pur sapendo che esistono altre città identiche sotto lo stesso sole.
Il modello mentale che il libro ci chiede di costruire suona quasi come un incantesimo, perché ogni processo possiede concettualmente un processore tutto suo, con il proprio contatore di programma logico che avanza lungo le istruzioni e i propri registri che custodiscono i valori di lavoro, mentre nella realtà dei circuiti esiste un solo contatore di programma fisico che appartiene all'unica CPU, e il sistema operativo provvede a caricarvi il contatore logico del processo che tocca eseguire e a riprenderlo indietro quando arriva il momento di servirne un altro. Guardato da lontano, in un intervallo di tempo abbastanza lungo, ogni processo ha compiuto progressi, eppure in ogni istante preciso uno solo sta davvero correndo, e per questo i progettisti parlano di pseudoparallelismo per distinguere questa raffinatezza dal parallelismo vero e proprio delle macchine con più processori, che possiedono tante CPU fisiche quanti sono i lavori da svolgere davvero nello stesso momento.
Da questo continuo va e vieni deriva un avvertimento che i programmatori imparano a rispettare con il sudore, e cioè che la velocità con cui un processo avanza non è uniforme né ripetibile, perché il processore potrebbe essergli sottratto in qualsiasi istante e restituirgli il testimone dopo millisecondi oppure molto più tardi, e un processo non può dunque fidarsi dei propri cicli di attesa per misurare il tempo. Il libro racconta di un processo audio che accompagna un video e spera di sincronizzarsi facendo girare a vuoto un ciclo di diecimila iterazioni, sicuro che quella attesa duri sempre lo stesso, e scopre invece che se la CPU passa a un altro processo proprio durante il ciclo la musica riparte quando le immagini sono già lontane, con il risultato deludente di un sonoro che cammina staccato dalle scene.
Passiamo alla nascita, e chiediamoci come faccia un processo a venire al mondo, perché nei sistemi semplici, come il controllore di un forno a microonde, tutti i processi necessari possono essere presenti fin dall'accensione, mentre nei sistemi di uso generale serve un meccanismo per crearli e distruggerli via via che serve. Il libro elenca quattro occasioni principali che danno origine a un processo nuovo, e cioè l'inizializzazione del sistema all'avvio, la richiesta esplicita di un processo già in esecuzione tramite una chiamata di sistema, il desiderio dell'utente che lancia un programma, e infine l'avvio di un lavoro a lotti nei grandi elaboratori di una volta, e in tutti questi casi, tecnicamente, è sempre un processo esistente che ordina al sistema operativo di fabbricarne un altro indicandogli quale programma dovrà viverci dentro, con qualcuno di essi che vive sullo sfondo con compiti specifici e porta il nome affascinante di daemon, spiriti custodi che dormono tutto il giorno e si svegliano al sopraggiungere della posta o di una richiesta.
In Unix il meccanismo di nascita si chiama fork, parola che significa biforcazione, e il
modo migliore di immaginarla è la fotocopia di un foglio, perché il processo che la invoca ottiene una
copia esatta di se stesso, con la stessa memoria, le stesse variabili e gli stessi file aperti, e da
quell'istante esistono due processi gemelli che ripartono dalla stessa riga del programma distinguendosi
soltanto per il valore restituito dalla chiamata. Il figlio fotocopiato però raramente desidera
continuare a fare ciò che faceva il genitore, e per questo esiste la famiglia delle chiamate
exec, che funziona come cancellare il testo del foglio fotocopiato e riscriverci sopra un
testo nuovo, cioè caricare un programma diverso nella memoria del processo, ed è esattamente ciò che fa
la tua shell quando batti un comando, perché prima fotocopia se stessa e poi, dentro la copia, riscrive
il programma che hai chiesto, con quel doppio passaggio che permette alla copia di sistemare i canali di
comunicazione prima di trasformarsi.
Da questa nascita deriva una parentela, e cioè che ogni processo ha esattamente un genitore ma può avere zero, uno o molti figli, formando con i suoi discendenti un albero che rende familiare la metafora dell'organismo che si divide come un'idrilla anziché di un animale che si riproduce a coppie, e in cui la memoria del figlio nasce come copia di quella del genitore pur restando due spazi distinti, talvolta condivisi in modalità copy-on-write, cioè copia su scrittura, per cui la pagina viene duplicata soltanto quando uno dei due tenta di modificarla, risparmiando spazio finché entrambi si limitano a leggere. Quando il computer si avvia, un processo speciale legge quanti terminali esistono, fabbrica un processo per ciascuno, i processi aspettano che qualcuno si presenti con nome e parola d'ordine, e dai comandi digitati nascono altri processi, così che tutta la discendenza forma un unico albero genealogico con radici profonde, mentre Windows, che non possiede una gerarchia così rigorosa e considera tutti i processi eguali, concede al genitore un gettone di controllo sul figlio che però può consegnare a chiunque, rendendo il legame molto più lasco di quello unixiano, dove un processo non può rinnegare i propri figli.
Come ogni attività, un processo prima o poi giunge al termine, e il libro distingue quattro modi di
concludere, cioè l'uscita normale compiuta volontariamente quando il lavoro è finito, l'uscita per
errore quando per esempio chiedi di compilare un file che non esiste e il programma te lo segnala
sparendo, la morte per errore fatale come una divisione per zero o un'istruzione illegale, e infine la
soppressione decisa da un altro processo autorizzato che invoca una chiamata apposita per ucciderlo, e
vale la pena di notare che in Unix la stessa chiamata che serve a terminare gentilmente un compagno di
lavoro è quella che dà il nome al gesto, perché è proprio kill.
Per tenere traccia di tutti questi abitanti il padrone di casa redige una scheda per ciascuno, e questa scheda ha un nome solenne, process control block, cioè blocco di controllo del processo, anche se a noi piace pensarlo come alla carta di identità amministrativa che il sistema operativo aggiorna di continuo. Dentro quella scheda vivono il contatore di programma da ripristinare, i registri salvati, il puntatore della pila, lo stato corrente, la priorità, i file aperti, il tempo di CPU già consumato, l'ora della prossima sveglia e persino l'identità del genitore, insomma ogni cosa che servirebbe a far ripartire il processo esattamente come era se il processore gli venisse restituito anche dopo mille interruzioni, e la tabella che raccoglie tutte queste schede, una per riga, è chiamata tabella dei processi, il registro più consultato di tutto il sistema.
È ora di sollevare il coperchio e osservare il passaggio di testimone, che avviene in un baleno ogni volta che scatta un'interruzione, per esempio quando il disco finisce di leggere un settore. L'hardware spinge nella pila il contatore di programma e lo stato corrente, consulta la tabella dei vettori di interruzione dove ogni dispositivo ha il proprio indirizzo di servizio, e salta alla routine indicata, che inizia salvando i registri nella scheda del processo interrotto e appoggiandosi a una pila provvisoria, gesti così primitivi che nessun linguaggio di alto livello sa esprimerli e che per questo vengono scritti in assembler, come hai imparato nella pagina dedicata. Poi una procedura scritta in C svolge il lavoro vero, il gestore dell'interruzione, che tra l'altro può risvegliare processi addormentati, e infine lo scheduler sceglie chi corre nel prossimo istante, la routine assembler ricarica i registri del fortunato e l'esecuzione riprende come se nulla fosse accaduto, il che significa che un processo può essere interrotto migliaia di volte e ritrovarsi sempre nello stato identico.
Abbiamo raccolto gli ingredienti per raccontare i tre stati fondamentali di un processo, e la storia del forno li renderà indimenticabili, quindi immagina di nuovo il nostro cuoco che sta impastando, e ricorda che lui può trovarsi in esecuzione quando tiene in mano il matterello, pronto quando il matterello è occupato da un collega e lui aspetta il proprio turno sulle spine pur potendo riprendere in un istante, e in attesa quando ha messo la torta nel forno e non può fare altro finché la campanella non suona, perché nessuna assegnazione di matterelli potrebbe sbloccarlo, dato che il vincolo è la cottura e non la disponibilità delle mani. La differenza fra pronto e in attesa è il cuore di tutta la faccenda, perché il processo pronto desidera soltanto che gli venga concessa la CPU mentre quello in attesa resta inchiodato anche se la CPU giace inoperosa, e questa distinzione governa ogni decisione dello scheduler, separando l'impazienza di chi vuole correre dal sonno di chi aspetta un evento esterno.
Le transizioni fra questi tre stati sono quattro, e ognuna ha una sua logica precisa che vale la pena di nominare una per una, perché la prima avviene quando il processo in esecuzione scopre di non poter proseguire, per esempio leggendo da un condotto dove nessuno ha ancora scritto, e allora si adagia da solo nello stato di attesa con una chiamata di sistema. La seconda e la terza sono le decisioni dello scheduler, che toglie la CPU a chi è in esecuzione da troppo tempo e la consegna a un pronto fortunato, operazioni che avvengono addirittura senza che il processo coinvolto se ne accorga, come capita a chi si vede chiudere il rubinetto dell'acqua da un idraulico senza preavviso. La quarta transizione scatta quando arriva l'evento atteso, il file finisce di leggere, il pacchetto giunge dalla rete, e il processo rientra tra i pronti, da cui verrà estratto al momento opportuno, e a guardare il giro da fuori esso si chiude su se stesso come un gioco dell'oca dove le caselle sono esattamente tre.
Resta la domanda che un lettore curioso si pone a questo punto, e cioè perché mai il padrone di casa decida di strappare il processore dalle mani di un processo che sta lavorando benissimo, e la risposta si racconta con una maestra elementare che gestisce una lettura collettiva, perché davanti a lei c'è un solo grande libro, le sue venti alunne e alunni desiderano tutti sentirsi leggere, e il metodo che funziona da centinaia d'anni è far leggere a turni brevi, strappare il libro dalle mani di chi sta leggendo prima che si accorga di essere stanco, passarlo alla compagna seguente e continuare così finché tutti hanno avuto la loro parte, senza mai permettere a un solo bambino di divorare l'intera storia mentre gli altri guardano. Questo metodo ha un nome, preemptive multitasking, cioè multitasking a prerilascio, perché un timer scatta a intervalli regolari, il sistema operativo riprende il controllo per un istante minuscolo, salva lo stato di chi correva e affida il testimone a un altro, e questa gentilezza forzata è il prezzo che ogni processo paga per vivere in una macchina che sembra dedicata a lui pur essendo condivisa da una folla.
Ci siamo chiesti chi riceve il libro, e la risposta compatta è che dentro il sistema operativo vive un componente chiamato scheduler, lo schedulatore, il cui mestiere è scegliere fra i processi nello stato pronto quale reggerà la CPU nel prossimo frammento di tempo e per quanto a lungo lo terrà. Le ricette che usa per decidere sono molte e sottili, perché alcune dividono il tempo in fette uguali per tutti, altre premiano i compiti più corti o i più importanti, altre ancora alzano la voce contro chi monopolizza, e tutte cercano di conciliare due desideri che tirano in direzioni opposte, cioè l'efficienza del sistema nel suo complesso e l'equità verso ciascun processo, con la consapevolezza che il fattore umano conta moltissimo, dato che una persona che aspetta una risposta trova lungo persino un decimo di secondo, e per questo i processi interattivi ricevono spesso un trattamento preferenziale.
Arriviamo al cugino leggero, e cioè al thread, il filo d'esecuzione, che il libro presenta partendo da un problema quotidiano, quello di chi scrive un libro con un elaboratore di testi molto grande, perché cancellare una sola frase nella prima pagina obbliga il programma a riformattare tutto il testo che segue prima di mostrarti la pagina seicento che desideri raggiungere, e l'attesa diventa insopportabile. La soluzione elegante è scrivere il programma con più thread che collaborano, uno che ascolta tastiera e mouse, uno che riformatta in silenzio sullo sfondo, e magari un terzo che ogni cinque minuti salva tutto sul disco per salvaguardarti dalla disgrazia di un calo di corrente, e questi tre fili condividono lo stesso documento nello stesso spazio di memoria, cosa che tre processi separati con spazi separati non potrebbero fare altrettanto facilmente, e questa capacità di collaborare da vicino è la ragione per cui i thread esistono.
Il modello classico del thread si capisce bene se pensiamo a un operaio artigiano dotato di due braccia indipendenti, perché il corpo con i suoi strumenti, la bancata da lavoro e il materiale è unico, eppure ogni braccio può svolgere un compito diverso servendosi delle stesse risorse, e tagliare una stecca mentre l'altra mano misura il punto esatto di foratura richiede due mani anziché due officine. Nel mondo dei programmi il corpo è il processo, con il suo spazio di indirizzi, i suoi file aperti e le sue variabili globali, mentre i bracci sono i thread, ciascuno con il proprio contatore di programma, i propri registri e la propria pila, e per questo gli vengono dati i nomi di processi leggeri, in gergo lightweight processes, oppure di fili, dato che un processo con più thread ospita più fili di esecuzione che procedono per conto loro dentro la stessa officina, così vicini da poter toccare gli stessi pezzi appoggiati sul banco.
La distinzione fra ciò che appartiene al corpo e ciò che appartiene a ogni braccio è così utile che il libro la dispone in due colonne affiancate, e noi la raccontiamo così, perché spazio di indirizzi, variabili globali, file aperti, allarmi pendenti e contabilità sono proprietà del processo condivise da tutti i suoi thread, mentre contatore di programma, registri e pila sono proprietà private di ciascun filo. Proprio la pila merita un attimo di attenzione, perché dentro di essa ogni thread custodisce la storia delle chiamate di funzione che ha fatto e non ha ancora concluso, con una cornice per ogni procedura in viaggio e le variabili locali al sicuro, e due thread che eseguono lo stesso codice stanno in realtà percorrendo strade diverse, così come due cuochi che leggono la stessa ricetta possono trovarsi a passi completamente diversi della preparazione, e per questo ogni filo ha bisogno di una pila tutta sua dove segnare i propri passi.
Anche i thread nascono e muoiono, e il loro ciclo di vita si governa con chiamate di libreria che rispecchiano quelle dei processi, perché un filo ne crea un altro con una chiamata di creazione indicando quale procedura dovrà eseguire, può attendere la fine di un compagno con una chiamata di attesa, e può anche offrirsi volontario per cedere la CPU a un altro filo con una chiamata di resa, gesto gentile e necessario perché i thread di un processo si fanno concorrenza senza che un orologio esterno li sgrani a forza, e la cortesia diventa regola di sopravvivenza, dato che un filo che si rifiutasse di mollare la presa bloccherebbe per sempre anche i suoi compagni di officina.
Finora i nostri processi hanno vissuto ciascuno nella propria casa, ma nella vita reale devono parlarsi, e il libro dedica a questa comunicazione fra processi un nome composto, interprocess communication, che gli anglofoni abbreviano in IPC, e tre questioni da risolvere, cioè come far passare le informazioni da un processo all'altro, come impedire che due processi si pestino i piedi mentre toccano dati comuni, e come garantire che le operazioni avvengano nell'ordine giusto quando esistono dipendenze, per esempio il processo che stampa deve aspettare che quello che produce abbia finito di generare righe, e già il guscio della tua console ti mostra la prima risposta con la pipeline, il condotto, dove l'uscita di un comando diventa l'ingresso del successivo senza che i due programmi si conoscano nemmeno.
Il secondo problema nasconde un pericolo che vale la pena di raccontare con cura, e il libro lo fa usando la coda di stampa, che noi trasportiamo in un condominio dotato di un registro per prenotare la lavanderia comune, con una casella per ogni fascia oraria e un cartellino che indica la prima casella libera, perché se due inquilini consultano il cartellino nello stesso istante leggono entrambi il numero sette, entrambi scrivono il proprio nome nella casella sette sovrascrivendo il nome dell'altro, e il cartellino viene aggiornato a otto per due volte, così che il registro appare perfettamente in ordine mentre una delle due prenotazioni è stata fatta sparire, esattamente come nel problema della stampante dove un file si perde per sempre pur lasciando la coda apparentemente coerente. Queste situazioni, in cui due o più processi leggono e scrivono dati condivisi e il risultato finale dipende da chi passa esattamente per primo, si chiamano race condition, condizioni di gara, e la loro crudeltà sta nel fatto che la maggior parte delle esecuzioni va benissimo mentre una volta ogni tanto accade qualcosa di inspiegabile, il che rende la caccia all'errore un incubo famoso tra i programmatori.
La medicina per la gara si chiama mutua esclusione, e l'idea è antica quanto le buone maniere, perché consiste nel vietare che due processi tocchino contemporaneamente il registro condominiale, stabilendo che mentre uno scrive gli altri aspettino il proprio turno con pazienza, e la parte di codice in cui un programma accede ai dati comuni viene chiamata regione critica, quasi fosse la stanza del registro con la porta stretta dove entra una persona alla volta. Perché la soluzione regga servono quattro garanzie precise, e cioè che mai due processi stiano insieme nella propria regione critica, che nessuno debba supporre velocità particolari dei compagni, che chi resta fuori dalla regione non intralci nessuno, e che nessuno aspetti in eterno il proprio turno, e tutto il resto del capitolo è una galleria di meccanismi che onorano queste quattro promesse, dai semafori ai monitor fino allo scambio di messaggi.
C'è infine una trappola più raffinata, e il libro la annuncia con una scena da tavola apparecchiata, perché immagina due persone sedute una di fronte all'altra, ciascuna con un bicchiere d'acqua davanti a sé e un'unica caraffa al centro, e immagina che per un rito assurdo ognuna abbia afferrato il bicchiere del vicino e attenda con cortesia che l'altro posi il proprio per poter versare, mentre l'altro fa esattamente lo stesso ragionamento, così che i due restano inchiodati in un abbraccio di attese reciproche da cui nessuna buona maniera li salverà. Questo abbraccio si chiama deadlock, stallo mortale, e accade quando due processi si bloccano a vicenda tenendo ciascuno una risorsa che l'altro esige, e basta un errore di ordine in due chiamate ai semafori perché l'intero sistema si fermi senza rumore, il che spiega perché i progettisti parlino di programmare la concorrenza come di un mestiere da fabbri pazienti che battono il ferro a piccoli colpi.
Vale la pena di fermarsi un istante per chiedersi che senso abbia tutto questo per chi un giorno voglia
costruire software da solo, e la risposta sta nel fatto che ogni scelta che abbiamo raccontato è stata
fatta da mani umane che volevano risolvere un problema preciso, perché il processo nasce per dare a ogni
programma la sua illusione di macchina privata, lo stato nasce per poter interrompere senza distruggere,
la fotocopia del fork nasce per dare al figlio la libertà di diventare altro, i thread
nascono per collaborare senza copiare, la mutua esclusione nasce per proteggere i dati comuni, e capire
questi perché significa possedere le stesse leve che i grandi progettisti hanno usato, con la
consapevolezza serena che un giorno, se vorrai, potrai disegnare anche tu un piccolo padrone di casa
tutto tuo.
Adesso tocca a te, e la teoria dei processi si presta a una caccia al tesoro sul tuo computer che non
richiede altro che curiosità e un terminale, quindi comincia aprendo gli occhi sul cortile dei processi
con il comando ps aux, che elenca ogni processo vivo con il suo identificatore numerico, il
suo genitore, la percentuale di processore e il suo stato, e nota subito quanta gente lavora in silenzio
anche quando la macchina sembra ferma, perché quasi tutte le righe mostrano il punto dello stato di
attesa che abbiamo raccontato con la storia del forno, mentre chi preferisce le finestre grafiche può
confrontare lo stesso spettacolo con il gestore attività o il monitor di sistema, dove le etichette
cambiano ma il concetto è identico.
Per il secondo esperimento lancia top oppure, se lo trovi installato, htop,
che mostrano la lista dei processi rinfrescata ogni secondo come una radiocronaca, e gioca a riconoscere
le lettere dello stato, perché la R indica chi è in esecuzione, la S chi dorme in attesa di un evento, la
D chi aspetta il disco e la Z chi è diventato uno zombie terminato ma non ancora raccolto dal genitore.
Lancia poi in un'altra finestra un sleep 600 e osserva come il processo compaia in stato S
senza consumare nulla, quindi avvia la compilazione di un programma o la copia di un file grande e
guarda i processi salire in R e scendere di nuovo, perché vedere lo stato cambiare davanti agli occhi
trasforma il diagramma dei tre stati da disegno in esperienza vissuta.
Per il terzo esperimento esplora la genealogia con pstree -p, che disegna l'albero dei
processi con i numeri di identificativo fra parentesi e mostra come tutto discenda da un antenato
comune, e cerca poi con gli occhi i rami che portano alla tua sessione, perché riconoscerai il processo
che presiede al terminale, la shell che ti legge i comandi e i figli che hai lanciato da essa, esattamente
la gerarchia padre e figlio della fotocopia, e se vuoi il dettaglio di un solo individuo puoi leggere la
sua scheda personale dentro la cartella /proc con un comando del tipo
cat /proc/numero/status, sostituendo al numero l'identificativo che hai appena imparato a
cercare.
Per il quarto esperimento metti in scena la nascita e la morte con i comandi sleep 300 &
seguito da jobs, che mostra il lavoro appena messo sullo sfondo, poi richiamalo in primo
piano con fg, sospendilo premendo insieme i tasti control e z, rimandalo sullo sfondo con
bg e liberati di lui con kill %1, e in questo giro hai eseguito tu stesso le
chiamate di sistema della creazione e della soppressione, perché la shell ha fotocopiato se stessa per
far nascere il sonno, e quando hai soppresso il lavoro hai parlato la stessa lingua del kill
di cui sopra, salendo di un livello la comprensione di ciò che accade a ogni pressione del tasto invio.
Per il quinto esperimento scendi nella cucina del fork con un programma minimo, perché ti
basta scrivere in un file una decina di righe in linguaggio C che chiamano fork e stampano
il proprio identificativo e quello del genitore, compilarle con gcc forkdemo.c -o forkdemo
e lanciare l'eseguibile, e vedrai due righe di uscita dove il figlio riporta un genitore identico al
processo che l'ha creato, mentre in un'altra finestra pstree -p ti mostra per un attimo il
ramo gemello della fotocopia prima che il figlio sparisca, e tutto il mistero della nascita diventa un
gesto che hai compiuto tu con le mani, senza più intermediari fra te e la chiamata di sistema.
Per il sesto esperimento conta i fili di un processo vivo, e il metodo più semplice è lanciare
htop e premere il tasto h maiuscolo per alternare la vista dei thread, oppure aprire il tuo
browser e osservare quanti fili custodisce un programma che sembra uno solo, perché dentro di esso ci
sono i bracci dell'operaio al lavoro, alcuni che disegnano, alcuni che scaricano, altri che ascoltano, e
lo stesso conteggio si può fare da terminale esplorando la cartella dei fili di un processo nel percorso
/proc/numero/task, dove ogni sottocartella corrisponde a un thread con la sua pila e
i suoi registri salvati, pronti a essere ripristinati al momento del cambio di turno.
Per l'ultimo esperimento chiudi il cerchio tornando alla formula del pseudoparallelismo, perché puoi
contare quanti processi popolano la tua macchina in un momento di calma con ps aux | wc -l
e osservare in top quanti di essi sono in esecuzione davvero, e scoprirai quasi sempre che
i dormienti sono una folla mentre i corridori sono pochissimi, esattamente come prevede il calcolo che
dice che se ogni processo aspetta il proprio ingresso per la maggior parte del tempo servono parecchi
inquilini nella memoria perché il processore resti occupato, e questa constatazione fatta da solo vale
più di molte spiegazioni, perché hai misurato con le mani il motivo per cui esiste il multitasking.
Questa pagina attinge al secondo capitolo, quello sui processi e sui thread, del volume Modern
Operating Systems di Andrew S. Tanenbaum e Herbert Bos, che il progetto custodisce nella biblioteca al
percorso books/Modern Operating Systems 4th Edition--Andrew Tanenbaum.pdf, e in particolare
alle sezioni 2.1 e 2.2 per il modello di processo, la creazione, gli stati e i thread, con le condizioni
di gara e la mutua esclusione che aprono la sezione 2.3 sulle comunicazioni, mentre il calcolo
dell'utilizzo del processore e i cenni sullo schedulatore preparano il terreno alle pagine future, e la
storia della torta di compleanno, del forno e della maestra che fa leggere i bambini a turno sono tutte
sue, portate qui con gratitudine e tradotte nel registro paziente di questo manuale.
Quando chiuderai questa pagina, portati dietro l'immagine dell'operaio dai due bracci e del cuoco che aspetta il forno, perché saranno loro a guidarti nel gradino successivo, in cui chiederemo dove abitino davvero i processi e come il padrone di casa riesca a dare a ognuno una città intera con strade, muri e cancelli pur avendo un'unica memoria fisica da spartire, e per scoprirlo la pagina successiva è dedicata alla memoria virtuale, dove vedremo insieme come nascono quelle città e come nessun inquilino possa varcare il muro del vicino senza permesso.