Processes, Jobs, and Signals
The question: which exact process will receive this action?
Section titled “The question: which exact process will receive this action?”A program file just sits there doing nothing. When Linux starts one, the kernel builds a process around it, with an address space, open resources, a security identity, some execution state, and a numeric process ID that everyone just calls a PID. Start the same program twice and you get two processes (each running instance needs its own state).
PIDs are temporary labels rather than permanent names, and Linux will happily hand the same number to something else once the original process exits. So a PID you wrote down needs fresh evidence about the command and its arguments before you send it anything. Check that the PID still belongs to the command you started; otherwise, the signal could affect unrelated work.
We’ll use sleep here precisely because it has nothing interesting to do. That leaves process state as the only moving part, instead of burying the lesson under some application’s own behavior.
Start a process long enough to inspect and save its PID in a shell variable immediately:
sleep 600 &lesson_pid=$!printf 'lesson_pid=%s\n' "$lesson_pid"The shell prints a job number and a PID, and $! expands to the most recent background PID in this shell.
The trailing & asks the shell to start the command without waiting around for it to finish, and the shell then records that background activity in a private structure of its own called the job table. Linux knows about the process itself. The shell adds the job abstraction on top, so a person can manage related work from one terminal session.
Verify before acting:
ps -p "$lesson_pid" -o pid=,ppid=,stat=,comm=,args=You should see your recorded PID, a status containing S, the command sleep, and the argument 600. If no row comes back at all, the process either ended or the PID was wrong. Start a new sleep 600, replace lesson_pid, and check again.
PPID is the parent process ID, and for this particular target it will usually point straight back at the shell that started sleep in the first place. STAT is a compact process-state field, where an S means the process is waiting in an interruptible sleep. That’s exactly what we’d expect from a program whose entire job is waiting.
A live process appears under /proc
Section titled “A live process appears under /proc”Linux presents every single running process as a directory underneath /proc, with the directory named after that process’s own PID. These aren’t files sitting on a disk anywhere (the kernel builds them on demand out of the live process). So /proc/<pid>/ is a plain-text window into something running, readable with tools you already know.
The status file lists a process’s identity and state as readable, newline-separated fields:
cat /proc/$lesson_pid/statusThe output is long, and the first few rows are the ones that matter here. Name, State, Pid, and PPid should all agree with the ps evidence you recorded a moment ago. If the directory doesn’t exist at all, the process has already exited and your recorded PID is stale, so start a fresh sleep 600, check the new PID, and try again.
Jobs are the shell’s view
Section titled “Jobs are the shell’s view”Run:
jobs -lThe current shell should list the background job along with its PID. A different terminal won’t inherit this shell’s job table at all, though ps can still see the process perfectly well from anywhere.
That difference is easy to miss, and it causes a lot of confusion later. A job number like %1 only means anything to the shell that created the entry. A PID belongs to the kernel’s system-wide view. The two identifiers can point at the same running activity, and they still come from different layers and have different lifetimes.
Bash job control explains the shell’s job identifiers. Use it when a job number and a Linux PID appear to name the same command.
Predict a pause
Section titled “Predict a pause”A signal is a small asynchronous notification, delivered to a process whether or not it was expecting anything. The receiving process responds according to the signal’s meaning and any handler it has installed, rather than interpreting a shell command. Each signal has defined semantics, and a program can handle some of them while others have fixed effects it gets no say in.
SIGSTOP forces a process straight into a stopped state, and a program gets no say in it at all. The process is still there and still holds its memory and its PID. It just doesn’t continue ordinary execution. SIGCONT makes a stopped process eligible to run again. Together they let us change process state without creating anything new.
Before sending a signal, predict which STAT letter will appear after a stop signal.
kill -STOP "$lesson_pid"ps -p "$lesson_pid" -o pid=,stat=,comm=The status should contain a T, which means stopped. So the observation is that the process kept the same PID and the same command, and it is no longer scheduled to continue ordinary execution.
Resume it:
kill -CONT "$lesson_pid"ps -p "$lesson_pid" -o pid=,stat=,comm=The T should disappear again. Notice the only thing that changed between those two observations was the signal.
Request a graceful termination
Section titled “Request a graceful termination”SIGTERM requests termination, and a program that needs to clean up after itself is perfectly free to handle the signal and do so. So it’s a much better first choice than SIGKILL, which the process gets no chance to handle at all. “Graceful” is still a request rather than a promise, and the only observable result is whether this particular process actually exits.
Predict whether the PID row will remain after SIGTERM, and predict whether the number wait hands back will be zero, then run:
kill -TERM "$lesson_pid"wait "$lesson_pid"printf 'wait reported %s\n' "$?"ps -p "$lesson_pid" -o pid=,stat=,comm=ps should print no process row at all. wait may report that the job was terminated, and that’s evidence of the signal outcome rather than a new failure.
wait lets the shell collect the child’s final status. The empty ps result answers a different question entirely: does that PID still identify a live process? Keeping the two observations separate stops us treating one message as proof of the whole lifecycle.
The number a process leaves behind
Section titled “The number a process leaves behind”Every process hands back one small number as it ends, and that number is its exit status. Zero means the program considered its own work successful, and anything else is a failure code whose meaning belongs to that particular program. The shell keeps only the most recent one, in $?, so read it on the very next line or lose it. That’s why the printf sits directly under the wait above, and putting the ps command between them would leave you reading the status of ps instead.
Your printf should have reported 143, and not 0 or 15. A process killed by a signal never reached the point of choosing a status for itself, so the shell reports 128 plus the signal number instead. SIGTERM is signal 15, and 128 plus 15 is 143.
This convention helps interpret later failures. In Bash on this VM, termination by signal 11 (SIGSEGV) is reported as 139. A program can also return 139 explicitly, so check the surrounding evidence before concluding that it crashed.
Clear the stale shell variable:
unset lesson_pidPractice: perform the lifecycle again
Section titled “Practice: perform the lifecycle again”Without copying the preceding sequence line by line:
- Start
sleep 600in the background. - Save its PID under a different variable name.
- Prove its command and argument before acting.
- Pause it and prove the state.
- Resume it and prove the state.
- Terminate only that PID and prove it is gone.
- Read the exit status, and say which signal it accounts for.
Common failures and recovery
Section titled “Common failures and recovery”- No
psrow comes back: never signal an unverified number. Start a new controlled target and replace the variable. kill: arguments must be process or job IDs: the variable is empty. Inspect it withprintf '<%s>\n' "$lesson_pid", and restart the target if you need to.waitsays the PID isn’t a child: go back to the terminal that started it. Verify withps, and only usekillif cleanup is still needed.- The process is still stopped when you try to clean up: send
CONT, verify its identity again, and then sendTERM. - You lost the variable entirely: use
jobs -lin the original shell. Don’t reach forkillallorpkillas a shortcut, however tempting it looks.
You’re ready to move on when your evidence artifact shows every signal went to a process you had verified, and that no practice target is still running.
Next we’ll follow bytes through the standard input, output, and error streams of commands we control.
Check your understanding
Section titled “Check your understanding”Knowledge check
Question 1 of 2
Practice complete
You've checked the key ideas. Revisit any explanation you need, then continue when it makes sense.