30
Arjun’s Systems Brief: Every command you type, every application you click, and every service running in the background on your Linux system is a process. I have spent years administering Linux servers, and understanding processes was the single most important foundational skill I learned. It changed how I diagnose problems, how I optimize performance, and how I think about what is actually happening inside the machine. Let me walk you through what a Linux process really is, how the kernel creates and manages them, and the tools you need to inspect them.
What Is a Linux Process?
A process is a running instance of a program. When a program sits on your hard drive, it is just a file. The moment you execute it, the kernel loads it into memory, allocates resources, and creates a process. Every shell command, every GUI application, every background service. They are all processes.
I think of it this way: a program is a recipe, and a process is the meal being cooked. You can have multiple meals (processes) running from the same recipe (program) at the same time. Open two terminal windows and run sleep 60 in each. You now have two separate sleep processes, each with its own Process ID, its own memory space, and its own lifecycle.
The Linux kernel tracks every process using a data structure called a task_struct. This structure holds everything the kernel needs to know: the process ID, parent process ID, scheduling priority, memory mappings, open file descriptors, signal masks, and security credentials. When you run ps or top, you are reading summaries of these kernel data structures through the /proc virtual filesystem.
How Linux Creates Processes: fork() and exec()
Linux creates processes using a two-step mechanism that has been the foundation of Unix systems since the 1970s. I will explain it in plain terms because the man page notation can be confusing for beginners.
Step one is fork(). When a process wants to create a new process, it calls fork(), which duplicates the calling process. The original process becomes the parent, and the new copy becomes the child. At this point, the child is an exact duplicate of the parent. Same code, same open files, same memory (though copy-on-write makes this efficient, so the kernel does not actually duplicate all the memory pages until one process writes to them).
Step two is exec(). The child process typically does not want to be a copy of its parent. It calls exec() (specifically execve()) to replace its own memory space with a new program. The fork creates the blank canvas, and exec paints a new picture on it.
Here is what happens when you type ls in your terminal:
1. Your shell (the parent) calls fork() to create a child process.
2. The child calls exec() to load the /usr/bin/ls binary.
3. The ls process runs, prints the directory listing, and exits.
4. The shell waits for the child to finish, then prompts you for the next command.
This fork/exec model is why every process (except PID 1) has a parent. It also explains why killing a process does not kill its parent. The shell survives because it is a separate process that merely spawned the child.
Why It Matters: Understanding fork/exec changes how you troubleshoot. When a background job misbehaves, you can trace it back to the shell that spawned it using the PPID column in ps -ef. When a script leaves orphaned processes, you know the parent exited without waiting for its children. And when you wonder why kill does not stop a service, the answer is usually that you are killing a child process while the parent (systemd) immediately restarts it. Knowing the process tree is the difference between guessing and diagnosing.
Understanding PIDs (Process IDs)
Every process on a Linux system gets a unique integer called a Process ID (PID). The kernel assigns PIDs sequentially, starting from 0 and incrementing up to a configurable maximum. When the counter reaches the limit, it wraps around and reuses lower numbers (skipping any still in use).
There are three PIDs every beginner should know:
PID 0 is the kernel’s idle process (also called the swapper or scheduler). It is not a real user-space process. You will never see it in ps output because it exists only in kernel space. Its job is to consume CPU cycles when nothing else needs to run.
PID 1 is systemd on every modern Linux distribution. It is the ancestor of all user-space processes. When the kernel finishes booting, it starts PID 1, and PID 1 then starts every other service, daemon, and user session. Killing PID 1 causes a kernel panic because the system loses its process manager.
PID 2 is kthreadd, the kernel thread daemon. It is the parent of all kernel threads. You can see this in the process tree output:
fosslinux@ubuntu:~$ ps -eo pid,ppid,stat,comm | head -10
PID PPID STAT COMMAND
1 0 Ss systemd
2 0 S kthreadd
3 2 S pool_workqueue_release
4 2 I< kworker/R-rcu_gp
5 2 I< kworker/R-sync_wq
6 2 I< kworker/R-kvfree_rcu_reclaim
7 2 I< kworker/R-slub_flushwq
8 2 I< kworker/R-netns
13 2 I< kworker/R-mm_percpu_wq
PID 1 (systemd) has PPID 0 because it is started directly by the kernel. PID 2 (kthreadd) also has PPID 0. Every kernel thread (kworker, ksoftirqd, rcu_preempt, migration) has PPID 2 because kthreadd spawned it. Kernel threads show their names in brackets in ps output and have no user-space memory, which is why their VSZ and RSS columns show 0.
The maximum PID value is controlled by /proc/sys/kernel/pid_max. On most systems it defaults to 32768, but it can be increased up to 4194304 on 64-bit systems. PIDs wrap around and get reused, which is why you should never hardcode PIDs in long-running scripts. Always look up the PID fresh each time you need it.
Process States: What Each Process Is Doing
At any given moment, every process on your system is in one of several states. You can see these states in the STAT column of ps and top. I have seen many beginners confused by zombie processes and D-state processes, so let me break down every state clearly.
R (Running or Runnable) means the process is either executing on a CPU core right now or sitting in the run queue waiting for its turn. A process in R state is actively doing work or ready to do work.
S (Interruptible Sleep) means the process is waiting for something to happen. Maybe it is waiting for keyboard input, a network packet, a timer to expire, or a child process to exit. This is the most common state for idle processes. A process in S state can be woken up by a signal.
D (Uninterruptible Sleep) means the process is waiting for I/O, usually disk I/O. The critical difference from S state is that a D-state process cannot be interrupted, not even by kill -9. If you see a process stuck in D state for an extended time, it usually indicates a disk problem or a hung NFS mount. You cannot kill it. You have to fix the underlying I/O issue.
T (Stopped) means the process has been suspended by a job control signal. Pressing Ctrl+Z sends SIGTSTP (signal 20) which stops the foreground process. The process remains in memory and can be resumed with fg (foreground) or bg (background).
Z (Zombie) means the process has already exited, but its parent has not yet collected its exit status. The process is dead. It is not using any resources. But its entry remains in the process table until the parent calls wait(). You cannot kill a zombie because it is already dead. The fix is to kill the parent process or send SIGCHLD to the parent to prompt it to reap the zombie.
I (Idle Kernel Thread) is a state specific to kernel threads that are idle and waiting for work. You will see this in the ps output for kworker threads.
X (Dead) means the process has been terminated. You should never see this state in practice because the kernel removes the process immediately.
Additional BSD-style modifiers appear in the STAT column: < means high priority (low nice value), N means low priority (high nice value), s means session leader, l means multi-threaded, and + means the process is in the foreground process group. So when you see Ss, that means a session leader in interruptible sleep, which is exactly what your login shell is.
Pro Tip: When you see a D-state process that kill -9 cannot touch, do not waste time retrying the kill. Instead, run dmesg | tail -20 to check for disk I/O errors, or iostat -x 1 3 to see if a specific device is saturated. The fix is always at the I/O layer, not the signal layer. On NFS mounts, check network connectivity to the server before anything else.
Using ps to Inspect Processes
The ps command reads process data from the /proc virtual filesystem and prints a one-time snapshot. Unlike top, which refreshes continuously, ps captures a single moment. That makes it ideal for scripting, logging, and automated diagnostics.
The most common form is ps aux, which shows every process on the system in BSD style:
fosslinux@ubuntu:~$ ps aux | head -15 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.4 26836 16372 ? Ss Aug25 0:24 /usr/lib/systemd/systemd root 2 0.0 0.0 0 0 ? S Aug25 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? S Aug25 0:00 [pool_workqueue_release] root 4 0.0 0.0 0 0 ? I< Aug25 0:00 [kworker/R-rcu_gp] root 5 0.0 0.0 0 0 ? I< Aug25 0:00 [kworker/R-sync_wq] root 14 0.0 0.0 0 0 ? S Aug25 0:00 [ksoftirqd/0] root 15 0.0 0.0 0 0 ? R Aug25 0:14 [rcu_preempt]
The columns tell you everything: USER is who owns the process, PID is the unique identifier, %CPU and %MEM show resource consumption, VSZ is virtual memory size, RSS is resident (physical) memory, TTY shows the controlling terminal (? means no terminal, common for daemons), STAT is the process state, START is when it launched, TIME is cumulative CPU time, and COMMAND is the full command line.
The POSIX-style equivalent is ps -ef, which adds the PPID column:
fosslinux@ubuntu:~$ ps -ef | head -10 UID PID PPID C STIME TTY TIME CMD root 1 0 0 Aug25 ? 00:00:24 /usr/lib/systemd/systemd root 2 0 0 Aug25 ? 00:00:00 [kthreadd] root 3 2 0 Aug25 ? 00:00:00 [pool_workqueue_release] root 4 2 0 Aug25 ? 00:00:00 [kworker/R-rcu_gp] root 5 2 0 Aug25 ? 00:00:00 [kworker/R-sync_wq]
The PPID column is what I check first when tracing a suspicious process. If a process has an unexpected parent, something unusual launched it.
For custom output with native sorting, use the -o format specifier. I use this daily to find the heaviest processes without piping through sort:
fosslinux@ubuntu:~$ ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -10
PID COMMAND %CPU %MEM
2757 vmtoolsd 0.1 1.0
1285 vmtoolsd 0.1 0.3
65593 systemd-hostnam 0.0 0.2
2389 gnome-shell 0.0 3.7
17820 remmina 0.0 1.5
560 systemd-oomd 0.0 0.2
76 kswapd0 0.0 0.0
The --sort=-%cpu flag sorts by CPU usage in descending order. The leading minus sign means largest first. This is a native procps-ng feature that works on any modern Linux distribution.
To see the process tree and understand parent-child relationships, use pstree -p:
fosslinux@ubuntu:~$ pstree -p | head -20
systemd(1)-+-ModemManager(1836)-+-{ModemManager}(1845)
| |-{ModemManager}(1912)
| `-{ModemManager}(1915)
|-NetworkManager(1774)-+-{NetworkManager}(1800)
| |-{NetworkManager}(1801)
| `-{NetworkManager}(1802)
|-accounts-daemon(1638)-+-{accounts-daemon}(1694)
|-avahi-daemon(1571)---avahi-daemon(1663)
|-chronyd-starter(1583)---chronyd(1708)---chronyd(1719)
|-cron(1640)
PID 1 (systemd) is the root of the entire user-space process tree. Every service, every user session, every background job branches from it. The notation {name}(tid) shows individual threads within a process.
Using top for Real-Time Monitoring
While ps gives you a snapshot, top gives you a live, continuously refreshing dashboard. I use top when I need to watch resource usage trends in real time, and ps when I need a stable list for scripting or logging.
Running top -bn1 gives you a single batch-mode iteration, perfect for capturing output:
fosslinux@ubuntu:~$ top -bn1 | head -17
top - 19:48:28 up 6 days, 1:33, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 381 total, 1 running, 380 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 2.3 sy, 0.0 ni, 97.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 3350.6 total, 285.1 free, 1283.5 used, 2163.5 buff/cache
MiB Swap: 3862.0 total, 2944.8 free, 917.2 used. 2067.0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 26836 16372 11352 S 0.0 0.5 0:24.60 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.23 kthreadd
The header area shows system-wide statistics. The Tasks line tells you: 381 total processes, 1 currently running on a CPU, 380 sleeping, 0 stopped, 0 zombie. The CPU line breaks down usage into user (us), system (sy), nice (ni), idle (id), I/O wait (wa), hardware interrupt (hi), software interrupt (si), and steal (st, time stolen by the hypervisor in virtual machines). The memory lines show physical RAM and swap usage.
When running top interactively, the most useful keys are: P to sort by CPU, M to sort by memory, k to kill a process (prompts for PID and signal), u to filter by username, 1 to toggle per-CPU breakdown, and q to quit. I install htop on every server I manage because it provides a friendlier color-coded interface with mouse support, but top is available everywhere by default.
What Are Daemons?
A daemon is a background process that runs without a controlling terminal. When you see a ? in the TTY column of ps output, that process is a daemon. Daemons typically start at boot, run for the entire lifetime of the system, and provide services to users and other processes.
The naming convention is to add a trailing d to the process name: sshd (SSH daemon), cron (clock daemon), dockerd (Docker daemon), httpd (Apache web server). This is a convention, not a rule. Some daemons like systemd and NetworkManager do not follow the trailing-d pattern.
On modern Linux distributions, daemons are managed by systemd as services. I use systemctl to interact with them:
fosslinux@ubuntu:~$ systemctl status cron --no-pager | head -12
- cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-08-25 18:15:36 EDT; 6 days ago
Main PID: 1640 (cron)
Tasks: 1 (limit: 1582)
Memory: 1.4M (peak: 3.7M)
CPU: 2.669s
CGroup: /system.slice/cron.service
|-1640 /usr/sbin/cron -f -P
The systemctl status output tells you the service is active and running, its Main PID is 1640, it has been running for 6 days, and it is using 1.4M of memory. The CGroup line shows exactly which control group this service belongs to.
To stop, start, restart, or reload a daemon, I use:
sudo systemctl stop cron stops the service gracefully.
sudo systemctl start cron starts it.
sudo systemctl restart cron stops then starts it.
sudo systemctl reload cron sends a reload signal (usually SIGHUP) to re-read configuration without downtime.
If a daemon is not running, systemctl status will show it as inactive (dead) and display the last few log lines from journalctl, which is usually enough to diagnose the problem.
Insight: Every daemon you see in ps output with a ? in the TTY column was started by systemd (PID 1) during boot or on demand. When you run systemctl status, systemd reads the service’s unit file from /usr/lib/systemd/system/, checks the cgroup for resource usage, and queries the journal for recent log lines. The Main PID shown in the status output is the process that systemd tracks as the service’s primary worker. If that PID dies, systemd restarts the service if the unit file says Restart=always.
Exploring /proc: Where Process Data Lives
The /proc directory is a virtual filesystem that the kernel populates on the fly. It is not stored on disk. Every time you read from /proc, the kernel generates the data in real time from its internal data structures.
Every running process has a directory under /proc named after its PID. If you run ls /proc/, you will see a long list of numeric directories, one for each process on the system.
Inside each /proc/[PID]/ directory, there are dozens of files exposing different aspects of the process. The ones I use most often:
cmdline contains the full command line used to start the process, with arguments separated by null bytes. Read it with: cat /proc/1/cmdline | tr "\0" " "
status gives you a human-readable summary including the process name, state, PID, PPID, UID, GID, and memory usage.
fd/ is a directory of symbolic links to all open file descriptors. Count them with ls /proc/PID/fd | wc -l to see how many files a process has open.
environ contains the environment variables the process was started with, again with null-byte separators.
limits shows the current resource limits (soft and hard) for the process, including open file limits, memory limits, and CPU time limits.
The /proc/self directory is a special case. When any process reads from /proc/self, it resolves to that process’s own /proc/[PID] directory. This is useful in scripts where you do not know your own PID.
Worth Knowing: The /proc/[PID]/oom_score file tells you how likely the kernel is to kill a process when memory runs out. A higher score means the process is a more likely target. You can protect critical services by writing a negative value to /proc/[PID]/oom_score_adj, and systemd does this automatically for essential services like sshd.
Frequently Asked Questions
What happens when I run a command in the terminal?
Your shell calls fork() to create a child process, the child calls exec() to load the command’s binary, the command runs and exits, and the shell waits for it to finish before prompting you again. This fork/exec cycle happens for every single command you type.
Can I kill PID 1 (systemd)?
Technically you can try, but the kernel protects PID 1 from signals in most cases. Even if you could kill it, the result would be a kernel panic because PID 1 is the ancestor of all user-space processes and the system cannot function without it. Never kill PID 1.
What is the difference between ps and top?
ps takes a single snapshot of all processes at one moment in time. top refreshes every few seconds, giving you a live view. Use ps for scripting, logging, and getting a complete list including kernel threads. Use top when you need to watch resource usage trends in real time.
Why do zombie processes exist?
When a child process exits, the kernel keeps its exit status in the process table until the parent collects it by calling wait(). If the parent is busy or poorly written and never calls wait(), the child remains as a zombie. Zombies consume no resources except a process table entry. The fix is to kill the parent process, which causes init (PID 1) to adopt and reap the zombie.
What does D state mean and why can not I kill it?
D state means uninterruptible sleep, usually waiting on disk I/O. The kernel blocks all signals for processes in this state, including SIGKILL. You cannot kill a D-state process. You have to fix the underlying I/O problem. Check dmesg for disk errors and verify network connectivity if the process is waiting on NFS.
How do I find what is using all my CPU?
Run top and press P to sort by CPU usage. The process at the top of the list is the biggest consumer. Alternatively, use ps -eo pid,comm,%cpu --sort=-%cpu | head -10 for a sorted snapshot you can log or pipe to other commands.
Conclusion
Processes are the foundation of everything that happens on a Linux system. Understanding how they are created (fork/exec), how they are identified (PIDs), what states they can be in (R, S, D, T, Z), and how to inspect them (ps, top, /proc) gives you the diagnostic power to troubleshoot almost any system issue.
Start with ps aux to see what is running, top to watch it in real time, and pstree -p to understand the hierarchy. Once you are comfortable with those, explore the /proc filesystem to see exactly what the kernel knows about each process. For killing processes, see our guide on 5 Quick Ways to Kill a Process in Linux. For a deep dive into ps options and formatting, check out our Linux ps Command: 15 Practical Examples. And for understanding how systemd manages daemons and the boot process, read our systemd vs. init guide.