Processes in Linux with C — practice with a head chef, cooks working at the same time and a pass that runs real commands (and the service that closed too early)

The first version of my kitchen had a very rude head chef. It launched three cooks with fork(), printed “Service closed” and ended. The prompt came back in the terminal… and a couple of seconds later, messages from cooks finishing their dishes started appearing on top of whatever I was typing. The chef had gone home with the kitchen still full. I was missing the most important part: the chef had to wait for each cook with wait() before closing. That lesson (the parent waits, collects and only then moves on) is the thread running through today’s three programs.

This practice puts to work everything from the processes in C theory. There are three medium-level programs, all in the same kitchen: the chef launches several cooks at once and collects them as they finish; then the cooks return their times with exit(); and finally the chef sets up a “pass” that runs real Linux commands (wc, grep, head…) with fork + exec, including one that fails and another that does not even exist, because real programs have to know what to do when something goes wrong.

Before you start

Make sure you are clear on:

  • What fork() returns to each process (0 to the child, the child’s PID to the parent, -1 on failure) and why the child ends with exit().
  • That memory is copied: what a child changes, the parent does not see.
  • That wait() returns the PID of the first child to finish and waitpid() waits for a specific one.
  • That the exit code is read with WEXITSTATUS(status) and only goes from 0 to 255.

If any of them sounds shaky, review the theory before going on.

Compile each program with gcc -Wall -std=c11 kitchen.c -o kitchen and run it with ./kitchen from a Linux terminal (on Windows, with WSL). The PIDs you see will be different from mine and will change on every run: that is normal, the system hands them out.

Program 1: the kitchen service

The chef opens the service with three cooks: paella, salad and croquettes. All three work at the same time and each dish takes a different time (3, 1 and 2 seconds, simulated with sleep). The chef has to wait for the three of them and, every time one finishes, say which cook it was. What makes it tricky: wait() returns a PID, not “cook 2”, and on top of that the cooks do not finish in the order they were created.

The idea

First, a loop that creates the three cooks. Inside, the if (pids[i] == 0) separates the cook’s code, which always ends in exit(0). The chef keeps each PID in a pids array so it can recognise later who it is collecting. Second, another loop that calls wait() three times (once per child) and looks up the returned PID in the array to translate it into “cook number such-and-such”.

Before coding, think about who runs each line. The first loop is started by the chef alone, but every fork() creates a process that is also “inside” the loop. If that process does not leave with exit(), it will keep looping and creating cooks.

The code

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

#define N 3

int main(void) {
    const char *dishes[N] = {"paella", "salad", "croquettes"};
    int seconds[N] = {3, 1, 2};   /* how long each dish takes (simulated with sleep) */
    pid_t pids[N];                /* the chef notes each cook's PID */

    printf("Chef %d: opening service with %d cooks\n", getpid(), N);

    for (int i = 0; i < N; i++) {
        pids[i] = fork();
        if (pids[i] < 0) {
            perror("fork");
            exit(1);
        }
        if (pids[i] == 0) {
            /* COOK CODE: prepares the dish and leaves */
            printf("  Cook %d (PID %d): starting %s\n", i + 1, getpid(), dishes[i]);
            sleep(seconds[i]);
            printf("  Cook %d (PID %d): finishing %s\n", i + 1, getpid(), dishes[i]);
            /* essential: without exit, the child would keep looping and create more cooks */
            exit(0);
        }
    }

    /* CHEF CODE: waits for the N cooks in the order they finish */
    for (int k = 0; k < N; k++) {
        pid_t done = wait(NULL);
        /* looks up in the list which cook had that PID */
        int num = -1;
        for (int i = 0; i < N; i++) {
            if (pids[i] == done) {
                num = i + 1;
            }
        }
        printf("Chef: collecting cook %d (PID %d)\n", num, done);
    }
    printf("Chef: service closed\n");
    return 0;
}

The program has two very different parts. The first is the loop that creates the cooks:

for (int i = 0; i < N; i++) {
    pids[i] = fork();
    if (pids[i] < 0) {
        perror("fork");
        exit(1);
    }
    if (pids[i] == 0) {
        /* COOK CODE: prepares the dish and leaves */
        printf("  Cook %d (PID %d): starting %s\n", i + 1, getpid(), dishes[i]);
        sleep(seconds[i]);
        printf("  Cook %d (PID %d): finishing %s\n", i + 1, getpid(), dishes[i]);
        /* essential: without exit, the child would keep looping and create more cooks */
        exit(0);
    }
}

The chef goes round three times and calls fork() each time. The returned value is stored directly in pids[i]: in the chef it will be the PID of the new cook; in the cook it will be 0. That is why only the newborn cook satisfies if (pids[i] == 0): it prints, sleeps for as long as its dish takes and leaves with exit(0). It never goes back to the loop. The chef, on the other hand, does not enter the if and goes straight to the next round, so it creates all three almost at once.

And a question that always comes up: if the child is a copy of the chef, doesn’t it also have its pids array? Yes, but its copy is half filled and it does not use it for anything: the useful array is the chef’s.

The second part is the collection:

/* CHEF CODE: waits for the N cooks in the order they finish */
for (int k = 0; k < N; k++) {
    pid_t done = wait(NULL);
    /* looks up in the list which cook had that PID */
    int num = -1;
    for (int i = 0; i < N; i++) {
        if (pids[i] == done) {
            num = i + 1;
        }
    }
    printf("Chef: collecting cook %d (PID %d)\n", num, done);
}
printf("Chef: service closed\n");

Three wait(NULL) calls, one per cook. Each wait blocks the chef until some child ends and returns its PID in done. The inner loop walks through pids looking for that number; when it finds it, num becomes the position + 1 (cooks are numbered from 1). The initial -1 is an “impossible” value that would give away an unknown PID.

Output

Chef 469: opening service with 3 cooks
  Cook 1 (PID 470): starting paella
  Cook 3 (PID 472): starting croquettes
  Cook 2 (PID 471): starting salad
  Cook 2 (PID 471): finishing salad
Chef: collecting cook 2 (PID 471)
  Cook 3 (PID 472): finishing croquettes
Chef: collecting cook 3 (PID 472)
  Cook 1 (PID 470): finishing paella
Chef: collecting cook 1 (PID 470)
Chef: service closed

Let’s check it against the clock. All three start at second 0 (the order of the three “starting” lines is up to the scheduler: in my run cook 3 came out before cook 2). The salad takes 1 s, the croquettes 2 s and the paella 3 s, so they finish in the order 2, 3, 1, and the chef collects them in exactly that order: wait() returns whoever finishes first, not whoever was created first. “Service closed” comes out last, when nobody is left in the kitchen. The whole program takes about 3 seconds, not 3 + 1 + 2 = 6, because the cooks work in parallel.

Kitchen timeline Three cooks start at the same time and take 3, 1 and 2 seconds; the head chef collects them with wait() at seconds 1, 2 and 3. The head chef collects each cook when they finish 0 s 1 s 2 s 3 s Cook 1 paella exit(0) Cook 2 salad exit(0) Cook 3 croquettes exit(0) Head chef collects 2 collects 3 collects 1 wait()
wait() returns whoever finishes first, not whoever was created first.

The trace of the collection, with the PIDs from my run (470, 471 and 472 for cooks 1, 2 and 3):

Round kwait returns doneSearch in pidsnumMessage
0 (second 1)471470 ≠, 471 =, 472 ≠2collecting cook 2
1 (second 2)472470 ≠, 471 ≠, 472 =3collecting cook 3
2 (second 3)470470 =, 471 ≠, 472 ≠1collecting cook 1

Questions you might ask

**Why does the chef call wait exactly N times?** Because each wait collects one child. With fewer, some cook would stay a zombie until the chef ended; with more, the extra wait would return -1 straight away (no children left), so it does not block, but you would get a PID of -1 that matches nobody.

Why store the PIDs in an array? Because wait() only gives you a number. Without the array, the chef would know that “process 471 has finished”, but not which dish it was. It is the same idea as a numbered order ticket.

Why do the cooks’ messages start with two spaces? Just to read the output more easily: that way the chef’s messages and the cooks’ are easy to tell apart at a glance, and they come out mixed because they work at the same time.

What if two cooks took the same time? They would finish almost together and the order between them would depend on the scheduler: sometimes you would collect one first and sometimes the other. The program would still be correct because it looks up each PID instead of assuming an order.

**What happens if I remove the cook’s exit(0)?** Cook 1, after finishing its dish, would go back to the loop with i = 1 and create its own cook, and so on. You will see it in full detail in the exercises.

Putting the wait() inside the first loop, right after the fork(). The program works, but the chef waits for each cook before creating the next: the dishes are made one after another and it takes 6 seconds instead of 3. You have lost the whole point of having several processes.

Try it yourself

  • Add a fourth cook ("flan", 4 seconds) by changing only N and the two arrays. Predict the collection order before running it.
  • Change wait(NULL) to waitpid(pids[k], NULL, 0) and notice that now the chef collects in the order 1, 2, 3, even though cook 1 is the last to finish.

Program 2: how long each dish took

Now the chef wants numbers. Four cooks (paella, salad, croquettes and flan) take 25, 8, 12 and 15 minutes, and each one returns its time to the chef. The chef adds them up and works out the average per dish. The problem is the one from the theory hook: a shared variable is no good, because memory is copied. We have to use the only way back we have for now: the exit() exit code.

The idea

Each cook calls exit(minutes[i]): its exit code is the data. The chef collects each one with waitpid(pids[i], &status, 0), in list order so the running total comes out in order, checks with WIFEXITED that it ended properly and gets the minutes with WEXITSTATUS. At the end, total / N with decimals.

Before coding, think about the limit of the channel. The exit code is one byte: 25, 8, 12 and 15 fit easily, but a time of 300 minutes would arrive as 44.

The code

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

#define N 4

int main(void) {
    const char *dishes[N] = {"paella", "salad", "croquettes", "flan"};
    int minutes[N] = {25, 8, 12, 15};   /* each cook returns it with exit() */
    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) {
            printf("  Cook %d: %s in %d minutes\n", i + 1, dishes[i], minutes[i]);
            /* the exit code is a number from 0 to 255: good for sending a small value */
            exit(minutes[i]);
        }
    }

    int total = 0;
    for (int i = 0; i < N; i++) {
        int status;
        /* waitpid waits for THAT cook: this way we collect in list order */
        waitpid(pids[i], &status, 0);
        if (WIFEXITED(status)) {
            int m = WEXITSTATUS(status);   /* the number the child passed to exit() */
            total += m;
            printf("Chef: %s -> %d min (running total %d)\n", dishes[i], m, total);
        }
    }
    /* (double) so the division is not done with integers */
    printf("Chef: %d minutes in total, %.1f average per dish\n", total, (double)total / N);
    return 0;
}

The new part is the collection:

int total = 0;
for (int i = 0; i < N; i++) {
    int status;
    /* waitpid waits for THAT cook: this way we collect in list order */
    waitpid(pids[i], &status, 0);
    if (WIFEXITED(status)) {
        int m = WEXITSTATUS(status);   /* the number the child passed to exit() */
        total += m;
        printf("Chef: %s -> %d min (running total %d)\n", dishes[i], m, total);
    }
}

waitpid(pids[i], &status, 0) waits for that specific cook. If the salad cook finishes before the paella cook (it certainly will, since they do nothing but print), it does not matter: its status is kept until the chef comes to ask for it, and the chef asks in list order. WIFEXITED(status) checks that the child ended with exit (not because of a signal) and only then does WEXITSTATUS(status) make sense, getting the byte with the code. Each value is added to total.

This is what the status integer of the paella cook looks like inside:

The bits of status The integer status is 6400: the high byte holds the exit code 25 and the low byte is 0. Where wait() stores the exit code exit code = 25 signal and other data = 0 0 15 0 14 0 13 1 12 1 11 0 10 0 9 1 8 0 7 0 6 0 5 0 4 0 3 0 2 0 1 0 0 bit status = 25 × 256 = 6400 WEXITSTATUS(status) = (status >> 8) & 0xFF = 25 Only one byte fits: exit(300) arrives as 300 – 256 = 44
That is why printing status gives 6400 and you need WEXITSTATUS to read the 25.

The exit code 25 sits in the high byte (bits 8 to 15), and that is why the whole integer is 25 × 256 = 6400. WEXITSTATUS does exactly the operation in the drawing: shift 8 bits to the right and keep one byte.

Output

  Cook 1: paella in 25 minutes
  Cook 2: salad in 8 minutes
  Cook 3: croquettes in 12 minutes
Chef: paella -> 25 min (running total 25)
Chef: salad -> 8 min (running total 33)
Chef: croquettes -> 12 min (running total 45)
  Cook 4: flan in 15 minutes
Chef: flan -> 15 min (running total 60)
Chef: 60 minutes in total, 15.0 average per dish

Checking by hand: 25 + 8 = 33, 33 + 12 = 45 and 45 + 15 = 60 minutes, the same running totals the chef prints. The average: 60 / 4 = 15.0 minutes per dish. That (double)total is essential: without it, 60 / 4 would be done with integers (here it is exact, but with 61 minutes you would get 15 instead of 15.25).

Look at the order of the lines too. The chef’s messages always come out in list order because it uses waitpid with each PID. The cooks’ messages, on the other hand, come out whenever each one gets the CPU: in my run, cook 4 spoke after the chef had already collected three. The only thing guaranteed is that “Cook 4: flan…” comes out before “Chef: flan…”, because the chef cannot collect cook 4 until it has ended (and to end, it prints first).

Round iDishwaitpid waits forWEXITSTATUStotal
0paellacook 12525
1saladcook 2833
2croquettescook 31245
3flancook 41560

Questions you might ask

**Why waitpid and not wait?** With wait you would also collect all four, but in the order they finish, and you would have to look up each PID in the array to know which dish it was (as in program 1). With waitpid you set the order and the running total comes out in menu order.

**What is WIFEXITED for if they all call exit?** For not trusting blindly. If a cook died because of a signal (a segmentation fault, for example), WEXITSTATUS would give a meaningless value and you would add it as if it were minutes. With the if, that cook just does not count.

**Why not use a global array real_minutes that the children fill in?** Because each child would fill in its own copy. It is exactly the mistake of the counter in the theory hook.

What would happen with a 300-minute dish? The chef would get 44 (300 − 256) and the total would be wrong without any warning. For data like that you need files or pipes, which will come in the next topics.

Adding status instead of WEXITSTATUS(status). With 25 minutes you would add 6400: a service lasting several days.

Try it yourself

  • Make the chef also print the slowest and the fastest dish, keeping the maximum and the minimum while it collects.
  • Change the flan’s time to 300 and check that the total comes out as 89 instead of 345: the chef gets 44 minutes (300 − 256), and 25 + 8 + 12 + 44 = 89.

Program 3: the kitchen pass

The pass is the spot where the chef checks the orders before they go out. Our chef first writes today’s orders to a file orders.txt and then launches five Linux commands, one after another, the way a shell would: count the lines (wc -l), count the pizzas (grep -c pizza), look at the first two orders (head -n 2), try to read a bookings file that does not exist (cat bookings.txt) and try to run dishwasher, a program that is not installed. For each command it notes the exit code and at the end it counts how many had problems.

The idea

A run function that does what the shell does with each command: fork(), execvp in the child (with perror + exit(127) in case the exec fails) and waitpid in the parent, returning the exit code. Another function, show, prints the command with a $ in front so the output looks like a terminal. The commands go in an array of arrays of char *, each one ending in NULL, which is exactly what execvp asks for.

Before coding, think about the two ways of “failing”. If cat runs but cannot find the file, the exec worked and it is cat that returns a non-zero code. If dishwasher does not exist, it is the exec that fails, and your program sets the code.

The code

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

/* prints the command as you would see it in the terminal: $ wc -l orders.txt */
void show(char *argv[]) {
    printf("$");
    for (int i = 0; argv[i] != NULL; i++) {
        printf(" %s", argv[i]);
    }
    printf("\n");
    fflush(stdout);   /* so it comes out before the child writes */
}

/* fork + exec + waitpid: the same thing the shell does with each command */
int run(char *argv[]) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return -1;
    }
    if (pid == 0) {
        execvp(argv[0], argv);
        /* if we are still here, the command does not exist or could not run */
        perror(argv[0]);
        exit(127);
    }
    int status;
    waitpid(pid, &status, 0);
    return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}

int main(void) {
    /* 1. the chef writes today's orders to a file */
    FILE *f = fopen("orders.txt", "w");
    if (f == NULL) {
        perror("orders.txt");
        return 1;
    }
    fprintf(f, "table 1: paella\ntable 2: pizza margherita\ntable 3: croquettes\ntable 4: four cheese pizza\n");
    fclose(f);

    /* 2. the commands to launch, each one ending in NULL */
    char *commands[][5] = {
        {"wc", "-l", "orders.txt", NULL},
        {"grep", "-c", "pizza", "orders.txt", NULL},
        {"head", "-n", "2", "orders.txt", NULL},
        {"cat", "bookings.txt", NULL},
        {"dishwasher", NULL},
    };
    int n = sizeof commands / sizeof commands[0];

    /* 3. one by one: launch, wait and note how it went */
    int failures = 0;
    for (int i = 0; i < n; i++) {
        show(commands[i]);
        int code = run(commands[i]);
        printf("  -> exit code %d\n", code);
        if (code != 0) {
            failures++;
        }
    }
    printf("Pass finished: %d commands, %d with problems\n", n, failures);
    return 0;
}

The heart of the program is run:

/* fork + exec + waitpid: the same thing the shell does with each command */
int run(char *argv[]) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return -1;
    }
    if (pid == 0) {
        execvp(argv[0], argv);
        /* if we are still here, the command does not exist or could not run */
        perror(argv[0]);
        exit(127);
    }
    int status;
    waitpid(pid, &status, 0);
    return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}

It is the theory’s launch function with two improvements. The first is the pid < 0 case: if the child cannot be created, it returns -1 instead of carrying on with an invalid PID. The second is the final return: WIFEXITED(status) ? WEXITSTATUS(status) : -1 returns the code only if the child ended with exit; if a signal killed it, it returns -1. The ? : operator is an if/else inside an expression: “if the left part holds, it is worth the middle part; otherwise, the right part”.

show has a detail you do not see at first glance:

/* prints the command as you would see it in the terminal: $ wc -l orders.txt */
void show(char *argv[]) {
    printf("$");
    for (int i = 0; argv[i] != NULL; i++) {
        printf(" %s", argv[i]);
    }
    printf("\n");
    fflush(stdout);   /* so it comes out before the child writes */
}

The loop walks through argv until it finds the final NULL (the same one execvp needs) and prints each word with a space in front. The fflush(stdout) makes sure the $ … line comes out before whatever the child writes. In a terminal, printf already flushes the buffer when it reaches \n, but if you redirect the output to a file (./pass > report.txt) the buffer is only flushed when it fills up, and the $ line could end up after the child’s output or even copied into it by fork().

Finally, the table of commands:

/* 2. the commands to launch, each one ending in NULL */
char *commands[][5] = {
    {"wc", "-l", "orders.txt", NULL},
    {"grep", "-c", "pizza", "orders.txt", NULL},
    {"head", "-n", "2", "orders.txt", NULL},
    {"cat", "bookings.txt", NULL},
    {"dishwasher", NULL},
};
int n = sizeof commands / sizeof commands[0];

char *commands[][5] is an array of rows, and each row has 5 pointers to text. Short rows are filled with NULL automatically (in C, whatever you do not initialise in an array with braces is 0), so they all end in NULL even though we only write it once. sizeof commands / sizeof commands[0] works out how many rows there are: the total size divided by the size of one row. If you add a command, n updates itself.

Output

$ wc -l orders.txt
4 orders.txt
  -> exit code 0
$ grep -c pizza orders.txt
2
  -> exit code 0
$ head -n 2 orders.txt
table 1: paella
table 2: pizza margherita
  -> exit code 0
$ cat bookings.txt
cat: bookings.txt: No such file or directory
  -> exit code 1
$ dishwasher
dishwasher: No such file or directory
  -> exit code 127
Pass finished: 5 commands, 2 with problems

Command by command:

  • wc -l counts 4 lines: the four tables written by fprintf. Code 0.
  • grep -c pizza counts 2: table 2 (margherita) and table 4 (four cheese). Code 0.
  • head -n 2 shows the first two lines as they are. Code 0.
  • cat bookings.txt: the exec worked (cat exists), but cat cannot find the file, writes its own error message and ends with 1. That 1 is decided by cat.
  • dishwasher: there is no program with that name in the PATH, execvp fails and returns, our perror writes dishwasher: No such file or directory and the child leaves with 127. That 127 is decided by our code.

Total: 5 commands, 2 with a non-zero code. The error messages are written by the system (perror and cat use the configured language).

The five commands of the pass Five commands launched with fork, exec and waitpid: three end with 0, cat with 1 and the missing command with 127. The pass: one command, one child, one code command what happens in the child code $ wc -l orders.txt exec ok → wc counts 4 lines 0 $ grep -c pizza orders.txt exec ok → 2 matches 0 $ head -n 2 orders.txt exec ok → first 2 lines 0 $ cat bookings.txt exec ok; cat cannot find the file 1 $ dishwasher exec fails → perror and exit(127) 127 The parent does fork → exec in the child → waitpid and notes the code. Just like your shell.
cat decides the 1 (exec worked); our code sets the 127 because exec failed.
RoundCommandDoes exec work?Codefailures
0wc -l orders.txtyes00
1grep -c pizza orders.txtyes00
2head -n 2 orders.txtyes00
3cat bookings.txtyes (cat fails)11
4dishwasherno1272

Questions you might ask

**Why is the exec done in a child and not in the chef itself?** Because exec replaces the process. If the chef called execvp("wc", …), it would turn into wc, count the lines and end: the other four commands would never be launched.

**Why waitpid right after each fork?** Because we want the commands to run one after another, as in the terminal. If we launched all five and waited at the end, their outputs would mix and we would not know which code belongs to which without looking up the PIDs.

Why 127 and not 1? By convention: bash uses 127 for “command not found”. That way, whoever reads the code can tell “the command failed” (1, 2…) from “the command does not even exist” (127).

**Does grep -c count pizzas or lines?** Lines that contain the pattern, not occurrences. Here it makes no difference because each table orders at most one pizza, but a line table 5: pizza and pizza would count as 1.

**What if the child calls exec and then I want to know its PID?** It is still the same: exec does not change the PID. The parent’s waitpid(pid, …) works even though the child is already wc.

Forgetting the exit(127) after the perror. When dishwasher fails, the child would return from run to the loop in main and keep launching commands as if it were the chef: the pass would run twice at the same time.

echo $? in the terminal shows you the exit code of the last command. Try cat bookings.txt; echo $? and you will see the same 1 the chef collected.

Try it yourself

  • Add the command {"sort", "orders.txt", NULL} and one with three more arguments, such as {"grep", "-n", "-i", "PIZZA", "orders.txt", NULL} (careful: you will need rows of 6 or more pointers).
  • Make the chef stop the pass as soon as a command returns 127 (“a machine is missing in the kitchen”) with a break.

Common mistakes in this practice

  • **The child without exit():** it follows the parent’s loop and creates extra processes, or runs code that only the parent should run.
  • **wait() inside the creation loop:** everything works, but in series; you lose the parallelism.
  • **Adding status instead of WEXITSTATUS(status):** the numbers come out multiplied by 256.
  • **Returning a number greater than 255 with exit:** it arrives truncated (300 → 44) without any warning.
  • **Forgetting the final NULL in the execvp arguments:** exec keeps reading garbage memory.
  • Assuming the order of the children’s messages: only what you order with wait/waitpid is guaranteed.

Watch it step by step in Python Tutor

Python Tutor runs code line by line and draws every variable (choose C in the drop-down). It cannot create real processes, so instead of fork() we simulate what exit() and WEXITSTATUS do with the minutes of three cooks: how the code is put into the status integer and how it is taken out again.

#include <stdio.h>

/* we simulate what exit() and WEXITSTATUS do on Linux with three cooks */
int main(void) {
    int minutes[3] = {25, 8, 12};
    int total = 0;

    for (int i = 0; i < 3; i++) {
        /* this is how the system stores the exit code inside status */
        int status = (minutes[i] & 0xFF) << 8;
        /* and this is how WEXITSTATUS gets it out */
        int m = (status >> 8) & 0xFF;
        total += m;
        printf("cook %d: status=%d -> %d min (total %d)\n", i + 1, status, m, total);
    }
    return 0;
}

Output:

cook 1: status=6400 -> 25 min (total 25)
cook 2: status=2048 -> 8 min (total 33)
cook 3: status=3072 -> 12 min (total 45)

What to watch step by step:

  1. In the first round, when status is computed, you will see 6400: the 25 shifted 8 bits, just like in the bits diagram.
  2. On the m line, the value is 25 again. That line is literally what WEXITSTATUS does.
  3. Watch total grow: 25, 33, 45. It is the same running total as program 2 (without the flan).
  4. Compare status and m in each round: 2048 versus 8 and 3072 versus 12. If you had added status, the total would be 11520 instead of 45.

Summary

  • For several children to work at once, create them all in a loop and collect them afterwards, in another loop with one wait per child.
  • wait() returns the first one to finish; storing the PIDs in an array lets you know who it was.
  • waitpid(pid, &status, 0) collects a specific child, and with it you decide the collection order.
  • exit(n) + WIFEXITED + WEXITSTATUS is the simplest channel for a child to return a number from 0 to 255.
  • fork + exec in the child + waitpid in the parent is what a shell does with every command; after the exec always come perror and exit(127).
  • A non-zero code can come from the program that ran (cat → 1) or from a failed exec (127, set by you).

Checklist: have you practised it?

  • Have you run the three programs and checked the outputs?
  • Could you explain why the service in program 1 takes 3 seconds and not 6?
  • Can you say from memory what status holds when a child calls exit(12)?
  • Can you tell a 1 from cat apart from a 127 from your own code?
  • Have you tried the “Try it yourself” tasks without looking at the theory?

If you answered yes to almost everything, you are ready for the processes in C exercises, where I no longer give you the code. See you there!

Next step: exercisesProcesses in Linux with C — exercises with cooks that multiply, buffers that double and exit codes that lie (and the kitchen that hired seven cooks)

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *