Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Gestione dei Processi e Input/Output in Sistemi Operativi, Appunti di Sistemi Operativi

Libro di Michael Dahlin, Thomas Anderson tradotto in italiano (13 capitoli).

Tipologia: Appunti

2017/2018

In vendita dal 09/09/2018

maDave
maDave 🇮🇹

12 documenti

1 / 10

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Lezione 3 (PROF 08/03/18)
CreateProcess:
Quando si vuol creare un processo si usa una system call chiamata CreateProcess. Bisogna creare
ed inizializzare opportunamente il Process control block (PCB). Affinché possa lavorare bisogna
allocargli uno spazio di indirizzamento riservato al processso. Carico il programma e gli argomenti
e al momento opportuno lo farò partire (nel momento in cui parte l’istruzione di start). La system
call di Windows è molto complicata.
Gestione dei processi in UNIX:
1. Fork: Crea il PCB che è esattamente uguale a chi ha chiamato la fork (ovvero clonami). Non ha
parametri in ingresso, e restituisce un risultato. Il padre si mette in wait.
1.a. Errori nella fork: PCB tutti occupati
1.b.Cosa fa la fork?
1.i. Crea ed inizializza il PCB
1.ii. Nella memoria allocata precedentemente, prende la memoria del padre e la
copia nel figlio
1.iii. Eredita il contesto di esecuzione del genitore (file aperti, program counter,
parola di stato)
1.iv. Informa lo scheduler che esiste un nuovo processo pronto per essere
eseguito.
2. Exec: Cambio il programma del processo che si sta considerando (quindi carico il nuovo programmo
nello spazio di indirizzamento del figlio, SOLO SE VOLESSI CAMBIARE PROGRAMMA).
1.c. Nessun errore:
1.v. Il PCB non cambia
1.vi. Lo spazio di indirizzo cambia e i riferimenti al codice
1.vii. Si resettano i segnali pendenti
1.viii. Mantiene lo stack del kernel (per il passaggio di contesto)
1.ix. Eredita tutte le risorse del padre
1.d.Errori nella exec: Se l’indirizzo del programma non è esistente la exec ritorna un codice di
errore.
OSS: Per far partire un nuovo processo fork ed exec sono eseguite insieme.
3. Wait: Chi la invoca aspetta che un determinato programma finisca di eseguire ciò che sta facendo. Il
figlio il cui padre non ha fatto la wait viene chiamato processo zombie e mantiene il suo PCB
fin quando il padre non esegue la wait. Se il padre finisce prima del figlio il figlio diventa
“orfano”, restituendo il proprio valore al momento dell’exit al primo parente che trova
nell’albero di ereditarietà dei processi.
4. Signal: Un processo notifica varie informazioni ad un altro processo
5. Terminazione del processo: Quando termina restituisce un valore al padre restituendo tutte le risorse
che tale processo stava usando (in particolare il PCB). Può avvenire in due modi:
1.e. Terminazione naturale
1.f. Processo killato
Come funziona?
pf3
pf4
pf5
pf8
pf9
pfa

Anteprima parziale del testo

Scarica Gestione dei Processi e Input/Output in Sistemi Operativi e più Appunti in PDF di Sistemi Operativi solo su Docsity!

Lezione 3 (PROF 08/03/18)

CreateProcess:

Quando si vuol creare un processo si usa una system call chiamata CreateProcess. Bisogna creare

ed inizializzare opportunamente il Process control block (PCB). Affinché possa lavorare bisogna

allocargli uno spazio di indirizzamento riservato al processso. Carico il programma e gli argomenti

e al momento opportuno lo farò partire (nel momento in cui parte l’istruzione di start). La system

call di Windows è molto complicata.

Gestione dei processi in UNIX:

  1. Fork: Crea il PCB che è esattamente uguale a chi ha chiamato la fork (ovvero clonami). Non ha parametri in ingresso, e restituisce un risultato. Il padre si mette in wait. 1.a. Errori nella fork: PCB tutti occupati 1.b. Cosa fa la fork? 1.i. (^) Crea ed inizializza il PCB 1.ii. Nella memoria allocata precedentemente, prende la memoria del padre e la copia nel figlio 1.iii. Eredita il contesto di esecuzione del genitore (file aperti, program counter, parola di stato) 1.iv. (^) Informa lo scheduler che esiste un nuovo processo pronto per essere eseguito.
  2. Exec: Cambio il programma del processo che si sta considerando (quindi carico il nuovo programmo nello spazio di indirizzamento del figlio, SOLO SE VOLESSI CAMBIARE PROGRAMMA ). 1.c. Nessun errore: 1.v. (^) Il PCB non cambia 1.vi. Lo spazio di indirizzo cambia e i riferimenti al codice 1.vii. Si resettano i segnali pendenti 1.viii. Mantiene lo stack del kernel (per il passaggio di contesto) 1.ix. Eredita tutte le risorse del padre 1.d. Errori nella exec: Se l’indirizzo del programma non è esistente la exec ritorna un codice di errore.

OSS: Per far partire un nuovo processo fork ed exec sono eseguite insieme.

  1. Wait: Chi la invoca aspetta che un determinato programma finisca di eseguire ciò che sta facendo. Il figlio il cui padre non ha fatto la wait viene chiamato processo zombie e mantiene il suo PCB fin quando il padre non esegue la wait. Se il padre finisce prima del figlio il figlio diventa “orfano”, restituendo il proprio valore al momento dell’exit al primo parente che trova nell’albero di ereditarietà dei processi.
  2. Signal: Un processo notifica varie informazioni ad un altro processo
  3. Terminazione del processo: Quando termina restituisce un valore al padre restituendo tutte le risorse che tale processo stava usando (in particolare il PCB). Può avvenire in due modi: 1.e. Terminazione naturale 1.f. Processo killato

Come funziona?

Si cerca un PCB vuoto, e viene copiato il PCB del processo che ha eseguito la fork (vengono

memorizzate tutte le informazioni precedenti come la priorità, i diritti, i file aperti e con quali diritti

può accederli).

Il valore restituito dalla fork è:

  • (^) 0 per il figlio
  • Per il padre vengono restituiti due possibili valori:
    • PID del figlio
    • Valore negativo relativo al codice d’errore in caso non si è riuscita a fare la fork.

NON DOVREMMO CONTROLLARE CHE IL PID NON SIA UN VALORE NEGATIVO?

Unix I/O

La visione di UNIX è quella di vedere tutto il sistema come un file (quindi tutte le periferiche

verranno viste come file).

Operazioni sui file: Apertura, Scrittura, Lettura, Chiusura. Il file è protetto dai diritti (metadati del

file). Chiedo al kernel di leggere e di scrivere sul file e quando ho finito chiudo il file aperto in

precedenza ( APRIRE E CHIUDERE UN FILE È OBBLIGATORIO. )

Open :

  • Se il file esiste => Apre il file e ne restituisce il suo file descriptor.
  • Se non esiste:
    • Se il file non esiste restituisce un errore
    • Lo crea e lo apre
    • (^) …… Ne esistono a centinaia.

LEZIONE 3 (DAL LIBRO)

Lo scorso capitolo si focalizzava sui meccanismi di cui necessitiamo nel sistema operativo per

implementare il processo di astrazione. Un processo è un’istanza di un programma – il kernel

provvede ad un efficiente sandbox per eseguire codice non affidabile in modalità utente ed eseguire

codice direttamente nel processore. Questo capitolo si concentrerà sui i modi che utilizzeremo per

realizzare il processo di astrazione: quali funzionalità svolgono i sistemi operativi per offrirti le

applicazioni, cosa dovrebbe essere messo nelle librerie al livello utente, e come dovrebbe essere

organizzato il sistema operativo.

creassimo una nuova libreria, possiamo collegarla alle nuove applicazioni, e convertire così la vecchia con la nuova interfaccia. Comunque, se avessimo bisogno di cambiare l’interfaccia delle system call, dovremmo cambiare sia il kernel sia tutte le applicazioni, o dovremmo supportare le vecchie e le nuove versioni fin quando tutte le app non sono state convertite. Molte applicazioni sono scritte da sviluppatori di terze parti, fuori dal controllo del venditore del sistema operativo. Quindi, cambiare l’interfaccia delle system call è un enorme step, richiedendo coordinamento tra tutte le principali aziende.

Figura 3.

Una delle idee chiave in UNIX, responsabile del maggiore successo, è costruire la sua interfaccia

per essere più semplice e intuibile possibile, la gran parte di tutte le innovazioni possono accadere

senza cambiare l’interfaccia del sistema operativo. Le system call UNIX, sono anche altamente

portabili – il sistema operativo può essere trasportato al nuovo hardware senza aver la necessità di

riscrivere il codice delle applicazioni. Come mostrato in figura 3.2, il kernel può essere visto come

un “piccolo gemello”, abilitando le innovazioni al livello applicativo, e nell’hardware, senza aver il

bisogno di cambiare simultaneamente le altre parti del sistema.

  • Sicurezza. Comunque, la gestione delle risorse e la protezione sono responsabilità del kernel del sistema operativo. Come spiegato nel capitolo 2, le verifiche sulla protezione non possono essere implementate nelle librerie a livello utente poiché il codice delle applicazioni può saltare tutti i controlli eseguiti dalla libreria.
  • Affidabilità. Migliorare l’affidabilità è un altro motivo per mantenere il kernel minimale. Il codice di quest’ultimo necessita di potenza per configurare i vari dispositivi hardware, come il disco, o il controllo di protezione dei confini delle applicazioni. Comunque, i moduli del kernel sono tipicamente non protetti da altri, e quindi un bug nel codice del kernel potrebbe corrompere l’utente o i dati del kernel. Questo ha portato molti sistemi ad utilizzare la filosofia “ciò che si può fare al livello utente, dovrebbe essere fatto”. Una versione estrema di questo approccio è quella di isolare le parti privilegiate, ma meno critiche, del sistema operativo come il file system o la finestra di sistema, dal resto del kernel. Questo è chiamato microkernel design. In un microkernel, il kernel in sé è mantenuto piccolo, la maggior parte delle funzionalità di un tradizionale kernel sono messe un insieme di processi a livello utente, o server, acceduti da applicazioni utente attraverso comunicazioni tra processori.
  • Performance. Infine, trasferire il controllo al kernel è molto costoso di una chiamata di procedura da una libreria, e trasferire il controllo al server del file system al livello utente via kernel è ancora più costoso. I progettisti dell’hardware hanno tentato di ridurre i costi di questi confini, ma le loro performance rimangono tutt’ora un problema. Microsoft Windows NT, un precursore di Windows 7, era stato inizialmente progettato come un microkernel, ma molte delle sue funzionalità sono state trasferite al kernel per problemi di performance.

Non ci sono risposte facili! Indagheremo sul come dovrà essere costruita un’interfaccia di

chiamata di sistema e dove metteremo le funzionalità del sistema operativo, studiando i casi di

UNIX e altri sistemi.

3.1 Gestione del processo (Qual è l’interfaccia della system call per una gestione di processo?)

In un computer moderno, quando un utente clicca in un file o in una icona di un'applicazione, esse vengono eseguite. Come avviene questo processo e chi è chiamato? Certamente, potremmo implementare tutto ciò che potrebbe accadere nel kernel – disegnare l'icona per ogni possibile oggetto nel file system, mappare le posizioni del mouse con un'icona desiderata, catturare il click di un mouse e avviare il processo. Nei primi sistemi operativi, il kernel controllava secondo le sue necessità. L'utente richiedeva dei lavori, il sistema operativo li prendeva, istanziando un nuovo processo quando arrivava il momento di eseguire il lavoro.

Un approccio differente era quello di permettere ai programmi utente di creare e gestire i propri processi. Questo ha portato a grandi innovazioni. Oggi, i programmi che creano e gestiscono i processi includono gestioni della finestra, web server, interpreti da linea di comando, codice sorgente per il controllo

dei sistemi, database, compilatori e sistemi di preparazione dei documenti. Possiamo andare avanti, ma ti sei fatto un'idea. Se creare un processo è qualcosa che è un processo può fare, allora ognuno può costruire una nuova versione di queste applicazioni, senza ricompilare il kernel o forzare qualcuno ad usarlo. Una motivazione per la gestione dei processi a livello utente era quella di permettere agli sviluppatori di scrivere il proprio interprete a linea di comando. Una Shell è un sistema di controllo del lavoro; entrambi Windows e Unix hanno una Shell. Molti task sono stati sviluppati come una sequenza di step che fanno qualcosa, ognuno dei quali può avere il proprio programma. Con una shell, puoi buttar giù una sequenza di step, come una sequenza di programmi da eseguire per ognuno di questi step. Così, possiamo vedere una versione primitiva di un sistema di scripting. Esempio 1 : Per compilare file multipli in un unico file eseguibile: gcc –c sourcefile1.c gcc –c sourcefile2.c ln –o program sourcefile1.o sourcefile2.o 3.1.1 Gestione del processo delle finestre Un approccio al processo di gestione è quello di aggiungere una sistem call per creare un processo, e altre system call per le altre operazioni del processo. Questo risulta molto più semplice in teoria ma complesso nella pratica. In Windows, c’è una routine chiamata, CreateProcess, chiamata nel seguente modo: boolean CreateProcess(char *prog, char *args); Chiameremo il processo creatore il parent, e il processo che è stato creato, il figlio. Di quali step abbiamo bisogno per fare una CreateProcess? Come spiegato nel capitolo scorso abbiamo bisogno di:

  • Creare e inizializzare il PCB nel kernel
  • Creare e inizializzare un nuovo spazio di indirizzamento
  • Caricare il programma (prog) nel nuovo spazio di indirizzamento
  • Copiare gli argomenti (args) nelle memoria del suo spazio di indirizzamento
  • Inizializzare il constesto hardware per poter iniziare l’esecuzione al comando “start”
  • (^) Informare lo scheduler che un nuovo processo è pronto per essere eseguito. Sfortunatamente, ci sono alcuni aspetti del processo che il padre dovrebbe controllare, come: i suoi privilegi, dove invia gli input/output, dove dovrebbe memorizzare i suoi file, cosa usare come priorità di schedulazione, e molto altro. Non possiamo fidarci del processo figlio per settare i propri privilegi o priorità, e sarebbe inconveniente aspettarsi che ogni applicazione include il codice per capire il suo contesto. Così l’interfaccia reale è un po’ più complicata in pratica. FIGURA 3. 3.1.2 Processo di gestione UNIX Unix usa differenti approcci per la gestione di un processo, uno dei quali è complicato nella teoria ma semplice nella pratica. UNIX divide “CreateProcess” in due step, chiamati fork e execute , illustrati nella figura 3. Figura 3. La fork UNIX crea una copia completa del processo padre, con un’unica chiave d’eccezione. (Abbiamo bisogno in qualche modo di distinguere quale copia è il padre e qual è il figlio). Il processo figlio setta i suoi privilegi, priorità e l’I/O per il programma che dovrebbe essere startato, per esempio, chiudendo alcuni file, aprendone altri, riducendo la sua prirotià se sta lavorando in background etc. Poichè il figlio sta eseguendo esattamente lo stesso codice del padre, esso è verificato per creare il contesto di un nuovo programma correttamente. Una volta che il contesto è settato, il processo figlio chiama la exec UNIX. Quest’ultima prende la nuova immagine eseguibile nella memoria e inizia a farla partire. Può sembrare uno spreco fare una copia completa del processo padre, solo per sovrascrivere la copia che abbiamo preso nella nuova immagine eseguibile nella

istruzioni, ed entrare in pausa prima di procedere al prossimo step. Sarebbe duro costruire una shell senza la wait.

Comunque, la chiamata di atezza è opzionale in Unix. Peresempio in Chrome non è necessario aspettare fin quando la fork non ha finito. In ogni modo, molte shell di UNIX hanno un’opzione per eseguire operazioni in background, semplicemente concatentando ‘&’ alla linea di comando. (Come per la fork anche la wait è un po’ ambigua). E’ utilizzata per mettere in attesa il processo corrente nell’attesa che un altro processo finisca; ma è anche utilizzata nella sincronizzazione dei thread, per aspettare su una variabile di condizione. Per disambiguare, utilizzeremo il termine “UNIX wait” per riferisci alla chiamata wait di UNIX. Stranamente, aspettare il completamento di un thread è una funzionalità chiamata “ thread join” , molto analogia alla UNIX wait. Windows ha semplicemente una funziona chiamata “WaitForSingleObject” che aspetta il completamento di un processo, thread, o si mette in sospensione su una variabile di condizione. Infine, come definito nello scorso capito, UNIX fornisce una facilitazione per un processo di inviare ad un altro processo notifiche o upcall. In UNIX, la notificazione è inviata tramite la chiamta signal. I segnali sono usati per terminare un’applicazione, sospenderla per debuggarla, rieseguirla dopo una sospensione, scadenza del timer, e altri motivi. Nel caso di default, quando un applciazione non ha un gestore di segnali, il kernel implementa uno standard per se stesso.

3.2 Input/Output I computer hanno una larga scala di input e output device. Per trattare con tutti questi, dovremmo specializzare l’interfaccia delle applicazione per ogni device, adattandola ad ogni sua specifica caratteristica (del device in considerazione). Dopo tutto, un disco è un po’ differente da una rete ed entrambi sono diversi da una tastiera. Nei primi computer si prese l’approccio di specializzare l’interfaccia per i device, ma ebbe un significativo svantaggio: ogni volta che un nuovo tipo di device è intentato, l’interfaccia della system call dovrebbe essere aggiornata per gestire quel device. Una delle prime innovazioni in UNIX era quella di regolarizzare tutti i device input e output dietro una singola interfaccia comune. Infatti, UNIX fece un enorme passo avanti: usava la stessa interfaccia per leggere e scrivere i file per per le comunicazioni tra interprocessori. Questo approccio è stato così di successo che è tutt’ora utilizzato nei nostri sistemi. Definiremo in questa sezione tale interfaccia, e nella prossima, come usarla per costruire una shell. Le idee di base dell’interfaccia I/O di UNIX sono:

  • Uniformità. Tutti i device I/O, operazioni sui file e le comunicazioni tra interprocessori usano lo stesso insieme di system call: aprire, chiude, leggere e scrivere.
  • Aprire prima di usare. Prima che un applciazione faccia I/O, deve prima chiamare l’apertura di un device, file o un canale di comunicazione. Questo da al sistema operativa una chance di controllare i permessi di accesso e di gestire la contabilità interna. Alcuni device, come la stampante, permettono un solo accesso alla volta – l’”open call” restituisce un errore se il device è in uso.
  • Orientato ai byte. Tutti i device, anche quelli che trasferiscono blocchi di dati a dimensione fissa, sono acceduto con array di byte. Similmente, i file le i canali di comunicazioni sono acceduti in termini di byte, memorizzando anche strutture dati nei file ed inviandoli attraverso i canali.
  • Lettura nel kernel bufferizzato. Lo stream dei dati, sottoforma di rete o tastiera, è memorizzato nel kernel buffer e restituito alle applicazioni che lo richiedendo. Questo permette alla interfaccia di system call in lettura di essere la stessa per i device di lettura in streaming da quelli in lettura a blocchi, come dischi e flash memory. In entrambi i casi, se nessun dato è disponibile immediatamente, la chiamata di lettura si blocca fin quando non arrivano, concedendo il processore a qualche altro task pronto per svolgere il proprio lavoro.
  • Scrittura nel kernel bufferizzato. In ogni modo, i dati in uscita sono memorizzati nel kernel buffer per la trasmissione quando un device diventa disponibile. Nel caso normale, la system call di scrittura copia i dati dal kernel del buffer e li restituisce immediatamente. Questo disaccopia le applicazioni dai device, permettendo ognugna di andare alla propria velocità. Se le applicazioni generassero dati molto più velocemente di quanto un device possa ricevere, la system call di scrittura si blocca fin quando il kernel ha sufficente spazio per memorizzare dati nuovi nel buffer.
  • (^) Chiusure esplicite. Quando un applicazione ha finito con il device o file, chiama la chiusura. Questi segnali per il sistema operativo, permettono di decrementare il reference-count nei device, e utilizzare il garbage collector per ogni struttura dati del kernel inutilizzata. Per una comunicazione tra interprocessori, abbiamo bisogno di alcuni concetti: Figura 3.
  • Pipe. Una pipe UNIX è un buffer del kernel con due file descrittori, uno per la scrittura (per mettrere dati nella pipe) e una per la lettura ( per estrarre dati dalla pipe), come illustrato nella figura 3.6. I dati sono letti esattamente nella stessa sequenza in cui sono stati scritti, ma fin quando i dati sono bufferizzati, l’esecuzione del problema “produttore-consumatore” può essere disaccoppiata, riducendo l’attesa nei casi comuni. La pipe termina quando viene chiuso il canale o esce.
  • (^) Rimpiazzare il file descriptor. Manipolando i file descriptor del processo figlio, la shell può utilizzare il figlio per leggere i suoi input, o infiare i suoi output, al file o una pipe invece di utilizzare la tastiera o lo schermo. In questo modo, il processo figlio non ha bisogno di essere consapevole di chi sta fornendo o consumando i suoi I/O. La shell usa questa redirezione usando un sistema speciale chiamato dup2(from,to) che rimpiazza il file descriptor con una copia di un altro file descriptor.
  • Attesa di letture multiple. Per una computazione client-server, unserver potrebbe avere una pipe per processi client multipli. Normalmente, la lettura si bloccherà se non c’è nessun dato da leggere, e potrebbe essere inefficiente per un server controllare ogni pipe se si vuole estrarre ma non c’è nessun lavoro da fare. La system call UNIX select(fd[],number) è indirizzato a questo. La select permette al server di aspettare un input da ogni insieme di file descriptor; restituisce un file descriptor che ha dati, ma non fa lettura di dati. Windows ha un equivalente funzione chiamata WaitForMultipleObjects. Figura 3. 3.3 Implementazione di una Shell Le dozzine di system call UNIX illustrate nella figura 3.7, sono abbastanza da poter costruire una flessibile e potente linea di comando, la quale è eseguite interamente a livello utente senza nessun speciale permesso. Come menzionato,il processo che crea la shell è responsabile per l’apertura di un file descriptor per la lettura dei comandi dagli input, chiamato stdin and per la scrittura degli output, chiamato stdout. Figura 3. La figura 3.8 illustra il codice delle operazioni di base di una shel. La shell legge una linea di comanda dall’input, e forka un processo per eseguire quel comando. La fork automaticamente suplica tutti i file descrittori aperti nel padre, incrementando il reference-count per ognuno di questi file descriptor, così l’input e l’output del figlio è lo stesso del padre. IL padre aspetta che il figlio finisca prima di leggere il prossimo comando da eseguire. Poichè il comando di lettura e scrittura di apertura di un file descriptor è lo stesso se il file descriptor rappresenta una tastiera, schermo, file, device o pipe, il programma UNIX non ha bisogno di preoccuparsi da dove viene il suo input, o dove il suo output è ridiretto. Questo è utile in molti modi:
  • Un programma può essere un file di comandi. Un programma normalmente è un insieme di istruzioni macchina, ma su UNIX un programma può essere un file contente una lista di comandi per una shell che li interpreta. Per disambiguare, i programmi shell sono identificati in UNIX mettendo “#! interprete” alla prima linea di codice del file, dove “interprete” è il nome della sheel eseguibile.

Figura 3. La figura 3.9 illustra come due processori comunicano attrraverso il sistema operativo nella relazione produttore-consumatore. Via shell, stabiliamo una pipe tra il produttore e il consumatore. Un processo calcola e produce uno stream di dati output, ed emette tali dati come un sequenza per la system call di scrittura nella pipe all’interno del kernel. Ogni scrittura può avere una dimensione variabile. Assumentdo che è presente una stanza all’interno del kernel buffer, il kernel copia i dati nel buffer e li restituisce dirrettamente indietro al produttore. In qualche punto più in la, il sistema operativo schedulerà il processo e lo eseguirà. Il consumatore eseguegue una serie di chiamate di lettura. Poichè la pipe è solamente uno stream di byte, il consumatore può leggere i dati in uscita in blocchi di dimensione variabile. Ogni system call in lettura fatta dai consumatori restituisce il prossimo blocco di dati in uscita dal kernel buffer. Il processo consumatore può calcolare il suo input, ed inviare il suo output nello schermo, file o al prossimo consumatore. Il kernel buffer permette ad ogni processo di essere eseguito alla propria velocità. Non c’è alcun requisito che ogni processo deve avere esattamente lo stesso lavoro da fare. Se il produttore è più veloce del consumatore, il kernel buffer riempie, e quando il produttore tenta di scrivere in un buffer pieno, il kernel entra in stallo fin quando non può memorizzare quei dati. Allo stesso modo succede per il produttore. In Unix, quando il produttore finisce, chiude la sua parte di pipe, ma ancora ci potrebbero essere dati nella coda del kernel da parte del consumatore. Eventualmente, il consumatore legge l’ultima parte dei dati, e la system call di lettura restituisce un “end of file”. Ad ogni modo, per il consumatore, non c’è nessuna differenza tra leggere da una pipe e leggere un file. Utilizzando il kernel buffer per scomporre l’esecuzione del produttore e del consumatore vengono ridotti il numero e il costo dei cambi di contesto. I computer moderni fanno un’esteso uso di mascheramento dell’hardware per migliorare le performance, ma i mascheramenti diventano inefficienti se il programma viene eseguito solo per un breve periodo prima di passare il processore ad un altro task. Il kernel buffer permette al sistema operativa di eseguire ogni processo abbastanza a lungo per beneficiare del riuso, piuttosto che alternare il produttore e il consumatore in ogni system call.