Module 2 · Unit 3 · Lesson 1 of 5
Process Concept
The fundamental abstraction of a running program. What a process is, how it differs from a program, what a running process owns, and how the operating system creates, runs, and terminates it.
01 Introduction
In the earliest computers, only one program ran at a time. It had the machine entirely to itself: the whole address space, every register, every device. Modern systems are radically different. A laptop runs a browser, a music player, a code editor, a chat client, a backup daemon, and dozens of system services, all apparently at once. The abstraction that makes this possible is the process.
A process is a program in execution. It is the unit of work that the operating system schedules, the unit of isolation that the memory manager protects, and the unit of ownership that the file system tracks. Almost every mechanism in the rest of this course — threads, scheduling, synchronization, memory management — is defined in terms of processes. Getting the concept precisely right now makes everything that follows easier.
This lesson establishes the process concept carefully. The next four lessons in the unit build on it: states, control structures, scheduling, and context switching. By the end of the unit you will have a complete picture of the abstraction that every modern operating system is organised around.
Prerequisites from earlier units
This lesson assumes you have completed the course VM setup (Lesson 1.0) and have a working Linux environment with the toolchain from Lesson 2.5. The interactive activity and worked example in this lesson run inside the VM, so make sure it is running before you begin.
02 Learning objectives
By the end of this lesson you will be able to
- Define a process.
- Differentiate processes and programs.
- Explain process execution.
- Identify process resources.
- Analyse process lifecycle events.
This lesson contributes to U3.1 and to course learning outcomes CLO2 and CLO6.
03 Required reading
- OSTEP — Chapter 4, The Abstraction: The Process, and Chapter 5, Interlude: Process API. The clearest introduction to the process abstraction and the fork/exec/wait API that Linux implements.
- Operating System Concepts — Chapter 3, Sections 3.1–3.3 (process concept, process states, and process control block). The classical taxonomy of what a process is and what the kernel tracks about it.
-
Linux man pages:
man 2 fork,man 2 execve,man 2 wait,man 2 exit,man 5 proc,man 1 ps. These document the exact behaviour of the system calls and the process interface. - Love, R. — Linux Kernel Development, 3rd ed., Chapter 3 (Process Management). How Linux implements the process abstraction; useful for connecting the textbook to the real system.
Reading strategy
Read OSTEP Chapter 4 first for the abstraction, then
Chapter 5 for the fork/exec/wait API. Read
Silberschatz Sections 3.1–3.3 for the formal taxonomy. Keep
the man pages open while working through the worked example —
the fork(2) page in particular has subtle
details that the textbook glosses over.
04 Core content
4.1 From program to process
A program is a passive artefact: an executable file on disk, containing machine instructions and static data. It does not run, does not consume CPU, and does not own any memory beyond the space it occupies on storage.
A process is that program in execution. It has a program counter that advances, a stack that grows and shrinks, a heap that allocates and frees, registers that change values, and file descriptors that reference open files. Where a program is a noun, a process is a verb.
A process is a program in execution — an active entity with a program counter, a set of registers, and an address space, together with whatever resources the operating system has granted it.
Two consequences follow immediately from this definition.
First, the same program can give rise to many processes at once.
Running two copies of gcc means two separate
processes, each with its own program counter, its own address
space, and its own open files. Neither knows the other exists
(unless they deliberately share something). Second, a single
process may execute code from several different programs over
its lifetime — through the exec family of system
calls — but only one at a time.
This distinction is worth restating with an example. On a Linux
system, the shell itself is a process. When you type
ls, the shell asks the kernel to create a new
process that runs the ls program. The new process
is not the shell, and it is not the same as the ls
file on disk. It is a running instance of that file, with its
own resources and its own lifetime. When the program finishes,
the process ends and its resources are reclaimed.
4.2 The memory image of a process
From the process's own point of view, memory is a large, private, contiguous address space. In reality, the operating system maps portions of that address space onto physical memory, files, or nothing at all. But the abstraction is what programs see, and it has a conventional layout that is worth understanding precisely.
The five regions each have a distinct purpose:
- Text segment
-
The executable code. Read-only in almost all modern systems;
a write to the text segment causes a fault. Because it is
read-only, the same physical pages can be shared between
processes running the same program — the mechanism behind
shared libraries and the reason a hundred instances of
bashdo not consume a hundred copies of the binary. - Data segment
- Global and static variables that have an initial value in the source code. Loaded from the executable file at process start.
- BSS segment
- Global and static variables without an initial value. The kernel zero-fills this region at load time; it is not stored in the executable file, which is why uninitialised globals cost no disk space. The name is historical: "Block Started by Symbol" from the IBM 7094 assembler.
- Heap
-
Dynamically allocated memory. Grows upward as
mallocand its equivalents request more space. Managed by the C library's allocator, which asks the kernel for additional pages only when necessary. - Stack
- Local variables, function arguments, return addresses, and saved registers. Grows downward as function calls nest more deeply. Automatically managed by the compiler — every function entry and exit adjusts the stack pointer.
The layout is a convention, not a kernel requirement. The kernel gives each process a private virtual address space and lets the loader lay out the segments; the specific addresses vary depending on the executable format, the architecture, and the state of ASLR. What matters for this course is that the regions are conceptually distinct and that each behaves differently under growth, sharing, and protection.
4.3 The machine state
The address space is only half of what a process is. The other half is the machine state: the values of the CPU registers at any given moment.
-
Program counter (PC, or
ripon x86-64). The address of the next instruction to execute. -
Stack pointer (SP, or
rsp). The current position in the process's stack, which changes with every function call and return. -
Frame pointer (FP, or
rbp). A stable reference to the current stack frame, used by debuggers and by some compilers for stack unwinding. - General-purpose registers. Temporary values used by the currently executing code. The precise set depends on the architecture: x86-64 has sixteen, ARM64 has thirty-one.
- Status register (flags). Condition codes (zero, carry, overflow), interrupt mask, and the current privilege level.
Together, the address space and the machine state are the process's context. If the operating system saves the context of one process and restores the context of another, the second process resumes exactly where it left off — as if nothing had happened. This is the trick that underlies multiprogramming, and it is examined in detail in Lesson 3.5.
4.4 What resources a process owns
Beyond memory and registers, each process holds a collection of operating-system resources. The set is worth cataloguing because it defines what the kernel must track — and because each element will come up again later in the course.
| Resource | Description | Covered in |
|---|---|---|
| Process ID (PID) | A unique integer identifying the process within the system. | This lesson |
| Address space | The virtual memory mappings the process can access. | Module 3 |
| File descriptors | Handles to open files, pipes, sockets, and devices. | Module 4 |
| Signal handlers | Functions or dispositions associated with asynchronous signals. | This unit |
| Credentials | Real, effective, and saved user and group IDs. | Module 5 |
| Working directory | The current directory used to resolve relative paths. | Unit 2 |
| Resource limits | Maximum CPU time, file size, memory, open files, and so on. | Module 4 |
| Environment | The set of name-value strings inherited from the parent. | This lesson |
On Linux, most of this information is visible through the
/proc/<pid> pseudo-filesystem. The directory
contains a status file with the process's
credentials and state, a maps file describing
memory regions, an fd subdirectory listing open
file descriptors, and many others. The interactive activity at
the end of this lesson will have you inspect several of them.
4.5 Process creation and termination
Processes do not appear spontaneously. On a UNIX-like system,
every process except the very first is created by another
process, called its parent. The creating
operation is fork.
fork: the creation primitive
fork is unusual among system calls because it
returns twice: once in the parent and once in the child. Both
processes resume from the same point in the code, but the
return value distinguishes them. The parent receives the
child's PID; the child receives zero. This asymmetry lets a
single piece of code branch into parent behaviour and child
behaviour.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid == 0) {
printf("child · pid = %d, parent = %d\n", getpid(), getppid());
} else {
printf("parent · pid = %d, child = %d\n", getpid(), pid);
}
return 0;
}
The two printf lines may appear in either order.
The scheduler does not guarantee that the parent runs first
after fork returns; either process may be
dispatched next. This non-determinism is a recurring theme in
concurrent programming and one you will see again in Unit 6.
Copy-on-write makes fork efficient
Early UNIX implementations physically copied the entire
address space on every fork. That is obviously
wasteful — the child usually calls exec
immediately afterward, discarding everything that was copied.
Modern Linux uses copy-on-write: the parent's
pages are marked read-only and shared with the child, and a
page is copied only when either process attempts to write to
it. Programs that fork and then exec
without modifying much memory pay almost nothing.
exec: replacing the program
After fork, the parent has three options: continue
running alongside the child, wait for the child to finish using
wait, or terminate independently. The child, if it
wants to run a different program, typically calls one of the
exec family of system calls, which replaces the
child's address space with that of a new program.
#include <unistd.h>
#include <stdio.h>
int main(void) {
/* On success, execve never returns — the process image is replaced */
char *args[] = { "ls", "-l", "/home", NULL };
execvp("ls", args);
/* If we reach this line, exec failed */
perror("execvp");
return 1;
}
The combination fork + exec is how a
shell runs a command. The shell forks a child, and the child
immediately replaces its image with the requested program. The
shell itself continues running and waits for the child to
finish.
exit and wait: termination and reaping
A process terminates when it calls exit, returns
from main, or is killed by a signal. On
termination the kernel releases the process's resources. The
process's exit status is held briefly for the parent to collect
through wait; a process that has terminated but
whose status has not yet been collected is called a
zombie.
The wait call serves two purposes. It blocks the
parent until a child terminates, and it reaps the child's
entry from the kernel's process table. Without a
wait, a terminated child would remain a zombie
indefinitely, consuming a small amount of kernel memory. A
parent that creates many children without ever reaping them
can exhaust the process table and prevent new processes from
being created — a real failure mode worth knowing about.
4.6 Lifecycle events
Summarising, a process passes through a predictable sequence of significant events:
-
Creation
A parent calls
fork. The kernel allocates a PID, an address space, and a process control block (PCB). The process enters the New state. - Admission Resources are confirmed. The process is moved to the Ready queue, where it waits for the scheduler to run it.
- Execution The scheduler dispatches the process; it runs, blocks on I/O, is preempted, and repeats as the OS decides. The next four lessons examine each of these in turn.
-
Replacement (optional)
A child process may call
execto replace its own image with a new program, inheriting the same PID. -
Waiting
A parent that needs its child's result blocks in
waituntil the child terminates. - Termination The process exits, normally or via a signal. Its resources are released; its exit status is held for the parent.
- Reaping The parent collects the exit status. The process's entry is finally removed from the kernel's tables.
Each of these events corresponds to a state transition that the kernel tracks. Lesson 3.2 introduces the formal state model; the events above are the "why" behind the states.
4.7 The first process
There is one process on a Linux system that was not created by
fork. At boot, the kernel creates PID 1
— the init process — directly. Its responsibilities are to
initialise the system, start the services the system needs, and
(through the modern init systems) become the ancestor of every
other process that will ever run. If PID 1 terminates, the
kernel panics: no other process can take over the tree.
On modern Linux distributions, PID 1 is usually
systemd. On older or minimal systems it might be
sysvinit, upstart, or a shell. The
specific program is less important than the role: PID 1 is the
root of the process tree, the ancestor of everything.
You can inspect this tree with pstree or
ps -ejH. The hierarchy that appears shows the
parent-child relationships between all processes currently
running. Every process other than PID 1 has a parent — and the
chain always leads back to PID 1.
4.8 Why processes are the fundamental abstraction
It is worth stepping back to ask why the process is so central. Several properties make it the right unit of organisation:
- Isolation. Each process has its own address space, so a bug in one cannot corrupt another. The kernel enforces this on every memory access.
- Ownership. Every resource — file descriptors, memory, CPU time — is owned by a process. This makes resource accounting, limits, and cleanup tractable.
- Schedulability. The CPU runs one process at a time; the scheduler's job is to choose which. The process is the natural unit for that decision.
- Failure containment. When a process crashes, the kernel can terminate it cleanly without affecting other processes. This is what keeps a bug in one application from taking down the whole system.
- Composability. Processes can be combined through pipes and sockets, each running independently, which is the foundation of the UNIX programming model.
Threads, which we will study in Unit 4, relax the isolation property to gain performance. But the process remains the fundamental unit — threads live inside processes, sharing their resources and their fate.
4.9 The process API in one place
For reference, the essential process-management system calls on Linux:
| Call | Purpose | Notes |
|---|---|---|
fork |
Create a new process | Returns 0 in child, child PID in parent, −1 on error |
execve |
Replace the current process image | Never returns on success; the process is a "new" program with the same PID |
wait, waitpid |
Wait for a child to terminate | Blocks the parent; reaps the child's process-table entry |
exit, _exit |
Terminate the process | Runs atexit handlers, flushes stdio, then enters the kernel |
getpid, getppid |
Get the process or parent PID | Simple, fast, always successful |
kill |
Send a signal to a process | Named for its most common use but can send any signal |
clone |
Create a process or thread (Linux-specific) | Generalisation of fork; used by pthreads under the hood |
We will return to these calls repeatedly. For now, the goal is to recognise them and to understand that they are the interface between user space and the kernel's process manager.
05 Worked example
To make the process concept concrete, trace what happens when a shell runs a simple command.
ls /home at the shell prompt
- The shell reads a line. It is already a process — likely with PID 4321, say — with its own address space, file descriptors, and open file handles.
- The shell calls fork. The kernel duplicates the shell's address space (using copy-on-write, so no physical pages are copied until a write occurs). A new PCB is created, a new PID is assigned — say 4322 — and the child process enters the Ready queue.
-
Both processes resume from the fork return.
The parent's copy of
forkreturns 4322 (the child's PID). The child's copy returns 0. -
The child branches. Because
forkreturned 0, the child knows it is the child. It callsexecve("/bin/ls", ["ls", "/home"], environ). -
The kernel performs the exec. The child's
old address space is discarded. A new address space is
created from the
/bin/lsexecutable: text segment from the file, data segment from the file, zeroed BSS, a fresh stack, a fresh heap. The PID remains 4322 — the process is the same, only its image has changed. -
ls runs. It opens
/home, reads the directory entries, and writes them to its standard output — which is still the terminal, since file descriptors survivedexec. - ls exits. The kernel marks the process as terminated, retains the exit status for the parent, and releases most resources. The process is now a zombie.
-
The parent collects. The shell calls
wait4, retrieves the status, and reaps the zombie. The process entry is finally removed. - The shell prompts again. It loops back to read the next command.
Why the fork/exec split matters
Because fork and exec are separate,
the child has a chance to modify its environment between the
two calls — change directory, redirect file descriptors, set
resource limits, drop privileges. This is how the shell
implements redirection and pipelines, and how privileged
programs drop root privileges before executing untrusted code.
A single "spawn" system call could not offer this flexibility.
Following the example in your VM
You can observe this sequence directly on your course VM. In a terminal:
# See the shell's PID
$ echo $$
4321
# Run ls under a trace of the fork/exec/wait family
$ strace -f -e trace=clone,execve,wait4 ls /home
# The output will show clone() (the underlying call fork uses),
# execve() for /bin/ls, and eventually wait4() in the parent.
The clone system call is what fork
uses on Linux; fork is a thin wrapper around it.
Seeing it in the trace is a direct confirmation of the process
model.
06 Interactive activity
Activity · explore processes on a live Linux system
Open a terminal in your course VM and work through the following. Record what you see at each step — the goal is to connect the concepts of this lesson to a running system.
-
See your own process. Run
echo $$. The number printed is the PID of your current shell. Note it down. -
See the process tree. Run
pstree -p | head -30. Find your shell's PID in the tree and trace back to PID 1. What is PID 1 on your system? (Modern Ubuntu usessystemd.) -
Inspect the process's memory layout. Run
cat /proc/$$/maps | head -20. Each line describes one region of the shell's address space. Can you identify the text, heap, stack, and shared-library mappings? -
Inspect open files. Run
ls -l /proc/$$/fd. What file descriptors does your shell currently have open? Why are 0, 1, and 2 there? -
Watch a process start and stop. In one
terminal, run
sleep 300 &. Note the PID it prints. In the same terminal, runps -p <PID> -o pid,ppid,stat,cmd. What state is the process in? Then runkill <PID>and check again. -
Create a child. Run
bash -c 'echo parent=$$; (echo child=$BASHPID)'. Which PID is the parent, which is the child? -
Observe a zombie. Write a small C program
that forks a child, lets the child exit immediately, and
has the parent sleep without calling
wait. Compile it with the course flags, run it, and while it is sleeping, runps aux | grep Zin another terminal. What does the state column show? -
Trace the fork/exec cycle. Run
strace -f -e trace=clone,execve,wait4 bash -c 'ls /tmp'. Identify theclonecall (fork), theexecvecall (exec), and thewait4call (wait). This is the full lifecycle of a short-lived command.
Reflect: for each item, identify which of the process resources listed in section 4.4 you were observing. Which resource was hardest to see directly, and why?
07 Knowledge check
Check your understanding
Question 1
A user runs the same program twice. Are the two executions the same process, the same program, both, or neither? Explain.
Show answer
Question 2
In the layout of a process address space, why do the heap and stack grow toward each other rather than away?
Show answer
Question 3
What is a zombie process, and why does the kernel keep one around?
Show answer
wait, the status is available. Once reaped,
the entry disappears. Zombies consume a small amount of
kernel memory but no CPU or address space; a parent that
never reaps its children can accumulate them until the
process table is exhausted and new processes cannot be
created.
Question 4
Explain the purpose of copy-on-write in fork,
and why it makes the fork/exec pattern efficient.
Show answer
exec, which discards the
child's entire address space anyway — with copy-on-write,
the kernel never had to copy it. Programs that
fork without exec (a web
server forking a worker, for example) also benefit,
because pages that neither process modifies remain
shared indefinitely.
Question 5
Why is the process tree always rooted at PID 1, and what happens if PID 1 terminates?
Show answer
fork from an existing process,
so the parent-child relationships form a tree whose
root is PID 1. If PID 1 terminates, the kernel panics:
there is no process left to take over the tree, and the
system's assumptions about process ancestry are broken.
This is why init systems are carefully written and why
PID 1 is protected from most signals — a signal that
would ordinarily terminate a process is ignored by PID 1
unless it has installed a handler.
08 Unit discussion
Discussion prompt
The fork/exec pair is the standard
way to create a process on UNIX-like systems. Windows, by
contrast, offers a single CreateProcess call that
both creates the process and loads the program. Meanwhile,
some modern language runtimes and container environments use
clone directly, and others (such as WebAssembly
runtimes) discard the fork model entirely in favour of
instantiating an isolated module.
Argue for or against the UNIX approach. Consider: the ability
to modify the child's environment between fork
and exec; the cost of duplicating an address
space that will be immediately discarded; the conceptual
simplicity for programmers; and the way each model handles
the case of "run a program, but with one small tweak to the
environment."
Post your response to the Unit 3 discussion forum and reply to at least two classmates. Supports CLO2 and CLO8.
09 Practice exercise
This ungraded exercise consolidates the lesson and prepares you for Lab 2. Aim to spend 45–60 minutes on it.
-
Write and run a fork program. Compile and
run the
forkexample from section 4.5. Run it five times. Does the output order change between runs? Why or why not? -
Add a loop. Modify the program so that
both parent and child print their PID five times each. How
many distinct lines do you see? How many would you see if
you called
forktwice in a row? -
Create a zombie. Write a program in which
the parent forks a child, the child exits immediately, and
the parent
sleeps for 30 seconds without callingwait. In another terminal, runps -ef | grep Zwhile the parent sleeps. What do you see? Then modify the program to callwaitand confirm the zombie disappears. -
Trace the exec. Run
strace -f -e trace=execve bash -c 'echo hello'. Whichexecvecalls appear? What does the trace tell you about how the shell runs a builtin versus an external program? -
Inspect /proc. Explore the
/proc/selfdirectory of your shell. Which of the resources from the table in section 4.4 can you find? Openstatus,maps, and thefdsubdirectory. Describe what each shows. -
Write a small shell. Write a C program
that reads a line from standard input, splits it into a
command and arguments, forks a child, and uses
execvpin the child to run the command. The parent shouldwaitfor the child and then loop to read the next line. This is the essence of a shell, and it is the seed of the process-management exercises in Lab 2. - Reflect. In your own words, describe the difference between a process and a program. Give one example of two processes running the same program, and one example of a single process that has run code from multiple programs over its lifetime.
10 Summary
- A program is a passive file; a process is that program in execution, with its own address space, registers, and resources.
- The address space has five conventional regions: text, data, BSS, heap (growing upward), and stack (growing downward).
- The machine state — program counter, stack pointer, general registers, status register — is the other half of the process. Together with the address space, it constitutes the process context.
- A process owns many resources beyond memory: PID, file descriptors, signal handlers, credentials, working directory, resource limits, and environment.
-
On UNIX-like systems, processes are created by
fork, typically followed byexec, and terminate throughexit. A terminated process whose status has not been collected is a zombie. -
Copy-on-write makes
forkefficient by deferring the copy of the address space until either process writes. - All processes form a tree rooted at PID 1, which is created directly by the kernel at boot.
Looking ahead
Lesson 3.2 examines the states a process occupies during its life and the transitions between them. The lifecycle events from section 4.6 correspond directly to the state transitions you will see next.
11 References
-
Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C.
Operating Systems: Three Easy Pieces, Chapters 4 and 5.
Available online at
ostep.org. - Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 3.
- Love, R. Linux Kernel Development, 3rd ed., Chapter 3 (Process Management). Addison-Wesley, 2010.
-
Linux man-pages:
man 2 fork,man 2 execve,man 2 wait,man 2 exit,man 5 proc,man 1 ps,man 1 pstree,man 1 strace. - Bach, M. J. The Design of the UNIX Operating System, Chapter 6. Prentice Hall, 1986. (Classical reference for the process model.)