Unix identifies each process by a PID, an integer unique among running processes. Additional OS context tracked per process includes the parent PID, process accounting (CPU time used), open files, working directory, signal handlers, and umask.
Process Creation with fork
#include <unistd.h>
int fork(void);
fork() creates a new child process that is a duplicate of the calling process’s executable image, same code, same address space layout.
- Data in the child is a copy of the parent’s, not shared.
- The child differs from the parent only in its PID, its PPID, and the return value of
fork(). - Return value in the parent: the child’s PID.
- Return value in the child: 0.
- Return value on error: negative.
Executing a New Program with exec
fork() alone only duplicates the calling process. To run a different program, use the exec() family.
#include <unistd.h>
int execl(const char *path, const char *arg0, ..., const char *argN, NULL);
path: location of the executable.arg0throughargN: command line arguments, terminated byNULL.
execl() replaces the current process image with a new one. Code and data are replaced, but the PID and other state such as open files are preserved.
Every Unix executable is invoked as if main() were called:
int main(int argc, char *argv[], char *envp[])
argc: number of command line arguments, including the program name.argv: NULL-terminated array of C strings,argv[0]is the program name.envp: NULL-terminated array ofkey=valuestrings, the environment.
Process Termination
#include <stdlib.h>
void exit(int status);
status = 0: normal termination.status != 0: problematic execution, by convention.exit()never returns.
exit() (the C library function) flushes and closes open C stdio streams, calls registered exit handlers, then invokes _exit().
_exit() (the system call) performs immediate termination: closes all open file descriptors, reparents any children to init, and sends SIGCHLD to the parent. It does not flush C stdio buffers.
Parent-Child Synchronization
#include <sys/types.h>
#include <sys/wait.h>
int wait(int *status);
- Blocks the calling (parent) process until at least 1 child terminates.
- Returns the PID of the terminated child.
status, passed by address, stores the child’s exit status. PassNULLif not needed.- Cleans up the remaining child resources not released by
exit().
Variants:
waitpid()
Wait for a specific child process.waitid()
Wait for any child process to change status.
Process States in Unix
Unix refines the generic 5-state model with additional states.
- Running
Process is executing. - Sleeping (Blocked)
Process waiting for a resource, e.g. I/O. - Stopped
Process suspended by aSIGSTOPsignal, resumed bySIGCONT. - Zombie
Process has terminated via_exit()but its process table entry has not been cleaned up by await()call.
Zombie process arises in 2 cases:
- Parent terminates before the child
initbecomes the child’s pseudo parent and callswait()on its behalf. - Child terminates before the parent, and the parent never calls
wait()
The child remains a zombie, potentially filling the process table.
kill and Signals
int kill(pid_t pid, int sig);
Sends a signal to a process, the Unix analog of a hardware interrupt, routed through the kernel.
SIGSTOP: stop the process.SIGKILL: kill the process.SIGCONT: restart a stopped process.
Implementing fork
Simplified steps:
- Create the address space of the child process.
- Allocate a new PID.
- Create kernel process data structures, e.g. a process table entry.
- Copy kernel environment from the parent, e.g. scheduling priority.
- Initialize child process context: PID, PPID, zero CPU time.
- Copy memory regions (program, data, stack) from the parent.
- Acquire shared resources: open files, current working directory.
- Initialize the hardware context by copying registers from the parent.
- Add the child to the scheduler’s ready queue.
Linux additionally provides clone(), a more general primitive than fork() that allows partial resource sharing between parent and child, used to implement threads.