How does Linux actually run programs?
The simple flow is:
Program
↓
Process
↓
Threads
↓
Scheduler
↓
CPULet’s build this from the ground up.
Linux Processes & CPU
Imagine your Linux server has these applications running:
Nginx
Docker
SSH
Python
Java
Kubernetes agentsYou might think:
“These are programs running on the server.”
Technically, Linux sees something more specific.
Linux sees processes.
Understanding processes is extremely important for DevOps/SRE because we’ll constantly troubleshoot things like:
High CPU
High memory
Process crashed
Process stuck
Zombie processes
Too many processes
Application not responding
CPU starvationAnd commands like:
ps aux
top
htop
kill
nice
renice1. First: What is a Program?
Let’s start with the simplest concept.
A program is a set of instructions stored on disk.
For example:
/usr/bin/python
/usr/sbin/nginx
/bin/bashThese are programs.
You can think of a program as:
A recipe stored on disk.
For example:
python
↓
instructions stored on diskNothing is necessarily running yet. The file exists, but there isn’t necessarily a process executing it.
2. Program vs Process
This is one of the most important Linux concepts.
Suppose you have:
/usr/bin/pythonThat’s a program.
Now you execute:
python script.pyLinux creates a process.
So:
Program
↓
Execution
↓
ProcessA simple analogy: Program = Recipe
Recipe for making coffeeProcess = Someone actually making the coffee
Person
↓
following recipe
↓
making coffeeThe recipe exists even when nobody is making coffee.
Likewise:
A program is passive; a process is an executing instance of a program.
3. You Can Run the Same Program Multiple Times
This is a really useful example.
Suppose you run:
python script.pyThen Linux creates:
Process 1You run it again:
python script.pyLinux can create:
Process 2So,
Python Program
/ \
↓ ↓
Process 1 Process 2
PID 100 PID 200Same program. Different processes. Each process gets its own process identity and execution context.
4. What is a Process?
A process is more than just the program’s code.
A running process has things like:
PID
Memory
CPU state
Open files
Environment variables
Security credentials
Current working directory
Signals
ThreadsLinux keeps track of all this information.
The kernel maintains a data structure representing each process, commonly referred to as the process descriptor.
Linux maintains information about every running process.
5. PID — Process ID
Every process gets a unique PID.
PID means:
Process ID
For example:
PID PROCESS
1 systemd
120 sshd
450 nginx
900 pythonYou can see processes using:
psOr:
ps auxExample:
USER PID %CPU %MEM COMMAND
root 1 0.0 0.1 /sbin/init
root 450 0.2 0.5 nginx
ubuntu 900 1.5 2.0 python app.pyThe PID is how Linux identifies a particular process.
6. PID 1
After the kernel starts userspace:
Linux Kernel
↓
systemd
↓
PID 1So:
PID 1 → systemdThis is special because it is the first userspace process.
We can check it:
ps -p 1 -o pid,comm,args7. PPID — Parent Process ID
Now we introduce another important concept.
PPID = Parent Process ID
Processes can create other processes.
For example:
systemd
│
├── sshd
├── nginx
└── dockerSuppose:
systemd PID = 1
nginx PID = 500Then nginx may have:
PID = 500
PPID = 1Meaning:
“My parent process is PID 1.”
You can see this with:
ps -efExample:
UID PID PPID CMD
root 1 0 /sbin/init
root 500 1 nginxThe relationship is:
PID 1 systemd
│
▼
PID 500 nginx8. Parent and Child Processes
This creates a process tree.
For example:
systemd (PID 1)
│
├── sshd
│ └── bash
│ └── python
│
├── nginx
│ ├── nginx worker
│ └── nginx worker
│
└── dockerYou can visualize processes using:
pstreeFor example:
systemd
├─sshd
│ └─bash
│ └─python
├─nginx
│ ├─nginx
│ └─nginx
└─dockerThis is extremely useful when troubleshooting.
9. How Does a Process Get Created?
Now we reach two very important Linux concepts:
fork()
exec()10. fork()
fork() creates a new process.
Imagine:
Parent Process
│
│ fork()
▼
Child ProcessFor example:
bash
│
│ fork()
▼
new processThe child initially gets a copy of much of the parent’s process state.
Modern Linux uses mechanisms such as copy-on-write, so the kernel doesn’t immediately duplicate every memory page physically.
For now, remember:
fork()creates a new process.
11. exec()
Now imagine the child process wants to run another program.
It uses an exec-family system call.
Conceptually:
Process
↓
exec()
↓
Load another programFor example:
bash
↓
fork()
↓
child
↓
exec()
↓
lsSo the flow can be:
bash
│
├── fork()
│
▼
child process
│
├── exec()
│
▼
lsThis is a fundamental Linux process model.
12. Why are fork() and exec() separate?
This is a great question.
Linux separates:
Create processfrom:
Run a different programSo you can:
fork()to create a child and then:
exec()to replace the child’s program image with another program.
This provides a lot of flexibility.
13. Example: Running ls
When you type:
lsinside your shell, conceptually something like this happens:
Your shell
│
│ fork()
▼
Child process
│
│ exec()
▼
ls program
│
▼
CPU executes lsThen ls finishes.
The child process exits.
The shell continues.
This is a simplified model, but it’s an excellent mental model.
14. Threads
Now we have:
Program
↓
ProcessBut a process can contain multiple threads.
A thread is an execution path within a process.
Think of a process as a company:
Process
│
├── Thread 1
├── Thread 2
├── Thread 3
└── Thread 4Threads within the same process generally share things such as:
Memory
Code
Open resourcesbut each thread has its own execution state, including its own stack and CPU register state.
15. Why Do We Need Threads?
Imagine a web server.
It receives:
Request 1
Request 2
Request 3
Request 4It could potentially use multiple threads:
Web Server Process
│
├── Thread 1 → Request 1
├── Thread 2 → Request 2
├── Thread 3 → Request 3
└── Thread 4 → Request 4This allows multiple tasks to make progress concurrently.
Modern applications use threads heavily.
Java applications are a great example.
16. Process vs Thread
This distinction is important for interviews.
Process
A process is an executing program instance with its own process resources and address space.
Thread
A thread is an execution unit inside a process.
A process can have:
1 threador:
100 threadsdepending on the application.
A simple picture:
Process
│
├── Memory
├── Files
├── Resources
│
├── Thread 1
├── Thread 2
└── Thread 317. Now: Who Gives CPU Time to Processes?
This is where the scheduler comes in.
Suppose your CPU has only one core.
But you have:
Process A
Process B
Process C
Process DThey all want CPU time. The CPU can’t literally execute all four at exactly the same instant on one core.
So Linux has a scheduler.
The scheduler decides:
Which runnable task should run on which CPU, and when?
Conceptually:
Process A ─┐
Process B ─┤
Process C ─┼──→ Scheduler ──→ CPU
Process D ─┘18. What Is CPU Scheduling?
Imagine:
CPU
│
├── Process A
├── Process B
├── Process C
└── Process DThe scheduler gives each runnable task opportunities to execute.
For example:
CPU timeline
A → A → B → C → A → D → B → CThe actual Linux scheduler is much more sophisticated than simple round-robin scheduling, but this gives the basic idea.
19. What if the CPU has 8 cores?
Now things get interesting.
Suppose:
CPU
├── Core 1
├── Core 2
├── Core 3
├── Core 4
├── Core 5
├── Core 6
├── Core 7
└── Core 8Multiple threads can execute simultaneously.
And, a program might need to perform:
1. Load a value
2. Add two numbers
3. Compare values
4. Store a result
5. Jump to another instructionThe CPU continuously executes instructions. But your Linux server might have thousands of processes and threads.
For example:
Java application
├── Thread 1
├── Thread 2
├── Thread 3
└── Thread 4
Nginx
├── Worker 1
└── Worker 2
Python
├── Thread 1
└── Thread 2
Docker
├── Thread 1
├── Thread 2
└── ...So Linux has a problem:
Which thread should each CPU core execute next?
That’s the scheduler’s job.
The scheduler is part of the Linux kernel.
Conceptually:
Linux Kernel
│
▼
Scheduler
│
┌────────────┼────────────┐
▼ ▼ ▼
CPU 0 CPU 1 CPU 2It decides which runnable tasks should run.
A task can be:
Processor more precisely in Linux:
ThreadLinux schedules threads.
Get Oshadhi Wickramasinghe’s stories in your inbox
Join Medium for free to get updates from this writer.
That’s an important detail.
Runnable doesn’t mean “currently running”
Suppose you have:
CPU cores = 2and:
Thread A
Thread B
Thread C
Thread DAll four want CPU time.
You might have:
CPU 0 → Thread A
CPU 1 → Thread B
Thread C → waiting
Thread D → waitingC and D are runnable, but they aren’t currently executing.
Then the scheduler may decide:
CPU 0 → Thread C
CPU 1 → Thread Dand A/B wait for another opportunity.
20. Concurrency vs Parallelism
These terms are often confused.
Concurrency
Multiple tasks are making progress over time.
A → B → A → C → B → AParallelism
Multiple tasks literally execute at the same time on different CPU cores.
Core 1 → A
Core 2 → B
Core 3 → CThis distinction is very useful when discussing CPU performance.
What is Context Switching?
Now we reach the important part.
Suppose:
CPU Core 1
↓
Thread AThread A is currently executing.
Then the scheduler decides:
“Thread B should run now.”
Linux needs to stop A and start B.
But Linux can’t simply forget what A was doing.
It needs to remember A’s execution state.
So:
Thread A
↓
Save A's state
↓
Scheduler
↓
Load B's state
↓
Thread BThis is called a:
Context switch
The context is basically the information needed to continue executing a thread.
For example, the CPU has registers containing information about the current execution.
A simplified view:
Thread A
├── CPU registers
├── Instruction position
├── Stack
└── Other execution stateWhen Linux switches away from A, it needs to preserve the necessary state.
Later, when A gets CPU time again:
Saved A state
↓
Restore A state
↓
Continue A21. Process States
A process isn’t always running.
Linux processes can have different states.
The common ones you’ll encounter are:
R → Running / Runnable
S → Interruptible Sleep
D → Uninterruptible Sleep
T → Stopped
Z → Zombie22. Running / Runnable — R
R means the process is running or ready to run.
For example:
RThe process is either currently executing on a CPU or is runnable and waiting for CPU scheduling.
This distinction is important:
Runnable does not necessarily mean currently executing.
23. Sleeping — S
S means the process is sleeping in an interruptible state.
For example, a process might be waiting for:
Network data
User input
Timer
File operationIt doesn’t need the CPU right now.
So:
Process
↓
Waiting
↓
SleepThis is completely normal.
24. Uninterruptible Sleep — D
This one is important for DevOps/SRE.
D usually means:
Uninterruptible sleep
Often associated with waiting for I/O.
For example:
Application
↓
Disk I/O
↓
Waiting25. Stopped — T
A process can be stopped.
For example:
Ctrl + Zcan suspend a foreground process.
Then you might see:
TThe process still exists, but it isn’t currently executing.
26. Zombie — Z
This is one of the most interesting process states.
A zombie is:
A process that has finished execution but whose parent hasn’t yet collected its exit status.
Imagine:
Parent
│
└── Child
│
└── exitsThe child is finished.
But the parent still needs to call something like:
wait()to collect the child’s exit status.
Until then:
Child → ZombieIt doesn’t mean the program is still running.
It’s already finished.
27. Why do zombies exist?
Because Linux needs to preserve information about the terminated child, especially its exit status, until the parent acknowledges it.
So:
Child process
↓
exit()
↓
Zombie
↓
Parent calls wait()
↓
Zombie removedA small number of zombies isn’t necessarily a disaster.
But many zombies can indicate a poorly behaved parent process.
28. Orphan Processes
Now let’s look at the opposite situation.
Suppose:
Parent
│
└── ChildThe parent dies before the child.
Now,
The child becomes an orphan process.
Linux needs to give that child a new parent.
Traditionally, PID 1 adopts orphaned processes.
On modern Linux systems, process reparenting can involve other mechanisms such as subreapers, but for your initial understanding:
Orphaned processes are reparented so they can still be properly managed and reaped.
29. Foreground vs Background
Now let’s move from kernel concepts to something you’ll use every day.
Suppose you run:
ping google.comThe command occupies your terminal.
That’s a foreground process.
Terminal
↓
ping
↓
You waitYou can usually stop it with:
Ctrl + C30. Background Processes
You can start a command in the background using:
ping google.com &The & means:
Run this command in the background.
Now:
Terminal
├── shell
└── ping → backgroundYou can continue using your terminal.
You can see jobs with:
jobs31. Ctrl + Z
Suppose you run:
ping google.comThen press:
Ctrl + ZThe process is suspended.
You can check:
jobsYou may see:
[1]+ StoppedThen:
bgcontinues it in the background.
Or:
fgbrings it back to the foreground.
This is a great practical exercise for understanding process states and job control.
32. Signals
Now we reach another very important Linux concept:
signals.
A signal is a mechanism for notifying a process that something happened or requesting an action.
For example:
Process
↑
SignalYou can send a signal with:
killDespite the name, kill does not always mean "immediately terminate."
It means:
Send a signal to a process.
33. SIGTERM
For example:
kill PIDnormally sends:
SIGTERMSIGTERM means roughly:
“Please terminate gracefully.”
The application can handle SIGTERM and perform cleanup.
For example:
Receive SIGTERM
↓
Close files
↓
Finish current work
↓
Cleanup
↓
ExitThis is extremely important in Docker and Kubernetes.
34. SIGKILL
Another signal is:
SIGKILLYou can send it with:
kill -9 PIDSIGKILL tells the kernel:
Terminate this process immediately.
The process cannot catch or ignore SIGKILL.
That’s why you shouldn’t immediately use:
kill -9as your first option.
Normally try:
kill PIDfirst.
If the process doesn’t terminate and you understand the consequences, then SIGKILL may be appropriate.
35. The Big Picture: Program → Process → Thread → Scheduler → CPU
Now let’s connect everything.
Suppose you have a Python application.
Python program
↓
Process created
↓
Threads
↓
Linux Scheduler
↓
CPUMore realistically:
Program
python app.py
│
▼
Process
PID 500
│
┌────────┼────────┐
▼ ▼ ▼
Thread 1 Thread 2 Thread 3
│ │ │
└────────┼────────┘
▼
Linux Scheduler
│
┌────────┼────────┐
▼ ▼ ▼
CPU 1 CPU 2 CPU 336. CPU-Bound vs I/O-Bound
This connects directly to what we just learned.
CPU-bound
The application spends most of its time using the CPU.
Examples:
Machine learning
Video encoding
Compression
Large calculations
CryptographyI/O-bound
The application spends a lot of time waiting for:
Disk
Network
DatabaseFor Example,
Application
↓
Database request
↓
Waiting...
↓
Database response
↓
CPU processing37. Now Let’s Understand ps aux
ps auxLet’s start.
You might see:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 12345 4567 ? Ss Aug19 0:03 /sbin/init
root 500 0.2 0.5 50000 12000 ? S Aug19 0:10 nginx
ubuntu 800 1.5 2.0 100000 40000 pts/0 R+ 23:10 0:02 python app.pyYou can already understand:
USERWho owns the process.
PIDProcess ID.
%CPUCPU usage.
%MEMMemory usage.
STATProcess state.
COMMANDThe program/process command.
So:
ubuntu 800 1.5 2.0 ... R+ ... python app.pymeans:
A process belonging to
ubuntu, with PID800, is using approximately 1.5% CPU and 2% memory, and its current process status includesR, meaning runnable/running.
We’re already starting to read ps aux instead of memorizing it.
Learning Path
PROCESSES & CPU
│
┌───────────────┴───────────────┐
↓ ↓
Process CPU
│ │
┌────┼────┐ Scheduler
↓ ↓ ↓ │
PID PPID State CPU cores
│ │ │
│ R/S/D/T/Z Context switch
│ │
↓ ↓
Parent/Child CPU utilization
│
fork()
│
exec()
│
Signals
│
├── SIGTERM
├── SIGKILL
└── SIGSTOP
│
├── Zombie
└── Orphan







