Procesos en Linux con C — ejercicios con cocineros que se multiplican, búferes que se duplican y códigos de salida que mienten (y la cocina que contrató a siete cocineros)

Quería que mi jefe de cocina contratara a tres cocineros, así que metí el fork() en un bucle de tres vueltas. Ejecuté y la terminal se llenó: «Cocinero 1», «Cocinero 2», «Cocinero 2», «Cocinero 3», «Cocinero 3», «Cocinero 3», «Cocinero 3». Siete mensajes para tres cocineros. Conté los PID: todos distintos. No se estaba repitiendo la salida: de verdad había más procesos de los que yo había pedido. El culpable era una sola línea que faltaba, y es el primer fragmento de «¿Qué falla aquí?» de hoy.

Estos ejercicios cierran el tema de procesos. Si vienes directamente, repasa antes la teoría y la práctica de procesos en C, porque aquí ya no te doy el código hecho.

Cómo usar este artículo

  1. Lee el enunciado y la salida esperada de cada nivel, y escribe tu programa sin mirar las soluciones.
  2. Si te atascas más de diez minutos, abre la pista 🔵 de ese nivel, y solo esa.
  3. En «¿Qué falla aquí?», predice la salida antes de leer la explicación. Equivocarte en la predicción es justo lo que hace que se te quede.
  4. Compara tu solución con la mía y, sobre todo, lee el «Por qué está hecho así»: ahí están las decisiones que separan un programa que funciona de uno que funciona por casualidad. Al final tienes el chuletario para repasar.

En todos los ejercicios los PID de tu salida serán distintos de los míos y cambiarán en cada ejecución. Lo que tiene que cuadrar es la relación entre ellos (el PID que recibe el padre es el del hijo, etc.) y el resto de los datos.

Nivel 1 (básico): el cocinero se presenta

Escribe un programa en el que el jefe crea un cocinero con fork(). El cocinero imprime su PID y el de su jefe y termina. El jefe espera a que el cocinero acabe y después imprime su propio PID y el valor que le devolvió fork(). Comprueba en la salida que los números cuadran entre los dos mensajes. Controla también el caso en que fork() falle.

Salida esperada (con tus PID):

Cocinero: mi PID es 444 y mi jefe es 443
Jefe: mi PID es 443 y fork() me devolvió 444

El jefe tiene que imprimir después del cocinero siempre, no solo cuando el planificador quiera. ¿Qué llamada lo garantiza? Y recuerda que el PID del hijo, visto desde el padre, es justo lo que devuelve fork().

Nivel 2 (intermedio): ¿cuántos platos habéis hecho?

Dos cocineros preparan 4 y 7 platos. Cada uno le devuelve al jefe su número de platos (sin variables compartidas: ya sabes que no funcionan). El jefe espera a los dos en el orden en que terminen, imprime cuántos platos hizo cada cocinero (identificándolo por su número, 1 o 2) y el total. Es el ejercicio 6.5 de la práctica con números fijos en lugar de aleatorios, para que la salida se pueda comprobar.

Salida esperada (el orden de los dos cocineros puede cambiar entre ejecuciones):

Cocinero 2: 7 platos
Cocinero 1: 4 platos
Total de platos: 11

El número viaja con exit(). Para leerlo, wait(&estado) y WEXITSTATUS. Y para saber de qué cocinero es, compara el PID que devuelve wait con los que guardaste al crear a los hijos.

Nivel 3 (desafío): las estadísticas del servicio

Cinco cocineros tardan 4, 8, 2, 10 y 6 minutos. Cada minuto se simula con 0,1 segundos de espera (usleep), así que el que menos tarda termina antes. Cada cocinero devuelve su tiempo al jefe. El jefe recoge a los cinco en el orden en que llegan, imprime quién llega y con qué tiempo, y al final calcula el tiempo medio y la desviación típica (la de la población: raíz cuadrada de la media de los cuadrados de las diferencias con la media), con dos decimales. Es el ejercicio 6.6 de la práctica con tiempos fijos.

Salida esperada:

Llega el cocinero 3: 2 min
Llega el cocinero 1: 4 min
Llega el cocinero 5: 6 min
Llega el cocinero 2: 8 min
Llega el cocinero 4: 10 min
Tiempo medio: 6.00 min
Desviación típica: 2.83 min

Guarda cada tiempo en un array según llega. Para la desviación necesitas sqrt de <math.h>, y al compilar hay que añadir -lm al final: gcc -Wall -std=c11 nivel3.c -o nivel3 -lm. Con -std=c11, usleep está escondida: pon #define _DEFAULT_SOURCE antes de los #include.

¿Qué falla aquí?

Tres fragmentos, tres errores que me encontré (o que vi en compañeros) haciendo la práctica. Lee cada uno, predice qué imprime y luego sigue leyendo.

Fragmento 1: el bucle que contrata de más

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            printf("Cocinero %d en marcha (PID %d)\n", i + 1, getpid());
        }
    }
    /* cada proceso espera a todos sus hijos antes de terminar */
    while (wait(NULL) > 0) {
    }
    return 0;
}

Predice: ¿cuántas líneas «Cocinero … en marcha» salen?

Salida real:

Cocinero 1 en marcha (PID 394)
Cocinero 2 en marcha (PID 395)
Cocinero 2 en marcha (PID 397)
Cocinero 3 en marcha (PID 398)
Cocinero 3 en marcha (PID 396)
Cocinero 3 en marcha (PID 399)
Cocinero 3 en marcha (PID 400)

Siete, y con siete PID distintos. El cocinero que nace en la vuelta i = 0 imprime su mensaje… y como no hay ningún exit(), sale del if y sigue en el bucle, con su propia copia de i. En la vuelta i = 1 hace su propio fork(), igual que el jefe. Cada proceso vivo al empezar una vuelta crea un hijo nuevo, así que el número de procesos se duplica en cada vuelta: 1 → 2 → 4 → 8. Y cada hijo nuevo imprime una línea con la vuelta en la que nació.

Árbol de procesos del fork en bucle Sin exit, cada hijo sigue el bucle: main crea 3 hijos, el primero crea 2, el segundo 1 y el nieto 1; en total 8 procesos y 7 mensajes. fork() en un bucle sin exit(): 8 procesos i=0 i=1 i=2 i=1 i=2 i=2 i=2 main Cocinero 1 Cocinero 2 Cocinero 3 Cocinero 2 Cocinero 3 Cocinero 3 Cocinero 3 1 + 2 + 4 = 7 mensajes cada hijo sigue el bucle
Cuenta los nodos de cada color: 1 dice «Cocinero 1», 2 dicen «Cocinero 2» y 4 dicen «Cocinero 3».

La cuenta, vuelta a vuelta:

Vuelta iProcesos antes del forkHijos nuevos (cada uno imprime «Cocinero i+1»)Procesos después
0112
1224
2448

1 + 2 + 4 = 7 líneas y 8 procesos en total (contando al jefe, que no imprime). El while (wait(NULL) > 0) hace que cada proceso espere a todos sus hijos, por eso nadie termina antes de tiempo, pero el orden de las líneas lo decide el planificador.

Con 10 vueltas serían 2¹⁰ = 1024 procesos y 1023 líneas. Con un bucle infinito, el sistema se queda sin procesos libres: es lo que se conoce como fork bomb. Un exit() que falta no es un detalle.

El arreglo es una línea: el cocinero termina dentro de su rama.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            printf("Cocinero %d en marcha (PID %d)\n", i + 1, getpid());
            exit(0);   /* el cocinero termina aquí y no vuelve al bucle */
        }
    }
    while (wait(NULL) > 0) {
    }
    return 0;
}

Salida real del arreglo:

Cocinero 2 en marcha (PID 408)
Cocinero 1 en marcha (PID 407)
Cocinero 3 en marcha (PID 409)

Tres líneas, tres cocineros. (Fíjate en que el 2 habló antes que el 1: los tres trabajan a la vez y el orden no está garantizado.)

Haz que todo hijo termine en la última línea de su rama con exit(), aunque creas que no hace falta. Es la forma más simple de no tener nunca un hijo «suelto» ejecutando código del padre.

Fragmento 2: la comanda que se recibe dos veces

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    printf("Comanda recibida. ");
    pid_t pid = fork();
    if (pid == 0) {
        printf("Cocinero: a los fogones\n");
        exit(0);
    }
    wait(NULL);
    printf("Jefe: plato servido\n");
    return 0;
}

Predice: «Comanda recibida.» se imprime antes del fork(), una sola vez… ¿no?

Salida real:

Comanda recibida. Cocinero: a los fogones
Comanda recibida. Jefe: plato servido

Sale dos veces. Y no porque el hijo vuelva a ejecutar el printf: el hijo nace después de esa línea. La clave es que printf no escribe directamente en la pantalla, sino en un búfer en memoria que se vacía cuando llega un \n (en una terminal), cuando se llena o cuando el proceso termina. «Comanda recibida. » no tiene \n, así que en el momento del fork() sigue en el búfer… y el búfer es memoria del proceso, así que se copia al hijo. Cada uno añade su línea y, al terminar, cada uno vacía su búfer completo.

El búfer copiado por fork() El texto sin salto de línea sigue en el búfer al hacer fork, así que padre e hijo lo imprimen cada uno. fork() también copia lo que hay en el búfer Padre, antes de fork() búfer de stdout: «Comanda recibida. « Hijo: su búfer al terminar Comanda recibida. Cocinero: a los fogones Padre: su búfer al terminar Comanda recibida. Jefe: plato servido Arreglo: fflush(stdout) antes de fork(), y no queda nada que copiar
El texto no se imprime dos veces por culpa del hijo: se imprime dos veces porque hay dos búferes.

Este fallo es aún más traicionero si rediriges la salida a un fichero (./buffer > log.txt). Ahí printf no vacía el búfer ni siquiera con \n, y cualquier mensaje anterior al fork() puede aparecer duplicado aunque termine en salto de línea.

El arreglo es vaciar el búfer antes de crear al hijo:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    printf("Comanda recibida. ");
    fflush(stdout);   /* vacía el búfer ANTES de fork: no queda nada que copiar */
    pid_t pid = fork();
    if (pid == 0) {
        printf("Cocinero: a los fogones\n");
        exit(0);
    }
    wait(NULL);
    printf("Jefe: plato servido\n");
    return 0;
}

Salida real del arreglo:

Comanda recibida. Cocinero: a los fogones
Jefe: plato servido

Ahora «Comanda recibida. » sale una vez: el fflush(stdout) la escribió en la pantalla antes del fork() y el hijo heredó un búfer vacío. El hijo continúa en la misma línea porque no hubo salto de línea, que es justo lo que pedía el código.

La regla práctica: fflush(stdout) justo antes de cada fork() si has impreso algo. No cuesta nada y te ahorra este misterio.

Fragmento 3: el cocinero más lento del mundo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        exit(7);   /* el cocinero ha tardado 7 minutos */
    }
    int estado;
    wait(&estado);
    printf("El cocinero ha tardado %d minutos\n", estado);
    return 0;
}

Predice: el cocinero hizo exit(7). ¿Cuántos minutos imprime el jefe?

Salida real:

El cocinero ha tardado 1792 minutos

1792 minutos: casi 30 horas para un plato. El 7 está ahí, pero escondido: wait no guarda el código de salida tal cual, sino en el segundo byte del entero estado, es decir, multiplicado por 256. 7 × 256 = 1792. El byte bajo se reserva para la señal que podría haber terminado al proceso.

Cuando un resultado te sale múltiplo de 256 (256, 512, 1792…), casi seguro que estás imprimiendo estado en lugar de WEXITSTATUS(estado).

El arreglo usa las macros de <sys/wait.h>:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        exit(7);   /* el cocinero ha tardado 7 minutos */
    }
    int estado;
    wait(&estado);
    if (WIFEXITED(estado)) {
        printf("El cocinero ha tardado %d minutos\n", WEXITSTATUS(estado));
    }
    return 0;
}

Salida real del arreglo:

El cocinero ha tardado 7 minutos

WIFEXITED confirma que el cocinero terminó con exit y WEXITSTATUS saca el 7 del segundo byte.

Si quieres comprobar el desplazamiento con tus propios ojos, imprime estado >> 8: da el mismo 7. Pero en tu código usa la macro, que es lo portable.

Soluciones

Solución del nivel 1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    }
    if (pid == 0) {
        /* el cocinero se presenta */
        printf("Cocinero: mi PID es %d y mi jefe es %d\n", getpid(), getppid());
        exit(0);
    }
    /* el jefe espera para que su mensaje salga siempre después */
    wait(NULL);
    printf("Jefe: mi PID es %d y fork() me devolvió %d\n", getpid(), pid);
    return 0;
}

Salida real:

Cocinero: mi PID es 444 y mi jefe es 443
Jefe: mi PID es 443 y fork() me devolvió 444

Comprobación: el cocinero dice que su PID es 444, y el jefe dice que fork() le devolvió 444. El cocinero dice que su jefe es 443, y el jefe dice que su PID es 443. Los dos pares cuadran, que es justo lo que pedía el enunciado.

Por qué está hecho así:

  • **wait(NULL) antes del mensaje del jefe.** Sin él, el orden de las dos líneas dependería del planificador. Pasamos NULL porque no nos interesa cómo terminó el cocinero, solo que haya terminado.
  • **exit(0) al final de la rama del hijo.** Aunque después no haya ningún bucle, si el cocinero no saliera ejecutaría el wait (que le devolvería -1 al no tener hijos) y el printf del jefe: verías dos líneas «Jefe» y la segunda diría que fork() devolvió 0.
  • **return 1 si pid < 0.** Si no hay hijo, no tiene sentido seguir; el 1 avisa a quien lanzó el programa de que algo ha ido mal.

Solución del nivel 2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    int platos[2] = {4, 7};   /* platos que prepara cada cocinero */
    pid_t pids[2];

    for (int i = 0; i < 2; i++) {
        pids[i] = fork();
        if (pids[i] < 0) {
            perror("fork");
            return 1;
        }
        if (pids[i] == 0) {
            /* cada cocinero devuelve su número de platos como código de salida */
            exit(platos[i]);
        }
    }

    int total = 0;
    for (int k = 0; k < 2; k++) {
        int estado;
        pid_t fin = wait(&estado);   /* el primero que acabe, sea cual sea */
        if (WIFEXITED(estado)) {
            int p = WEXITSTATUS(estado);
            /* averiguamos qué cocinero era comparando el PID */
            int num = (fin == pids[0]) ? 1 : 2;
            printf("Cocinero %d: %d platos\n", num, p);
            total += p;
        }
    }
    printf("Total de platos: %d\n", total);
    return 0;
}

Salida real:

Cocinero 2: 7 platos
Cocinero 1: 4 platos
Total de platos: 11

En mi ejecución terminó antes el cocinero 2, y el programa lo identificó bien. Total: 4 + 7 = 11 platos, el mismo resultado sea cual sea el orden de llegada.

La recogida, con lupa:

int total = 0;
for (int k = 0; k < 2; k++) {
    int estado;
    pid_t fin = wait(&estado);   /* el primero que acabe, sea cual sea */
    if (WIFEXITED(estado)) {
        int p = WEXITSTATUS(estado);
        /* averiguamos qué cocinero era comparando el PID */
        int num = (fin == pids[0]) ? 1 : 2;
        printf("Cocinero %d: %d platos\n", num, p);
        total += p;
    }
}
printf("Total de platos: %d\n", total);
Vuelta kwait devuelve¿Es pids[0]?numWEXITSTATUStotal
0PID del cocinero 2no277
1PID del cocinero 1sí1411

Por qué está hecho así:

  • **wait y no waitpid**, porque el enunciado pide recoger en el orden en que terminan. Con waitpid(pids[k], …) siempre saldría primero el cocinero 1.
  • **(fin == pids[0]) ? 1 : 2**: con dos hijos basta una comparación. Con más, harías el bucle de búsqueda del programa 1 de la práctica.
  • **if (WIFEXITED(estado))** antes de leer el código: si un cocinero muriera por una señal, no sumamos un número sin sentido.
  • 4 y 7 caben en un byte. Si un cocinero preparara 300 platos, esta solución no serviría (llegarían 44).

Solución del nivel 3

#define _DEFAULT_SOURCE   /* pide a glibc usleep(), que -std=c11 esconde */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <math.h>

#define N 5

int main(void) {
    int minutos[N] = {4, 8, 2, 10, 6};
    pid_t pids[N];

    for (int i = 0; i < N; i++) {
        pids[i] = fork();
        if (pids[i] < 0) {
            perror("fork");
            return 1;
        }
        if (pids[i] == 0) {
            /* cada minuto simulado dura 0,1 s: el que menos tarda acaba antes */
            usleep(minutos[i] * 100000);
            exit(minutos[i]);
        }
    }

    int recogidos[N];   /* los tiempos, en el orden en que llegan */
    for (int k = 0; k < N; k++) {
        int estado;
        pid_t fin = wait(&estado);
        int num = 0;
        for (int i = 0; i < N; i++) {
            if (pids[i] == fin) {
                num = i + 1;
            }
        }
        recogidos[k] = WEXITSTATUS(estado);
        printf("Llega el cocinero %d: %d min\n", num, recogidos[k]);
    }

    /* media: suma entre N, en double para no perder decimales */
    double suma = 0;
    for (int k = 0; k < N; k++) {
        suma += recogidos[k];
    }
    double media = suma / N;

    /* desviación típica: raíz de la media de los cuadrados de (x - media) */
    double cuadrados = 0;
    for (int k = 0; k < N; k++) {
        cuadrados += (recogidos[k] - media) * (recogidos[k] - media);
    }
    double desviacion = sqrt(cuadrados / N);

    printf("Tiempo medio: %.2f min\n", media);
    printf("Desviación típica: %.2f min\n", desviacion);
    return 0;
}

Salida real:

Llega el cocinero 3: 2 min
Llega el cocinero 1: 4 min
Llega el cocinero 5: 6 min
Llega el cocinero 2: 8 min
Llega el cocinero 4: 10 min
Tiempo medio: 6.00 min
Desviación típica: 2.83 min

Los cocineros llegan en el orden de sus tiempos: el 3 (2 min, 0,2 s), el 1 (4 min), el 5 (6 min), el 2 (8 min) y el 4 (10 min). Todo el programa tarda lo que el más lento, 1 segundo, porque los cinco esperan a la vez.

Ahora las cuentas a mano. La media: (2 + 4 + 6 + 8 + 10) / 5 = 30 / 5 = 6,00. Para la desviación típica:

Tiempo xx − media(x − media)²
2−416
4−24
600
824
10416
Suma40

40 / 5 = 8, y la raíz de 8 es 2,828…, que con dos decimales es 2,83. Cuadra con la salida.

La parte de procesos es la recogida:

int recogidos[N];   /* los tiempos, en el orden en que llegan */
for (int k = 0; k < N; k++) {
    int estado;
    pid_t fin = wait(&estado);
    int num = 0;
    for (int i = 0; i < N; i++) {
        if (pids[i] == fin) {
            num = i + 1;
        }
    }
    recogidos[k] = WEXITSTATUS(estado);
    printf("Llega el cocinero %d: %d min\n", num, recogidos[k]);
}

Por qué está hecho así:

  • **usleep(minutos[i] * 100000)**: usleep recibe microsegundos, así que 4 minutos son 400 000 µs = 0,4 s. Con sleep (segundos enteros) el programa tardaría 10 segundos.
  • **#define _DEFAULT_SOURCE en la primera línea**: -std=c11 pide C estándar «puro» y glibc esconde las funciones que no lo son, como usleep. Esa macro, definida antes de cualquier #include, le pide que las muestre (lo viste en el tema del preprocesador: las macros cambian lo que ve el compilador).
  • **recogidos[k] en el orden de llegada**: la media y la desviación no dependen del orden, así que no hace falta reordenar; pero así la salida cuenta la historia real del servicio.
  • **double para suma, media y cuadrados**: con enteros, la media de 4, 8 y 3 (15 / 3) daría bien, pero la de 4 y 7 daría 5 en lugar de 5,5.
  • **-lm al compilar**: sqrt está en la biblioteca matemática, que gcc no enlaza por defecto. Sin -lm verás undefined reference to 'sqrt'.

Míralo paso a paso en Python Tutor

Python Tutor ejecuta el código línea a línea y dibuja las variables (elige C en el desplegable). Como no puede crear procesos, vamos a ver la parte del fragmento 3 que se puede simular: qué entero recibe el padre según el número que el hijo pasa a exit(), incluido el caso que se sale de rango.

#include <stdio.h>

/* qué le llega al padre según el número que el hijo pasa a exit() */
int main(void) {
    int codigos[3] = {7, 255, 300};

    for (int i = 0; i < 3; i++) {
        /* el sistema solo guarda el byte bajo (0-255), desplazado 8 bits */
        int estado = (codigos[i] & 0xFF) << 8;
        int recibido = (estado >> 8) & 0xFF;   /* lo que daría WEXITSTATUS */
        printf("exit(%d): estado=%d, WEXITSTATUS=%d\n", codigos[i], estado, recibido);
    }
    return 0;
}

Salida:

exit(7): estado=1792, WEXITSTATUS=7
exit(255): estado=65280, WEXITSTATUS=255
exit(300): estado=11264, WEXITSTATUS=44

Qué observar paso a paso:

  1. En la primera vuelta, estado vale 1792: el mismo número del fragmento 3. recibido vuelve a ser 7.
  2. En la segunda vuelta, 255 da estado 65280 (255 × 256) y recibido 255: es el máximo que cabe.
  3. En la tercera vuelta, fíjate en codigos[i] & 0xFF: de 300 solo se queda 44 (300 − 256). Por eso exit(300) llega al padre como 44.
  4. Compara las dos columnas de cada línea: estado siempre es recibido × 256. Eso es todo lo que hace la macro WEXITSTATUS al revés.

Chuletario: procesos en C

Lo esencial en 30 segundos

  • fork() crea una copia del proceso: devuelve 0 al hijo, el PID del hijo al padre y -1 si falla.
  • El hijo termina siempre con exit() dentro de su rama.
  • La memoria se copia: lo que cambia el hijo no lo ve el padre.
  • exec sustituye el programa (mismo PID) y no vuelve si funciona. Para lanzar otro programa: fork + exec en el hijo.
  • wait(&estado) espera al primero que termine; waitpid(pid, &estado, 0), a uno concreto.
  • El código de exit(n) se lee con WEXITSTATUS(estado) y va de 0 a 255.
  • fflush(stdout) antes de fork() si has impreso algo.

Quiero… / Código / Resultado

Quiero…CódigoResultado
Mi PID y el de mi padregetpid(), getppid()Dos pid_t (enteros)
Crear un hijopid_t pid = fork();Dos procesos desde aquí
Saber si soy el hijoif (pid == 0)Solo el hijo entra
Esperar a cualquier hijopid_t fin = wait(&estado);PID del que acabó, -1 si no quedan
Esperar a uno concretowaitpid(pid, &estado, 0);Bloquea hasta que acabe ese
Devolver un númeroexit(7);El padre lee 7 con WEXITSTATUS
Saber si acabó con exitWIFEXITED(estado)Verdadero o falso
Saber qué señal lo matóWIFSIGNALED(estado), WTERMSIG(estado)Por ejemplo, 6 con abort()
Ejecutar otro programa (lista)execlp("date", "date", NULL);Se convierte en date
Ejecutar otro programa (array)char *a[] = {"ls", "-l", NULL}; execvp(a[0], a);Se convierte en ls -l
Avisar si exec fallaperror(a[0]); exit(127);ls: No such file or directory, código 127

Plantillas de los ejercicios

Crear N hijos que trabajan a la vez y recogerlos:

pid_t pids[N];
for (int i = 0; i < N; i++) {
    pids[i] = fork();
    if (pids[i] < 0) { perror("fork"); exit(1); }
    if (pids[i] == 0) {
        /* trabajo del hijo i */
        exit(resultado);          /* 0..255 */
    }
}
for (int k = 0; k < N; k++) {
    int estado;
    pid_t fin = wait(&estado);    /* el primero que acabe */
    if (WIFEXITED(estado)) {
        int r = WEXITSTATUS(estado);
        /* buscar fin en pids[] para saber quién era */
    }
}

Que una orden se ejecute como en la shell y saber cómo ha ido:

pid_t pid = fork();
if (pid == 0) {
    execvp(argv[0], argv);        /* argv termina en NULL */
    perror(argv[0]);
    exit(127);
}
int estado;
waitpid(pid, &estado, 0);
int codigo = WIFEXITED(estado) ? WEXITSTATUS(estado) : -1;

Con estas plantillas salen los tres niveles: el 1 con N = 1, el 2 con N = 2 y la suma de los códigos (11 platos) y el 3 con N = 5, usleep en el hijo y la media 6,00 y la desviación 2,83 al final.

Síntoma / Causa probable

SíntomaCausa probable
Salen más mensajes de hijos de los que creasteFalta exit() en la rama del hijo dentro de un bucle
Un texto anterior al fork() sale dos vecesQuedaba en el búfer: falta \n o fflush(stdout)
El código de salida sale 256, 512, 1792…Imprimes estado en vez de WEXITSTATUS(estado)
exit(300) llega como 44El código solo tiene un byte (0-255)
El padre no ve lo que cambió el hijoLa memoria se copia, no se comparte
No sale nada de lo que hay después del execNormal: exec funcionó y sustituyó el programa
El prompt aparece antes que los mensajes del hijoEl padre no hizo wait()
unknown type name 'pid_t'Falta <sys/types.h> (o <sys/wait.h>) con -std=c11
implicit declaration of function 'usleep'Falta #define _DEFAULT_SOURCE antes de los #include
undefined reference to 'sqrt'Falta -lm al final de la orden de gcc

Antes de entregar, pregúntate

  1. ¿Todos mis hijos terminan con exit() dentro de su rama?
  2. ¿Hago un wait (o waitpid) por cada hijo que creo?
  3. ¿Leo el código de salida con WIFEXITED y WEXITSTATUS, y cabe en un byte?
  4. ¿He explicado en la memoria que los PID y el orden de los mensajes cambian en cada ejecución?
Sigue aprendiendoTodos los artículos de C

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *