System Call che operano su processi Getpid, fork, exec, wait, waitpid, exit, dup, dup2 1 Process identifier: getpid,getppid – pid indice del processo all’interno della tabella dei processi – ppid indice del processo padre all’interno della tabella dei processi – il kernel ha pid 0 e init ha pid 1 – si possono ottenere con pid_t getpid(void) pid_t getppid(void) 2 Creazione : fork() pid_t fork(void); – crea un nuovo processo con indice pid – lo spazio di indirizzamento del nuovo processo è un duplicato di quello del padre – padre e figlio hanno due tabelle dei descrittori di file diverse (il figlio ha una copia di quella del padre) – Ma condividono la tabella dei file aperti (e quindi anche il puntatore alla locazione corrente di ogni file) 3 Creazione : fork() (2) pid_t fork(void); – restituisce 0 al figlio e pid al padre, – oppure -1 (solo al padre) in caso di fallimento • es. la tabella dei processi non ha più spazio ... 4 Creazione di processi (2) • Spazio di indirizzamento di padre e figlio dopo una fork terminata con successo copia 232 - 1 0 Stack 232 - 1 Stack Area vuota Area vuota heap Data heap Data Text Text padre 0 figlio 5 Creazione di processi (3) • Come prosegue l’esecuzione nei processi padre e figlio 232 - 1 &x 45 232 - 1 Stack &x 0 Area vuota heap Data 0 Stack Area vuota PC = istruzione successiva a fork Text SI padre (pid=34) 0 heap Data Text SI figlio (pid = 45) 6 Creazione di processi (4) /* frammento che crea un nuovo processo */ int pid; /* pid del processo creato */ … IFERROR( pid = fork(),”main: creazione”); if ( pid ) { /* siamo nel padre */ ... } else { /* siamo nel figlio */ ... } 7 Terminazione : exit() void exit(int status); Prevede un parametro (status) con il quale in figlio puo’ inviare messaggi al padre sul suo stato di terminazione La exit ha il seguente effetto: – chiude tutti i descrittori di file elibera lo spazio di indirizzamento, – invia un segnale SIGCHLD al padre e salva il primo byte (0-255) di status nella tabella dei processi in attesa che il padre esegua wait/waitpid – se il processo padre è terminato, il processo ‘orfano’ viene adottato da init (cioè ppid viene settato a 1) – se eseguita nel main è equivalente ad una return 8 Attesa di terminazione del figlio pid_t wait(int *status) – – Restituisce il pid del processo terminato, oppure un codice di errore <0 Inoltre nella variabile status passata per riferimento vengono salvate • • – – informazioni sul tipo di terminazione del figlio il byte meno significativo ritornato con la exit() Per decodificare le informazioni sullo stato del figlio si utilizzano delle maschere definite in <sys/wait.h> Ad esempio: • WIFEXITED(status) restituisce vero se il figlio e’ terminato volontariamente • WEXITSTATUS(status) restituisce lo stato di terminazione 9 Effetti della wait • Supponiamo che il processo P abbia chiamato wait(&status) – Se tutti i figli del processo sono ancora in esecuzione allora il processo P rimane in attesa – Se almeno un figlio Q del processo P e’ terminato e il suo stato non e’ ancora stato rilevato (cioe’ Q e’ nello stato zombie) la wait ritorna immediamente il valore dello stato di terminazione di Q – Se P non ha figli, la wait non e’ bloccante e restituisce un codice di errore <0 10 Attesa di terminazione pid_t waitpid(pid_t pid,int *status,int options) – la waitpid permette di fare attese non bloccanti • WNOHANG specificato nelle opzioni indica di non attendere se nessun figlio è ancora terminato – permette di attendere un figlio con un pid specifico (pid) 11 Esempio int status ; /* conterra’ lo stato */ IFERROR( pid = fork(),”main: creazione”); if ( pid ) { /* siamo nel padre */ sleep(20); /* aspetta 10 secondi */ pid = wait(&status); if (WIFEXITED(status)) { /*!=0 se il figlio e’terminato normalmente, (exit o return) non ucciso da signal */ printf(“stato %d\n”, WEXITSTATUS(status)); } else { /* siamo nel figlio */ printf(“Processo %d, figlio.\n”,getpid()); exit(17); /*termina con stato 17 */ } 12 Esempio Cosa accade se eseguiamo un main contenente il codice dell’esempio : $ a.out & -- avvio l’esecuzione in bg Processo 1246, figlio. -- stampato dal figlio 13 Esempio Prima che i 20 secondi siano finiti ... $ a.out & -- avvio l’esecuzione in bg Processo 1246, figlio. -- stampato dal figlio $ ps -l … S UID PID PPID ………… CMD … Z 501 1246 1245 …………… a.out -- il figlio e’ un processo zombie 14 Esempio Quando il padre si risveglia ed esegue la wait() ... $ a.out & -- avvio l’esecuzione in bg Processo 1246, figlio. -- stampato dal figlio $ ps -l … S UID PID PPID ………… CMD … Z 501 1246 1245 …………… a.out -- il figlio e’ un processo zombie (Z) $ Stato 17. -- stampato dal padre $ 15 Differenziazione : le exec() • execve – è l’unica chiamata di sistema vera • execl, execlp,execle,execv, execvp – sono funzioni di libreria con differenze sul tipo di parametri – alla fine invocano la execve • tutte i tipi di exec – differenziano un processo da padre rimpiazzando il suo spazio di indirizzamento con quello di un file eseguibile passato come parametro 16 Differenziazione : le exec() (2) • execl, execlp,execle,execv, execvp, execve – è possibile richiedere che la exec() cerchi il file nelle directory specificate dalla variabile di ambiente PATH è (p nel nome) – è possible passare un array di argomenti secondo il formato di argv[] (v nel nome) – è possible passare un array di stringhe che descrivono l’environment (e nel nome) – è possibile passare gli argomenti o l’environment come lista (terminato da NULL) (l nel nome) 17 Differenziazione : le exec() (3) • execl, execlp,execle,execv, execvp, execve – le exec() non ritornano in caso di successo!!! – Restituiscono -1 in caso di fallimento • non trova il file, il file non è eseguibile etc... 18 Effetti della exec • Il processo dopo exec: – Mantiene la stessa process structure (salvo le informazioni relative al codice): • Stesso pid • Stesso pid del padre – Ha codice, dati globali, heap e stack nuovi – Mantiene la user area (a parte il program counter e le informazioni sul codice) e lo stack kernel: • n particolare quindi mantiene le stesse risorse (es file aperti) 19 Esempio : una shell semplificata int pid, status; char * argv[]; while (TRUE) { type_prompt(); argv = read_comm(); /*ciclo infinito*/ /* stampa prompt*/ /*legge command line*/ IFERROR3(pid = fork(),”Nella fork”,continue); if (pid) {/* codice padre */ wait(&status); if (WIFEXITED(status)) …/*gest. stato*/ } else {/*codice figlio*/ IFERROR(execvp(argv[0],argv),”nella execvp”); } 20 Duplicazione dei descrittori di file: dup() e dup2() int dup(int oldfd); int dup2(int oldfd,int newfd); – – creano entrambi una copia del descrittore di file oldfd, entrambi i descrittori puntano alla stessa locazione della tabella dei file aperti e possono essere utilizzati per lavorare sullo stesso file – dup cerca la prima posizione libera – dup2 rende newfd una copia del file descriptor oldfd 21 Es: redirezione con dup() e dup2() Es. voglio ridirigere lo standard output (file descriptor 1) su un file pippo int fd; ... IFERROR(fd=open(“pippo”,O_WRONLY|O_TRUNC|O_CREAT,0644), ”nella open”); dup2(fd,STDOUT); /* duplica fd sullo standard output*/ close(fd); /* fd non serve piu’ */ printf(“Questo viene scritto in pippo!”); ... 22 Come la shell implementa la redirezione ... • Es. $ ls -l > pippo – Il processo shell si duplica con una fork()e si mette in attesa della terminazione del figlio con una wait – Il figlio apre in scrittura il file pippo (creandolo o troncandolo) – Il figlio duplica il descrittore di pippo con la dup2 sullo stdout e chiude il descrittore originario – Il figlio invoca una exec di ls -l, la quale conserva i descrittori dei file, e quindi va a scrivere in pippo ogni volta che usa il file descriptor 1 – Quando il figlio termina, il padre riprende la computazione con i sui descrittori di file invariati (padre e figlio hanno ognuno la propria tabella dei descrittori e casomai puntano alla stessa locazione della tabella dei file aperti) 23