Processes๐
Part of a pathway: How Modern Software Really Runs on a CPU
Consult the map
-
How Modern Software Really Runs on a CPU โ step 7 of 17
โ How the OS Scheduler Actually Decides ยท you are here ยท Namespaces and cgroups โ
This article covers interactive process management โ services that start at boot and persist across reboots are a different discipline: systemd's job, and a topic of its own.
Something is consuming all the CPU. An application is hung and won't respond. A background job you kicked off is still running and you need to stop it. The server is sluggish and you need to find the culprit in 30 seconds.
Every one of these situations requires the same starting point: understanding what's running, what resources it's using, and how to control it. Processes are the living instance of a program โ and managing them is one of the most frequent tasks in Linux administration.
Where You Might Have Seen This๐
If you've used Windows, most of this maps directly โ Task Manager, taskkill, End Task versus End Process Tree. What doesn't map is the gap between the two: Windows gives you two levers (End Task, or the nuclear End Process Tree), where Linux gives you a whole vocabulary of signals instead, covered below.
| Windows | Linux Equivalent |
|---|---|
| Task Manager (Processes tab) | top or htop |
| Task Manager (Details tab) | ps aux |
| Services (services.msc) | Processes + systemd (covered in Efficiency) |
tasklist |
ps aux |
taskkill /PID 1234 |
kill 1234 |
taskkill /F /PID 1234 |
kill -9 1234 |
| End Task | kill PID (SIGTERM) |
| End Process Tree | kill -9 PID (SIGKILL) |
What Is a Process?๐
When you run a command, Linux creates a process โ an isolated instance of that program with its own memory space, file descriptors, and execution state. Every process has:
- A PID (Process ID): unique numeric identifier
- A PPID (Parent Process ID): the process that created it
- An owner: the user whose permissions it runs with
- A state: running, sleeping, stopped, or zombie
graph TD
INIT["๐ต systemd (PID 1)\nThe init process โ parent of all others"]
SSHD["sshd (PID 892)\nSSH daemon"]
BASH["bash (PID 1234)\nYour login shell"]
CMD["ls (PID 1235)\nCommand you ran"]
NGINX["nginx master (PID 445)\nWeb server"]
WORKER["nginx worker (PID 446)\nHandles requests"]
INIT --> SSHD
INIT --> NGINX
SSHD --> BASH
BASH --> CMD
NGINX --> WORKER
style INIT fill:#d69e2e,stroke:#cbd5e0,stroke-width:2px,color:#000
style SSHD fill:#2d3748,stroke:#63b3ed,stroke-width:2px,color:#fff
style BASH fill:#2d3748,stroke:#63b3ed,stroke-width:2px,color:#fff
style CMD fill:#2d3748,stroke:#68d391,stroke-width:2px,color:#fff
style NGINX fill:#2d3748,stroke:#fc8181,stroke-width:2px,color:#fff
style WORKER fill:#2d3748,stroke:#fc8181,stroke-width:2px,color:#fff
Every process on the system descends from PID 1: systemd (or init on older systems). When a parent process exits, its children are re-parented to PID 1.
That tree is the map. ps is how you actually read it.
Viewing Processes: ps๐
ps (process status) is the primary tool for inspecting running processes โ a single, deliberate look at exactly what's running right now, not a live feed you have to sit and watch.
The Essential ps Invocations๐
ps aux # (1)!
ps auxf # (2)!
ps -u www-data # (3)!
ps # (4)!
ps aux | grep nginx # (5)!
pgrep -la nginx # (6)!
- The classic: every process, full details.
- Tree view โ shows parent/child relationships.
- Processes owned by a specific user.
- Processes for the current terminal only.
- Find a specific process by name.
- Cleaner alternative โ lists matching PIDs with their command line, no
grepneeded.
Reading ps aux Output๐
ps aux
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.1 168940 11232 ? Ss Mar09 0:05 /sbin/init
# nginx 445 0.0 0.2 108876 18440 ? Ss 10:00 0:00 nginx: master
# nginx 446 0.1 0.4 109264 33280 ? S 10:00 2:14 nginx: worker
# jsmith 1234 0.0 0.1 24256 8420 pts/0 Ss 14:23 0:00 -bash
| Column | Meaning |
|---|---|
USER |
Process owner |
PID |
Process ID |
%CPU |
CPU usage (averaged) |
%MEM |
Physical memory percentage |
VSZ |
Virtual memory size (KB) |
RSS |
Resident set size โ actual RAM in use (KB) |
STAT |
Process state |
START |
When process started |
TIME |
Total CPU time consumed |
COMMAND |
The command and its arguments |
Process States๐
The STAT column tells you what the process is doing:
| Code | State | Meaning |
|---|---|---|
R |
Running | Actively using CPU |
S |
Sleeping | Waiting for event (interruptible) |
D |
Disk wait | Waiting for I/O (uninterruptible) |
T |
Stopped | Paused (Ctrl+Z or SIGSTOP) |
Z |
Zombie | Finished but parent hasn't acknowledged yet |
s |
Session leader | First process in a session |
l |
Multithreaded | Has multiple threads |
+ |
Foreground | In the foreground process group |
< |
High priority | Nice value negative |
N |
Low priority | Nice value positive |
What to watch for:
- Lots of
Dprocesses โ disk I/O bottleneck Z(zombie) processes โ parent not collecting exit status (usually harmless in small numbers)T(stopped) โ process was paused, possibly accidentally
top and htop: Live Monitoring๐
ps is a snapshot. top and htop are live views that update continuously.
top โ Always Available๐
- Launches the live process monitor.
- Filter to one user's processes.
- Monitor a specific PID.
The rest of top's controls are worth learning by pressing keys and watching what happens, not by reading a list: press ? inside top for the full legend. Two are worth knowing before you're mid-incident, because they change what you're looking at: M sorts by memory, P by CPU.
Reading the top header:
top - 14:23:15 up 47 days, 3:12, 2 users, load average: 0.15, 0.10, 0.08 # (1)!
Tasks: 187 total, 1 running, 185 sleeping, 0 stopped, 1 zombie # (2)!
%Cpu(s): 5.2 us, 1.3 sy, 0.0 ni, 92.8 id, 0.7 wa, 0.0 hi, 0.0 si, 0.0 st # (3)!
MiB Mem : 15826.1 total, 8234.5 free, 4127.9 used, 3463.7 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 10834.6 avail Mem
- Uptime and load averages (1-, 5-, 15-minute).
- Process states at a glance.
- CPU breakdown: user, system, idle, wait (I/O), etc.
wa (wait) above 5-10% indicates an I/O bottleneck. us (user) high means application code is consuming CPU. sy (system) high means kernel activity โ often disk or network.
htop โ If Available๐
htop is a friendlier alternative with color, a tree view, and mouse support. Install it if it's not there (dnf install htop / apt install htop). Use it exactly like top but with arrow keys to navigate and F-keys for actions.
Finding Processes๐
top is good for "what's consuming the most, right now" โ a leaderboard. It's the wrong tool the moment you already know which process you're after and just need its PID. That's a search problem, not a monitoring one.
pgrep nginx # (1)!
# 445
# 446
pgrep -la nginx # (2)!
# 445 nginx: master process /usr/sbin/nginx -g daemon off;
# 446 nginx: worker process
pgrep -u www-data # (3)!
ps aux | grep "[n]ginx" # (4)!
ps aux | { head -1; grep "[n]ginx"; } # (5)!
ss -tlnp | grep ":8080" # (6)!
lsof -i :8080 # (7)!

- By name โ returns PIDs only.
- By name, with command details.
- By user.
- By name using
ps. The bracket trick ([n]ginx) stopsgrepfrom matching its own process. - Same idea, but keeps the column headers
grepwould otherwise strip out. - Find the PID of whatever is listening on a port.
- Same idea with
lsof, if it's installed.
Never Lose the Header Row Again
ps aux | grep nginx finds the process, but strips the one line that tells you which column is which: you're left guessing whether that number is %CPU or %MEM. The fix is one habit: ps aux | { head -1; grep "[n]ginx"; }. head -1 prints the header, then grep keeps reading the same piped output right where head left off and filters the rest. Same trick works with any command that prints a header row: docker ps, kubectl get pods, netstat.
A PID by itself is just a number. What makes it useful is what you can do to it next.
Signals: Communicating With Processes๐
Signals are messages you send to a process to tell it to do something โ stop, restart, reload configuration, or terminate.
kill PID # (1)!
kill -9 PID # (2)!
kill -1 PID # (3)!
kill -STOP PID # (4)!
kill -CONT PID # (5)!
killall nginx # (6)!
pkill -u jsmith # (7)!
pkill -9 -u jsmith # (8)!
- Default: SIGTERM (15) โ a polite request to stop.
- SIGKILL โ forceful, cannot be caught or ignored.
- SIGHUP โ reload configuration.
- Pause the process (SIGSTOP).
- Resume a paused process (SIGCONT).
- SIGTERM to every process named
nginx. - SIGTERM to all of jsmith's processes.
- Forcefully kill all of jsmith's processes.
The Essential Signals๐
| Signal | Number | Meaning | Can Be Caught? |
|---|---|---|---|
SIGHUP |
1 | Hangup โ reload config | Yes |
SIGINT |
2 | Interrupt โ what Ctrl+C sends | Yes |
SIGQUIT |
3 | Quit with core dump | Yes |
SIGKILL |
9 | Kill immediately | No โ cannot be caught or ignored |
SIGTERM |
15 | Terminate gracefully | Yes |
SIGSTOP |
19 | Pause execution | No โ cannot be caught |
SIGCONT |
18 | Resume execution | No |
SIGUSR1/2 |
10/12 | User-defined (varies by app) | Yes |
The SIGTERM vs SIGKILL Decision๐
Prefer SIGTERM Over SIGKILL
kill -9 is tempting because it always works. But it's also dangerous:
- The process cannot clean up โ open files may be corrupted, database transactions left open, locks not released
- Child processes are orphaned (reparented to PID 1)
- Logs may be incomplete
Always try SIGTERM first: kill PID, then wait 5-10 seconds. If the process doesn't exit, then consider kill -9 PID.
Legitimate uses for SIGKILL: a process that's genuinely hung and not responding to SIGTERM after a reasonable wait, or when a process is stuck in D state (uninterruptible disk wait).
SIGHUP for Config Reloads๐
Many services use SIGHUP to reload their configuration without restarting:
pgrep nginx # (1)!
# 445
kill -HUP 445 # (2)!
kill -1 445 # (3)!
nginx -s reload # (4)!
- Find the process PID.
- Send SIGHUP to reload the config without restarting.
- Equivalently, by signal number.
- nginx-specific wrapper for the same thing.
For services managed by systemd, systemctl reload servicename is the preferred approach: it handles the signal and verifies the reload succeeded.
Everything so far has been about controlling processes someone else started (a service, a stuck script, something already running when you got there). The other half of process management is controlling the ones you start.
Background Jobs and Job Control๐
When you run a command, it runs in the foreground by default โ your terminal is blocked until it finishes. Job control lets you manage multiple commands in one terminal session.
long-running-command & # (1)!
# [1] 1234 โ job number and PID
^Z # (2)!
# [1]+ Stopped long-running-command
bg %1 # (3)!
fg %1 # (4)!
jobs # (5)!
# [1]- Running long-running-command &
# [2]+ Stopped another-command
kill %1 # (6)!
- Run a command in the background immediately.
- Pause a running command and move it to the background โ press
Ctrl+Zwhile it's running. - Resume job 1 in the background.
- Resume job 1 in the foreground.
- List current jobs.
- Kill a job by its job number.
nohup โ Survive Logout๐
By default, background jobs receive SIGHUP when you log out, killing them. nohup prevents this:
nohup ./long-script.sh & # (1)!
nohup ./long-script.sh > /var/log/myscript.log 2>&1 & # (2)!
pgrep -la long-script # (3)!
- Sends output to
nohup.outby default. - Better: send output to an explicit log file.
- Check it's still running after you log out.
For long-running tasks, consider tmux or screen for persistent terminal sessions: they're more flexible than nohup.
Common Scenarios๐
The server feels sluggish. Find what's consuming resources.
ps aux --sort=-%cpu | head -10 # (1)!
ps aux --sort=-%mem | head -10 # (2)!
top # (3)!
uptime # (4)!
# load average: 8.45, 7.23, 6.12 โ high on a 4-core system
top # (5)!
iotop # (6)!
# or: ps aux | awk '$8=="D"' โ processes in disk wait
- Quick snapshot โ who's consuming CPU?
- Who's consuming memory?
- Live view; press
Pto sort by CPU. - Check load-average context.
- If load is high but CPU isn't, look for a high
wa(I/O wait) in the CPU line. - What's doing disk I/O? (
iotopif installed.)
An application is hung and won't respond to normal shutdown.
pgrep -la myapp # (1)!
# 1234 myapp --config /etc/myapp/config.yml
kill 1234 # (2)!
ps -p 1234 # (3)!
kill -9 1234 # (4)!
ps -p 1234 # (5)!
- Find the PID.
- Try graceful termination first, then wait ~10 seconds.
- Check if it's gone. If it's still there, escalate.
- Force kill.
- Verify it's gone โ this should show nothing.
You see a process consuming resources but don't know what it's doing.
lsof -p 1234 # (1)!
lsof -p 1234 -i # (2)!
ls -la /proc/1234/fd/ # (3)!
cat /proc/1234/cmdline | tr '\0' ' ' # (4)!
cat /proc/1234/environ | tr '\0' '\n' # (5)!
ls -la /proc/1234/cwd # (6)!
- What files does it have open?
- What network connections does it have?
- The same open files, via
/proc. - What command started it?
- What environment variables does it have?
- What's its current working directory?
You need to run a script that takes hours and you need to log out.
nohup ./backup-script.sh > /var/log/backup.log 2>&1 & # (1)!
echo $! # (2)!
disown # (3)!
tmux new-session -s backup # (4)!
# run your script inside tmux, then Ctrl+B, D to detach
# later: tmux attach -t backup
pgrep -la backup-script # (5)!
tail -f /var/log/backup.log
- Option 1 โ
nohup(simple): run detached, logging to a file. - Print the PID so you can track it.
- Detach from the current shell so logging out doesn't signal it.
- Option 2 โ
tmux(better; you can reattach later). - Check whether your background job is still running.
Quick Reference๐
Process Inspection๐
| Command | What It Shows |
|---|---|
ps aux |
All processes, full details |
ps auxf |
All processes, tree view |
ps -u username |
Processes by user |
pgrep name |
PIDs for named processes |
pgrep -la name |
PIDs and commands for named processes |
top |
Live process monitor |
top -p PID |
Monitor a specific PID |
Signals๐
| Command | Effect |
|---|---|
kill PID |
Send SIGTERM (graceful stop) |
kill -9 PID |
Send SIGKILL (force kill) |
kill -HUP PID |
Send SIGHUP (reload config) |
kill -STOP PID |
Pause process |
kill -CONT PID |
Resume paused process |
killall name |
Send SIGTERM to all matching |
pkill -9 name |
Force kill all matching |
Job Control๐
| Command | Effect |
|---|---|
command & |
Run in background |
Ctrl+Z |
Pause foreground job |
bg %1 |
Resume job 1 in background |
fg %1 |
Bring job 1 to foreground |
jobs |
List current jobs |
kill %1 |
Kill job 1 |
nohup cmd & |
Run, survive logout |
Practice Exercises๐
Exercise 1: Find Resource Consumers
Without using top, find:
- The process consuming the most CPU
- The process consuming the most memory
- Their PIDs and who owns them
Solution
ps aux --sort=-%cpu | head -3 # (1)!
ps aux --sort=-%mem | head -3 # (2)!
ps aux --sort=-%cpu | awk 'NR<=6 {printf "%-10s %-8s %-6s %-6s %s\n", $1, $2, $3, $4, $11}' # (3)!
- Most CPU โ
--sortusespscolumn names. - Most memory.
- Or combined โ show PID, user, CPU%, MEM%, and command.
Exercise 2: Gracefully Stop and Verify
Find the nginx master process PID, send it SIGTERM, wait, and verify it stopped.
Solution
pgrep -la nginx | grep master # (1)!
# 445 nginx: master process /usr/sbin/nginx
kill 445 # (2)!
sleep 3 # (3)!
ps -p 445 # (4)!
pgrep nginx # (5)!
# No output = no nginx processes running
- Find the master process PID.
- Send SIGTERM.
- Wait a moment...
- ...then verify โ if it shows nothing, the process is gone.
- Or check via
pgrepโ no output means no nginx processes running.
Note: In production, use systemctl stop nginx rather than killing directly โ systemd handles the signal, verifies shutdown, and updates the service state.
Exercise 3: Background Job Management
Run sleep 300 in the background, verify it's running, then pause it, then kill it.
Solution
sleep 300 & # (1)!
# [1] 2345
jobs # (2)!
# [1]+ Running sleep 300 &
ps -p 2345 # (3)!
kill -STOP 2345 # (4)!
ps -p 2345 -o pid,stat,command # (5)!
kill 2345 # (6)!
jobs # (7)!
# (empty)
- Start in the background.
- Verify it's running.
- Confirm via
psโ shows the sleep process. - Pause it.
- Check state โ should show
T(stopped). - Kill it (or
kill %1using the job number). - Verify it's gone.
Every scenario above reduces to the same four moves: see what's running (ps, top), name the one you care about (pgrep), talk to it (signals), and decide how it runs relative to your terminal (job control). That's the whole loop โ the tools change, the loop doesn't.
Quick Recap๐
- Every process has a PID, PPID, owner, and state; all descend from PID 1 (systemd)
ps auxโ snapshot of all processes;ps auxfโ with parent/child treepgrep -la nameโ find PIDs by name; cleaner thanps aux | greptopโ live view;Msorts by memory,Pby CPU,1shows per-core- Process states:
Rrunning,Ssleeping,Ddisk wait,Zzombie,Tstopped - SIGTERM (15) โ polite stop, process can clean up; SIGKILL (9) โ force kill, cannot be caught; always try SIGTERM first
- SIGHUP (1) โ reload configuration; standard for web servers and daemons
- Job control:
command &runs in background,Ctrl+Zpauses,fg/bgto move,nohupto survive logout
What's Next?๐
You've covered four of the five Essentials categories. The commands from the command line, users and access, text pipelines, and process management are now in your toolkit. Where you go next depends on why you're here:
-
Continuing Linux Essentials โ the final category, where all of these commands become repeatable automation.
-
Continuing the How Modern Software Really Runs on a CPU pathway โ the isolation mechanism a container actually relies on.
Further Reading๐
Command References๐
man psโ all ps options including output formattingman topโ complete top reference and interactive commandsman killโ signal list and usageman signalโ the full POSIX signal specificationman pgrepโ process search with pattern matchingman lsofโ list open files for a process
Deep Dives๐
- Brendan Gregg: Linux Performance โ authoritative Linux performance tools including process analysis
- Linux Process States โ detailed explanation of all process states
- Understanding /proc filesystem โ kernel documentation for /proc
Official Documentation๐
- Red Hat: Managing Processes โ RHEL process management guide
- Linux Kernel Documentation: /proc โ the /proc virtual filesystem