Menu

Class 10: Fork


Reading
APUE pp. 135-136, 180-184, 220-225, Sections 5.4,7.3, 8.6

Homework
Printout the Homework and answer the questions on that paper.


How processes are born
With the exception of one or two kernel processes, every process has a parent process, which the process that created it. The output of ps allows you to see this relationship: the PID column is the process ID, and PPID is the process's parent's ID.
The system call pid_t getppid(void); gets the process id of a process's parent.
A process creates a child process (thereby becoming a parent) via the system call fork.
NAME
     fork - create a new process

SYNOPSIS
     #include <sys/types.h>
     #include <unistd.h>

     pid_t fork(void);
	
Try man -s2 fork for gory details. The concept behind fork is perhaps not what you expect. First of all, if the call to fork fails, -1 is returned. With that out of the way, here's the weirdness. When a process calls fork, the kernel creates a new process that is a clone of the original, and both processes simply continue executing at the instruction following the call to fork. The child process is an exact duplicate of the clone save for one thing: the value returned by fork in the child process is zero, in the parent process it is the process id of the child.
#include <stdio.h>
#include <unistd.h>

int main()
{
  pid_t t = fork();
  if (t == -1) { printf("fork failed!\n"); exit(1); }
  if (t == 0)
    printf("I am the child.   My pid is %i.\n",getpid());
  else
    printf("I am the parent.  My pid is %i.\n",getpid());
  return 0;
}
This simple example gives the clue to how this strange concept of fork really works. After forking, both processes check the return value, determine whether they are child or parent, and work accordingly. The fact that both processes are identical copies of one another (except for fork's return value) has interesting consequences. One consequence is that their file descriptors refer to exactly the same system open file table entries. That means that they share not just the same files, but the same connections ... so when one reads a bit, it changes the offset so that when the next reads, it starts where the first left off.

Watch parent & child share system file open table entries
We'd like to give an example that shows that the child and parent really do share system file open table entries by opening a file to read, forking, and having both child and parent read from the file.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
  int fd = open(argv[1],O_RDONLY);

  pid_t cid;
  if ((cid = fork()) == -1) { fprintf(stderr,"Fork failed!\n"); return 1; }

  if (cid == 0) { sleep(1); } // forces alternation, without it both proc's
                              // may read same char, b/c read is not atomic
  char c;
  while(read(fd,&c,1) > 0)
  {
    if (cid == 0)
      printf("%c   <-- child\n",c);
    else
      printf(" %c  <-- parent\n",c);
    sleep(2);
  }
  return 0;
}
	
Running this program, we see that the child and parent processes are not just reading from the same file, but actually from the same connection to that file. This means that it isn't just the vnode that's the same, but the system open file table entry.
bash$ gcc ssofte.c
bash$ ./a.out data.txt 
 H  <-- parent
e   <-- child
 l  <-- parent
l   <-- child
 o  <-- parent
    <-- child
 W  <-- parent
o   <-- child
 r  <-- parent
l   <-- child
 d  <-- parent
.   <-- child
 
  <-- parent
	
It's interesting to modify this program and see what happens. For example, what happens if we make the call to fopen after the call to fork? Can you explain what happens?

Wait
Often after a fork both parent and child go on computing simultaneously. But sometimes, one process should wait for the other to complete before it continues. The C standard library function wait does that for us. Interesting uses of this will be easier to find when we have a few more tools at our disposal (notably exec, which is covered next class). However, we can see wait get used in a simple example.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main( int argc, char *argv[] )
{
  pid_t pid;
  pid = fork();
  if( pid == -1 ) exit( 1 );

  if( pid == 0 )
  {
    printf("I am a child.\n");
    exit(2);
  }
  else
  {
    int status;
    wait(&status);
    printf("My child exited with (%i).\n", WEXITSTATUS(status));
    exit(0);
  }
}

Fork is fun on multi-core machines
A problem that is of great importance for cryptography is the "discrete logarithm" problem. Given positive integers b, y and n, 0 < b,y < n, the integer k satisfying bk mod n = y is the discrete log logby mod n.

Below is a very simple program to compute discrete logarithms, that tests all numbers in the range 1..n to find the one (if it exists) that is logby mod n. Next to it is another version that forks and, following the fork, lets the parent search in the range 1..n/2 and the child search in the range n/2..n. On a multi-core machine, the two processes may well be assigned different CPUs so that they can actually run in parallel. Indeed, if you test the two programs on input b = 3811, y = 7279 and n = 29917 on one of the lab machines, you should see the forking version taking about half the time!
mlog1.cmlog2.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

// Find log[x](y) modulo n, i.e. k such that x^k mod n = y
// Try: mlog1 3811 7279 29917
//             x    y     n   ---> 19863

int main(int argc, char **argv)
{
  int x = atoi(argv[1]), y = atoi(argv[2]), n = atoi(argv[3]);
  int k, a, b, i, z;

  a = 1; b = n;

  for(k = a; k < b; ++k)
  {
    // compute z, where z = x^k mod n
    z = 1;
    for(i = 0; i < k; ++i)
      z = z*x % n;

    // if z == y, we've found log[x](y) mod n.
    if (z == y) { printf ("%i\n",k); return 0; }
  }
}
		
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

// Find log[x](y) modulo n, i.e. k such that x^k mod n = y
// Try: mlog2 3811 7279 29917
//             x    y     n   ---> 19863

int main(int argc, char **argv)
{
  int x = atoi(argv[1]), y = atoi(argv[2]), n = atoi(argv[3]);
  int k, a, b, i, z;

  // Parent and child split range 1...n into two
  if (fork()){ a = 1;   b = n/2; }
  else       { a = n/2; b = n;   }

  for(k = a; k < b; ++k)
  {
    // compute z, where z = x^k mod n
    z = 1;
    for(i = 0; i < k; ++i)
      z = z*x % n;

    // if z == y, we've found log[x](y) mod n.
    if (z == y) { printf ("%i\n",k); return 0; }
  }
}