Lab 2 — fork(), wait(), and Writing to Files

CMPS 3600 • Fall 2025

Overview

In this lab you will practice Linux system programming using fork(2) and wait(2), along with basic file I/O via open(2), write(2), and close(2). You will create a parent/child process pair where the child computes a Fibonacci value and writes results to a file, while the parent reaps the child and reports the child's exit status.

Learning Objectives

Background: Processes & fork()

A process is a running program with its own virtual address space, registers, and OS-managed resources (file descriptors, signals, etc.). Creating new processes underpins multitasking: every shell command you run typically launches at least one new process.

Process tree (conceptual)

shell (PID 100)
└─ your program (parent, PID 200)
   └─ your program (child,  PID 201)
Why this matters: Understanding fork()/wait() is foundational for concurrency, pipelines, servers, and later IPC topics. Exit codes are how processes communicate simple results back to their parents and the shell.

Warm‑Up: See the Two Execution Paths

Compile and run this minimal example to visualize control flow after fork():

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
  pid_t pid = fork();
  if (pid < 0) { perror("fork"); return 1; }
  if (pid == 0) {
    // child
    printf("child: pid=%d (parent=%d)\n", getpid(), getppid());
    _exit(0);
  } else {
    // parent
    int status;
    pid_t w = wait(&status);
    if (w > 0 && WIFEXITED(status)) {
      printf("parent: child %d exited with %d\n", w, WEXITSTATUS(status));
    }
  }
  return 0;
}

Notice: child uses _exit()/exit(); parent uses wait() and status macros.

Explore:
  • Run it multiple times. Do messages appear in the same order? Why can they interleave?
  • After running, type echo $? in the shell. What exit code does the shell see?
  • Temporarily comment out the wait(). Use top or ps -o pid,ppid,stat,cmd -u $USER to spot a child in Z state.

Tools: strace and top

strace shows system calls/signals; top shows processes and states.

$ strace -q -f ./yourprog 2>err.txt    # trace parent + child syscalls
$ strace -e trace=signal ./yourprog 2>signals.txt
$ top -u YOUR_USERNAME                  # watch states; child shows 'Z' if zombie
What to look for:
  • In err.txt, find fork/clone and the child's _exit.
  • In the parent, look for wait4/waitpid returning the child's PID.
  • In top, observe that any Z child disappears once the parent calls wait().

Step 1 — Setup

Work in your Lab 2 folder:

$ cd ~
$ cd cs3600
$ cp /home/fac/dfanucchi/p/cs3600/lab-start.sh ./
$ cp /home/fac/dfanucchi/p/cs3600/lab-fix.sh   ./
$ ./lab-start.sh
$ cd 2
$ cp /home/fac/dfanucchi/public_html/cs3600/examples/2/*.c .
$ cp /home/fac/dfanucchi/public_html/cs3600/examples/2/Makefile .
$ make

Copy sample files and confirm they build without warnings (use -Wall).

Step 2 — Assignment (vrlab2.c)

Modify vrlab2.c to replicate the lab02 example:

All supporting examples are in ~/cs3600/examples/2/: alarm.c, fork1.c, fork2.c, fork3.c, and fork4.c.
Use them as reference while building vrlab2.c.

  1. In main, read the first command-line argument (argv[1]). Use atoi() to convert it into n. If missing, print:
    Usage: ./lab2 n
    and exit with code 1.
  2. Call fork() to create a child process.
  3. Child:
    • Open log for writing with open(2).
    • Compute fib(n), where the sequence starts 1, 1, 2, 3, ….
    • Write the date/time and result of fib(n) to log using write(2).
    • Close the file with close(2).
    • Exit with status code n.
  4. Parent:
    • Immediately call wait() to reap the child.
    • Print the child’s exit code to stdout (child PID optional).
    • Exit with status 0.
Headers you will need
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>       /* time / ctime */
#include <fcntl.h>      /* open O_* flags */
#include <sys/stat.h>  /* file modes */
#include <unistd.h>     /* POSIX / fork / write / close */
#include <sys/wait.h>   /* wait / macros */

Use WIFEXITED(status) and WEXITSTATUS(status) to decode the child's exit. Parent/child do not share memory after fork().

Step 3 — Build & Run

$ make           # if you have a provided Makefile; otherwise:
$ gcc vrlab2.c -Wall -o lab2
$ ./lab2
Usage: ./lab2 <n>

$ ./lab2 22
my child (19897) exited with code: 22

$ cat log
2025-09-03 13:00:00
fib 22 = 17711
Try these experiments:
  • Pass n=0, n=1, n=2. Do your base cases match the sequence definition?
  • Guard against negative n. What exit code should you choose on invalid input?
  • Log multiple values fib(1..n) instead of a single fib(n). What changes are needed?
  • Open the file with O_APPEND and run your program multiple times. How does the log differ?

Step 4 — Verify with strace and top

$ strace -q -f -e trace=write ./lab2 21 2>err
$ cat log
$ cat err
$ top -u YOUR_USERNAME
Expected observations:
  • strace shows write calls from both parent and child (depending on your printf and file writes).
  • The parent executes wait4/waitpid and receives the child's PID/status.
  • No Z processes remain in top after the parent exits (child has been reaped).

Optional Extensions (Challenge)

Files to Submit / Check

FAQ

Q: Why not share a variable between parent and child?
A: After fork, they have separate address spaces. Communicate via exit status, files, or IPC (coming later).

Q: The output order changes between runs. Is that a bug?
A: No — parent and child run concurrently. Don't assume a fixed interleaving.

Q: Should the child use _exit or exit?
A: Either is fine for this lab. _exit skips stdio flushing; exit flushes stdio and runs atexit handlers. Avoid double‑flushing if both parent and child share buffered streams.

Q: Why exit with n instead of the Fibonacci value?
A: Exit codes are limited (0–255); returning n keeps behavior predictable while we log the computed result in the file.

Q: What if I open the log with O_APPEND?
A: Each run appends a new record (great for history). Using O_TRUNC overwrites the previous run.