Linux Processes: ps, top, htop, kill, Signals and nice
Linux processes are inspected with ps aux (a one-time snapshot) and with top or htop (a live list with load). A process is stopped with kill PID, which by default sends a polite SIGTERM; kill -9 (SIGKILL) is needed only when the process does not respond. Priority is set with nice when starting a program and with renice for a running process, in the range from -20 to 19.
Every running program in Linux is a process with its own PID number, owner, state and priority. You can view processes with ps, top and htop, stop them with kill, and change their priority with nice and renice. Below we go through how to read the output of these commands, which signals to send and when, and why kill -9 should not be the first reflex.
What a process and a PID are
A process is a program while it is running: its code, memory, open files and a set of attributes. The kernel gives each process a unique identifier, the PID, and also records the PPID — the number of the parent that started it. The very first process in the system, with PID 1, is usually systemd, and it becomes the ancestor of all the others.
A new process appears in two steps. First the parent calls fork() and gets its own copy, then the copy calls execve() and replaces its code with the code of the new program. When you type ls in the terminal, bash does exactly this and waits for the child to finish through wait().
$ echo $$ # PID of the current shell
4182
$ ps -o pid,ppid,cmd -p $$
PID PPID CMD
4182 4170 -bash
Viewing processes with ps
ps takes a one-time snapshot: it shows the processes exactly at the moment it runs and then exits. Without arguments it shows only the processes of the current terminal, so in practice one of two variants is almost always used.
$ ps aux | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 168540 12780 ? Ss 09:12 0:03 /sbin/init
root 2 0.0 0.0 0 0 ? S 09:12 0:00 [kthreadd]
www-data 1532 1.2 2.1 412308 86420 ? S 09:15 1:47 php-fpm: pool www
postgres 1610 0.4 3.8 221004 154312 ? Ss 09:15 0:31 postgres: checkpointer
Here a means processes of all users, u the detailed format with the owner, and x includes processes without a terminal (daemons). The System V-style alternative is ps -ef: it shows the PPID, which is handy when you need to find out who started whom.
| Column | What it means |
|---|---|
PID | process identifier |
%CPU, %MEM | share of the processor and physical memory |
VSZ | virtual memory in KB (including what is not loaded into RAM) |
RSS | physical memory actually in use, in KB |
STAT | process state (R, S, D, Z, T) and flags |
TIME | total CPU time, not the running time |
Useful ps tricks
# top 10 by memory
ps aux --sort=-%mem | head -11
# top 10 by CPU
ps aux --sort=-%cpu | head -11
# custom columns
ps -eo pid,ppid,user,ni,stat,etime,cmd
# process tree
ps -ef --forest
pstree -p
# find a PID by name
pgrep -a nginx
pidof nginx
A classic mistake is searching for a process with ps aux | grep nginx and getting the grep itself in the output. Use pgrep or the trick grep [n]ginx, which does not match its own command line.
Process states: R, S, D, Z, T
The letter in the STAT column is not noise but the real state of the process. It determines how to interpret the load and whether kill can help at all.
| Code | State | What it means in practice |
|---|---|---|
R | Running | executing or waiting in the CPU run queue |
S | Interruptible sleep | waiting for an event (network, input, timer); the usual state of most processes |
D | Uninterruptible sleep | waiting for I/O inside the kernel, signals are not delivered |
Z | Zombie | finished, but the parent has not collected its exit code |
T | Stopped | suspended by the SIGSTOP signal or Ctrl+Z |
Extra symbols after the letter are useful too: s — session leader, + — foreground process in the terminal, < — raised priority, N — lowered priority, l — multithreaded.
A process in state D
A process in D is inside a system call and waits, for example, for a reply from a disk or a hung NFS share. It does not receive any signal, not even SIGKILL. That is why kill -9 is useless here: you need to find the cause of the wait — check iostat, dmesg and the state of the network storage.
Zombie processes
A zombie is already dead: it uses no CPU and no memory, and only occupies a line in the process table. It cannot be killed — it has already finished. It disappears when the parent calls wait(), or when the parent itself exits and the zombie is "adopted" by PID 1.
# find zombies and their parents
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
A single zombie is harmless. If there are hundreds of them and the number keeps growing, the parent program has a bug: fix it or restart it.
top: processes and load in real time
top shows the same list but refreshes it every few seconds and displays a system summary. It is the first command people run when "the server is slow".
top - 14:21:07 up 12 days, 3:02, 2 users, load average: 1.84, 1.20, 0.95
Tasks: 213 total, 2 running, 211 sleeping, 0 stopped, 0 zombie
%Cpu(s): 23.1 us, 4.2 sy, 0.0 ni, 70.3 id, 1.9 wa, 0.0 hi, 0.5 si, 0.0 st
MiB Mem : 7821.4 total, 412.6 free, 3105.8 used, 4303.0 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 4398.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1532 www-data 20 0 412308 86420 14212 R 38.2 1.1 1:47.31 php-fpm
1610 postgres 20 0 221004 154312 148900 S 6.0 1.9 0:31.08 postgres
The %Cpu(s) line matters most: us — user code, sy — kernel, id — idle, wa — waiting for disk. A high wa with an idle processor is a clear sign that the bottleneck is I/O.
top hotkeys
| Key | Action |
|---|---|
P / M | sort by CPU / by memory |
1 | show each CPU core separately |
c | full command line |
u | filter by user |
k | send a signal to a process |
r | change a process's nice value |
H | show threads |
q | quit |
For scripts and logs the batch mode is useful: top -b -n 1 | head -20 prints one snapshot without interaction. To watch a specific process, use top -p 1532.
Load average is a queue, not a percentage
The three numbers of load average are the average queue length over 1, 5 and 15 minutes. In Linux, processes in states R and D go into it, but ordinary sleep S does not. To judge LA, compare it with the number of cores (nproc): on 4 cores a value of 4 means full load, and 12 means a queue three times larger than the capacity.
If LA is high while the processor is idle, look for processes in D: ps -eo stat,pid,cmd | grep '^D'. It is almost always a slow disk or network storage.
htop: a convenient replacement for top
htop shows the same information but with colored per-core bars, scrolling, mouse support and a process tree. It usually has to be installed first: sudo apt install htop or sudo dnf install htop.
F3or/— search for a process by name;F4— filter that shows only matches;F5ort— "parent — children" tree;F6— choose the sort column;F7/F8— decrease or increase nice;F9— signal menu for the selected process;Space— mark several processes for a bulk action.
On servers where you cannot install anything, top is enough: all the key operations are available there too.
How to stop a process: kill and signals
The kill command does not really "kill" anything; it sends a signal to the process. What happens next depends on the signal and on how the program handles it.
| Signal | Number | Purpose |
|---|---|---|
SIGHUP | 1 | terminal hangup; many daemons reload their config |
SIGINT | 2 | interrupt, the same as Ctrl+C |
SIGKILL | 9 | forced termination by the kernel, cannot be caught |
SIGTERM | 15 | a request to exit gracefully; the default for kill |
SIGSTOP | 19 | suspend, cannot be caught |
SIGCONT | 18 | resume a suspended process |
SIGTSTP | 20 | suspend from the terminal, Ctrl+Z |
Numbers are given for x86 and ARM; the full list is printed by kill -l. Naming the signal is more reliable than using its number.
kill 1532 # SIGTERM
kill -TERM 1532 # the same, explicitly
kill -HUP $(pidof nginx) # reload the configuration
kill -STOP 1532; kill -CONT 1532
kill -0 1532 && echo "process is alive"
Signal 0 sends nothing; it only checks whether the process exists and whether you have the right to signal it — handy in scripts.
Why not start with kill -9
SIGTERM gives the program a chance to close files, flush buffers, remove lock files and complete transactions correctly. SIGKILL cuts everything off instantly: unwritten data is lost, temporary files remain, and a database may go into recovery on the next start.
The right sequence is:
- Send
kill PID(SIGTERM). - Wait 5–30 seconds, checking with
kill -0 PID. - Only if the process is still alive and not in state
D—kill -9 PID.
For services under systemd it is better not to touch PIDs manually at all: systemctl stop sends SIGTERM itself, waits for the timeout and then finishes with SIGKILL.
pkill and killall: by name
pkill -f "python worker.py" # by the full command line
pkill -u alice # all processes of a user
killall -TERM node # all processes named node
pgrep -l -f worker # check first what the pattern will match
Before a mass pkill, always run pgrep with the same pattern: -f matches any part of the command line and can easily catch unrelated processes.
You can send signals only to your own processes; for others you need sudo. The error Operation not permitted means exactly that.
Process priority: nice and renice
The nice value tells the scheduler who should yield the processor. The range is from -20 (the highest priority) to 19 (yields to everyone), the default is 0. In top it is shown in the NI column, while PR is the derived internal priority.
# start a heavy job with lowered priority
nice -n 10 tar czf backup.tar.gz /var/www
# lower the priority of a running process
renice -n 15 -p 1532
# raise it (root only)
sudo renice -n -5 -p 1610
# all processes of a user
sudo renice -n 10 -u alice
An ordinary user can only lower the priority of their own processes; negative values are available only to root. It is important to understand the limits of nice: it affects only the CPU. If a task slows the system through the disk, use ionice -c3 command — the idle class, which gets the disk only when nobody else needs it.
Background processes and shell jobs
A command can be started in the background by adding &, or suspended with Ctrl+Z. The shell keeps a list of jobs, which are managed with built-in commands.
$ long_task &
[1] 5120
$ jobs
[1]+ Running long_task &
$ fg %1 # bring to the foreground
^Z # suspend (SIGTSTP)
$ bg %1 # continue in the background
When the terminal is closed, the shell sends SIGHUP to its jobs, and they exit. To let a process survive logout, start it with nohup command &, detach it with the disown command, or use tmux. For permanent work it is better to set it up as a systemd service.
Typical diagnostic scenarios
Who is eating the CPU
top -o %CPU # or P inside top
ps -eo pid,user,%cpu,etime,cmd --sort=-%cpu | head
If a process consumes CPU without any benefit, first lower its priority with renice — this is safe and gives you time to investigate.
Who has taken the port
sudo ss -ltnp | grep :8080
sudo lsof -i :8080
The output contains the PID, and from it you can find the executable (exe), the working directory (cwd) and the arguments (cmdline) in /proc/PID/.
The process disappeared unexpectedly
A common cause is the OOM Killer, which kills processes when memory runs out. Check with dmesg -T | grep -i oom or journalctl -k | grep -i "killed process".
Summary
A small set of commands is enough for working with Linux processes: ps aux for a snapshot, top and htop for monitoring, kill with the right signal for stopping, and nice/renice for priorities. Read the state of a process before acting: zombies and processes in D cannot be cured with signals. Start with SIGTERM and keep kill -9 for the last resort. Processes, memory, load average and system calls are covered in detail with practice in the Linux course.
FAQ
How is ps aux different from ps -ef?
These are two syntaxes of the same command: aux is the BSD style with the %CPU, %MEM and STAT columns, -ef is the System V style with the PPID column. Both show the same set of processes — all processes in the system.
How do I find out how many processes are running in the system?
The easiest way is to look at the Tasks line in top, or run ps -e --no-headers | wc -l. The number of threads is shown by ps -eLf --no-headers | wc -l.
Why does a process not stop even after kill -9?
Either it is in state D and waits for I/O inside the kernel, or it is a zombie that has already finished. In the first case look for a problem with the disk or network storage; in the second, fix or restart the parent.
What happens if I kill the process with PID 1?
The kernel protects init: ordinary signals, including SIGKILL from a user, are not delivered to it unless it handles them. If init does exit, the system goes into a kernel panic.
How do I limit a process's memory usage?
For a systemd service set MemoryMax= in the unit file; for a one-off run use systemd-run --scope -p MemoryMax=500M command. This works through cgroups and is more reliable than the old ulimit -v.
This topic is covered in depth with practice in the course “Linux: с нуля до уверенного администрирования”.
Go to course