What are Processes and How Do I Manage Them?
Every command creates a process — here's how to see them, control them, and stop them
After this lesson, you will be able to:
- Explain the difference between a program (file on disk) and a process (running instance with a PID)
- Use
psandps auxto view running processes and interpret output columns - Run processes in the background with
&and manage them usingjobs,fg, andbg - Use
Ctrl+C,Ctrl+Z,kill, andkill -9to send signals to processes - Describe the Unix process lifecycle:
fork(),exec(), run,exit(),wait() - Use
echo $?to check exit status and explain the convention that 0 means success
What’s Actually Running on Your Computer?
When you type ls and see a file listing, something happened between your keypress and the output: the operating system created a process, ran the ls program inside it, collected the output, and cleaned up. All in milliseconds.
Every command, every program, every background service is a process. Right now, your terminal is a process. Your shell (bash) is a process. When you compile with gcc, that’s a process. When your compiled program runs, that’s another process. Understanding processes is understanding how your computer actually works.
We’ll use sleep — a command that just waits for a specified number of seconds — as our running example throughout this lesson. It’s perfect for experimenting because it runs long enough for you to inspect, suspend, and kill it.
What Is a Process?
A process is a running instance of a program. Think of it this way: a program is a recipe (instructions sitting on disk), and a process is someone actually cooking that recipe (instructions being executed, using memory and CPU time).
Every process has:
- PID — Process ID, a unique number assigned by the kernel
- PPID — Parent Process ID, the PID of the process that created this one
- UID — User ID, which user owns this process
- State — Running, sleeping, stopped, or zombie
Key insight: A program and a process are different things. A program is a file on disk (like
/usr/bin/ls). A process is that program loaded into memory and running. You can have multiple processes running the same program simultaneously — like two terminal windows both running bash.
From Java: In CSCD 210, you may have created processes with
ProcessBuilderorRuntime.getRuntime().exec("cmd"). Those are Java wrappers around the same Unix system calls you’ll learn here. Under the hood, the JVM callsfork()andexec()— the same functions you’ll use directly in C during Week 10. In Unix/C, there’s no class hierarchy or exception handling around process creation. You callfork(), check the return value, and manage the child yourself.
Quick Check: Can two processes run the same program at the same time?
Yes. Each process gets its own copy of the program in memory, its own PID, and its own state. Two people can both run ls at the same time — each gets their own process with a different PID. This is fundamental to how multi-user Unix systems work.
/usr/bin/ls sits on disk. You type ls in two different terminals at the same time. How many processes are created?/usr/bin/ls) can have multiple processes running simultaneously, each with its own PID, memory, and state. Option C is wrong — ls is an external program, not a builtin.
Viewing Processes: ps and top
The ps command shows a snapshot of running processes. Start with the simplest form:
ps
This shows only processes in your current terminal — probably just bash and ps itself. To see everything on the system:
ps aux
The output has several columns. Here’s what they mean:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
student 1234 0.0 0.1 12345 6789 pts/0 Ss 09:00 0:00 bash
student 5678 2.3 0.5 45678 12345 pts/0 S+ 09:15 0:02 ./myprogram
The key columns: PID (process ID — you’ll need this to kill processes), %CPU (how much CPU it’s using), STAT (its state), and COMMAND (what program is running).
To find a specific process, combine ps with grep:
ps aux | grep 'myprogram'
For a live, continuously updating view, use top:
top
top refreshes every few seconds, showing the busiest processes at the top. Press q to quit, M to sort by memory usage, P to sort by CPU usage. It’s like the Activity Monitor (macOS) or Task Manager (Windows), but in the terminal.
VS Code comparison: In VS Code, you might have seen processes via Task Manager or Activity Monitor when a build was slow.
psis the command-line version — and unlike a GUI, you can script it.ps aux | grep 'gcc'instantly finds all active compilations across the system.
ps aux | grep 'python' do?ps aux lists all running processes, then the pipe sends that list to grep 'python', which filters for lines containing "python." This is how you find a specific process when you know part of its name.
Background Processes and Job Control
Normally, when you run a command, the shell waits for it to finish before showing a new prompt. That’s called running in the foreground. But sometimes you want to start a long-running command and keep using your terminal.
Running in the Background
Add & after any command to start it in the background:
sleep 60 &
The shell prints the job number and PID, then gives you a new prompt immediately:
[1] 12345
$
The sleep command is running (it will wait for 60 seconds), but your terminal is free. Use jobs to see what’s running in the background:
jobs
[1]+ Running sleep 60 &
Suspending and Resuming
What if you started a command in the foreground and realized you need your terminal back? You don’t have to kill it:
- Press
Ctrl+Z— this suspends (pauses) the process - Type
bg— this resumes it in the background - Or type
fg— this brings it back to the foreground
Let’s try it with our running example:
sleep 120 # Starts in foreground — terminal is locked
Press Ctrl+Z:
[1]+ Stopped sleep 120
Now resume it in the background:
bg
[1]+ sleep 120 &
The process is running again, but now in the background. Use fg to bring it back to the foreground when you want, or just let it finish on its own.
The trick: The
Ctrl+Zthenbgcombo is something you’ll use constantly. You start a compilation or test run, realize it’s slow, and want your terminal back. Suspend it, background it, and keep working.
Quick Check: What's the difference between Ctrl+Z and Ctrl+C?
Ctrl+Z suspends the process — it’s paused but still alive. You can resume it with fg or bg. Ctrl+C terminates the process — it sends SIGINT, and the process dies. The key difference: Ctrl+Z is reversible, Ctrl+C is not.
sleep 300 in the foreground and realize you need your terminal back. You press Ctrl+Z, then type bg. What is the state of the sleep process?Ctrl+Z suspends (pauses) the process. bg resumes it in the background, so it keeps running but your terminal is free. Use fg to bring it back to the foreground if needed. This Ctrl+Z → bg combo is one of the most useful job control patterns.
Signals: Talking to Processes
Signals are short messages the kernel sends to processes. You’ve already used two of them without knowing it:
| Signal | Number | Keyboard | Effect |
|---|---|---|---|
| SIGINT | 2 | Ctrl+C |
Interrupt — politely asks the process to stop |
| SIGTSTP | 20 | Ctrl+Z |
Suspend — pauses the process |
| SIGTERM | 15 | — | Terminate — politely asks the process to exit |
| SIGKILL | 9 | — | Kill — forcefully destroys the process (cannot be caught) |
| SIGCONT | 18 | — | Continue — resumes a suspended process |
Sending Signals with kill
Despite its name, kill doesn’t always kill — it sends a signal. By default, it sends SIGTERM:
sleep 300 & # Start a background process
ps aux | grep 'sleep' # Find its PID
kill 12345 # Send SIGTERM (polite request to stop)
If the process ignores SIGTERM, escalate to SIGKILL:
kill -9 12345 # SIGKILL — forced termination, no cleanup
To kill all processes with a given name:
killall sleep # Kills every process named "sleep"
Watch out: Always try
kill(SIGTERM) beforekill -9(SIGKILL). SIGTERM lets the program clean up — close files, free memory, save state. SIGKILL terminates instantly with no cleanup, which can leave temporary files or corrupted data. Think of SIGTERM as knocking on the door; SIGKILL is kicking it down.
./slow_program and it's taking forever. You want to pause it and get your terminal back, but you don't want to kill it. What do you press?Ctrl+Z sends SIGTSTP, which suspends (pauses) the process. You can then type bg to resume it in the background or fg to bring it back. Ctrl+C sends SIGINT, which terminates the process — you'd lose it. Ctrl+D sends EOF (end-of-file), not a signal.
The Process Lifecycle
Every process in Unix follows this lifecycle:
fork()— the parent process creates a copy of itself (a new child process)exec()— the child replaces itself with a new program- Run — the child executes the program
exit()— the child finishes and returns an exit statuswait()— the parent collects the exit status
When you type ls in bash, here’s what happens behind the scenes:
- Bash calls
fork()— now there are two copies of bash - The child calls
exec("/usr/bin/ls")— it stops being bash and becomesls lsruns, prints the file listing to stdoutlscallsexit(0)— 0 means success- Bash (the parent) calls
wait(), collects the exit status, and shows a new prompt
Why this matters: The fork/exec model is the foundation of Unix. Every program you’ve ever run in a terminal was launched this way. In Week 10, you’ll implement this yourself in C — writing a mini-shell that forks, execs, and waits. The concepts you learn here as a user will become the system calls you write as a programmer. See Lesson 5.1: Fork & Exec for the full implementation.
Zombie Processes
What happens if a child process finishes but the parent never calls wait()? The child becomes a zombie — it’s done executing, but its entry stays in the process table because the parent hasn’t collected the exit status yet. Zombies don’t consume CPU or significant memory; they’re just leftover bookkeeping entries.
You can spot zombies with ps — they show a state of Z:
ps aux | grep ' Z'
Zombies are cleaned up when the parent eventually calls wait(), or when the parent itself exits (at which point the init process adopts and reaps the zombie). In practice, a few zombies are harmless. Thousands of them indicate a parent with a bug.
ls in bash, what sequence of operations does the shell use to run it?ls program via exec. The parent (bash) waits for the child to finish. Option B is wrong because replacing the shell would destroy it. Option D is wrong because ls is an external program that needs its own process (built-in commands like cd are different — they run directly in the shell).
Exit Status
Every process returns an exit status (also called a return code) when it finishes. Check the most recent exit status with echo $?:
ls /home
echo $? # 0 — success
ls /nonexistent
echo $? # 2 — error (no such file or directory)
The convention is simple: 0 means success, any non-zero value means an error. Different programs use different non-zero values to indicate different kinds of errors.
From Java: In Java,
System.exit(0)means success,System.exit(1)means error. C uses the exact same convention:return 0;frommain()signals success to the operating system. The shell checks this value with$?to know if your program worked. You’ll use this in every C program you write.
Quick Check: What does exit status 0 mean? What about non-zero?
Exit status 0 means success. Any non-zero value means error or failure. Different non-zero values can indicate different errors (e.g., 1 for general errors, 2 for misuse of a command). Check it with echo $? after any command.
echo $? and see 0. What does this tell you?return 0; from main() sets this. Any non-zero value indicates an error. A segfault typically produces exit status 139 (128 + signal 11). $? is how the shell (and scripts) check whether the last command worked.
Handling Infinite Loops
When you start writing C programs, you’ll inevitably write one with an infinite loop. Here’s your escape plan:
Option 1: Press Ctrl+C — this sends SIGINT, which terminates most programs:
./buggy_program # Stuck! Infinite loop
# Press Ctrl+C # SIGINT sent — program stops
Option 2: If Ctrl+C doesn’t work (rare, but possible if the program catches SIGINT), open a second terminal and kill it manually:
ps aux | grep 'buggy_program' # Find the PID
kill 12345 # Try SIGTERM first
kill -9 12345 # SIGKILL if SIGTERM didn't work
The trick: When your C program freezes,
Ctrl+Cis almost always enough. Savekill -9for truly stubborn processes. You’ll encounter this scenario more than once this quarter — now you know exactly what to do.
Ctrl+C but it doesn't stop. What should you try next?Ctrl+C (SIGINT) doesn't work, the program may be catching that signal. Open a second terminal, find the process with ps aux | grep programname, and send SIGTERM with kill PID. If that still doesn't work, kill -9 PID sends SIGKILL, which cannot be caught or ignored.
Try It Yourself
These exercises let you practice process control hands-on:
Exercise 1: Run
sleep 100 &to start a background process. Usejobsto see it. Bring it to the foreground withfg. Suspend it withCtrl+Z. Resume it in the background withbg. Finally, kill it withkill %1(the%1refers to job number 1).
Exercise 2: Run
topand observe your system’s processes. Which process uses the most CPU? The most memory? PressMto sort by memory,Pto sort by CPU. Pressqto quit.
Exercise 3: Open two terminal windows. In the first, run
sleep 500. In the second, useps aux | grep 'sleep'to find the PID, thenkillit. Verify it stopped by runningps aux | grep 'sleep'again.
Processes Connect Everything
Every Unix concept connects through processes. When you type a command, the shell forks a process. When you pipe commands together, each stage is a separate process connected by a pipe. When you Ctrl+C a stuck program, you’re sending a signal to a process. When you check echo $?, you’re reading a process’s exit status.
In Week 10, you’ll write C code that creates processes with fork(), launches programs with exec(), and connects them with pipe(). The concepts you just learned as a user are the same system calls you’ll implement as a programmer.
Next: the transition from Java to C. You’ve been using Unix tools written by other people — now it’s time to write your own. In Lesson 2.3, you’ll write your first C program and see how gcc transforms source code into a native binary that runs directly on the CPU.
Why this matters: Understanding processes explains why things work the way they do. Why
./myprogramis required (the shell needs a path to fork and exec). WhyCtrl+Ckills a program (it sends a signal). Whygccproduces a file you can run directly (native binary, not bytecode running on a VM). Processes are the thread that connects the entire course.