Ir al contenido

fork, exec y contar «Hello!»

La pregunta 1 de los dos E1 de 2024 fue la misma idea: ¿cuántas veces se imprime «Hello!»? (3 pts). Se resuelve dibujando el árbol de procesos y anotando, en cada proceso, el valor de las variables que decide cada if.

  1. fork() devuelve 0 en el hijo y el pid del hijo en el padre. Desde ese punto ambos ejecutan la misma línea siguiente.
  2. El hijo recibe una copia de toda la memoria: variables, buffers de stdio, semáforos y mutex con su valor actual. Nada se comparte después del fork (salvo memoria compartida explícita).
  3. Solo se copia el hilo que llamó a fork.
  4. execv reemplaza el programa: lo que sigue en el código viejo no se ejecuta, y el nuevo programa empieza en main con otro argc/argv.
  5. Los hilos comparten la memoria de su proceso. Si el hilo recibe &pid, lee el pid de su proceso.
Se hereda (se copia)No se hereda
text, data, BSS, heap y stack (copias independientes)el PID (el hijo tiene uno nuevo) y el PPID
descriptores de archivo abiertos, que comparten el offsetseñales pendientes
credenciales (UID/GID), entorno, directorio actuallocks de archivos (fcntl) y memoria bloqueada (mlock)
handlers y máscara de señalestimers y alarmas; E/S asíncrona (AIO)
tablas de páginas (con COW), contexto de hardware, estructuras IPCajustes de semop (semundo)

En el ejemplo fork2.c de clase (static int g = 5; int loc = 9;), el hijo hace loc++; g-- e imprime loc=10 g=4, y el padre hace loc--; g++ e imprime loc=8 g=6. Son copias: nada de lo que haga uno afecta al otro.

  • En Linux, fork() es clone() con ciertos flags, y usa COW (copy-on-write): padre e hijo comparten las páginas marcadas como solo lectura, y una página se duplica recién cuando alguno la escribe. El costo real de fork es copiar las tablas de páginas y crear el task_struct.
  • Si el hijo hace exec enseguida, las páginas nunca se copian. Por eso fork + exec es barato. (vfork = CLONE_VFORK: el padre se suspende hasta que el hijo hace exec o exit.)
  • Un hilo en Linux es un clone con CLONE_VM (comparte el espacio de direcciones), además de CLONE_FS, CLONE_FILES, CLONE_SIGHAND y CLONE_THREAD. Para el kernel, un hilo es simplemente un proceso que comparte recursos.
  • Árbol de procesos: cada task_struct apunta a su parent y tiene una lista de children. init_task es estático, y for (t = current; t != &init_task; t = t->parent); sube hasta init. La lista de tareas es circular y doblemente enlazada (for_each_process).

Árbol de procesos: ¿cuántos «Hello!»?

Avanza evento por evento. Recuerda: fork copia el proceso entero (variables y semáforos incluidos, pero solo el hilo que llamó), execv reemplaza la imagen y exit lo termina.

0void *thread_print(void *value) {
1 if (*((pid_t *) value))
2 printf("Hello!\n");
3}
4int main() {
5 pid_t parent;
6 pid_t grandparent = fork();
7 if (!grandparent) {
8 parent = fork();
9 printf("Hello!\n");
10 if (!parent) exit(0);
11 } else {
12 pthread_create(&t, NULL, thread_print, &grandparent);
13 pthread_join(t, NULL);
14 }
15 printf("Hello!\n");
16 parent = fork();
17 pthread_create(&t2, NULL, thread_print, &parent);
18 pthread_join(t2, NULL);
19}
«Hello!» impresos0
Procesos creados1
  • P
0/14
Pulsa «Siguiente evento» o «Reproducir». Intenta predecir el total antes de terminar.

Con el código de la segunda pestaña: argc = 1 al inicio, execv("./hello-fork-exec", {"1","2"}) y un semáforo iniciado en 1 después del segundo fork.

Ver solución

4 veces.

  1. P tiene argc = 1 → no entra al primer if.
  2. P hace fork → H1 (pid = 0) hace execv y el programa reinicia con argc = 2 → imprime (1) y termina.
  3. P hace fork → H2. Cada proceso hace su propio sem_init(&mutex, 0, 1): son semáforos distintos (copias).
  4. El thread de P hace sem_wait (1→0), imprime (2) y hace sem_post porque pid ≠ 0.
  5. El thread de H2 hace sem_wait (1→0), imprime (3) y no hace sem_post porque su pid = 0.
  6. El main de P hace sem_wait (1→0) → imprime (4). El main de H2 hace sem_wait con valor 0 → se bloquea para siempre.

Autoevaluación

0/5 correctas · 0 respondidas
1. ¿Cuántos procesos existen al final de fork(); fork(); fork();?
2. Después de pid = fork();, ¿qué vale pid en el hijo?
3. Un proceso con 4 hilos llama a fork() desde el hilo 2. ¿Cuántos hilos tiene el hijo?
4. Si execv tiene éxito, ¿qué pasa con las líneas que lo siguen?
5. Un semáforo global sem_t s iniciado en 0 antes de fork. El padre hace sem_post(&s). ¿Se despierta un sem_wait(&s) del hijo?

Fuente ·for_partial/E1_OS_2024_I__Respuestas_.pdf (P1) · for_partial/E1_OS_2024_II__Solucionario_.pdf (P1) · código del curso so/OST-02-Processes_Syscalls_Signals · prueba propia verif_parcial/hello24.c, hellofe.c