sequenceDiagram
autonumber
participant P as Pare
participant F as Fill
P->>P: printf A
P->>F: fork → crea el fill
P->>P: printf B
F->>F: printf B
Problemes de classe · fork() i wait()
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).
fork()retorna0al fill, el PID del fill al pare i-1si falla. Després delfork, tots dos processos continuen just a la instrucció següent, cadascun amb la seva còpia de la memòria.wait(&st)bloqueja el procés fins que un dels seus fills acaba i en retorna el PID. Si no té fills per esperar, retorna-1immediatament (amberrno == ECHILD).waitpid(pid, &st, 0)espera un fill concret.- L’estat cru que deixa
waitastté aquesta forma: si el fill faexit(n),st = n << 8; si el mata un senyal, els 7 bits baixos valen el número del senyal (i el bit0x80indica core dump). - Les macros de
<sys/wait.h>ho desxifren:WIFEXITED(st)iWEXITSTATUS(st)per a una sortida normal;WIFSIGNALED(st)iWTERMSIG(st)per a una mort per senyal. - El buffer d’
stdioviu a la memòria del procés, així que elforktambé el duplica.
Convencions dels enunciats. Tots els programes suposen aquests #include:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>En tots els exemples s’usa printf("...\n"). En un terminal, stdout és line-buffered: el \n buida el buffer abans del fork().
Analitza el codi i respon:
int main(void) {
printf("A\n");
fork();
printf("B\n");
return 0;
}- Quants processos hi ha al final?
- Quantes línies
Ai quantesBs’imprimeixen?
Repte: què canvia si redirigeixes la sortida a un fitxer (./programa > out.txt)?
Pots veure el raonament
- 2 processos (el pare i un fill).
Auna vegada (s’imprimeix abans delfork, només hi ha el pare) iBdues vegades (una per procés). L’ordre relatiu de les duesBno importa: són idèntiques.
int main(void) {
pid_t pid = fork();
if (pid == 0) {
printf("Soc el fill\n");
exit(0);
}
wait(NULL);
printf("Soc el pare\n");
return 0;
}- Quin valor té
piden cada procés? - Pot sortir primer Soc el pare? Per què?
Pots veure el raonament
- Al fill,
pid == 0; al pare,pidés el PID del fill (> 0). - No. El pare es bloqueja al
wait(NULL)fins que el fill acaba, i el fill imprimeix abans de ferexit. La sortida és única:Soc el fill→Soc el pare.
sequenceDiagram
autonumber
participant P as Pare
participant F as Fill
P->>F: fork → crea el fill
P->>P: wait bloquejat
F->>F: printf Soc el fill
F->>F: exit 0
F-->>P: desbloqueja el wait
P->>P: printf Soc el pare
int main(void) {
fork();
fork();
printf("Hola\n");
return 0;
}Quants Hola s’imprimeixen? Dibuixa l’arbre de processos.
Pots veure el raonament
4 x Hola. Cada fork duplica el nombre de processos: 2² = 4. El segon fork l’executen tots dos processos que existeixen després del primer.
flowchart TD
P["P · pare original"] -->|"fork 1"| C1["C1"]
P -->|"fork 2"| C2["C2"]
C1 -->|"fork 2"| C1b["C1b"]
Els quatre processos (P, C1, C2, C1b) arriben al printf.
Completa els buits perquè el pare mostri el codi amb què ha acabat el fill.
int main(void) {
pid_t pid = fork();
if (pid == 0) {
exit(7);
}
int st;
______(1)______;
printf("El fill ha acabat amb codi %d\n", ______(2)______);
return 0;
}Pots veure el raonament
wait(&st)(owaitpid(pid, &st, 0))WEXITSTATUS(st)
Sortida: El fill ha acabat amb codi 7.
Abans d’usar WEXITSTATUS convé comprovar WIFEXITED(st): el valor només té sentit si el fill ha acabat amb exit i no mort per un senyal. Recorda que st conté 7 << 8 = 1792, no 7.
int main(void) {
for (int i = 0; i < 3; i++) {
fork();
}
printf("X\n");
return 0;
}- Quantes
Xs’imprimeixen? Dibuixa l’arbre. - I si dins del bucle es fa
if (fork() == 0) exit(0);?
Pots veure el raonament
- 2³ = 8. Els fills també executen les iteracions que els queden, així que el nombre de processos es duplica a cada volta. Cada aresta indica en quina iteració
ineix el procés:
flowchart TD
P["Pare original"] -->|"i=0"| A["A"]
P -->|"i=1"| B["B"]
P -->|"i=2"| C["C"]
A -->|"i=1"| D["D"]
A -->|"i=2"| E["E"]
B -->|"i=2"| F["F"]
D -->|"i=2"| G["G"]
Hi ha 8 processos (el pare i 7 fills) i tots arriben al printf.
- 1 sola
X. Cada fill mor a la iteració en què neix, així que només el pare original arriba alprintf. Es creen 3 fills en total (han existit 4 processos), però cap d’ells imprimeix.
int main(void) {
if (fork() && fork()) {
printf("A\n");
} else {
printf("B\n");
}
return 0;
}- Quants processos es creen en total?
- Quantes
Ai quantesBs’imprimeixen?
Pots veure el raonament
L’operador && avalua el segon operand només si el primer és cert:
- El pare
Prep del primerforkun PID ≠ 0 (cert), per tant avalua el segonforki creaC2. - El primer fill
C1rep0(fals) i, per curtcircuit, no executa el segonfork.
flowchart TD
P["P"] -->|"fork 1 · retorna PID al pare"| C1["C1 · rep 0 · no fa el segon fork"]
P -->|"fork 2"| C2["C2 · rep 0 al segon fork"]
| Procés | 1r fork |
2n fork |
Condició | Imprimeix |
|---|---|---|---|---|
P |
PID ≠ 0 | PID ≠ 0 | cert | A |
C1 |
0 | no s’executa | fals | B |
C2 |
PID ≠ 0 (heretat de P) |
0 | fals | B |
- 3 processos (
P,C1,C2). - 1
Ai 2B.
int main(void) {
int x = 10;
pid_t pid = fork();
if (pid == 0) {
x++;
printf("fill: x=%d\n", x);
exit(0);
}
wait(NULL);
printf("pare: x=%d\n", x);
return 0;
}Quina és la sortida? Per què?
Pots veure el raonament
fill: x=11
pare: x=10
Després del fork, cada procés té la seva còpia de l’espai d’adreces (amb copy-on-write: les pàgines es comparteixen fins que algú escriu, i llavors el sistema en fa una còpia privada). Quan el fill fa x++, modifica la seva x, no la del pare.
sequenceDiagram
autonumber
participant P as Pare · x=10
participant F as Fill · x=10
P->>F: fork → còpia de la memòria
P->>P: wait bloquejat
F->>F: x++ → x=11
F->>F: printf fill x=11
F->>F: exit 0
F-->>P: desbloqueja el wait
P->>P: printf pare x=10
El pare crea N fills; el fill i acaba amb exit(i). El pare espera tots els fills i en suma els codis.
#define N 4
int main(void) {
for (int i = 1; i <= N; i++) {
pid_t pid = ______(1)______;
if (pid == ______(2)______) {
exit(______(3)______);
}
}
int status, suma = 0;
pid_t p;
while ((p = ______(4)______) > 0) {
if (______(5)______)
suma += ______(6)______;
}
printf("suma=%d\n", suma);
return 0;
}Pregunta extra: què passa si oblidem l’exit(i) del fill?
Pots veure el raonament
fork()0iwait(&status)WIFEXITED(status)WEXITSTATUS(status)
Sortida: suma=10 (1 + 2 + 3 + 4).
wait retorna -1 amb errno == ECHILD quan ja no queden fills, i això acaba el bucle.
Pregunta extra. Sense l’exit(i), els fills continuen el bucle i creen més processos (una fork bomb acotada), i a més arriben al printf. La suma ja no surt 10 i s’imprimeixen moltes línies suma=....
int main(void) {
if (fork() == 0) {
printf("A\n");
if (fork() == 0) {
printf("B\n");
exit(0);
}
wait(NULL);
printf("C\n");
exit(0);
}
wait(NULL);
printf("D\n");
return 0;
}- Quina és la sortida? És única o n’hi ha diverses de possibles?
- I si s’elimina el
wait(NULL)del fill (el d’abans deprintf("C\n"))?
Pots veure el raonament
A B C D, única. L’avi espera el fill, i el fill espera el nét.
sequenceDiagram
autonumber
participant AV as Avi
participant F as Fill
participant N as Net
AV->>F: fork → crea el fill
AV->>AV: wait bloquejat
F->>F: printf A
F->>N: fork → crea el net
F->>F: wait bloquejat
N->>N: printf B
N->>N: exit 0
N-->>F: desbloqueja el wait
F->>F: printf C
F->>F: exit 0
F-->>AV: desbloqueja el wait
AV->>AV: printf D
Acontinua sent la primera iCcontinua sortint abans queD(l’avi espera el fill, que imprimeixCabans d’acabar). Però ara el fill ja no espera el nét, així queBpot sortir en qualsevol posició posterior aA. Sortides possibles:A B C D,A C B DiA C D B. Si el nét sobreviu al fill, queda orfe i l’adoptainit/systemd.