sequenceDiagram
autonumber
participant P as Pare
participant F as Fill
P->>F: fork → crea el fill
F->>F: exit 0
Note over F: zombi (Z) des d'aquí
P->>P: sleep 30
Note over P,F: ps mostra el fill com a defunct
P->>P: el pare acaba
Note over F: init adopta el fill i el recull
Problemes de classe · zombis, orfes i exec
Introducció
Tots es poden resoldre amb paper i llapis. Per a cadascun, dibuixa l’arbre de processos i anota què fa cada procés i en quin ordre. Intenta resoldre’l abans d’obrir el raonament; després, si vols, compila’l i comprova-ho (o segueix-lo amb GDB).
exec*reemplaça la imatge del procés (codi, pila, heap, dades). No crea cap procés nou: el PID no canvia. Si té èxit, no retorna; només torna (amb-1) si falla.- Nomenclatura:
l= arguments en llista,v= arguments en vector,p= cerca alPATH,e= entorn explícit. Les llistes d’arguments acaben ambNULL. - Els descriptors oberts sobreviuen a l’
exec(excepte els marcats ambFD_CLOEXEC). Els buffers d’stdio no: viuen a la memòria del procés i es descarten. - Zombi: fill que ja ha acabat però el pare encara no ha fet
wait/waitpid. Apareix apsamb estatZi<defunct>. Només ocupa una entrada a la taula de processos (guarda l’estat de sortida). No es pot matar ambkill -9: ja és mort. - Orfe: fill viu el pare del qual ha acabat. L’adopta
init/systemd(o un subreaper), que faràwaitper ell.
Convencions dels enunciats. Tots els programes suposen aquests #include:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>int main(void) {
printf("abans\n");
execlp("echo", "echo", "hola", NULL);
printf("despres\n");
return 0;
}- Què s’imprimeix?
- En quina circumstància s’imprimiria
despres?
Repte: què passa amb abans si redirigeixes la sortida a un fitxer (./programa > out.txt)?
Pots veure el raonament
abansihola. Maidespres: després d’unexecamb èxit, el codi d’aquest programa ja no existeix dins el procés.- Només si
execlpfalla (per exemple, el programa no existeix alPATH): llavors retorna-1i el codi continua. Per això el patró correcte ésexeclp(...); perror("execlp"); exit(1);.
Repte. Amb la sortida redirigida, stdout és completament buferitzat: abans\n es queda al buffer, i l’exec el descarta juntament amb la resta de la memòria. Al fitxer només hi ha hola.
int main(void) {
if (fork() == 0)
exit(0);
sleep(30);
return 0;
}- Durant aquests 30 s, en quin estat és el fill? Què mostraria
ps? - Com deixaria de ser zombi?
- Serveix
kill -9 <pid_fill>?
Pots veure el raonament
- Zombi: ha acabat, però el pare no ha fet
wait.psel mostra ambSTAT = ZiCMD = [programa] <defunct>. - Si el pare fa
wait/waitpid, o si el pare acaba (el fill passa ainit/systemd, que el recull). - No. Un zombi ja és mort: no rep senyals. Cal actuar sobre el pare.
int main(void) {
char *args[] = { "ls", "-l", ______(1)______ };
execvp(______(2)______, ______(3)______);
perror("execvp");
exit(1);
}Pots veure el raonament
NULL(el vector ha d’acabar amb un punter nul)"ls"(oargs[0])args
Executa ls -l i no torna. Amb execv (sense p) caldria donar la ruta completa, "/bin/ls", perquè no cerca al PATH.
int main(void) {
printf("PID abans: %d\n", getpid());
fflush(stdout);
execl("/bin/sh", "sh", "-c", "echo PID despres: $$", NULL);
return 1;
}Quina relació hi ha entre els dos PID que s’imprimeixen?
Pots veure el raonament
Són iguals. $$ en un shell és el PID del propi shell, que és el nostre procés amb la imatge reemplaçada per sh. L’exec no crea cap procés: el transforma.
Fixa’t que el fflush(stdout) és necessari només perquè, amb la sortida redirigida, PID abans es perdria en l’exec (vegeu F1 i D1).
int main(void) {
pid_t pid = fork();
if (pid == 0) {
execl("/bin/ls", "ls", "/noexisteix", NULL);
perror("execl");
exit(99);
}
int st;
waitpid(pid, &st, 0);
printf("codi=%d\n", WEXITSTATUS(st));
return 0;
}- Què imprimeix el programa tal com està?
- I si canviem
"/bin/ls"per"/bin/lss"?
Pots veure el raonament
lss’executa correctament (l’execté èxit) però falla pel seu compte: escriu perstderralguna cosa comls: cannot access '/noexisteix': No such file or directoryi surt amb codi 2. El pare imprimeixcodi=2. Elperrori l’exit(99)no s’executen.- Ara és l’
execel que falla (ENOENT):perrorescriuexecl: No such file or directory, el fill surt amb 99 i el pare imprimeixcodi=99.
sequenceDiagram
autonumber
participant P as Pare
participant F as Fill
P->>F: fork → crea el fill
P->>P: waitpid bloquejat
F->>F: execl de bin ls amb èxit
Note over F: ara el fill és ls
F->>F: ls falla i surt amb codi 2
F-->>P: desbloqueja el waitpid
P->>P: printf codi=2
Moraleja: cal distingir «l’exec ha fallat» de «el programa llançat ha fallat» mitjançant codis de sortida diferents.
int main(void) {
execl("/bin/echo", "hola", "mon", NULL);
return 1;
}- Què s’imprimeix?
- I amb
execl("/bin/echo", "mon", NULL);?
Pots veure el raonament
mon. El primer argument després de la ruta ésargv[0](el «nom» amb què s’invoca), no un paràmetre. S’executa/bin/echoambargv = {"hola", "mon"}, iechoignoraargv[0].- Una línia buida:
argv = {"mon"}i no hi ha res per mostrar (echoescriu només el salt de línia).
Per això, quan es vol passar hola com a argument cal repetir el nom: execl("/bin/echo", "echo", "hola", NULL).
int main(void) {
for (int i = 0; i < 3; i++)
if (fork() == 0)
exit(0);
sleep(10);
wait(NULL);
sleep(10);
return 0;
}Quants zombis hi ha a t = 5 s, t = 15 s i t = 25 s?
Pots veure el raonament
- t = 5 s: 3 zombis. Els tres fills han acabat i el pare dorm.
- t = 15 s: 2 zombis. El
wait(NULL)només n’ha recollit un. - t = 25 s: 0. El pare va acabar a t = 20 s; els dos zombis restants van passar a
init/systemd, que els va recollir.
sequenceDiagram
autonumber
participant P as Pare
participant F as 3 fills
P->>F: fork 3 vegades
F->>F: exit 0 → 3 zombis
P->>P: sleep 10
Note over P,F: t=5 s · 3 zombis
P->>F: wait recull un fill
Note over P,F: t=15 s · 2 zombis
P->>P: sleep 10 i el pare acaba a t=20 s
F-->>F: init adopta i recull els 2 restants
Note over P,F: t=25 s · 0 zombis
int main(void) {
if (fork() == 0) {
sleep(2);
printf("el meu pare es %d\n", getppid());
exit(0);
}
printf("pare acaba\n");
exit(0);
}- En quin ordre i amb quina diferència de temps surten les línies?
- Quin valor aproximat imprimeix
getppid()? - Pot aparèixer el prompt del shell abans de la segona línia?
Pots veure el raonament
pare acabagairebé a l’instant;el meu pare es ...uns 2 s després.- 1 en un sistema clàssic (
init), o el PID desystemdo d’un subreaper (per exemplesystemd --user, o el procés principal d’un contenidor). No és el PID original del pare. - Sí: el shell només espera el pare, que ja ha acabat. El fill orfe continua escrivint al terminal després.
sequenceDiagram
autonumber
participant S as Shell
participant P as Pare
participant F as Fill
participant I as init
S->>P: executa el programa
P->>F: fork → crea el fill
P->>P: printf pare acaba i exit
P-->>S: el shell recupera el prompt
I->>F: adopta el fill orfe
F->>F: després de 2 s printf amb ppid 1
./run ls -l /tmp ha d’executar ls -l /tmp i mostrar com ha acabat.
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "us: %s ordre [args...]\n", argv[0]);
return 1;
}
pid_t pid = fork();
if (pid < 0) { perror("fork"); return 1; }
if (pid == 0) {
execvp(______(1)______, ______(2)______);
perror("execvp");
exit(127);
}
int st;
______(3)______;
if (WIFEXITED(st))
printf("ha acabat amb %d\n", ______(4)______);
else if (WIFSIGNALED(st))
printf("mort pel senyal %d\n", ______(5)______);
return 0;
}Pots veure el raonament
argv[1]&argv[1](el vector des de l’ordre; ja acaba enNULLperquè l’estàndard garanteixargv[argc] == NULL)waitpid(pid, &st, 0)WEXITSTATUS(st)WTERMSIG(st)
Nota: el 127 és la convenció dels shells per a «ordre no trobada».
El pare crea 3 fills (el fill i dorm i segons) i, mentrestant, «treballa», recollint cada fill tan bon punt acaba, sense quedar-se bloquejat.
int main(void) {
for (int i = 1; i <= 3; i++)
if (fork() == 0) {
sleep(i);
exit(0);
}
int vius = 3, st;
while (vius > 0) {
pid_t p = waitpid(______(1)______, &st, ______(2)______);
if (______(3)______) {
vius--;
printf("recollit %d\n", p);
} else if (p == 0) {
printf("treballant...\n");
usleep(500000);
} else {
perror("waitpid");
break;
}
}
return 0;
}Pots veure el raonament
-1(qualsevol fill)WNOHANG(tornar immediatament)p > 0(s’ha recollit un fill)
Amb WNOHANG, waitpid retorna 0 si hi ha fills vius però cap ha acabat, > 0 si n’ha recollit un i -1 si hi ha un error (per exemple ECHILD, sense fills). La sortida alterna línies treballant... amb tres recollit <pid>.