Lab 2 — fork(), wait(), and Writing to Files
CMPS 3600 • Fall 2025
CMPS 3600 • Fall 2025
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.
fork()
duplicates a process and how the return value differs in parent vs child.wait()
and status macros to correctly reap a child process and avoid zombies.open
/write
/close
and understand why we use them here instead of stdio.strace
and observe process states with top
.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.
fork()
creates a new child process by duplicating the calling parent process.
Immediately after the call there are two execution flows:
0
in the child.wait()
lets the parent collect the child's exit status and release the child's entry in the process table.Zombie
(state Z
): a finished child whose parent hasn't called wait()
yet. Zombies disappear once reaped.Process tree (conceptual)
shell (PID 100)
└─ your program (parent, PID 200)
└─ your program (child, PID 201)
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.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.
wait()
. Use top or ps -o pid,ppid,stat,cmd -u $USER to spot a child in Z
state.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
err.txt
, find fork
/clone
and the child's _exit
.wait4
/waitpid
returning the child's PID.top
, observe that any Z
child disappears once the parent calls wait()
.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
).
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
.
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
.
fork()
to create a child process.log
for writing with open(2)
.fib(n)
, where the sequence starts 1, 1, 2, 3, …
.fib(n)
to log
using write(2)
.close(2)
.n
.wait()
to reap the child.stdout
(child PID optional).0
.#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()
.
$ 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
n=0
, n=1
, n=2
. Do your base cases match the sequence definition?n
. What exit code should you choose on invalid input?fib(1..n)
instead of a single fib(n)
. What changes are needed?O_APPEND
and run your program multiple times. How does the log differ?strace
and top
$ strace -q -f -e trace=write ./lab2 21 2>err
$ cat log
$ cat err
$ top -u YOUR_USERNAME
strace
shows write
calls from both parent and child (depending on your printf
and file writes).wait4
/waitpid
and receives the child's PID/status.Z
processes remain in top
after the parent exits (child has been reaped).clock_gettime
).timestamp,pid,n,fib(n)
.n
using 64‑bit integers and detect overflow.fork()
) and reap all with wait()
/waitpid()
.~/cs3600/2/vrlab2.c
~/cs3600/2/Makefile
~/cs3600/2/
.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.