munotes®

Creating a Process: fork

Get access to whole semester resourcesSemester Pass

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 childShared with the parent
Text (the instructions)not copied, shared read onlyyes
Data, heap, stackcopiedno
Program counter and registerscopiedno
Open file descriptorscopied, and they refer to the same open filesthe file position is shared
Process idno: the child gets a new oneno
Parent's process idthe child's parent id is the caller's idno

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 returnsin which processmeaning
0in the childyou are the child
a positive numberin the parentthat is your child's process id
-1in the parentthe 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.

munotes.in69

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 21

Read it carefully, because four things are in those four lines.

  1. "Before the fork" was printed once. There was one process then.
  2. The last line was printed twice, once by each process, because both of them reached it.
  3. The child's fork returned 0 and the parent's returned the child's id, 21.
  4. 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
8
munotes.in70

Creating 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.

  1. The shell reads ls.
  2. The shell calls fork. There are now two shells, identical.
  3. In the child (fork returned 0), the child calls exec to replace itself with ls. That

is the next chapter.

  1. In the parent (fork returned the child's id), the shell calls wait on that id, and

blocks. It moves to the waiting state of Chapter fifteen.

  1. ls runs and exits.
  2. The kernel wakes the shell. wait returns. 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

ParentChild
fork returnsthe child's process id, a positive number0
getpid() givesits own idits own, different, id
getppid() givesits own parent's idthe parent's id
Memoryits owna copy
Open filesits ownthe 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.

munotes.in71

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

  • fork creates 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.
  • fork and exec are 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

  1. What does fork return, and where? 0 in the child, the child's process id in the parent,

and -1 in the parent if it failed.

  1. 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.

  1. A program calls fork four 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.

  1. Why are fork and exec two 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.

  1. Which runs first after a fork? Undefined. Both are ready and the scheduler chooses.
munotes.in72

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.

Issue
Done!