230 likes | 347 Views
S ystem C all che operano su processi. Getpid, fork, exec, wait, waitpid, exit, dup, dup2. 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
E N D
System Callche operano su processi Getpid, fork, exec, wait, waitpid, exit, dup, dup2
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)
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) • Macondividono la tabella dei file aperti (e quindi anche il puntatore alla locazione corrente di ogni file)
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 ...
Creazione di processi (2) copia • Spazio di indirizzamento di padre e figlio dopo una fork terminata con successo 232 - 1 232 - 1 Stack Stack Area vuota Area vuota heap heap Data Data Text Text 0 0 figlio padre
Creazione di processi (3) • Come prosegue l’esecuzione nei processi padre e figlio 232 - 1 232 - 1 Stack Stack &x &x 45 0 Area vuota Area vuota PC = istruzione successiva a fork heap heap Data Data Text Text 0 0 SI padre (pid=34) SI figlio (pid = 45)
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 */ ... }
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
Attesa di terminazione del figlio pid_t wait(int *status) • Restituisce il pid del processo terminato, oppure un codice di errore <0 • Inoltrenella 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
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
Attesa di terminazione pid_t waitpid(pid_t pid,int *status,int options) • la waitpid permette di fare attese non bloccanti • WNOHANGspecificato nelle opzioni indica di non attendere se nessun figlio è ancora terminato • permette di attendere un figlio con un pid specifico (pid)
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 */ }
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
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
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 $
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 diexec • differenziano un processo da padre rimpiazzando il suo spazio di indirizzamento con quello di un file eseguibile passato come parametro
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)
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...
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)
Esempio : una shell semplificata int pid, status; char * argv[]; while (TRUE) { /*ciclo infinito*/ type_prompt(); /* stampa prompt*/ argv = read_comm(); /*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”); }
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 • dup2rendenewfduna copia del file descriptor oldfd
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!”); ...
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 unawait • 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)