Operating Systems · Chapter 1
Processes
A process is a program in execution. It is the unit the operating system uses to isolate programs from one another and to share the processor among them. Each process has its own address space, its own register state, and its own set of kernel-managed resources.
Program and process
A program is a file: machine instructions and initialized data stored in an executable format such as ELF. A process exists only while the program runs. The kernel creates it by building a fresh address space, mapping the executable's sections into that space, and pointing the processor's registers at the first instruction.
One program can back many processes at once. Two terminal windows each running bash are two processes executing the same file. They share nothing except the read-only instructions, which the kernel maps into both address spaces from a single copy in physical memory.
The address space
Every process sees memory as one contiguous range of virtual addresses, laid out in regions with distinct purposes:
- Text holds the machine instructions. It is mapped read-only and executable, so a stray write into code raises a fault.
- Data and BSS hold global and static variables. Data contains initialized values copied from the file; BSS is zero-filled at load time and occupies no space in the file.
- Heap holds memory allocated at run time through
malloc. It grows toward higher addresses when the allocator requests more pages from the kernel withbrkormmap. - Memory-mapped region holds shared libraries such as
libcand any files the process maps withmmap. - Stack holds one frame per active function call: arguments, local variables, and the return address. It grows toward lower addresses.
- Kernel is mapped into every process but is accessible only when the processor runs in kernel mode.
These addresses are virtual. Two processes can both use address 0x401000; the memory management unit translates each process's addresses through that process's own page table to different physical frames. This translation is what prevents one process from reading or overwriting another's memory.
Process states
A process moves through a small set of states during its lifetime. The kernel records the current state and changes it in response to scheduling decisions, interrupts, and system calls.
| State | Meaning |
|---|---|
| Ready | Runnable, waiting in the run queue for a processor. |
| Running | Executing instructions on a processor. A machine with N cores has at most N running processes at any instant. |
| Blocked | Waiting for an event: a disk read, a network packet, a lock, or a child's exit. |
| Terminated | Finished executing; its exit status has not yet been collected by the parent. |
The process control block
The kernel stores everything it knows about a process in a process control block (PCB). All PCBs are reachable from the process table. On Linux the PCB is struct task_struct, and the process table is the set of these structures linked together and indexed by process ID.
The saved processor state is the field that makes multitasking possible. When a process stops running, its register values are written into its PCB; when it runs again, they are loaded back. The process cannot observe that it was ever stopped.
The context switch
A process runs directly on the processor, in user mode, with no kernel code executing alongside it. The kernel regains control only when one of three events transfers control to it:
- a system call, when the process executes a trap instruction to request a kernel service;
- an interrupt, raised by a device or by the hardware timer;
- an exception, such as a page fault or a division by zero.
The timer interrupt guarantees that the kernel regains control even from a process that never makes a system call. At each timer interrupt the scheduler may decide that the current process has used its time slice and switch to another. This operation is a context switch.
The direct cost of a context switch is the work shown above: a few hundred instructions to save and restore state. The indirect cost is larger. The incoming process finds the processor caches and the translation lookaside buffer filled with the previous process's data, and runs slowly until they are refilled.
Creating a process: fork, exec, wait
Unix creates processes with two separate system calls. fork() creates a new process; execve() replaces the program running in a process. Separating the two lets the parent configure the child, for example by redirecting its standard output, between the two calls.
fork
fork() creates a child process that is a duplicate of the caller: the same program, the same memory contents, the same open files, and the same position in the code. The call returns twice, once in each process:
- in the parent, it returns the child's process ID;
- in the child, it returns
0; - on failure, it returns
-1in the parent and no child exists.
The kernel does not copy the parent's memory at fork time. Parent and child share the same physical pages, marked read-only; a page is copied only when one of them writes to it. This technique, copy-on-write, makes fork() fast even for large processes.
exec
execve(path, argv, envp) discards the calling process's address space and loads a new program into it. The process ID, parent, and open file descriptors are kept. On success execve() never returns, because the code that called it no longer exists.
wait
waitpid(pid, &status, 0) blocks the parent until the specified child terminates, then returns the child's exit status and releases the child's last kernel resources.
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid < 0) {
perror("fork"); /* no child was created */
return 1;
}
if (pid == 0) { /* child */
char *argv[] = { "ls", "-l", NULL };
execv("/bin/ls", argv);
perror("execv"); /* reached only if exec failed */
_exit(127);
}
int status; /* parent */
waitpid(pid, &status, 0);
if (WIFEXITED(status))
printf("child %d exited with %d\n", pid, WEXITSTATUS(status));
return 0;
}
Termination, zombies, and orphans
A process terminates by calling exit(), by returning from main, or by receiving a signal it does not handle. The kernel then frees the process's memory and closes its files, but keeps its process table entry, which holds the exit status the parent has not yet read.
A terminated process whose status has not been collected is a zombie. It uses no memory or processor time, but it occupies a process ID and a slot in the process table; ps shows it in state Z. A parent that creates children and never calls wait() accumulates zombies until fork fails because the system has run out of process IDs.
If a parent terminates before its children, the children become orphans. The kernel reassigns each orphan to init (PID 1) or to a designated subreaper process, which calls wait() on it when it exits. Orphans therefore never remain zombies indefinitely.
Review
What is the difference between a program and a process?
A program is an executable file on disk. A process is a running instance of a program, with its own address space, register state, and kernel resources. One program can back many processes at once.
Why can two processes use the same virtual address without interfering?
Each process has its own page table. The memory management unit translates the same virtual address in two processes to two different physical frames.
How does the kernel regain control from a process that never makes a system call?
Through the hardware timer interrupt, which transfers control to the kernel at a fixed interval regardless of what the process is executing.
What does fork() return, and to whom?
The child's process ID to the parent, 0 to the child, and -1 to the parent if no child could be created.
Why does a successful execve() never return?
It replaces the calling process's address space with the new program, so the code that made the call no longer exists.
What is a zombie process, and what removes it?
A terminated process whose exit status has not been collected. It is removed when its parent calls wait() or waitpid(), or, if the parent has exited, when init or a subreaper reaps it.