Sistemi operativi: Processi

Iniziamo questo piccolo viaggio all’interno dei moderni sistemi operativi comprendendo cosa succede ai programmi quando vengono caricati nella memoria centrale (RAM) e diventano processi. L’articolo si riferisce al capitolo 3 del libro “Sistemi operativi. Concetti ed esempi.” di  Abraham SilberschatzPeter Baer GalvinGreg GagneR. Melen, decima edizione.

Per una maggiore comprensione del testo, occorre una discreta conoscenza del linguaggio di programmazione C (qui i migliori da noi consigliati) e Java (qui i migliori da noi consigliati)

Programmi e processi

Un programma è definito come un’entità passiva presente su di un file su disco che contiene una lista di istruzioni (detto eseguibile). Un programma diventa un processo quando il suo eseguibile è caricato in memoria centrale (RAM);

In tal caso un processo è un’entità attiva (programma in esecuzione) con:

Lo stato di un processo è rappresentato dal valore del contatore di programma e dal contenuto dei registri nel processore. La struttura di un processo nella memoria centrale si presenta con sezioni nel seguente ordine:

  1. Sezione di testo contenente il codice eseguibile
  2. Sezione dati dove sono collocate le variabili globali
  3. Heap  memoria allocata dinamicamente durante l’esecuzione del programma
  4. Stack  memoria utilizzata durante le chiamate di funzioni ad esempio per i parametri di funzione di indirizzi di ritorno e le variabili locali
Area della memoria occupata da un processo
L’area di memoria occupata da un programma

Sia la sezione di testo che la sezione di dati presentano dimensioni fisse, (non cambiano durante l’esecuzione del programma), viceversa, le sezioni stack e heap  modificano la loro dimensione in maniera dinamica durante l’esecuzione del programma,  basta considerare che ogni qualvolta si richiama una funzione (restituisce un valore) o una procedura (void, non restituisce nulla) nel codice, nello stack viene inserito un record di attivazione con i parametri della funzione, le sue variabili locali e l’indirizzo di ritorno

Per definizione, sia la sezione stack che la sezione heap crescono l’una verso l’altra ed è compito del sistema operativo garantire che non si sovrappongono.

esempio codice C con sezione testo e comportamento stack in evidenza per un processo
Esempio della sezione testo di un processo e composizione dello stack

Due processi distinti, anche se dello stesso programma, non equivalgono allo stesso processo, perché presentano solo le stesse sezione dati e sezione testo, mentre le rimanenti sezioni (stack e heap)  sono diverse. 

LINUX                                                                                                         

Il comando size è usato per determinare la dimensione in byte delle sezioni per un programma in memoria. Per esempio, può essere invocato sull’eseguibile della Bash:

ale@FX705GE:~$ size /usr/bin/bash  
text    data     bss     dec     hex filename
1112469   47356   40056 1199881  124f09 /usr/bin/bash

Il campo data si riferisce a dati inizializzati,  il campo bss Block Start by Symbol fa riferimento ai dati non inizializzati. Per esempio, l’eseguibile Bash ha una sezione testo di dimensione 1112469 byte (campo dec).

struttura di un processo con le sezioni presentate dal comando size
La struttura di un processo letta dal comando size

Stati di un processo

Ogni processo è soggetto a cambiamenti del suo stato. Esistono 5 stati:

  1. Nuovo: il processo è stato creato
  2. Esecuzione: si eseguono le istruzioni del processo
  3. Attesa: il processo è in attesa di un evento
  4. Pronto: il processo attende di essere assegnato ad un unità di elaborazione
  5. Terminato: il processo ha terminato l’esecuzione
Diagramma stati ed evoluzione di un processo
Diagramma di stato per un processo

Ciascuna unità di elaborazione presenta un solo processo in esecuzione anche se molti processi potrebbero essere pronti o in attesa.  

Nei sistemi operativi si rappresenta il processo attraverso un blocco di controllo PCB (process control block o task control block) che contiene anche le informazioni connesse ad un specifico processo, cioè:

  • Stato del processo (uno dei 5 possibili)
  • Contatore di programma ovvero l’indirizzo della successiva istruzione da eseguire del processo
  • Registri della CPU (che dipendono dall’architettura CPU)
  • Informazioni sullo scheduling di CPU ovvero la priorità di processo, i puntatori alle code di scheduling e tutti gli altri parametri attinenti allo scheduling
  • Informazioni sulla gestione di memoria
  • Informazione di accounting quali la quota d’uso della CPU il tempo di utilizzo
  • Informazioni sullo stato del input-output ovvero la lista di dispositivi di input output assegnati ad un determinato processo l’elenco dei file aperti e così via

Dunque un PCB process control block dispone di tutte le informazioni relative al processo

LINUX                                                                                                             

Nel file <linux/sched.h> è possibile individuare una struttura dati C, detta task_struct, che rappresenta il process control block PCB. Questa struttura descrive un processo indicando:

  • Stato
  • Le informazioni sullo scheduling
  • Le informazioni sulla gestione della memoria
  • La lista dei file aperti
  • I puntatori al processo padre e un elenco dei suoi figli e fratelli.
    • Il padre (o genitore) di un processo è il processo che lo ha creato
    • i figli sono processi generati dal processo padre
    • i fratelli sono processi con lo stesso padre

Nel kernel Linux, l’insieme dei processi attivi è rappresentato da una lista doppiamente concatenata di task_struct, il kernel mantiene un puntatore di nome current al processo attualmente in esecuzione.

Scheduling dei processi

L’obiettivo della multiprogrammazione è di avere sempre un processo in esecuzione, per massimizzare l’uso della CPU.

L’obiettivo del time-sharing, invece, è di commutare l’uso della CPU fra i vari processi spesso, di modo che gli utenti possono interagire con ciascun programma mentre è in esecuzione.

time sharing con 6 utenti che usano una sola CPU
Idea del time-sharing con più utenti

Il raggiungimento di questi obiettivi è possibile attraverso lo scheduler dei processi, che seleziona un processo da eseguire da un insieme di processi disponibili. È ovvio che se ogni core della CPU può eseguire un solo processo alla volta, aumentando il numero di core disponibili aumenta il numero di processi eseguibili.

Quad core AMD Opteron processor
I quattro core del processore AMD Opteron

Se vi sono più processi che core, i processi in eccesso devono attendere che un core sia libero. Diremo grado di multiprogrammazione il numero di processi in memoria in un istante di tempo. Per bilanciare gli obiettivi della multiprogrammazione e time-sharing, suddividiamo i processi in due categorie:

  • processo I/O bound se impiega la maggior parte del proprio tempo nell’eseguire operazioni di input-output (ad esempio scrive delle configurazioni sui file o invia un documento alla stampante).
  • processo CPU bound  se impiega la maggior parte del proprio tempo nell’elaborazione (ad esempio operazioni aritmetiche su variabili intere contenute nei registri).
lista di processi con uso della struttura PCB
La lista dei process control block

Ogni processo viene inserito nella coda dei processi pronti (ready queue) definita mediante una lista concatenata. L’intestazione della coda contiene il puntatore al primo PCB process control block, mentre ogni PCB process control block presenta un puntatore al successivo process control block (PCB) della coda.

Fintantoché non vi è un core libero per poter eseguire il processo, questo rimane nella coda dei processi pronti ready queue in attesa di essere eseguito. I processi in stato di attesa di un determinato evento vengono collocati nella coda di attesa wait queue

Una volta in stato di esecuzione, il processo potrebbe passare dallo stato di esecuzione allo stato di attesa per via di uno dei seguenti eventi:

  • viene evocata una richiesta di input-output da parte del processo, quindi viene inserito in una coda di attesa I/O
  • il processo genera un processo figlio e ne attende la terminazione
  • il processo viene rimosso dalla CPU per via di un’interruzione per poi essere inserito nella coda dei processi pronti
Code dei processi in un sistema operativo
La coda dei processi pronti e l’insieme delle code dei processi in attesa

Si evidenzia dal diagramma di accodamento che, in realtà, esistono più wait queue, dove le frecce del diagramma indicano il flusso di processi nel sistema. 

Leggi anche:  Come costruire un Server NextCloud con un vecchio portatile

Terminata l’attesa, il processo passa dallo stato di attesa allo stato pronto, quindi viene nuovamente inserito nella coda di processi pronti ready queue. Tutto ciò si ripete ciclicamente fintanto che il processo non esaurisce la sua utilità;

Al termine del ciclo di vita del processo, questo viene rimosso da tutte le code e si procede alla deallocalizzazione del suo PCB process control block.

Cambio di contesto

In un sistema general purpose, le interruzioni sono eventi comuni. Quando si presenta un’interruzione, il sistema operativo esegue un salvataggio dello stato corrente della CPU, cioè salva il contesto del processo corrente memorizzando i valori dei registri della CPU, lo stato del processo e le informazioni sulla gestione della memoria (ovvero il contesto). Il ripristino delle stato consente di poter riprendere l’elaborazione dal punto in cui si era interrotta.

esempio di cambio di contesto tra due processi
Un esempio di cambio di contesto tra due processi P0 e P1

La procedura di salvataggio e ripristino dello stato attinenti ai processi è nota come cambio di contesto context switch.

All’occorrenza di un cambio di contesto, il sistema salva il contesto attinente al processo in esecuzione nel suo PCB process control block, per poi caricare il contesto del processo successivo.

Il cambio di contesto è, quindi, puro overhead visto che è definito da operazioni di gestione dei processi e non alla relativa esecuzione. La durata di questo cambio di contesto dipende molto dall’architettura e dal numero di registri presenti nella stessa (ad esempio una architettura RISC risulta essere avvantaggiata per via del numero di registri superiore rispetto all’architettura CISC).

Creazione dei processi

diagramma di creazione nuovo processo con la system call fork
Creazione di un nuovo processo con la system call fork()

Un processo in esecuzione può creare numerosi nuovi processi. Diciamo processo genitore (o padre) il processo creante, mentre i processi creati vengono detti processo figlio. Il tutto è rappresentabile dal diagramma albero di processi.

Albero dei processi in un sistema Unix
Albero dei processi in un sistema Unix

Quasi tutti i sistemi operativi identificano un processo attraverso un intero univoco detto identificatore del processo o PID process identifier

Quando un processo padre genera un processo figlio, esistono due possibilità per ciò che riguarda l’esecuzione:

  1. Il processo padre continua l’esecuzione in modo concorrente con i propri processi figlio
  2. Il processo padre attende che uno o tutti i suoi processi figlio terminino l’esecuzione

Esistono ancora due possibilità per quanto riguarda lo spazio di indirizzi del nuovo processo:

  1. Il processo figlio può essere un duplicato del processo padre (stesso programma e dati del genitore, ma processi distinti)
  2. Nel processo figlio si può caricare un nuovo programma

Le differenze sono facilmente visibili attraverso uno sguardo al sistema operativo Unix (o Linux) e al sistema operativo Windows.

LINUX   

In Linux, dove si predilige il termine task anziché processo, il primo processo eseguito è systemd (avente PID process identifier sempre uguale a 1) ed è il padre di tutti i processi utente successivi. Seguono i processi figli logind (responsabile degli utenti  collegati direttamente al sistema) e sshd (secure shell daemon per le connessioni in remoto con ssh secure shell). Un figlio di logind può essere la shell bash, che viene utilizzata per richiamare determinati comandi. 

Albero dei processi Linux con systemd, logind, sshd e relativi processi figli
Albero dei processi in Linux con Systemd

Il comando pstree mostra la struttura ad albero di tutti i processi nel sistema.

ale@FX705GE:~$ pstree

In genere un processo figlio presenta PID process identifier pari a zero, mentre il processo padre presenta un PID process identifier assegnato all’atto della creazione.

Schema di relazione processo genitore e processo figlio in Linux
La relazione genitore – figlio in Linux

È compito del programmatore definire le azioni che il processo figlio deve svolgere,  oltre che verificare se il processo padre deve continuare con l’esecuzione delle sue istruzioni o attendere che il processo figlio termini l’esecuzione attraverso la chiamata di sistema wait().

WINDOWS

La creazione di processi Windows richiede l’uso della funzione CreateProcess() che richiede ben 10 parametri, tra i quali 8 specifici e due istanze delle strutture STARTUPINFO e PROCESS_INFORMATION (forniscono le proprietà del nuovo processo, quali dimensione della finestra, aspetto e riferimenti ai file input e output standard, detti anche handle). 

Creazione di un nuovo processo in C per il sistema operativo Windows
Creazione di un nuovo processo in C per il sistema operativo Windows

La funzione successiva è la WaitForSingleObject() poiché il processo genitore viene messo in attesa del processo figlio.

system call wait() in C per il sistema operativo Windows
Wait in C per Windows

Terminazione dei processi

Un processo termina la sua esecuzione attraverso la chiamata di sistema exit(). Il sistema operativo procede a liberare tutte le risorse del processo terminato (memoria fisica e virtuale, file aperti e arie della memoria per gli input output).

Un processo figlio che invoca la chiamata di sistema exit() può riportare l’informazione di stato al suo genitore, recuperata da quest’ultimo tramite chiamata di sistema wait().  È anche possibile che un processo genitore termini l’esecuzione di uno dei suoi processi figli per i seguenti motivi:

  • Il processo figlio eccede nell’uso di risorse assegnate.
  • Il compito svolto dal processo figlio non è più richiesto .
  • Il processo genitore termina e il sistema operativo blocca l’esecuzione del processo figlio.

È possibile che un processo genitore possa arrestarsi in maniera anomala lasciando orfani i suoi figli; In tal caso si parla di procedura terminazione a cascata quando il sistema operativo termina un processo genitore e procede a terminare anche i suoi figli.

LINUX   

In Linux (o Unix) un processo termina:

  • direttamente con la chiamata di sistema exit() 
  • indirettamente attraverso l’istruzione return nel main()

Un processo genitore attende la terminazione del processo figlio attraverso la chiamata di sistema wait() alla quale viene fornito come parametro un intero che permetta di conoscere lo stato del figlio.

int main(){
    pid_t pid;
    int status;
    ...
    pid = wait(&status);
    ...
    exit()
}

Alla terminazione del processo, la sua voce nella tabella dei processi rimane fintantoché il processo genitore non incontra la chiamata di sistema wait(); In questo caso si parla di processo zombie. Tutti i processi passano per lo stato di zombie in un breve periodo di tempo consecutivo alla loro terminazione e precedente alla wait() del padre.

Nel caso di terminazione di un processo padre con conseguenti processi figli orfani, il processo systemd  diventa il nuovo genitore dei processi orfani, con il compito di terminarli (ad esempio invocando periodicamente la wait()). È possibile che un altro processo possa ereditare i processi orfani per gestirne la terminazione (ad esempio il processo bash).

ANDROID   

In Android abbiamo i seguenti tipi di processo con relative priorità:

  • Processo in primo piano: il processo è visibile sullo schermo ed è l’applicazione con cui l’utente sta interagendo.
  • Processo visibile come processo non visibile in primo piano che esegue attività alle quali il processo in primo piano fa riferimento.
  • Processo di servizio similare al processo di background ma esegue un’attività che è visibile all’utente.
  • Processo in background ovvero processo che svolge un’attività non visibile all’utente.
  • Processo vuoto ovvero un processo che non contiene componenti attive associate un’applicazione.
gerarchia d'importanza dei processi in Android
Gerarchia d’importanza dei processi in Android

Per recuperare risorse di sistema, Android procede interrompendo prima i processi vuoti, poi i processi in background, poi quelli di servizio e così via.

Leggi anche:  Inventor Professional 2020-Tutorial-S01E01-Panoramica del software

Comunicazione tra processi

I processi eseguiti in concorrenza possono essere di due tipologie:

  1. Processo indipendente: se non influisce su altri processi o ne subisce l’influsso (non condivide dati con altri processi)
  2. Processo cooperante: se influenza o può essere influenzato da altri processi in esecuzione (può condividere dati con altri processi)

Disporre di un ambiente che permetta la cooperazione tra processi è utili per le seguenti ragioni:

  • La condivisione delle informazioni permette a più utenti possono accedere alle stesse informazioni.
  • La velocizzazione del calcolo perché l’attività è suddivisibile in sotto-attività eseguibili in parallelo.
  • La modularità è un altro requisito che richiede un ambiente dove i processi possono cooperare.

Il meccanismo che consente lo scambio di dati e informazioni fra i processi cooperanti viene detto comunicazione tra processi IPC inter process communication, il quale presenta due modelli fondamentali:

I due meccanismi di condivisione dati nell’IPC

Memoria condivisa

Un processo alloca una zona di memoria condivisa (solitamente nel suo spazio degli indirizzi) per permettere la comunicazione tra processi cooperanti e i processi interessati alla cooperazione possono accedere alla zona di memoria condivisa senza eseguire letture/scritture in simultanea.

Il modello produttore-consumatore è un comune paradigma per processi cooperanti; Un processo produttore produce informazioni inserite in una zona di memoria condivisa (buffer) che vengono prelevate e consumate da un processo consumatore. Il buffer può avere una dimensione fissa (buffer limitato) o non definita (buffer illimitato). Il modello si presta alla metafora del paradigma client server, dove il server è un produttore e i client sono i consumatori.

Scambio di messaggi

I processi possono scambiare i messaggi (a lunghezza fissa o variabile) attraverso le primitive send(msg) and receive(msg). È necessario un canale di comunicazione, realizzabile con uno dei seguenti metodi:

  • Naming suddiviso in comunicazione diretta o indiretta
  • Sincronizzazione suddiviso in comunicazione sincrona o asincrona
  • Code con buffer a gestione automatica o esplicita

Naming

Nella comunicazione diretta sia il trasmittente che il ricevente devono nominarsi a vicenda per poter comunicare. Quindi:

  • Ogni coppia di processi stabilisce automaticamente un canale per poter comunicare e i processi devono conoscere la reciproca identità
  • Un canale è associato a due processi dunque esiste una coppia di processi per ogni canale

Le primitive si modificano così:

  • send(processo_destinatario, messaggio)
  • receive(processo_mittente, messaggio)
Comunicazione tra processi con il metodo naming a comunicazione diretta
Naming a comunicazione diretta

Una variante della comunicazione diretta (asimmetrica nell’indirizzamento) prevede che solo il trasmittente nomini il ricevente.

La comunicazione indiretta, invece, richiede l’invio di messaggi a porte (o mailbox), cioè oggetti identificati in maniera univoca con cui i processi possono introdurre e prelevare messaggi.

mailbox tra processi
Mailbox tra processi

La comunicazione tra processi è possibile solo se questi condividono la stessa mailbox, quindi le primitive

  • send(mailbox_1, messaggio)
  • receive(mailbox_1, messaggio)

presenteranno come primo parametro la mailbox alla quale inviare o ricevere il messaggio.

Lo schema richiede un canale di comunicazione con le seguenti caratteristiche:

  • Un canale di comunicazione si stabilisce tra due processi soltanto se condividono la stessa mailbox.
  • Un canale di comunicazione può essere associato a più di due processi.
  • Tra ogni coppia di processi comunicanti possono essere presenti più canali differenti, ciascuno corrispondente ad una specifica mailbox.

Alla presenza di più di due processi si ha il problema di capire quale processo dovrà ricevere il messaggio; 

La soluzione al problema dipende dallo schema scelto tra questi:

  • Un canale di comunicazione è associabile solo a due processi.
  • La primitiva receive() può essere eseguita da un solo processo alla volta.
  • Il sistema decide arbitrariamente quale processo riceverà il messaggio attraverso un algoritmo di selezione

Una mailbox può appartenere o ad un processo o al sistema.  Se la mailbox appartiene ad un processo, al  termine di quest’ultimo è necessario informare tutti i processi che inviano messaggi alla suddetta mailbox. Se la mailbox appartiene al sistema, invece, essa ha vita autonoma e non è legata a nessun processo in particolare. È compito del sistema operativo offrire un meccanismo che permette ad un processo di eseguire le operazioni di creazione, rimozione, invio e ricezione messaggi mediante mailbox. 

Il diritto di proprietà di una mailbox può passare ad altri processi attraverso opportune chiamate di sistema.

Sincronizzazione

In questa metodologia lo scambio dei messaggi può essere sincrono (bloccante) oppure asincrono (non bloccante). Da ciò discendono quattro tipologie:

  1. Invio sincrono il processo che invia il messaggio si blocca nell’attesa che il processo ricevente la mailbox riceve il messaggio
  2. Invio asincrono il processo invia il messaggio e riprende la propria esecuzione
  3. Ricezione sincrona il ricevente si blocca nell’attesa dell’arrivo di un nuovo messaggio
  4. Ricezione asincrona il ricevente riceve un messaggio valido o un valore nullo

Parliamo di rendez-vous qualora le primitive send() and receive() sono bloccanti.

Code

Coda dei messaggi per la comunicazione tra processi
Esempio di una coda di messaggi

In questa metodologia si enfatizza la dimensione della coda che gestisce i messaggi. Tre modi possibili per realizzare la coda:

  • Capacità zero (sistema scambio di messaggi senza buffering): la coda non ha lunghezza pertanto il canale di comunicazione non può avere messaggi in attesa, quindi il mittente deve fermarsi finché il ricevente non preleva il messaggio
  • Capacità limitata: la coda presenta una lunghezza finita, qundi il mittente deve controllare la disponibilità della coda:
    • se la coda non è piena, può proseguire la sua esecuzione.
    • se la coda è piena, deve mettersi in attesa fintanto che la coda non sia più piena.
  • Capacità illimitata presentando una lunghezza di coda infinita, il numero di messaggi interno è indefinito e il mittente non si ferma mai.

Le code capacità limitata e capacità illimitata vengono chiamate sistema con buffering automatico

Le due metodologie possono coesistere in un unico sistema per via dei vantaggi apportati dai rispettivi modelli.

MEMORIA CONDIVISA IN POSIX

Lo standard POSIX fornisce vari meccanismi per la comunicazione tra processi IPC, tra le quali anche la memoria condivisa. Nello standard si associa ad un file una regione di memoria condivisa, dunque un processo deve creare un oggetto memoria condivisa attraverso la chiamata di sistema:

fmc = shm_open(name, O_CREAT | O_RDWR, 0666);
  • Il parametro name specifica il nome dell’oggetto memoria condivisa, di modo che qualunque altro processo potrà accedervi semplicemente specificando il nome dell’oggetto memoria condivisa.
  • Il secondo parametro O_CREAT | O_RDWR definisce sia la richiesta di creare l’oggetto memoria condivisa che aprirlo in modalità lettura e scrittura.
  • L’ultimo parametro 0666 definisce le autorizzazioni di directory (disponibile a tutti gli utenti)

La chiamata di sistema restituisce un intero usato come descrittore di file per l’oggetto memoria condivisa (i file vengono trattati con un int, verificato anche sul codice Linux sulla struct_file). 

Il passaggio successivo alla creazione dell’oggetto richiede di specificare la dimensione di questo attraverso la funzione

ftruncate(fmc, 4096);
  • Il primo parametro fmc è l’oggetto memoria condivisa creato in precedenza
  • Il secondo parametro 4096 specifica la dimensione dello stesso oggetto in byte (4096 byte)

La funzione 

ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fmc, 0);

crea un file mappato in memoria contenente l’oggetto memoria condivisa e restituendo un puntatore al file, che verrà utilizzato per accedere alla memoria condivisa. Il modello produttore consumatore può avvalersi della memoria condivisa nel seguente modo:

  • Il processo produttore deve definire l’oggetto memoria condivisa quindi scriverci e utilizzare un apposito flag MAP_SHARED per far sì che le modifiche apportate all’oggetto memoria condivisa siano visibili a tutti i processi che condividono lo stesso oggetto.
  • Il processo consumatore dovrà semplicemente leggere il contenuto della memoria condivisa infine invocare la funzione che rimuova il segmento di memoria condivisa dopo l’accesso del consumatore.
Leggi anche:  KDE Connect: come gestire le notifiche Android sul portatile
WINDOWS
Gestione della comunicazione tra processi nel sistema Windows

La funzione di scambio messaggi tra processi in Windows viene detta chiamata di procedura locale avanzata ALPC, che presenta delle similitudini con la chiamata di procedura remota RPC

La comunicazione tra due processi è data da un oggetto porta che può essere di due tipi: 

  1. Porte di connessione 
  2. Porte di comunicazione 

I processi server pubblicano gli oggetti porte di connessione rendendoli visibili a tutti i processi.

Quando un processo client vuole accedere ai servizi offerti da un processo server, apre un handle all’oggetto porta di connessione del server e invia una richiesta di connessione alla porta del server; 

Questo crea un canale costituito da una coppia di porte private (una per i messaggi client-server l’altra per i messaggi server-client) e restituisce un handle al client. È previsto, inoltre, un meccanismo di callback che permette a client e server di accettare le richieste anche quando sono in attesa di una risposta dal canale

Alla creazione di un canale ALPC si possono scegliere tre tecniche di scambio di messaggi:

  1. Se la dimensione dei messaggi è piccola (fino a 256 byte) viene utilizzata la coda di porta come deposito intermedio e i messaggi sono copiati da un processo all’altro.
  2. Qualora i messaggi sono più grandi vengono comunicati attraverso un oggetto sezione (una regione di memoria condivisa associata al canale).
  3. Nel caso la quantità di dati è più grande rispetto all’oggetto sezione si ricorre alle API che consentono ai processi server di leggere e  scrivere direttamente nello spazio degli indirizzi di un client.

Pipe

Le pipe rappresentano uno dei primi IPC interprocess communication nei sistemi UNIX, oltre che essere presenti anche in Windows.

  • Le pipe convenzionali permettono la comunicazione unidirezionale, inaccessibile dall’esterno e sulla stessa macchina  tra due processi padre figlio  attraverso il modello produttore consumatore, dove:
    • Il produttore scrive all’estremità dedicata alla scrittura detta write-end 
    • Il consumatore legge alla restante estremità dedicata alla lettura o read-end
pipe in linux per la comunicazione tra processi
Un esempio di pipe unidirezionale

Se si necessita di una comunicazione bidirezionale, è necessario disporre di due pipe convenzionali tra gli stessi processi. In UNIX vengono considerate come file speciali, per cui è sufficiente creare la pipe convenzionale nel processo padre: 

pipe(int fd[]);

per poi effettuare una chiamata di sistema fork(), di modo che il processo figlio possa ereditare il file associato alla pipe necessario alla comunicazione. Ciò assicura che la comunicazione non sia accessibile dall’esterno e che sia possibile usare le chiamate di sistema read() e write(), trattando la pipe come normali file. Permettono di trasmettere solo dati byte-oriented.

Le pipe convenzionali sono presenti in Windows come pipe anonime, comportandosi allo stesso modo.

Le named pipe, invece, costituiscono uno strumento di comunicazione migliorato della pipe convenzionale o anonima poiché la comunicazione può essere bidirezionale half-duplex tra processi residenti sulla stessa macchina, per i quali non è necessaria la relazione padre figlio. In UNIX le named pipe sono dette FIFO e appaiono come normali file all’interno del file system. Vengono create mediante la chiamata di sistema

mk_fifo();

per poi essere manipolate con le usuali chiamate di sistema open(), read(), write(), close(), cioè come normali file. Le named pipe su sistema Windows risultano essere più ricche poiché permettono la comunicazione full-duplex e i processi comunicanti non devono per forza risiedere sulla stessa macchina, inoltre, possono trasmettere dati sia byte-oriented sia message-oriented.

Un esempio: GOOGLE CHROME

Con l’evoluzione della tecnologia, le pagine web godono di contenuti attivi come JavaScript flash e html5,  richiedendo al browser di gestire più siti contemporaneamente evitando possibili crash. Il browser Chrome di Google è progettato per affrontare tale problema con l’utilizzo di un’architettura multi processo. Chrome identifica tre diversi tipi di processi:

  1. Processo del browser che è responsabile della gestione dell’interfaccia utente e degli input-output da disco e da rete (ne esiste uno per ogni istanza di Chrome). 
  2. Processi render che contengono la logica per il rendering delle pagine web (quindi gestiscono HTML, JavaScript, immagini, …). Viene creato un nuovo processo di rendering per ciascun sito web aperto in una nuova scheda.
  3. Processo plugin contiene il codice per i plugin (Flash, QuickTime) e del codice aggiuntivo che permette al plugin di poter comunicare con i processi render associati e con il processo browser.

Il vantaggio di questo approccio multi processo è che un sito web viene eseguito in maniera isolata rispetto agli altri,  quindi se va in crash influenzerà solo il suo processo rendering mentre tutti gli altri processi rimangono illesi.

Chrome Adotta anche la modalità di esecuzione sandbox sui processi render di modo che l’accesso al disco alla rete siano limitati al fine di limitare gli effetti negativi dati da eventuale exploit di sicurezza.

Nota storica

Prima della definizione di processo, si consideravano sistemi batch (lotto) che eseguivano job (lavoro), dai quali discendono i moderni sistemi time sharing che eseguono programmi utente o task (attività). Per questo motivo, molte delle terminologie attinenti i processi presentano ancora la parola job in letteratura.

I programmatori preparavano i programmi su delle schede perforate e li passavano ad un amministratore di sistema, che aveva il compito di mandarli in esecuzione quando possibile. I calcolatori erano enormi e costosi mainframe, eseguivano una e una sola applicazione alla volta e per accelerare l’elaborazione lavori simili venivano raggruppati in lotti, da qui il termine di sistema batch.

Il sistema operativo di tali dispositivi consisteva nel trasferire automaticamente il controllo da un lavoro al successivo ed era sempre residente in memoria.

Organizzazione della memoria nei sistemi batch (pre processi)
Organizzazione della memoria nei sistemi batch

Per oggi è tutto, la settimana prossima continuiamo con i thread (capitolo 4). Vi invito a condividerlo e a commentare qui o sul forum. Prima di salutarvi vi ricordo:

  • di iscrivervi al nostro CANALE YOUTUBE ed attivare la CAMPANELLA per le NOTIFICHE (soprattutto per chi ama la programmazione MOBILE su sistemi Apple)
  • non tutti i video hanno un articolo alle spalle ed allora ecco il link per il LOGIN al sito (in alto a destra)
  • infine ecco tutti i nostri social dove potrete trovare di tutto! INSTAGRAM , FACEBOOK , TELEGRAM e TWITTER

Internet è stracolma di occasioni per risparmiare e di offerte interessanti per accaparrarsi un oggetto desiderato, magari senza dare in sacrificio uno dei propri organi non strettamente fondamentali alla sopravvivenza.
Proprio in quest’ottica noi VI CONSIGLIAMO Riprovaci.it e i suoi canali Telegram.

Ecco i link per iscrivervi direttamente ai canali:

CANALE PRINCIPALE

(offerte a tutto tondo: grandi e-commerce on-line come Amazon, Banggood, Gearbest ecc
ma anche store con negozi fisici come Unieuro, Mediaworld ecc
e poi ancora tante altre soluzioni per risparmiare)

CANALE OFFERTE LAMPO

(dedicato esclusivamente alle offerte lampo, quelle a tempo o quantità limitati, di Amazon

Iscrivetevi! 
E iniziate a risparmiare!

Author: Alessandro Giaquinto

Rispondi