Creating a Process: fork
Chapter Eighteen
Syllabus topic Module 1, "Processes - Operations on Processes"
Pages 69 to 72 of 452
In one line
fork makes a copy of the calling process, and then both the original and the copy return from it.
Why it is strange, and why it is designed that way
Every other function you have written returns once. fork returns twice: once in the process that called it, and once in a brand new process that did not exist when the call was made.
That is not a trick. It is the honest consequence of what the call does. The kernel copies the whole process, including the program counter, which was pointing at the instruction after the call. So the copy is a process that is exactly in the middle of returning from fork, and there is nothing else it could sensibly do but return.
The vocabulary: the process that called is the parent, the new one is the child. A child has one parent. A parent may have many children.
What is copied and what is shared
| Copied for the child | Shared with the parent | |
|---|---|---|
| Text (the instructions) | not copied, shared read only | yes |
| Data, heap, stack | copied | no |
| Program counter and registers | copied | no |
| Open file descriptors | copied, and they refer to the same open files | the file position is shared |
| Process id | no: the child gets a new one | no |
| Parent's process id | the child's parent id is the caller's id | no |
The file descriptor row is the one that surprises people and it is examined. The descriptors are copied, so both processes have descriptor 1. But they point at the same open file, so if both write, the writes interleave in one file, and if one moves the file position the other sees it move. Chapter ninety nine shows the table this happens in.
The return value, which is the whole call
fork returns | in which process | meaning |
|---|---|---|
| 0 | in the child | you are the child |
| a positive number | in the parent | that is your child's process id |
| -1 | in the parent | the fork failed and there is no child |
fork is the one call in this book with three possible answers rather than the usual two. The reason is that the child needs no id (it can ask with getpid) while the parent must be told its child's, or it could never wait for it.
The program that shows both sides
The first line of the program is not decoration. pid_t, the type a process id has, is a POSIX name and not part of standard C. Compiled with -std=c17, which asks for standard C and nothing else, the C library hides it, and the compiler says unknown type name 'pid_t'. #define _POSIX_C_SOURCE 200809L before the first include asks for the POSIX names as well. Every listing in this book that uses a POSIX type begins with it.
Creating a Process: fork
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
printf("before the fork, I am process %d\n", getpid());
pid_t r = fork();
if (r < 0) {
perror("fork");
return 1;
}
if (r == 0) {
printf("child : fork returned %d, I am %d, my parent is %d\n",
r, getpid(), getppid());
printf("child : this line is printed by process %d\n", getpid());
return 0;
}
wait(NULL); /* let the child finish and speak first */
printf("parent: fork returned %d, I am %d\n", r, getpid());
printf("parent: this line is printed by process %d\n", getpid());
return 0;
}$ gcc -std=c17 -Wall -Wextra -o twosides twosides.c
$ ./twosides
before the fork, I am process 21
child : fork returned 0, I am 22, my parent is 21
child : this line is printed by process 22
parent: fork returned 22, I am 21
parent: this line is printed by process 21Read it carefully, because four things are in those four lines.
- "Before the fork" was printed once. There was one process then.
- The last line was printed twice, once by each process, because both of them reached it.
- The child's
forkreturned 0 and the parent's returned the child's id, 21. - The child's parent id is the parent's id. The family relationship is recorded in the
kernel.
The wait at the end of the parent is not optional, and leaving it out was tried. Without it the parent often finished first, and the child's getppid() then answered 1 instead of 20, because a child whose parent has gone is given to the process the kernel keeps for the purpose. That is the orphan of the next chapter, and it appeared here by accident, which is the honest way to meet it.
The order of the last two lines is not guaranteed. Both processes are ready; which the scheduler runs first is its business. Run it twenty times and you will see both orders. A program that depends on the order is wrong, and Chapters thirty three to forty two are about that problem.
Counting: how many processes does a loop of forks make?
A favourite examination question, and the answer is a power of two.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
for (int i = 0; i < 3; i++) {
if (fork() < 0) {
perror("fork");
return 1;
}
}
printf("hello from %d\n", getpid());
while (wait(NULL) > 0) {
/* collect every child before finishing */
}
return 0;
}$ gcc -std=c17 -Wall -Wextra -o howmany howmany.c
$ ./howmany | wc -l
8Creating a Process: fork
Eight lines, so eight processes, from three forks.
The reason, and the general rule. After the first fork there are 2 processes. Both reach the second fork, so there are 4. All four reach the third, so there are 8. n forks in a loop give 2 to the power n processes, of which 2 to the power n minus 1 are new children. Three forks give eight processes and seven children.
The mistake to avoid: answering "three children". The children fork too.
Worked example: how a shell runs a command
This is what step 3 of Chapter eight's shell loop really does.
- The shell reads
ls. - The shell calls
fork. There are now two shells, identical. - In the child (
forkreturned 0), the child callsexecto replace itself withls. That
is the next chapter.
- In the parent (
forkreturned the child's id), the shell callswaiton that id, and
blocks. It moves to the waiting state of Chapter fifteen.
lsruns and exits.- The kernel wakes the shell.
waitreturns. The shell prints a new prompt.
Why fork and exec are two calls rather than one is the deepest question in this chapter, and it is asked. Because between step 2 and step 3 the child is a copy of the shell and can change things about itself before becoming ls: it can open a file and make it descriptor 1, so that the output goes to a file; it can close descriptors; it can change directory. All of shell redirection lives in that gap. One combined call to "run a program with these settings" would need a parameter for every setting anyone might ever want.
Distinctions that carry marks
| Parent | Child | |
|---|---|---|
fork returns | the child's process id, a positive number | 0 |
getpid() gives | its own id | its own, different, id |
getppid() gives | its own parent's id | the parent's id |
| Memory | its own | a copy |
| Open files | its own | the same open files, through copied descriptors |
What it does not mean
fork does not start the program again from the beginning. The child begins where the parent was, at the return from fork. Nothing above the call runs twice.
fork does not make the child run first, or second. Which runs first is the scheduler's business.
fork does not copy the memory immediately in a real system. Copying megabytes that are usually thrown away at once by exec would be waste, so the kernel marks it copied and copies only what is written to. That is copy on write, Chapter eighty three, and it is why fork is fast.
Creating a Process: fork
A failed fork is not impossible. The system may be out of process slots or out of memory, and fork then returns -1. Every one of these programs checks.
Quick revision
forkcreates a new process by copying the caller. It returns twice.- It returns 0 in the child, the child's process id in the parent, and -1 on failure.
- Copied: data, heap, stack, registers, descriptors. Shared: the text, read only, and the
open files the copied descriptors refer to, including the file position.
- The child gets a new process id; its parent id is the caller's id.
- n forks in a loop give 2 to the power n processes. Three forks give eight.
- The order in which parent and child run is not defined.
forkandexecare separate so that the child can change its own settings, which is where
all shell redirection happens.
- Real systems do not copy the memory at once: they use copy on write.
Test yourself
- What does
forkreturn, and where? 0 in the child, the child's process id in the parent,
and -1 in the parent if it failed.
- Why does it return twice? Because it copies the whole process including the program
counter, so the copy is also in the middle of returning from the call.
- A program calls
forkfour times in a loop. How many processes exist at the end? Sixteen,
of which fifteen are children. 4. Parent and child both write to standard output, which is redirected to a file. What happens? The descriptors were copied but refer to one open file, so both writes go to the same file and the file position is shared. The output interleaves.
- Why are
forkandexectwo calls? So that between them the child can change its own
environment, for example opening a file as descriptor 1 to redirect the output, before turning itself into the new program.
- Which runs first after a
fork? Undefined. Both are ready and the scheduler chooses.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.