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
Scarica

PPT - DISI