Processes and Threads
What a running program looks like to the operating system: processes and threads, their states, context switches, services and how to inspect or stop them.
Program, process and thread
Three words that are easy to mix up:
-
A program is a file on disk:
notepad.exe,/usr/bin/python3. It does nothing until someone starts it. - A process is a program that is running: the code loaded into memory plus everything the operating system keeps track of for it. Start Notepad three times and you have one program and three processes, each with its own memory and its own open file.
- A thread is one sequence of instructions being executed inside a process. Every process has at least one thread; many have dozens. A browser may use one thread for the user interface, others for downloading and decoding images, so the window keeps responding while a page loads. All threads of a process share its memory and open files.
The operating system schedules threads on the CPU cores, but it assigns resources — memory, files, permissions — to processes. How programmers use threads and processes, and the problems of sharing memory between threads, is explained in Concurrency Basics. This page is the operating system's view.
What the OS keeps for every process
For each process the kernel keeps a data structure, traditionally
called the process control block (PCB). Windows
calls it an EPROCESS structure, Linux a task_struct. It
holds:
- Identity: the process ID, the parent's process ID, and the program file it was started from.
- State: ready, running, waiting and so on (below), and scheduling information such as priority and CPU time used so far.
- Memory: the process's private address space and the page tables that map it to physical memory (see Memory Management).
- Open resources: files, network connections, registry keys and other objects the process is using. Windows calls these handles; Unix-like systems call open files file descriptors.
- Security context: the user account the process runs as and what it may do — an access token on Windows, user and group IDs on Linux. A process started by an ordinary user cannot do more than that user may, which is why malware started by a standard user does less damage than malware started by an administrator.
- Threads: for each thread, its own saved CPU registers, its own stack and a thread ID.
Process ID
A process ID (PID) is the number
the kernel gives a process when it starts, unique among the
processes running at that moment. Tools and commands use it to point
at one exact process — "stop PID 8424" is unambiguous, while "stop
notepad" might hit three windows. PIDs are reused after a process
ends, so a PID written down yesterday may belong to something else
today. A few are fixed: on Windows PID 0 is the System Idle Process
and PID 4 is System (the kernel's own threads); on Linux PID 1 is
the init system, usually systemd.
Process states
A process is not running all the time — on a busy system most processes are waiting almost always. The kernel tracks each one's process state, which says whether it can use the CPU right now. The classic model has five states (strictly, on a multithreaded system each thread has a state, but the idea is the same):
- New: being created; memory and PCB are being set up.
- Ready: could run, waiting only for a free CPU core.
- Running: executing on a core right now.
- Waiting (blocked): cannot continue until something happens — data from the disk or network, a key press, a timer, a lock.
- Terminated: finished; the kernel is releasing its resources.
Two details in the diagram explain a lot of everyday behavior. A process that is waiting uses no CPU at all, which is why hundreds of processes can exist while the CPU is 95% idle. And the only way from waiting back to running is through ready: when a slow disk finally delivers, the process still has to wait its turn.
This simulation runs a short scripted scenario for two processes on one core, and rejects a transition the model does not allow:
# Which event moves a process from which state to which state
TRANSITIONS = {
("new", "admit"): "ready",
("ready", "dispatch"): "running",
("running", "timeout"): "ready", # time slice used up
("running", "wait_io"): "waiting", # asked for disk or network data
("waiting", "io_done"): "ready",
("running", "exit"): "terminated",
}
state = {"editor": "new", "backup": "new"}
script = [
("editor", "admit"), ("backup", "admit"),
("editor", "dispatch"), ("editor", "wait_io"),
("backup", "dispatch"), ("editor", "dispatch"),
("editor", "io_done"), ("backup", "timeout"),
("editor", "dispatch"), ("editor", "exit"),
]
for step, (name, event) in enumerate(script, start=1):
old = state[name]
new = TRANSITIONS.get((old, event))
if new is None:
print(f"{step:2}. {name:6} {event:8} refused: not possible from {old}")
continue
state[name] = new
print(f"{step:2}. {name:6} {event:8} {old} -> {new}")
print("Final:", state)
1. editor admit new -> ready
2. backup admit new -> ready
3. editor dispatch ready -> running
4. editor wait_io running -> waiting
5. backup dispatch ready -> running
6. editor dispatch refused: not possible from waiting
7. editor io_done waiting -> ready
8. backup timeout running -> ready
9. editor dispatch ready -> running
10. editor exit running -> terminated
Final: {'editor': 'terminated', 'backup': 'ready'}
Step 4 is the important moment: while the editor waits for the disk,
the core is free, and the backup gets it at step 5. Step 6 shows why
the ready state exists: the editor cannot be dispatched while it is
still waiting. Real systems add more states — Linux
ps shows, among others, R (running or
ready), S (sleeping, that is waiting),
D (waiting for input/output — I/O — and not
interruptible), T (stopped) and Z (zombie,
below).
Context switches
A computer with 8 cores routinely runs a few thousand threads. It can, because the kernel keeps swapping which threads are on the cores. A context switch is that swap: the kernel saves the complete CPU state of the running thread — its registers, including the program counter (the address of the next instruction) and the stack pointer — into the thread's saved state, and loads the saved state of the next thread. The next thread continues exactly where it stopped, unaware it was ever paused.
A context switch happens for one of two reasons:
- Voluntary: the thread has to wait (for a file, the network, a lock) and gives up the core.
- Involuntary: the thread's time slice ran out, or a more important thread became ready, and the scheduler takes the core away. See CPU Scheduling.
Switches are fast — on the order of microseconds — but not free.
Switching between two threads of the same process only swaps
registers. Switching to a thread of a different process
also switches the address space, which may throw away cached address
translations, and the new thread starts with CPU caches full of the
previous thread's data. That hidden cost is one reason why creating
hundreds of busy threads does not make a program faster. Tens of
thousands of switches per second are normal on a desktop; Windows
Performance Monitor shows them as the counter
System › Context Switches/sec, and Linux shows them in the
cs column of vmstat.
Parent and child processes
Every process except the very first is started by another process.
The parent process is the
process that created another one, its child; the kernel records the
parent's PID (PPID) in the child's PCB. Double-click a file in File
Explorer and explorer.exe is the parent; type a command
in PowerShell and the PowerShell process is the parent. Together
these links form a process tree.
On Linux and macOS the tree is strict and visible: PID 1 (systemd
on most Linux distributions, launchd on macOS) starts
everything else, and a child is created by the parent calling
fork and then exec. Two Unix terms come up
in troubleshooting:
- An orphan is a child whose parent ended first. It keeps running and is adopted by PID 1 (or a designated "subreaper" process), so nothing breaks.
-
A zombie is a child that has already ended but
whose parent has not yet collected its exit status (with the
waitsystem call). It uses no CPU or memory, only a slot in the process table, and shows asZor<defunct>inps. You cannot kill a zombie — it is already dead. A few are harmless; a growing number means the parent program has a bug, and restarting the parent clears them.
Windows also records each process's parent, but treats the link more
loosely: when a parent ends, its children simply keep running. To
stop a parent together with everything it started, use
End process tree on the Details tab of Task
Manager, or taskkill /T.
Services and daemons
Many processes have no window at all. They start at boot, run whether or not anyone is logged on, and provide something other programs rely on: printing, networking, updates, remote access, antivirus scanning.
Windows services
A service is a Windows program that
runs in the background under the control of the Service Control
Manager, usually starting at boot and running without a user logged
on. Services run under their own accounts: the built-in accounts
LocalSystem (full control of the computer), LocalService and
NetworkService (limited rights), or a dedicated service account.
Services are hosted in Service Host processes
(svchost.exe). On PCs with more than about 3.5 GB of
RAM, Windows 10 and 11 give most services their own
svchost.exe, which is why Task Manager shows dozens of
them.
You manage services in the Services console
(services.msc), on the Services tab of Task Manager, or
from the command line. Each service has a
startup type:
| Startup type | Behavior |
|---|---|
| Automatic | Starts during boot. |
| Automatic (Delayed Start) | Starts shortly after boot has finished, so it does not slow down startup and sign-in. |
| Manual | Starts only when a user, a program or a trigger (such as a device being plugged in) starts it. |
| Disabled | Cannot be started at all, not even by hand. |
The Recovery tab of a service's properties sets what happens when it crashes, for example restart it after one minute. A classic help desk fix is restarting the Print Spooler service when print jobs are stuck (PowerShell must run as administrator for the restart; illustrative output):
Get-Service -Name Spooler
Restart-Service -Name Spooler
Status Name DisplayName ------ ---- ----------- Running Spooler Print Spooler
In Command Prompt, sc query spooler shows the state,
and net stop spooler and
net start spooler stop and start it.
Daemons on Linux and macOS
A daemon is the Unix name for a
background process that is not attached to any terminal or user
session and provides a service, usually from boot until shutdown.
Their names traditionally end in d: sshd (the
Secure Shell server), cron/crond
(scheduled tasks), systemd itself. On most Linux
distributions systemd starts and supervises daemons as
units; on macOS, launchd does the same job.
(The name comes from the helpful spirits of Greek mythology, not
from demons.)
systemctl status ssh
sudo systemctl restart ssh
sudo systemctl enable ssh # start automatically at boot
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-09-30 08:14:02 UTC; 1 day 2h ago
Main PID: 912 (sshd)
The unit is called ssh on Debian and Ubuntu and
sshd on Red Hat–based distributions. "enabled" is the
equivalent of the Windows startup type Automatic.
Viewing and stopping processes
Task Manager and Resource Monitor
Task Manager (Ctrl+Shift+Esc) is the first stop on Windows:
- Processes groups what runs into apps, background processes and Windows processes, with CPU, memory, disk and network use. "Not responding" means an app has stopped handling its window messages — often because it is waiting on something, not necessarily crashed. End task closes it; unsaved work is lost.
- Details is the technician's view: one line per process with its PID, status, the user it runs as, and optional columns (right-click a column header) such as threads, handles and command line. Here you find End process tree and Set priority.
- Services lists services and their state.
Resource Monitor (resmon, or from the
Performance tab of Task Manager) goes deeper. Its CPU tab can search
the handles of all processes: when Windows says "The action can't be
completed because the file is open in another program", search for
the file name to find which process holds it.
tasklist and taskkill
In Command Prompt, tasklist lists processes and
taskkill ends them, by image name (/IM) or
by PID (/PID). Without switches, taskkill
asks the program to close, like clicking its close button;
/F forces termination immediately; /T also
ends every child process. Illustrative output:
tasklist /FI "IMAGENAME eq notepad.exe"
taskkill /IM notepad.exe
taskkill /PID 8424 /F
Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ Notepad.exe 8424 Console 1 61,248 K SUCCESS: Sent termination signal to the process "Notepad.exe" with PID 8424. SUCCESS: The process with PID 8424 has been terminated.
PowerShell
PowerShell's Get-Process returns process objects you
can sort and filter; Stop-Process ends a process at
once (it does not ask the program to close first). The five
processes that have used the most CPU time (illustrative output):
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 -Property Id, ProcessName, CPU
Stop-Process -Id 8424
Id ProcessName CPU -- ----------- --- 6120 msedge 842.53125 5932 explorer 311.0625 7716 ms-teams 204.765625 3144 svchost 96.203125 9012 OneDrive 41.46875
CPU is the total processor time in seconds since the
process started, not its current load. Current load is what Task
Manager's CPU column shows.
ps, top and kill on Linux
ps takes a snapshot of the processes;
top (or the friendlier htop, if installed)
shows a live view sorted by CPU use. This ps command
shows PID, parent PID, owner and state as a tree (shortened,
illustrative):
ps -eo pid,ppid,user,stat,comm --forest
PID PPID USER STAT COMMAND
1 0 root Ss systemd
655 1 root Ss cron
912 1 root Ss sshd
4410 912 root Ss \_ sshd
4432 4410 alex S \_ sshd
4433 4432 alex Ss \_ bash
4501 4433 alex R+ \_ ps
kill does not really kill: it sends a
signal, and the signal decides what happens.
| Signal | Number | Meaning | Sent by |
|---|---|---|---|
SIGTERM |
15 | Please terminate. The program can save its work, close files and exit cleanly — or ignore it. | kill PID (the default) |
SIGKILL |
9 | Terminate now. The kernel removes the process; the program cannot catch, block or react to it. |
kill -9 PID or kill -KILL PID
|
SIGINT |
2 | Interrupt from the keyboard. | Ctrl+C in the terminal |
SIGHUP |
1 | Terminal closed; many daemons treat it as "reload your configuration". | kill -HUP PID |
kill 4620 # SIGTERM: ask politely
kill -9 4620 # SIGKILL: only if it is still running after a few seconds
pkill firefox # SIGTERM to every process whose name contains firefox (use -x for an exact match)
You can signal your own processes; signaling another user's
processes needs sudo.