Programmazione di sistema e gestione dei processi in C.

Slides:



Advertisements
Presentazioni simili
INFORMATICA Altre Istruzioni di I/O
Advertisements

PROCESS MANAGEMENT: STRUTTURE DATI
INFORMATICA Algoritmi fondamentali
Sottoprogrammi: funzioni e procedure
CONCLUSIONE - Nucleo (o Kernel) Interagisce direttamente con lhardware Interagisce direttamente con lhardware Si occupa dellesecuzione.
1 Introduzione ai calcolatori Parte II Software di base.
Programmazione concorrente
Procedure e funzioni A. Ferrari.
SC che operano su processi
Fondamenti di Informatica I CDL in Ingegneria Elettronica - A.A CDL in Ingegneria Elettronica - A.A Strutture dati dinamiche.
Fondamenti di Informatica I CDL in Ingegneria Elettronica - A.A CDL in Ingegneria Elettronica - A.A Strutture dati dinamiche.
I segnali.
1 System Call per Comunicazione tra Processi Pipe.
Il Software: Obiettivi Programmare direttamente la macchina hardware è molto difficile: lutente dovrebbe conoscere lorganizzazione fisica del computer.
Indirizzi delle variabili A ogni variabile sono associati tre concetti fondamentali: il valore memorizzato; il tipo dati di appartenenza; lindirizzo. Il.
Caratteri e stringhe di caratteri
Argomenti dalla linea dei comandi Gli argomenti possono essere passati a qualsiasi funzione di un programma, compresa la main(), direttamente dalla linea.
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Funzioni definite dall’utente
PROGRAMMI DI RICERCA E ORDINAMENTO
Prof.ssa Chiara Petrioli -- Fondamenti di programmazione, a.a. 2009/2010 Corso di Fondamenti di programmazione a.a. 2009/2010 Prof.ssa Chiara Petrioli.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE File Marco D. Santambrogio – Ver. aggiornata al 9 Maggio 2012.
Laboratorio di Linguaggi lezione VIII Marco Tarini Università dellInsubria Facoltà di Scienze Matematiche, Fisiche e Naturali di Varese Corso di Laurea.
Threads: Sistemi Operativi I Corso di Laurea in Ingegneria Informatica
I Thread.
Software di base Il sistema operativo è un insieme di programmi che opera sul livello macchina e offre funzionalità di alto livello Es.organizzazione dei.
Approfondimento delle classi
memoria gestita staticamente:
Sistemi Operativi GESTIONE DEI PROCESSI.
Strutture dei sistemi di calcolo Funzionamento di un sistema di calcolo Struttura di I/O Struttura della memoria Gerarchia delle memorie Architetture di.
1 LINUX: struttura generale The layers of a UNIX system. User Interface.
Il sistema operativo Vito Perrone
Strutture di controllo in C -- Flow Chart --
I File.
Sistemi Operativi - Shell 1 Elementi di programmazione concorrente Niccolo` Battezzati Politecnico di Torino Dip. Automatica e Informatica.
INTRODUZIONE l sistema operativo è il primo software che lutente utilizza quando accende il computer; 1)Viene caricato nella memoria RAM con loperazione.
1 ListaDiElem Cancella( ListaDiElem lista, TipoElemento elem ) { ListaDiElem puntTemp; if( ! ListaVuota(lista) ) if( lista–>info == elem ) { puntTemp =
CODIFICA Da flow-chart a C++.
Il linguaggio C Le funzioni C Language Il passaggio dei parametri
1 Scheduling in Windows 2000 Un thread entra in modalità kernel e chiama lo scheduler quando: Si blocca su un oggetto di sincronizzazione (semaforo, mutex,
Prof.ssa Chiara Petrioli -- Fondamenti di programmazione, a.a. 2009/2010 Corso di Fondamenti di programmazione a.a. 2009/2010 Prof.ssa Chiara Petrioli.
Unità Didattica 3 Linguaggio C
Laboratorio di Linguaggi lezione VII: variabili Globali e Locali Marco Tarini Università dellInsubria Facoltà di Scienze Matematiche, Fisiche e Naturali.
Esercizi Liste.
BIOINFO3 - Lezione 331 SUBROUTINE IN PERL Una subroutine (funzione, metodo, procedura o sottoprogramma), e` una prozione di codice all`interno di un programma.
2000 Prentice Hall, Inc. All rights reserved. Attivazione di funzioni La chiamata/attivazione di funzione viene indicata citando il nome della funzione.
Costruzione di una semplice Agenda telefonica Elettronica Esercizio sull'uso delle principali system call Unix.
Complessità di un algoritmo
1 I segnali. 2 Prima un po’ di teoria…... 3 Stati dei processi in UNIX Idle Sleeping Zombified Runnable Running Fork iniziata waitpid Fork terminata.
I processi.
T. MottaGenerazione e terminazione processi1 Creazione e terminazione dei processi Tommaso Motta
Prof.ssa Chiara Petrioli -- corso di programmazione 1, a.a. 2006/2007 Corso di Programmazione 1 a.a.2006/2007 Prof.ssa Chiara Petrioli Corso di Laurea.
La ricorsione.
Capitolo 6 Iterazione Lucidi relativi al volume: Java – Guida alla programmazione James Cohoon, Jack Davidson Copyright © The McGraw-Hill Companies.
Università di Torino – Facoltà di Scienze MFN Corso di Studi in Informatica Programmazione I - corso B a.a prof. Viviana Bono Blocco 7 – Array.
Allievi Elettrici - AA Le funzioni ricorsive in C
1 SC che operano su processi Getpid, fork, exec, wait, waitpid, exit, dup, dup2.
Relazione sulle strutture dati Svolta da: Buccella Simone Strutture di dati Aree di memoria Puntatore numericibooleani alfabetici Statici dinamici Puntatori.
I segnali.
Sistemi operativi di rete Ing. A. Stile – Ing. L. Marchesano – 1/18.
Linguaggio C: Le basi Stefano Cagnoni e Monica Mordonini
1 System Call che operano su processi Getpid, fork, exec, wait, waitpid, exit, dup, dup2.
Esercizi.
1 Laboratorio di Programmazione di Sistema - C Susanna Pelagatti Ricevimento: Me ,
1 System Call che operano su processi Getpid, fork, exec, wait, waitpid, exit, dup, dup2.
Il C `e un linguaggio di programmazione di uso generale, originariamente sviluppato per la scrittura del sistema operativo Unix, ed oggi disponibile su.
Conio.h contiene la dichiarazione di funzioni usate per richiamare alcune routine DOS di gestione del video e della tastiera. NECESSARIA PER system(‘’cls’’)
1 Informatica di Base Facoltà di Lingue e Letterature Straniere Corso di laurea in Relazioni Pubbliche.
10. Programmazione Ricorsiva Ing. Simona Colucci Informatica - CDL in Ingegneria Industriale- A.A
13. Strutture dati dinamiche Ing. Simona Colucci Informatica - CDL in Ingegneria Industriale- A.A
Transcript della presentazione:

Programmazione di sistema e gestione dei processi in C

Indice Dalla macchina astratta sequenziale C Alla macchina “parallela” –Processi e programmi Le primitive per la gestione dei processi –Creazione, sospensione, morte dei processi –Esecuzione dei programmi Semplici(ssime) applicazioni

Processo Una singola macchina astratta cui viene assegnato un compito –Non solo in ambito “informatico”: Processo chimico Processo industriale … –Sistema: Diversi processi che cooperano (o si coordinano, o competono) procedendo In parallelo Spesso in modo asincrono –Qui ci interessano processi realizzati mediante macchine astratte informatiche (C in particolare) Al momento si coordinano tra loro In futuro: –Internet –Controllo di impianti –Sistemi embedded –…

Processo C Macchina astratta C Che esegue un programma C

Sistema di processi su un calcolatore Processo 1: Macchina astratta C Programma 1 Processo 2: Macchina astratta C Programma 2 Processo n: Macchina astratta non C Programma n Hardware + Sistema Operativo Virtualizzazione

Struttura e primitive di un processo (C- Linux) PID Ogni processo (tranne init ) creati da altri processi  padri, figli, ecc. Memoria (stato) di un processo: –Segmento codice –Segmento dati Statici Dinamici –Stack –Heap –Segmento di sistema (interfaccia con il SO) Tabella file aperti Socket (vedi programmazione di rete) …

La gerarchia dei processi init P1(SO) Pn(SO) getty (SO) … login (SO) shell (user) Processo1 (comando) Processo1 Processo2

Primitive per la gestione di processi (C- Linux) Generare un processo figlio Attendere la terminazione di un processo figlio Terminare un processo figlio Sostituire il programma (segmento codice) eseguito da un processo

Generazione e terminazione di processi pid_t fork (void) esempio: fork ( ) –Biforca il processo in padre e figlio: al figlio restituisce sempre 0; al padre restituisce il pid  0 del figlio biforcato; restituisce  1 se la biforcazione del processo fallisce (restituisce solo al padre, ovviamente, visto che il figlio non è stato biforcato). ***Fig. 3 Dispensa Negri***

Tutti i segmenti del padre sono duplicati nel figlio, quindi sia il codice e le variabili (segmenti codice e dati), sia i file aperti utilizzati (segmento di sistema). La duplicazione del segmento di sistema prodotto dalla fork copia la tabella dei file aperti e pertanto entrambi i processi possono operare sullo stesso file aperto, mentre successivi file aperti da uno dei due processi dopo la fork saranno invece immessi nella tabella dei file aperti del solo processo che ha eseguito l’apertura. Il processo figlio eredita anche il valore del PC del processo padre, pertanto entrambi i processi dopo la fork si trovano ad eseguire la stessa istruzione del programma. Ciò significa che terminata l’esecuzione della fork entrambi i processi proseguono ad eseguire la porzione dello stesso programma che segue l’istruzione di invocazione della fork.

void exit (int stato) esempio: exit() –(per il momento senza parametro == exit(0) ) –Termina il processo corrente –Simile alla return : può essere superflua, e.g. se il programma giunge alla fine del codice

Primo esempio: il programma fork1 #include void main( ) {pid_t pid; pid=fork( ); if (pid==-1) {printf(“errore esecuzione fork”); exit();} else if (pid==0) {printf("sono il processo figlio\n"); exit( ); } else {printf("sono il processo padre\n"); exit( ); /* non necessaria */ } I “processi procedono” in modo asincrono Condividono le risorse (e.g., il terminale) Quindi l’ordine di esecuzione – e stampa dei risultati- non è prevedibile!

Primo esempio: risultato dell’esecuzione di fork1 NB: ma le due scritte potrebbero anche essere in ordine inverso

pid_t getpid () –Restituisce al processo che la chiama il valore del suo pid

Secondo esempio: il programma forkpid1 #include void main( ) {pid_t pid,miopid; pid=fork( ); if (pid==0) {miopid=getpid( ); printf("sono il processo figlio con pid: %i\n\n",miopid); exit( ); } else {printf("sono il processo padre\n"); printf("ho creato un processo con pid: %i\n", pid); miopid=getpid( ); printf("il mio pid e' invece: %i\n\n", miopid); exit( ); /* non necessaria */ } Un processo può creare n figli I figli possono creare figli di figli (nipoti) ecc. Il primo processo (radice): init, creato dal S.O.

Secondo esempio: risultato dell’esecuzione di forkpid1

Terzo esempio: il programma forkpid2 #include void main( ) {pid_t pid,miopid; pid=fork( ); if (pid==0) {miopid=getpid( ); printf("1)sono il primo processo figlio con pid: %i\n",miopid); exit( ); } else {printf("2)sono il processo padre\n"); printf("3)ho creato un processo con pid: %i\n", pid); miopid=getpid( ); printf("4)il mio pid e' invece: %i\n", miopid); pid=fork( ); if (pid==0) {miopid=getpid( ); printf("5)sono il secondo processo figlio con pid: %i\n",miopid); exit; } else printf("6)sono il processo padre\n"); printf("7)ho creato un secondo processo con pid: %i\n", pid); exit( ); /* non necessaria */ }

Terzo esempio: (un possibile) risultato dell’esecuzione di forkpid2

Che accade se un padre termina prima del(i) figli(o)? init … login shell Processo1 Processo2Processo3 Processo4 init … login shell Processo2Processo3 Processo4 La convenzione adottata da Linux è di far adottare i processi figli rimasti orfani (processi 2 e 3) e tutta la loro discendenza (processo 4) al processo init del sistema operativo.

Sincronizzare processi asincroni Elemento fondamentale della gestione del parallelismo: processi indipendenti fino a … pid_t wait (int *) –Sospende l’esecuzione del processo che la esegue e attende la terminazione di un – qualsiasi- processo figlio; –se un figlio è già terminato la wait del padre si sblocca immediatamente (nessun effetto) –ritorna il pid del processo figlio terminato

Esempio: –pid_t pid; –int stato; –pid = wait (&stato); stato, parametro passato per indirizzo: –codice di terminazione del processo –8 bit superiori: possono essere assegnati esplicitamente come parametro di exit ; –altri bit di stato assegnati dal S.O. per indicare condizioni di terminazione (e.g., errore)

exit con parametro void exit (int stato) Esempio: exit(5) –termina il processo e restituisce il valore 5 al padre; –se il padre è già terminato lo stato viene restituito all’interprete comandi; –dettaglio importante: il valore restituito è costituito dagli 8 bit superiori di stato  lo stato ricevuto da wait è il parametro di exit moltiplicato per 256

Quarto esempio: il programma forkwait1 #include void main( ) {pid_t pid, miopid; int stato_exit, stato_wait; pid=fork( ); if (pid==0) { miopid=getpid( ); printf("sono il processo figlio con pid %i \n", miopid); printf("termino \n\n"); stato_exit=5; exit(stato_exit); } else { printf("ho creato un processo figlio \n\n"); pid=wait (&stato_wait); printf("terminato il processo figlio \n"); printf("il pid del figlio e' %i, lo stato e' %i\n",pid,stato_wait/256); } NB: per stampare correttamente il valore di stato è necessario dividere il parametro ricevuto in stato_wait per 256

Quarto esempio: risultato dell’esecuzione di forkwait1

Però attenzione: Processo padre Caso 1 Processo figlio fork() printf("ho creato…"); wait(..) printf("terminato…"); miopid=getpid( ); … exit(stato_exit);

Processo padre Caso 2 Processo figlio fork() printf("ho creato…"); wait(..) printf("terminato…"); miopid=getpid( ); … exit(stato_exit); Se il figlio è già terminato la wait del padre si sblocca immediatamente Il sistema operativo memorizza il valore di stato nella parte di sistema operativo dedicata al processo, chiude tutti i file aperti presenti nella tabella dei file aperti del segmento di sistema del processo e passa il processo dallo stato di “attivo” allo stato di “zombie”. Il processo figlio dopo l’exit rimane quindi in vita, ma solo per aspettare che il processo padre possa recuperare lo stato.

Riassumendo: a) Un processo padre che non ha generato processi figli esegue una wait. In questo caso il sistema operativo restituisce il codice di errore -1 e non pone in attesa il processo padre. b) Un processo padre esegue una wait in presenza di un processo figlio che non esegue mai una exit (ad esempio per un ciclo infinito); in questo caso il processo padre rimane sospeso all’infinito. Questa situazione richiede un intervento esterno per forzare la terminazione di entrambi i processi. c) Un processo padre termina l’esecuzione del proprio programma, provocando la propria distruzione senza eseguire una wait, in presenza di uno o più processi figli attivi. In questo caso il sistema operativo prende tutti i processi rimasti orfani dalla morte del processo padre e li fa adottare al processo init. Quando questi processi figli eseguono l’exit passano allo stato zombie senza avere più un padre che li aspetti. Si noti che periodicamente il processo init esegue una wait proprio al fine di eliminare i processi zombie inutilmente presenti nel sistema. Si cominciano a intravvedere le difficoltà di una programmazione non più Orientata alla costruzione di un singolo algoritmo sequenziale –per quanto complesso – per la soluzione di un singolo problema!

pid_t waitpid (pid_t pid, int  stato, int opzioni) Esempio: waitpid (10, &stato, opzioni) Mette un processo in stato di attesa dell’evento di terminazione di un processo figlio con pid “pid” (10 in questo caso) e ne restituisce il pid (10 in questo caso); la variabile “ stato ” assume il valore di “ exit ” del processo figlio terminato. Il parametro “ opzioni ” specializza la funzione “ waitpid ”. Una variante di wait: la funzione waitpid

Sostituzione del programma in esecuzione exec –sostituisce i segmenti codice e dati (utente; non il segmento di sistema!  i file aperti rimangono aperti) del processo in esecuzione con codice e dati di un programma contenuto in un file eseguibile specificato; -il processo rimane lo stesso (stesso pid): programma  processo! -può passare parametri al nuovo programma ( main è una particolare funzione: main (!!) ) -esistono diverse varianti di exec

exec1 (char *nome_programma, char *arg0, char *arg1, …, NULL); –nome_programma: stringa che identifica completamente (pathname) il file eseguibile contenente il programma da lanciare –arg0, arg1, …: puntatori a stringhe da passare come parametri al main da lanciare; l’ultimo è NULL perché il numero di arg è variabile. –infatti: Il main, che è una particolare funzione, può avere a sua volta dei parametri! finalmente main () 

void main (int argc, char *argv[]) –argc : numero di parametri ricevuti –argv[] : vettore di puntatori a stringhe; ogni stringa è un parametro argv[0] contiene sempre il nome del programma exec1 provoca quindi l’esecuzione del (“chiama” il) programma il cui eseguibile si trova nel file nome_programma e gli passa come parametri (per indirizzo: sono puntatori) arg0, arg1, … )

Quinto esempio: il programma main1 #include void main (int argc, char *argv[ ] ) {int i; printf("\nsono il programma main1\n"); printf("ho ricevuto %i parametri\n", argc); for (i=0; i<argc; i++) printf("il parametro %i è: %s\n", i, argv[i]); }

Quinto esempio: risultato dell’esecuzione di main1 da riga di comando, senza parametri

Quinto esempio: risultato dell’esecuzione di main1 da riga di comando, con 3 parametri

Quinto esempio: il programma exec1 #include void main( ) { char P0[ ]="main1"; char P1[ ]="parametro 1"; char P2[ ]="parametro 2"; printf("sono il programma exec1\n"); exec1("/home/pelagatt/esempi/main1", P0, P1, P2, NULL); printf("errore di exec"); /*normalmente non si arriva qui!*/ }

Quinto esempio: risultato dell’esecuzione di exec1

Altre versioni di exec –execv : sostituisce alla lista di stringhe di exec1 un puntatore a un vettore di stringhe char  argv –execlp e execvp permettono di sostituire il pathname completo con il solo nome del file nel direttorio di default –execle e execve hanno un parametro in più che specifica l’ambiente di esecuzione del processo.

exec e fork in combinazione il padre crea uno o più figli e assegna loro un compito attende i loro risultati quando hanno finito e prodotto i risultati li raccoglie e li gestisce –interprete comandi

Sesto esempio: il programma forkexec1 #include void main( ) {pid_t pid; int stato_wait; char P0[ ]="main1"; char P1[ ]="parametro 1"; char P2[ ]="parametro 2"; pid=fork( ); if (pid==0) { printf("\nsono il processo figlio \n"); printf("lancio in esecuzione il programma main1\n"); exec1("/home/pelagatt/esempi/main1", P0, P1, P2, NULL); printf("errore di exec"); /*normalmente non si arriva qui!*/ exit( ); } else { wait(&stato_wait ); printf("\nsono il processo padre\n"); printf("il processo figlio è terminato\n"); exit( ); }

Sesto esempio: risultato dell’esecuzione di forkexec1

Settimo esempio Pseudocodice di un interprete comandi semplificato (programma simpleshell) che legge da terminale un comando, procede a creare un processo figlio dedicato all’esecuzione del comando, mentre il processo padre ne attende la sua terminazione prima di ripetere la richiesta di un altro comando.

Settimo esempio #include #define fine “logout” #define prompt “simpleshell:” void main( ) { pid_t pid; int stato_wait; …. while (! logout dell’utente) { printf (“%s”,prompt); [ lettura riga di comando e identificazione componenti del comando ] pid=fork( ); if (pid==0) {exec1(comando, arg0, arg1, … argn, NULL); printf("errore di exec"); /*normalmente non si arriva qui!*/ exit( ); } else wait(&stato_wait ); } exit( ); } a)il programma simpleshell

Settimo esempio simpleshell:./main1 sono il programma main1 ho ricevuto 1 parametri il parametro 0 è:./main1 simpleshell: b)risultato dell’esecuzione di simpleshell senza parametri

Settimo esempio simpleshell:./main1 par1 par2 par3 sono il programma main1 ho ricevuto 4 parametri il parametro 0 è:./main1 il parametro 1 è: par1 il parametro 2 è: par2 il parametro 3 è: par3 simpleshell: c)risultato dell’esecuzione di simpleshell con tre parametri

Ottavo esempio Si devono riempire tre tabelle da parte di 3 processi: –un padre e due figli (più precisamente, un figlio e un nipote) ogni tabella deve indicare: –il valore di variabili i,j,k, pid1, pid2 –in specifici punti (linee di codice) dell’esecuzione del programma eseguito dal processo; –Se nel momento indicato la variabile non esiste (perché non esiste il processo) la tabella deve riportare NE, –se la variabile esiste ma non se ne conosce il valore con certezza (perché non si sa a che punto si sia dell’esecuzione del singolo processo) la tabella deve riportare U; Si suppone che tutte le fork abbiano successo e che il S.O. assegni ai figli creati i valori di pid a partire da 500.

01:main() 02:{ 03:int i, j, k, stato; 04:pid_t pid1, pid2; 05:i=10; j=20; k=30; 06:pid1 = fork(); /*creazione del primo figlio / 07:if (pid1 == 0) { 08:j=j+1; 09:pid2 = fork(); /*creazione del secondo figlio */ 10:if (pid2 == 0) { 11:k=k+1; 12:exit();} 13: else { 14:wait(&stato); 15:exit(); } 16: } 17:else { 18:i=i+1; 19:wait(&stato); 20:exit(); } 21:}

Struttura delle 3 tabelle da compilare pid1pid2ijk Dopo l’istruzione 6 dopo l’istruzione 9 dopo l’istruzione 11 dopo l’istruzione 19 Istante Valore delle variabili

Valore delle variabili nel processo padre pid1pid2ijk Dopo l’istruzione 6 500U dopo l’istruzione 9 500UU *2030 dopo l’istruzione UU *2030 dopo l’istruzione U Istante Valore delle variabili * I = U perché il padre non esegue queste istruzioni e non si sa se quando un figlio è alla 9 o 11 nel frattempo il padre abbia eseguito la 18

Valore delle variabili nel primo processo figlio pid1pid2ijk Dopo l’istruzione 6 0U dopo l’istruzione dopo l’istruzione dopo l’istruzione 19 NE Istante Valore delle variabili

Valore delle variabili nel secondo processo figlio (nipote) pid1pid2ijk Dopo l’istruzione 6 NE dopo l’istruzione dopo l’istruzione dopo l’istruzione 19 NE Istante Valore delle variabili

Esempio conclusivo A: –“Sistema” che sceglie la soluzione (algoritmo) più rapida per un problema B: –“Sistema” che scompone un problema in due sottoproblemi da risolvere separatamente ma indipendentemente e ne combina le soluzioni

A #include … void main () {pid_t pid; intstato_wait, scelta; file *filedati, *filerisultati, *filedati1, *filedati2, *filerisultati1, *filerisultati2; [apre il file di dati in *filedati; copia *filedati in *filedati1 e *filedati2]; scelta = 1; pid = fork(); if (pid !== 0) {scelta = 2; pid = fork()}; if (pid == 0) {if (scelta == 1) {exec1 (“programma1”, NULL); exit (1)} if (scelta == 2) {exec1 (“programma2”, NULL); exit (2)} } else { pid = wait(&stato_wait); if (stato_wait/256 == 1) { [copia filerisultati1 in filerisultati]; pintf(“il primo programma a fornirmi i risutati è stato programma1;\n esso è stato eseguito dal processo %i\n”, pid) } else { [copia filerisultati2 in filerisultati]; pintf(“il primo programma a fornirmi i risutati è stato programma2;\n esso è stato eseguito dal processo %i\n”, pid) } };[chiude tutti i file] }

NB –programma1 risolve lo stesso problema di programma2 usando un diverso algoritmo –programma1 e programma2 ricevono rispettivamente i dati su cui operare nel file (puntato da) filedati1 e filedati2 lasciano il risultato in filerisultati/1/2. –Non è detto che il primo processo a terminare sia quello che esegue l’algoritmo più veloce.

B #include … void main () {pid_t pid; intstato_wait; file *filedati, *filerisultati, *fd1, *fd2, *fris1, *fris2; [apre –o crea-i vari file; da *filedati prepara i file *fd1, *fd2 da far elaborare a programma1 e programma2 rispettivamente]; scelta = 1; pid = fork(); if (pid !== 0) {scelta = 2; pid = fork()}; if (pid == 0) {if (scelta == 1) {exec1 (“programma1”, NULL); exit (1)} if (scelta == 2) {exec1 (“programma2”, NULL); exit (2)} } else { /* ilpadre attende la terminazione dei due programmi figli indipendentemente da quale dei due termina prima; se è “curioso”, oppure se nel frattempo ha assegnato altri compiti ad altri processi …*/ pid = wait(&stato_wait); [costruisce il rilsultato finale in *filerisultati, combinando le soluzioni lasciate dai due programmi rispettivamente in *fris1 e *fris2]; } [chiude tutti i file] }

NB –Ovviamente da un parallelismo puramente logico di questo tipo non ci si possono attendere grandi risultati pratici in termini di efficienza, anzi … –però il parallelismo logico diventa tanto più utile quanto più “indipendenti” diventano i singoli compiti dei vari processi –Quando poi il parallelismo logico diventa anche fisico (coprocessori) …