﻿# Processes and fork

> fork() copies the calling process and returns twice. What the child inherits, why variables are not shared, how several forks form a tree, why output can appear twice, and what zombies and orphans are.

Source: https://laszlonemes.com/university/operating-systems/processes-fork · Author: László Nemes

Session 2 · Operating Systems

fork() copies the calling process and returns twice. What the child inherits, why variables are not shared, how several forks form a tree, why output can appear twice, and what zombies and orphans are.

16 min read `PID, PPID, getpid and getppid` `fork return values` `Copied memory, not shared` `Process trees and exit in the child` `The stdio buffer trap and fflush` `Zombies, orphans and a record struct`

Everything else in this course – signals, pipes, message queues, shared memory – is about processes talking to each other. This session creates them. On Unix there is exactly one way for a program to start a new process: `fork()`, which makes a copy of the process that calls it. The copy runs the same code from the same point, and the only thing that tells the two apart is what `fork` returned.

It sounds simple, and it is – but it has consequences that surprise everyone once: variables are not shared after the fork, a child that forgets to `exit` keeps running the parent's code, output can appear twice, and a child nobody waits for turns into a zombie. All of these show up in the small example programs below.

> **The model in one breath:** `fork` duplicates the calling process – memory, open files, even the unwritten output buffer – and returns twice: the child's PID in the parent, 0 in the child. From then on there are two independent processes, and the parent is responsible for waiting for the child.

After this session you can

- Explain what a process is and read its PID and parent PID with getpid and getppid.
- Write the fork pattern with all three outcomes (-1, 0, child PID) and predict what each process prints.
- Count the processes that a sequence of forks creates, and make a child end with exit at the right place.
- Explain why a line printed before fork can appear twice, and prevent it with fflush.
- Recognise a zombie and an orphan, and avoid zombies by waiting for every child.

## What a process is

A **program** is a file on the disk. A **process** is a program that is running: the code loaded into memory, plus everything the kernel keeps track of while it runs (Fig. 1). The same program started twice gives two processes, with separate memory and separate numbers.

> **Fig. 1** · A process: private memory on the left, the bookkeeping the kernel keeps for it on the right.
>
> *Diagram:* A process is a running program plus everything the kernel keeps for it: its own memory (code, global data, heap, stack), its table of open file descriptors, its PID and its parent's PID, and the CPU state saved while it is not running.

The kernel identifies each process by its **PID**. Every process except the very first one was created by another process, its **parent**, whose PID it remembers as its **PPID**. A process can ask for both:

```c
#include <unistd.h>      // fork, getpid, getppid
#include <sys/wait.h>    // wait, waitpid

printf("my PID: %i, my parent: %i\n", getpid(), getppid());
```

Run it from the shell and the parent is the shell itself – compare with `echo $$`, which prints the shell's PID. `ps -f` shows both columns for your processes, `ps aux` for all of them.

- **process** – A running instance of a program: memory, open files, an ID and a state, managed by the kernel.
- **PID** – Process ID – the number the kernel gives a process while it runs. getpid() returns it.
- **PPID** – Parent process ID – the PID of the process that created this one. getppid() returns it.
- **child** – A process created by fork. The process that called fork is its parent.

## fork: one call, two returns

`fork()` takes no arguments. It creates a new process – the child – as a copy of the caller, and from that moment two processes execute the next line after the call. The return value is the only difference between them (Fig. 2):

| fork() returns | In which process | Meaning |
| --- | --- | --- |
| `-1` | the parent (there is no child) | Creating the child failed – too many processes or no memory. `errno` says why. |
| `0` | the child | You are the new process. Your parent is getppid(). |
| a positive number | the parent | You are the original process, and this is the child's PID – keep it to wait for the child. |

> **Fig. 2** · Before fork: one process. After it: two, at the same line, with different return values.
>
> *Diagram:* fork is called once and returns twice. Before the call there is one process; after it there are two, both continuing at the instruction after fork. In the parent fork returns the child's PID, in the child it returns 0; if no child could be created it returns -1 in the parent only.

The whole idiom is an `if` on that value. This is the course's first example, with the parent waiting for the child (`waitpid` gets its own session – [the next one](https://laszlonemes.com/university/operating-systems/exec-wait)):

fork.c – the three branches

```c
int notacommonvalue = 1;
pid_t child = fork();
if (child < 0)                       // -1: no child was created
{
    perror("fork");
    exit(1);
}
if (child > 0)                       // parent: child = the PID of the new process
{
    waitpid(child, &status, 0);      // wait until that child ends
    printf("The value is %i in parent process\n", notacommonvalue);   // 1
}
else                                 // child: fork returned 0
{
    notacommonvalue = 5;             // changes the child's own copy
    printf("The value is %i in child process\n", notacommonvalue);    // 5
}
```

```text
$ ./fork
The value is 1 before forking
The value is 5 in child process
The value is 1 in parent process (remain the original)
```

Good to know

**Who runs first?**

After `fork` both processes are ready to run, and the scheduler decides the order. Without `waitpid` the parent's and the child's lines could come in either order, and the order can change from run to run. Here the parent prints only after `waitpid` returns, so the child's line always comes first. Never rely on an order you have not enforced.

## Copied, not shared

The child gets a copy of the parent's memory: every variable, global or local, with the value it had at the moment of the fork. From then on the two copies are independent. When the child sets `notacommonvalue = 5`, the parent's variable stays 1 – that is what the example's name is about (Fig. 3).

> **Fig. 3** · Each process changes only its own copy. Even a global variable is not a shared variable.
>
> *Diagram:* fork gives the child a copy of the parent's memory. Right after fork both copies hold notacommonvalue = 1; when the child sets it to 5, only the child's copy changes, and the parent still prints 1. Processes do not share variables – not even globals.

This is the big difference from **threads**, which you may know from Java: threads of one process share the same memory, so a change in one is visible in the others (and needs `synchronized` or `volatile` to be safe). Processes share nothing by default. To pass data between them you need the kernel – the exit code now, then signals, pipes, message queues and shared memory in the coming sessions.

Good to know

**Open files are copied too – and the copies are linked**

The child also gets a copy of the file descriptor table: descriptor 1 in the child is the same standard output as in the parent, and a file the parent opened before the fork is open in the child too. The two descriptors refer to the same open file, so they share its offset – if one process reads 10 bytes, the other continues from byte 10. That shared link is what pipes will be built on.

## Several forks: the process tree

What does this print?

```c
fork();     // line 1
fork();     // line 2
fork();     // line 3
printf("hello\n");
```

Eight lines of `hello`. Every process that exists when a `fork` line runs executes it, children included. Line 1 turns one process into two, line 2 turns two into four, line 3 four into eight. With `n` forks in a row you get 2^n processes, forming a tree (Fig. 4).

> **Fig. 4** · fork(); fork(); fork(); – P has three children, its first child has two, and so on: eight processes, eight hellos.
>
> *Diagram:* Three fork calls in a row create a tree of 8 processes. Every process that exists when a fork line runs calls it, so line 1 makes 1 child, line 2 makes 2 and line 3 makes 4: 1 + 1 + 2 + 4 = 8 processes, and hello is printed 8 times.

### Creating exactly the children you want

Usually you want the parent to create a fixed number of children, each doing one job. The course examples put each child's job into a function. The function forks; the parent gets the PID back and returns it; the child does its work and then calls `exit` – it must never return to `main`:

fork3.c – a child in a function

```c
pid_t child1(void)
{
    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }
    if (pid > 0)
        return pid;              // parent: back to main with the child's PID

    // only the child gets here
    notacommonvalue = 5;
    printf("The value is %i in child1 process\n", notacommonvalue);
    exit(0);                     // the child must not return into main
}
```

fork3.c – main starts two children, then waits

```c
pid_t child1_pid = child1();       // both children run from here on
pid_t child2_pid = child2();

waitpid(child1_pid, &status, 0);   // then wait for each of them
waitpid(child2_pid, &status, 0);
```

Without the `exit(0)`, the child would return from `child1()` into `main` and carry on with the parent's code: it would call `child2()` itself and create a grandchild (Fig. 5).

> **Fig. 5** · The child's branch ends in exit. A forgotten exit lets the child run the parent's code and fork again.
>
> *Diagram:* When a function forks a child, the child must end with exit. With exit(0), main creates exactly two children. Without it, child 1 returns into main, runs the next child2() call itself and creates a grandchild, so one process too many exists and the waits go wrong.

The same rule in a loop, the usual way to start `N` children:

```c
for (int i = 0; i < 3; i++)
{
    pid_t pid = fork();
    if (pid == 0)
    {
        printf("child %i, PID %i\n", i, getpid());
        exit(0);                 // without this the child continues the loop and forks too
    }
}
while (wait(NULL) > 0)           // wait returns -1 when no child is left
    ;
```

Common mistake

**A fork in a loop without exit**

If the child does not leave the loop, it forks in the remaining rounds too – three rounds give eight processes instead of four, and with a larger loop the machine runs out of processes. Every child branch ends with `exit` (or an `exec`, next session).

## The stdio buffer trap

Run the first example with its output going into a pipe instead of the terminal, without the `fflush` line, and one line appears twice:

```text
$ ./fork | cat          # stdout is a pipe, no fflush before fork
The value is 1 before forking
The value is 5 in child process
The value is 1 before forking
The value is 1 in parent process (remain the original)
```

The cause is the stdio buffer from [the first session](https://laszlonemes.com/university/operating-systems/c-basics-files). `printf` does not write to the kernel immediately; it collects text in a buffer inside the process. When standard output is a terminal, the buffer is emptied at every `'\n'` (line-buffered). When it is a file or a pipe, the buffer is emptied only when it is full or the process exits (fully buffered). So at the moment of the fork, the line is still in the parent's memory – and `fork` copies memory. Both processes then hold the line, and both write it when they exit (Fig. 6).

> **Fig. 6** · fork copies the unwritten buffer. Two processes flush the same text.
>
> *Diagram:* When standard output is a file or a pipe, printf only puts the text into a buffer in the process's memory. fork copies that memory, buffer included, so the parent and the child both write the same unwritten line when they exit, and it appears twice. fflush(stdout) before fork empties the buffer first.

The fix is one line: empty the buffer before forking.

fork.c – flush, then fork

```c
printf("The value is %i before forking\n", notacommonvalue);
fflush(stdout);                  // empty the buffer BEFORE the process is copied
pid_t child = fork();
```

Common mistake

**It works on the terminal, so it must be right**

Tested by hand on the terminal, the program looks fine. Then its output is redirected to a file or piped into `grep` – the way scripts and automated tests run programs – and lines are duplicated or out of order. A `printf` without `'\n'` before a fork shows the bug even on the terminal. Put `fflush(stdout)` before every `fork`; `stderr` is unbuffered, so error messages are not affected.

## Zombies and orphans

When a process ends, the kernel keeps a small record of it: its PID and its **exit status**, for the parent to collect with `wait` or `waitpid`. Between the child's end and the parent's `wait`, the child is a **zombie**: it runs no code and holds no memory, but it still occupies an entry in the process table. `ps` shows it with state `Z` and the word `<defunct>`:

```c
pid_t pid = fork();
if (pid == 0)
    exit(0);             // the child ends at once...
sleep(30);               // ...the parent does not wait() for 30 seconds
waitpid(pid, NULL, 0);   // only now is the zombie removed
```

```text
$ ps -o pid,ppid,stat,cmd
  PID  PPID STAT CMD
 4321  4300 Ss   -bash
 4500  4321 S+   ./zombie
 4501  4500 Z+   [zombie] <defunct>
```

The opposite case is an **orphan**: the parent ends while the child still runs. The child is not killed. It is adopted – by PID 1, or by a process that registered itself as a "subreaper" (some desktop sessions do) – so `getppid()` suddenly returns a different number. The adopter waits for its adopted children, so orphans do not stay zombies (Fig. 7).

```c
if (fork() == 0)
{
    printf("parent before: %i\n", getppid());
    sleep(2);                                 // meanwhile the parent ends
    printf("parent after:  %i\n", getppid()); // a different PID: the child was adopted
    exit(0);
}
exit(0);                                      // the parent does not wait
```

> **Fig. 7** · Zombie: the child ended, the parent has not waited yet. Orphan: the parent ended first, and the child gets a new parent.
>
> *Diagram:* A zombie is a child that has ended while its parent has not yet called wait: it keeps only its exit status and appears as Z or defunct. An orphan is a child whose parent ended first: it is adopted by PID 1 or a subreaper, its PPID changes, and the adopter reaps it when it ends.

Common mistake

**Forgetting to wait**

One zombie is harmless. A long-running program that starts a child for every request and never waits collects zombies until the system refuses to create more processes – and then `fork` returns `-1`. The rule: every `fork` that returns a PID in the parent is matched by a `wait` or `waitpid` for that PID.

## A struct for a record

The other example of the week has nothing to do with processes: it is the start of the record keeper you build in the first assignment. A `struct` groups the fields of one record. With fixed-size `char` arrays the text lives inside the struct, so the struct can be copied with `=` and has a fixed size:

struktura.c – one record

```c
struct driver
{
    unsigned int id;
    char name[50];          // fixed-size arrays: the text lives inside the struct
    char email[50];
    char phone[50];
    unsigned int exp;
};
```

Passing a struct to a function by value copies all of it. Passing a pointer copies only its address, and the function reaches the caller's struct through `->`: `d->exp` is short for `(*d).exp`. A `const` pointer promises that the function only reads it (Fig. 8).

> **Fig. 8** · By value: a 160-byte copy. By pointer: an 8-byte address to the original.
>
> *Diagram:* Passing a struct by value copies every byte of it – 160 bytes for struct driver – and the function changes only its copy. Passing a pointer copies an 8-byte address; the function reaches the caller's struct, and const promises it will only read it.

struktura.c – creating a record, optionally from an existing one

```c
void driver_creation(const struct driver *createdriver)   // NULL: a new, empty record
{
    struct driver a = {0};          // every field 0, every string empty
    if (createdriver != NULL)
        a = *createdriver;          // struct assignment copies all fields, arrays included
    a.id = id++;
    /* ... read the fields, then append a to the file ... */
}
```

The example takes a pointer that may be `NULL`: `NULL` means "a new record", a real pointer means "start from this record and let the user keep fields". The `= {0}` initialiser makes every number 0 and every string empty, so no field contains garbage if it is skipped.

### Reading the fields safely

struktura.c – one text field from the keyboard

```c
void read_in_console_messages(const char *console_text, char *result)
{
    printf("%s", console_text);
    if (scanf(" %49[^\n]", result) != 1)   // the leading space skips a leftover '\n'
        result[0] = '\0';                   // end of input: an empty string, not garbage
}
```

`%49[^\n]` reads up to 49 characters until the end of the line – spaces included, unlike `%s` – and leaves room for the 0 in a `char[50]`. The space before it skips the newline left over from the previous input. Checking the return value matters for the same reason as with `fgets`: at the end of input, `scanf` writes nothing, and the buffer would keep whatever it held. The menu loop needs the same care with `getchar`:

struktura.c – the menu loop

```c
command = getchar();
if (command == EOF)          // Ctrl+D: leave the menu instead of looping forever
    break;
/* ... switch (command) ... */
int ch;
while ((ch = getchar()) != '\n' && ch != EOF)   // drop the rest of the line
    ;
```

In your assignment

**Records, IDs and input in your record keeper**

- Define one `struct` for a record, with fixed-size arrays and the sizes in the `scanf` widths (`char name[50]` ↔ `%49[^\n]`).
- Give every record an ID. The example reads the file at start-up and continues from the last ID it finds, so IDs stay unique across runs.
- Pass records to functions by pointer – `const` when the function only reads.
- Check every `scanf`, `fgets` and `getchar` for the end of input: the program must not loop forever when its input is a file or Ctrl+D is pressed.

The [CRUD skeleton guide](https://laszlonemes.com/university/operating-systems/crud-skeleton) shows how list, modify and delete fit around this.

## Practice

Compile each with `-Wall -Wextra`. Before running a fork program, write down what you expect it to print – then run it, and also run it as `./prog | cat`.

Task 1

**Who is who**

Fork once. In both processes print "parent" or "child", the own PID, the parent's PID, and in the parent also the child's PID. Check that the numbers fit together, and that the parent's parent is your shell (`echo $$`).

**Approach:**

Branch on the return value; don't forget the parent's wait (here: `wait(NULL)` after the print).

```c
pid_t pid = fork();
if (pid == 0)
    printf("child:  PID %i, parent %i\n", getpid(), getppid());
else
    printf("parent: PID %i, parent %i, child %i\n", getpid(), getppid(), pid);
```

Task 2

**Predict, then run**

How many times does this print `A`, and how many times `B`? Run it to the terminal and through `| cat`. Then add one line that makes the answer the same in both cases.

```c
printf("A");      // no '\n': stays in the buffer
fork();
printf("B\n");
```

**Explanation:**

`B` twice – one per process. `A` also twice, even on the terminal: without `'\n'` the `A` is still in the buffer when `fork` copies it. `fflush(stdout)` right before `fork` gives one `A`.

Task 3

**Three numbered children**

Start three children from a loop. Each prints its number (0, 1, 2) and its PID, sleeps a second, and exits. The parent prints "all done" only after all three have ended. Then delete the `exit` in the child and count the lines – explain the number.

**Approach:**

The loop from the "several forks" section, plus `sleep(1)` in the child. Without `exit`, every child continues the loop and forks as well: 1 + 2 + 4 = 7 "child" lines instead of 3, and all eight processes reach the end and print "all done".

Task 4

**Not shared**

The child sets a variable to 10 and exits; the parent waits and prints the variable. What does it print? Make the child return the value 10 to the parent another way.

```c
int sum = 0;
pid_t pid = fork();
if (pid == 0) { sum = 10; exit(0); }
waitpid(pid, NULL, 0);
printf("%i\n", sum);    // what does it print – and why?
```

**Approach:**

It prints 0 – the child changed its own copy. The simplest channel back is the exit code: `exit(10)` in the child, and `WEXITSTATUS(status)` after `waitpid(pid, &status, 0)` in the parent. It holds only 0–255; [the next session](https://laszlonemes.com/university/operating-systems/exec-wait) covers it.

Task 5

**See a zombie and an orphan**

Write the zombie program from this page and look at it with `ps -u $USER -o pid,ppid,stat,cmd` from a second terminal while the parent sleeps. Then write the orphan program and run it – which PID does the child report as its new parent?

**Approach:**

Start the zombie program with `&` so you keep the prompt, or use a second SSH session. The orphan's new parent is 1 on many systems; on a desktop session it may be another process – look it up with `ps -p`.

Task 6

**Change a record through a pointer**

Write `void raise_exp(struct driver *d)` that increases the experience of a driver by one, and `void print_driver(const struct driver *d)`. Call both from `main`. What does the compiler say if `print_driver` tries to change a field?

**Approach:**

`->` on the pointer, `&` at the call. Writing through a `const` pointer is a compile error ("assignment of member in read-only object").

```c
void raise_exp(struct driver *d)
{
    d->exp++;            // d->exp is (*d).exp – the caller's struct changes
}
raise_exp(&a);
```

## Summary

- A process is a running program with its own memory, file descriptors, PID and PPID (`getpid`, `getppid`).
- `fork` returns twice: the child's PID in the parent, 0 in the child; -1 means no child was created.
- After `fork` the memory is copied, not shared – a change in one process is invisible in the other. Open file descriptors are inherited.
- `n` forks in a row give 2^n processes; a child that should only do one job ends with `exit`.
- Output buffered by stdio is copied by `fork`: call `fflush(stdout)` before forking.
- A child that ended but was not waited for is a zombie (`Z`, `<defunct>`); a child whose parent ended is an orphan and gets adopted.
- A `struct` with fixed-size arrays is one record; pass it by pointer, and check every input call for the end of input.
