Processes
6 min read
Core idea
A process is a running program plus its bookkeeping
A program on disk is inert — bytes in a file. A process is what you get when the kernel loads that program into memory and gives it an execution context: a process ID (PID), an owner, an address space, open file descriptors, a current working directory, environment variables, scheduling state, and a parent process. Linux is multitasking, meaning the kernel rapidly switches the CPU between dozens or thousands of processes, creating the illusion that they all run at once.
Every process descends from another process through fork() — the kernel duplicates the parent, then the child usually calls exec() to replace itself with a different program. The first process at boot is init (or systemd), which has PID 1 and is the ancestor of every other process on the system. The result is a tree.
Signals are how the kernel and shell talk to processes
The kernel cannot just walk into a process's memory and tell it to quit. Instead it sends signals — short, named, asynchronous notifications that the process can choose to handle, ignore, or default-react to. Ctrl-C is the signal SIGINT (interrupt). Ctrl-Z is SIGTSTP (terminal stop). Closing a terminal sends SIGHUP. kill -9 sends SIGKILL, the only signal a process cannot catch or ignore — the kernel terminates the process immediately, with no chance to clean up.
The shell's kill, fg, bg, and nohup commands are all signal manipulation wrapped in convenient syntax. Once you internalize that the kernel speaks to processes only through signals (and the syscalls processes initiate themselves), the entire process-control surface collapses into a small, learnable model.
Why it matters
Diagnosing a slow or hung machine starts here
When a system goes sluggish, the question is always the same: which process is eating the CPU, the memory, the disk? ps, top, and pstree answer that question with no second-guessing. Without them, you are guessing; with them, you have evidence. Production triage of a real Linux box is largely the practice of reading top and ps output fluently and knowing what to do next.
Background and foreground are how the shell scales
Without job control, the shell can only run one program at a time, blocking until it finishes. With job control (& to background, Ctrl-Z to suspend, fg/bg to resume), one terminal session juggles a build, a tail of log files, an editor, and a long-running test in parallel. This is why developers live in a shell rather than opening a new terminal per task — the cost of context-switching with fg %2 is lower than the cost of windowing.
Key takeaways
Mental model
Process states as a finite state machine
The kernel cycles each process through a small set of states. Knowing which letter ps prints tells you exactly what the process is doing at that instant.
Foreground, background, and the kill chain
Practical application
-
Start with
topwhen the machine feels slow. PressMto sort by memory,Pto sort by CPU,1to expand per-core view,qto quit. The first row is uptime and load average; the second row shows process counts by state. Look for processes with %CPU near 100 or RSS climbing. -
Use
ps aux | grep <name>for a known program. This shows every process with that name across all users with full memory and CPU stats. Add| headto truncate. For tree views, preferpstree -porps auxf(the f flag draws ASCII art parent-child lines). -
Send SIGTERM first, SIGKILL only if it doesn't respond.
kill PIDis SIGTERM — the polite request. Wait a few seconds. If the process is still there,kill -9 PIDis the nuclear option. SIGKILL skips the process's cleanup handlers, which can leave open files locked or temp files behind. -
Match a job number to a PID with
jobs -l. Plainjobsshows job numbers;jobs -ladds the PIDs. Usekill %1orkill %commandnameto send signals by jobspec — easier than copying a PID by hand. -
Background carefully — append
&from the start, not after the fact.long-build &runs the build in the background and returns the prompt immediately. If you forgot, press Ctrl-Z to suspend, then typebgto resume in the background. Either way, the job appears injobs. -
Use
nohup(or better,systemd-run --user --scope) for jobs that must survive logout. A backgrounded process still receives SIGHUP when its controlling terminal closes — and most programs default-handle SIGHUP by exiting.nohup cmd > out.log 2>&1 &redirects output and ignores the hang-up; the process survives until you explicitly kill it. -
Use
niceonly when you actually need it. A backup job, a video encode, or a long compile that should not steal CPU from your editor:nice -n 19 make -j4. Rarely should you elevate priority with negative niceness — the kernel scheduler is better than you at deciding what runs when.
Example
Diagnosing a runaway process from one line of ps
A user reports their laptop fans are loud and the machine sluggish. You run:
$ ps aux --sort=-%cpu | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
me 12847 387.2 4.1 2.4G 680M ? Rl 09:14 142:03 node /opt/legacy/poller.js
me 2103 2.1 1.8 1.1G 290M tty1 Sl 08:22 3:21 /usr/bin/gnome-shell
root 482 1.8 0.4 280M 70M ? Ssl 08:21 2:47 /usr/lib/systemd/systemd-journal
Three pieces of information jump out:
%CPU 387.2— node is using nearly four full cores. The number is summed across cores, so values >100 are normal but flag the busiest processes.STAT Rl— Running, multi-threaded. The process is actively burning CPU, not stuck on I/O.TIME 142:03— over two hours of cumulative CPU time. This isn't a brief spike; it's a long-running consumption.
The right next step is to look at what node is doing: strace -p 12847 -c to sample its syscalls, or attach a profiler. If you just need the machine quiet for now, kill 12847 (SIGTERM) gives node a chance to exit cleanly; if it doesn't respond in five seconds, kill -9 12847. Save the diagnostic step for after the immediate symptom is gone.
Why & alone is not enough to survive logout
You start a long data export over SSH:
$ ./export-database.sh > export.log 2>&1 &
[1] 47218
You disconnect from the SSH session. An hour later the export is missing — half-finished, no error in the log. What happened?
When the SSH session ended, the shell sent SIGHUP to every job in its job table, including job [1]. The default action of SIGHUP is to terminate. Backgrounding with & does not change this; it only releases the shell prompt so you can do other things.
The fix has three layered options:
# Option 1: nohup (immediate, no setup)
$ nohup ./export-database.sh > export.log 2>&1 &
# Option 2: disown after the fact
$ ./export-database.sh > export.log 2>&1 &
$ disown %1
# Option 3: systemd-run (modern, gives you logs, status, restart policy)
$ systemd-run --user --scope ./export-database.sh
For one-off jobs nohup or disown is fine. For anything you actually depend on, run it under systemd or in a tmux/screen session — that way the process has a manager that survives more than just the terminal closing.
Related lessons
Related concepts
- Processeslinked concept
- Signalslinked concept
- Multitaskinglinked concept