Skip to content
elephantoo

Processes, signals & jobs

Lesson 19 of 31 15 min read

ps, pgrep, top, signals and kill, nice, and foreground/background job control.


Every running program on Linux is a process. Your shell is a process, every command you type starts new ones, and a server runs hundreds in the background. In this lesson you'll learn to list and inspect processes, stop them with signals, juggle jobs in your terminal and control their priority.

What is a process?#

A process is a running instance of a program. Each one has:

  • a PID (process ID), a unique number;
  • a PPID, the PID of the parent that started it (processes form a tree rooted at PID 1, which is systemd on most distros);
  • an owner (user), which decides what it may access;
  • a state, memory, open files, environment variables and a current directory.
Terminal
echo "My shell's PID is $$"
ps -o pid,ppid,user,stat,cmd
Output
My shell's PID is 4821
    PID    PPID USER     STAT CMD
   4821    4815 ada      Ss   -bash
   5310    4821 ada      R+   ps -o pid,ppid,user,stat,cmd

$$ is the shell's own PID. With no options, ps lists the processes in your current terminal.

Listing processes: ps#

The two classic forms are worth memorising:

Terminal
ps aux                 # BSD style: every process, with CPU/memory usage
ps -ef                 # System V style: every process, with PPID
ps aux --sort=-%cpu | head -5    # top CPU users
ps -eo pid,user,%mem,cmd --sort=-%mem | head -5   # pick your own columns
ps -ef --forest | less # show the parent/child tree

Key columns in ps aux:

ColumnMeaning
USERwho owns the process
PIDprocess ID
%CPU / %MEMshare of CPU and RAM
VSZ / RSSvirtual memory / resident (actually used) memory, in KiB
STATstate: R running, S sleeping, D waiting on disk (uninterruptible), T stopped, Z zombie
START / TIMEwhen it started / CPU time used
COMMANDthe command line

pstree -p (package psmisc) draws the tree nicely.

Finding processes by name: pgrep and pidof#

ps aux | grep name works, but it also matches the grep itself. pgrep is cleaner:

Terminal
sleep 600 &
sleep 700 &
pgrep sleep
pgrep -a sleep          # include the command line
pgrep -f "sleep 700"    # match against the full command line
Output
5321
5322
5321 sleep 600
5322 sleep 700
5322

pidof nginx returns the PIDs of a program by exact name, and pgrep -u www-data finds processes owned by a user.

Signals and kill#

Processes are controlled with signals, small messages from the kernel or another process. The important ones:

SignalNumberSent byEffect
SIGTERM15kill (default)ask the process to exit cleanly
SIGINT2Ctrl+Cinterrupt
SIGKILL9kill -9kill immediately; can't be caught or ignored
SIGHUP1closing a terminal / kill -HUPhang up; many daemons reload config on it
SIGTSTP20Ctrl+Zsuspend (stop) from the terminal
SIGSTOP / SIGCONT19 / 18kill -STOP / kill -CONTpause / resume
Terminal
kill $(pgrep -f "sleep 600")         # SIGTERM by PID
pkill -f "sleep 700"                 # SIGTERM by pattern
sleep 1
pgrep -a sleep || echo "no sleep processes left"
kill -l | head -3                    # list all signal names
Output
no sleep processes left
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
  • kill PID: by PID (also kill -TERM PID, kill -15 PID)
  • pkill pattern: by name or pattern (-f for the full command line, -u user for an owner)
  • killall name: by exact name (psmisc)

💡 Escalate gently. Send SIGTERM first and wait a few seconds; well-behaved programs save state and clean up temp files and locks. Use kill -9 only when a process ignores TERM. SIGKILL gives it no chance to clean up.

A process in state D (uninterruptible I/O, often a hung NFS mount or failing disk) can't even be killed with -9 until the I/O returns. A Z zombie has already exited and is waiting for its parent to collect its exit status; it uses no resources except a PID, and goes away when the parent does.

Foreground, background and jobs#

By default a command runs in the foreground: it owns your terminal until it finishes. Bash's job control lets you juggle several commands:

Terminal
sleep 300 &           # & starts it in the background
sleep 400 &
jobs -l               # list this shell's jobs (with PIDs)
kill %1               # job specs: %1, %2, %+ (current), %- (previous)
sleep 0.2
jobs
Output
[1]- 5340 Running                 sleep 300 &
[2]+ 5341 Running                 sleep 400 &
[1]-  Terminated              sleep 300
[2]+  Running                 sleep 400 &

(Terminated appears because job 1 was killed. Run kill %2 or fg to deal with the other one. jobs -l also shows PIDs.)

Interactively you'll also use:

Keys / commandEffect
Ctrl+Cinterrupt the foreground job
Ctrl+Zsuspend the foreground job (it shows as Stopped)
bg / bg %2resume a stopped job in the background
fg / fg %2bring a job to the foreground
waitwait for all background jobs to finish (useful in scripts)

A typical moment: you start tar on a huge directory, realise it will take ages, press Ctrl+Z, type bg, and carry on working.

Surviving logout: nohup, disown and tmux#

When you close a terminal, the shell sends SIGHUP to its jobs and they usually die. Options:

Terminal
nohup ./long-task.sh > task.log 2>&1 &   # ignore SIGHUP from the start
./long-task.sh &  disown                 # detach an already-running job from the shell

For interactive work on servers, use a terminal multiplexer: run tmux (or screen), start your work, detach with Ctrl+B then D, log out, and later tmux attach to pick up exactly where you were. For anything that should run permanently, write a systemd service (coming up soon).

Priorities: nice and renice#

The CPU scheduler favours processes with a lower niceness. It ranges from -20 (highest priority) to 19 (lowest), and the default is 0.

Terminal
nice -n 10 sleep 60 &
ps -o pid,ni,cmd -p $!
renice -n 15 -p $!
ps -o pid,ni,cmd -p $!
kill $!
Output
   PID  NI CMD
  5402  10 sleep 60
5402 (process ID) old priority 10, new priority 15
   PID  NI CMD
  5402  15 sleep 60

$! is the PID of the last background command. Regular users can only make processes nicer (raise the number); lowering it needs sudo. For disk-heavy jobs, ionice -c3 cmd gives the job idle I/O priority. A good combination for backups is nice -n 19 ionice -c3 tar ....

Live views: top and htop#

Terminal
top

top refreshes every few seconds. Useful keys: P sort by CPU, M by memory, k kill a PID, 1 show each CPU core, q quit. The header shows uptime, load average, task counts and memory (all covered in depth in the performance lesson).

htop (sudo apt install htop / sudo dnf install htop) is friendlier: colours, mouse support, a tree view (F5), search (F3), and kill (F9).

Inspecting a process: /proc and lsof#

Each process has a directory under /proc/<PID>:

Terminal
cat /proc/$$/status | grep -E '^(Name|State|PPid|VmRSS)'
ls -l /proc/$$/cwd
Output
Name:	bash
State:	S (sleeping)
PPid:	4815
VmRSS:	    5120 kB
lrwxrwxrwx 1 ada ada 0 Oct  1 10:56 /proc/4821/cwd -> /home/ada

lsof -p PID lists a process's open files, and lsof -i :8080 shows which process is using a port (or ss -ltnp, covered in networking).

Common mistakes#

  • Reaching for kill -9 first. It can corrupt data or leave lock files behind. Try plain kill first.
  • ps aux | grep foo matching itself. Use pgrep -a foo.
  • Closing the SSH session on a long job. Use tmux, nohup, or a systemd service.
  • Killing the wrong PID after the process restarted with a new one. Look it up again right before you kill.
  • Thinking zombies need killing. They're already dead. Fix or restart the parent if they pile up.

What's next#

Long-running background programs are services. Next you'll manage them properly with systemd and journalctl.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Which signal does plain kill <PID> send by default?

  2. 2.You pressed Ctrl+Z on a long-running command. How do you let it keep running in the background?

  3. 3.What does nice -n 10 ./backup.sh do?

Finished reading?

Mark this lesson complete to track your progress.