Process Management

 

Overview

Today we will look at Linux processes and how to manage them. We will see how processes work internally as well as how to list them, search for specific processes, and suspend or kill them.


 

What is a Process?

A process is an instance of a program that is running on a computer system. A single program, such as /usr/bin/ls for instance, is only stored as a program on disk once. But it may be running as multiple separate processes at once, if multiple users are running ls commands at the same time.

When a program is executed, the OS creates an address space for it (containing a global space, stack, and heap) and allocates some amount of memory to it. The process is then given CPU time to execute and access its memory. The OS ensures that each process can only access its own memory.

Each process contains the following properties:


 

Listing Processes

The ps command can be used to list running processes. By default it shows only processes that are children of the current session:

$ ps
    PID TTY          TIME CMD
  39446 pts/2    00:00:00 bash
  39793 pts/2    00:00:00 ps

We can search for all processes of a specific user with the -u flag:

$ ps -u ifinlay
    PID TTY          TIME CMD
 399230 ?        00:00:00 systemd
 399233 ?        00:00:00 (sd-pam)
 399256 ?        00:00:10 sshd-session
 399257 pts/0    00:00:00 bash
 399285 pts/0    00:00:02 vim
 400305 ?        00:00:00 sshd-session
 400306 pts/1    00:00:00 bash
 400323 pts/1    00:00:00 ps

We can also search for all processes using the -aux flags. The a flag includes all processes (not just ours), the u flag gives "user-oriented" fields such as state and CPU usage and the x flag includes processes not attached to a terminal (such as daemons).

$ ps -aux
  1 USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND                                                                                               
  2 root           1  0.0  0.0  24396 15364 ?        Ss   Feb02   0:05 /sbin/init
  3 root           2  0.0  0.0      0     0 ?        S    Feb02   0:00 [kthreadd]
  4 root           3  0.0  0.0      0     0 ?        S    Feb02   0:00 [pool_workqueue_release]
  5 root           4  0.0  0.0      0     0 ?        I<   Feb02   0:00 [kworker/R-kvfree_rcu_reclaim]
  6 root           5  0.0  0.0      0     0 ?        I<   Feb02   0:00 [kworker/R-rcu_gp]
  7 root           6  0.0  0.0      0     0 ?        I<   Feb02   0:00 [kworker/R-sync_wq]
  8 root           7  0.0  0.0      0     0 ?        I<   Feb02   0:00 [kworker/R-slub_flushwq]
...

On BSD-style unix, the hyphen before flags is optional and the ps command supports that. So some people just give this command as ps aux.

We can also use the -o flag to look for specific process properties:

$ ps -o pid,ppid,cmd
    PID    PPID CMD
  37541   37527 /bin/bash
  40107   37541 ps -o pid,ppid,cmd

Another command which can be used to get info on processes is pidof which simply tells us the PID(s) of any processes a given command is running:

$ pidof bash
37602 37541 35641

Which can be helpful for quickly finding the PID of a process you want to send a signal to.


 

Dynamic Process Info

The ps command gives you info on running processes at the time you run the commands, but of course does not update that info as time passes. The top command gives a dynamic process list that updates every few seconds. The program looks like the following:

A list of running processes

The process list will be updated with time and is sorted by CPU usage. If a system is lagging this makes it easy to see what is taking the most CPU time. You can quit out of top by hitting the 'q' key.

There is also a more powerful version of top called htop, which may need to be installed before it can be used. This program is more interactive and colorful than top:

A more colorful list of running processes

With htop, you can use the search for processes, change the sort criteria, change process priority and send signals directly from this program.


 

Process Life Cycle

Below is a diagram of the typical way processes are created and destroyed, for instance when running shell commands:

The Linux process life cycle

Every process (except for init) is created by its parent. This is done with a system call named fork (because the execution creates a "fork in the road" with the parent and child continuing on separately). The child then typically invokes a system call named exec to begin running a program from disk.

While the child is running, the parent process is waiting for the child to finish. When the child does (by calling the exit system call, control is returned to the parent. A "zombie" process is a process which has finished execution, but still has an entry in the process table. This state of affairs normally only lasts for milliseconds before the entry is removed.

While a process is executing, it can be placed in one of a variety of states. When a process is first created it is admitted into the "ready" state which means it can be executed, but isn't currently running. The OS scheduler can then move it to the "running" state where it is actively being run on the CPU. It can be interrupted and sent back to the "ready" state by the scheduler. It may need to wait for an I/O request (such as reading user input or a file) in which case it is in the "waiting" state. Finally when it exits, it is in the "terminated" state briefly before being removed.

The states of a process


 

Changing Niceness

The Linux concept of process priority is called "niceness". The more nice a process is, the more willingly it will give CPU time to other processes (and thus the lower its effective priority). Niceness is represented with an integer from -20 (highest) to 19 (lowest).

We can change the niceness value with the renice command:

$ renice -n 10 -p PID

Where "PID" is replaced with the process id of the process we wish to modify the niceness value for. We can only make our processes more nice as a regular user. Only root can make processes less, that is higher priority.

The default level of niceness is usually 0 with processes inheriting their niceness from their parent process. We can check current niceness values with ps:

$ ps -ax -o pid,ni,cmd

 

Setuid and Setgid

Normally when a program is launched by a user, the UID of the process matches that of the user. For example, wen you run the cp command, the process gets your UID and GID. This prevents that process from accessing things your user is not allowed to access (such as copying a file you don't have read permission for, or writing to a directory you don't have write permission for.

However, some programs have either the setuid or setgid permission bits set for them. This means that they run with the effective UID/GID of the owner of the executable and not the user who runs them.

An example of this is the passwd command. When we run this as a regular user, it needs to write our hashed password to the /etc/shadow file, which normal users are not even allowed to look at. We can see that the setuid bit is set in the ls output:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 118168 Apr 19  2025 /usr/bin/passwd

There is an 's' instead of an 'x' in the user execute permission field which indicates that the setuid bit is on. When this executable is executed by any user, the effective UID of the resulting process is not our user's UID, but the owner of the file (which in this case is root). That means this process is allowed to do anything root can, such as write to /etc/passwd.

This is only done for very few programs which are carefully written and reviewed. We don't want to make exceptions to the permission rules unless we absolutely have to, such as in this case.

The setgid bit is the same, except for the group instead of user. An example of a program with this bit set is crontab:

$ ls -l /usr/bin/crontab
52 -rwxr-sr-x 1 root crontab 51936 Jun 13  2025 /usr/bin/crontab

No matter who runs this program, it runs with the group crontab. This program is for scheduling repeated tasks, and so anyone in that group is allowed to schedule such tasks. This mechanism allows for that to take place.

These bits can be added to an executable with the chmod command, but is not very common as there are security implications of running a process as a different user/group especially when that user is root.


 

Signals

We can interact with processes by sending them signals, which are notifications sent to a process. Processes can catch signals and handle them in whatever way they want, although some signals cannot be caught (such as KILL and STOP).

Signals are sent with the kill command, despite the fact that KILL is only one signal which we can send (and is not even the default). When we don't specify which signal to send, it is actually TERM which is sent:

$ kill PID

Where PID is the process id of the process we wish to send the signal to. We can also specify the signal to send using either the four-letter name, or the integer code for the signal. So the following two commands are both equivalent to the one above:

$ kill -TERM PID
$ kill -15 PID

The TERM signal tells a process "please terminate". If the process does not catch this signal, it will be terminated. Some processes catch it to try to ensure a clean shut-down (such as saving files etc.). Generally one should try to send the TERM signal to a process we want to end. However, if it does not cause the process to stop, we can send the KILL signal instead:

$ kill -KILL PID
$ kill -9 PID

This signal cannot be caught and will always cause the process to be stopped. Of course we are only allowed to send signals to our own processes. Only the root user can send signals to processes of other users.

There are two "user" signals supported by Linux: USR1 and USR2. These can be caught by applications to do application-specific things. For example, the MySQL database server catches USR1 and flushes the log files if it receives it. The dd command which copies block data prints a status message when t receives USR1.

These user signals can be sent the normal way:

$ kill -USR1 PID
$ kill -USR2 PID

We can also send signals with the killall command which sends signals to processes by command instead of PID. For instance, we could send TERM to all apache2 processes using the following command:

$ killall apache2

This is equivalent to the following:

$ kill $(pidof apache2)