UP | HOME

Process Creation
Processes
COP-3402

Table of Contents

UNIX process creation

celldivision_small.jpeg

  1. Duplicate an existing process with fork()
  2. Replace new process's program code exec()

If all processes are created by fork, where does the first one come from?

Process creation workflow

LPI Figure 24-1

Fork duplicates the process

  • Same program running
  • Same open files (including stdio)
  • "Copy" of memory

LPI Figure 24-2 (open file tables)

LPI Figure 24-3 (copy-on-write)

If fork duplicates a process, how do we avoid both processes just doing the exact same thing?

fork() Return Value

man fork

  • PID of child to parent process
  • 0 to the child process
  • -1 on failure

Using fork()

simplefork.c

#include <stdio.h>
#include <unistd.h>    // fork()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// ctrl-z and ps
// man 2 fork

int main(int argc, char **argv) {
  pid_t pid;

  sleep(10);
  if (-1 == fork()) {
    perror("fork");
    exit(EXIT_FAILURE);
  }
  sleep(10);
  printf("hello, world!\n");
  sleep(10);
}

This example illustrates how fork duplicates a process. Suspend and use ps to show how they are two processes after fork is called (using the sleep to delay it).

Notice that hello, world! will be printed out twice, because the two processes are running the same program and both are running effectively concurrently.

We can also use getpid to show that the two programs indeed are two different processes:

printf("hello, world! %jd\n", (intmax_t) getpid());

But simply duplicating a process isn't all that useful alone. How can we make the processes do something different if they are the same program?

fork.c

#include <stdio.h>
#include <unistd.h>    // fork()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// ctrl-z and ps
// man 2 fork

int main(int argc, char **argv) {
  pid_t pid;

  switch (pid = fork()) {
  case -1:
    perror("fork");
    exit(EXIT_FAILURE);
    break;
  case 0:
    // child
    puts("inside child process\n");
    sleep(10);
    _exit(EXIT_SUCCESS);
    break;
  default:
    // parent
    printf("inside parent process. child pid: %jd\n", (intmax_t) pid);
    sleep(10);
    exit(EXIT_SUCCESS);
    break;
  }
}

This example shows what the only observable difference between the processes is, the return value of fork.

By testing this return value, we can make the processes do different things, even though they are running the identical program. Just like any program, conditionals (or a switch in this case or any control flow) can be used to alter the sequence of instructions. The program describes what all possible runs of the program will look like, while different input values can be used to alter what that flow of control will be.

In this case, we have one program with a control flow statement. Since fork returns 0 for the child and non-zero (the child's pid) for the parent, so depending on which process we are, the program will behave differently, in this case one will print "inside child process" and the other will print the child's pid

Notice that with fork, we can already do parallel programming, albeit where all concurrent code is written within the same program.

Using exec

simpleexec.c

#include <stdio.h>
#include <unistd.h>    // fork(), execve()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// man 2 execve

int main(int argc, char **argv) {
  char *prog = "getpid";  // what if the program doesn't exist?
  char *newargv[] = { NULL };
  char *newenv[] = { NULL };
  execve(prog, newargv, newenv);
  perror("execve");
  _exit(EXIT_FAILURE);
}

Notice that exec does not create a new pid. We can see this by printing the pid and then running a program that just prints its pid.

#include <stdio.h>
#include <unistd.h>    // getpid()
#include <inttypes.h>  // intmax_t

int main(int argc, char **argv) {
        printf("this process's pid is %jd\n", (intmax_t) getpid());
}

We can add sleep before the execve and put the process into the background to show the pid as well.

exec.c

#include <stdio.h>
#include <unistd.h>    // fork(), execve()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// 
// man 2 execve

int main(int argc, char **argv) {
  char *prog = "/usr/bin/ls";
  char *newargv[] = { NULL };
  char *newenv[] = { NULL };
  execve(prog, newargv, newenv);
  perror("execve");
  _exit(EXIT_FAILURE);
}

Here is an example of executing a larger, existing program, in this case ls. Notice that we get the same output that we would if we ran it with bash. This is because of what exec does, reusing whatever stdio was opened already for the process that calls exec.

Fork and exec

fork_exec.c

#include <stdio.h>
#include <unistd.h>    // fork()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// 
// man 2 execve

int main(int argc, char **argv) {
  pid_t pid;

  switch (pid = fork()) {
  case -1:
    perror("fork");
    exit(EXIT_FAILURE);
    break;
  case 0:
    // child
    puts("inside child process\n");
    char *prog = "/usr/bin/ls";
    char *newargv[] = { NULL, "./", NULL };
    char *newenv[] = { NULL };
    execve(prog, newargv, newenv);
    perror("execve");
    _exit(EXIT_FAILURE);
    break;
  default:
    // parent
    // sleep(10);
    printf("the parent is still running. child pid: %jd\n", (intmax_t) pid);
    exit(EXIT_SUCCESS);
    break;
  }
}

We can combine fork and exec to make a general-purpose tool that runs any given program in parallel to the current program.

We can also notice here that the parent process indeed keeps running; it prints the "the parent is still running".

fork_exec_sleep.c

#include <stdio.h>
#include <unistd.h>    // fork()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// 
// man 2 execve

int main(int argc, char **argv) {
  pid_t pid;

  switch (pid = fork()) {
  case -1:
    perror("fork");
    exit(EXIT_FAILURE);
    break;
  case 0:
    // child
    puts("inside child process\n");
    char *prog = "/usr/bin/sleep";
    char *newargv[] = { "/usr/bin/sleep", "10", NULL };
    char *newenv[] = { NULL };
    execve(prog, newargv, newenv);
    perror("execve");
    _exit(EXIT_FAILURE);
    break;
  default:
    // parent
    printf("child pid: %jd\n", (intmax_t) pid);
    sleep(10);
    exit(EXIT_SUCCESS);
    break;
  }
}

This example with sleep inside the parent shows that both prgrams are executing concurrently, since ps shows that both processes exist simultaneously.

Author: Paul Gazzillo

Created: 2025-09-18 Thu 09:59

Validate