Sitemap
Oshadhi Wickramasinghe

Oshadhi Wickramasinghe

Writing about DevOps, AWS, Kubernetes, Terraform, GitOps, CI/CD, monitoring, automation, and lessons from production environments.

How does Linux actually run programs?

11 min readAug 21, 2026

--

The simple flow is:

Program
↓
Process
↓
Threads
↓
Scheduler
↓
CPU

Let’s build this from the ground up.

Linux Processes & CPU

Imagine your Linux server has these applications running:

Nginx
Docker
SSH
Python
Java
Kubernetes agents

You 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 starvation

And commands like:

ps aux
top
htop
kill
nice
renice

1. 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/bash

These are programs.

You can think of a program as:

A recipe stored on disk.

For example:

python
↓
instructions stored on disk

Nothing 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/python

That’s a program.

Now you execute:

python script.py

Linux creates a process.

So:

Program
↓
Execution
↓
Process

A simple analogy: Program = Recipe

Recipe for making coffee

Process = Someone actually making the coffee

Person
↓
following recipe
↓
making coffee

The 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.py

Then Linux creates:

Process 1

You run it again:

python script.py

Linux can create:

Process 2

So,

             Python Program
/ \
↓ ↓
Process 1 Process 2
PID 100 PID 200

Same 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
Threads

Linux 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 python

You can see processes using:

ps

Or:

ps aux

Example:

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.py

The PID is how Linux identifies a particular process.

6. PID 1

After the kernel starts userspace:

Linux Kernel
↓
systemd
↓
PID 1

So:

PID 1 → systemd

This is special because it is the first userspace process.

We can check it:

ps -p 1 -o pid,comm,args

7. PPID — Parent Process ID

Now we introduce another important concept.

PPID = Parent Process ID

Processes can create other processes.

For example:

systemd
│
├── sshd
├── nginx
└── docker

Suppose:

systemd PID = 1
nginx PID = 500

Then nginx may have:

PID  = 500
PPID = 1

Meaning:

“My parent process is PID 1.”

You can see this with:

ps -ef

Example:

UID   PID   PPID   CMD
root 1 0 /sbin/init
root 500 1 nginx

The relationship is:

PID 1 systemd
│
▼
PID 500 nginx

8. Parent and Child Processes

This creates a process tree.

For example:

systemd (PID 1)
│
├── sshd
│ └── bash
│ └── python
│
├── nginx
│ ├── nginx worker
│ └── nginx worker
│
└── docker

You can visualize processes using:

pstree

For example:

systemd
├─sshd
│ └─bash
│ └─python
├─nginx
│ ├─nginx
│ └─nginx
└─docker

This 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 Process

For example:

bash
│
│ fork()
▼
new process

The 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 program

For example:

bash
↓
fork()
↓
child
↓
exec()
↓
ls

So the flow can be:

bash
│
├── fork()
│
▼
child process
│
├── exec()
│
▼
ls

This is a fundamental Linux process model.

12. Why are fork() and exec() separate?

This is a great question.

Linux separates:

Create process

from:

Run a different program

So 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:

ls

inside your shell, conceptually something like this happens:

Your shell
│
│ fork()
▼
Child process
│
│ exec()
▼
ls program
│
▼
CPU executes ls

Then 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
↓
Process

But 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 4

Threads within the same process generally share things such as:

Memory
Code
Open resources

but 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 4

It could potentially use multiple threads:

Web Server Process
│
├── Thread 1 → Request 1
├── Thread 2 → Request 2
├── Thread 3 → Request 3
└── Thread 4 → Request 4

This 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 thread

or:

100 threads

depending on the application.

A simple picture:

Process
│
├── Memory
├── Files
├── Resources
│
├── Thread 1
├── Thread 2
└── Thread 3

17. 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 D

They 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 D

The scheduler gives each runnable task opportunities to execute.

For example:

CPU timeline

A → A → B → C → A → D → B → C

The 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 8

Multiple 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 instruction

The 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 2

It decides which runnable tasks should run.

A task can be:

Process

or more precisely in Linux:

Thread

Linux 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 = 2

and:

Thread A
Thread B
Thread C
Thread D

All four want CPU time.

You might have:

CPU 0 → Thread A
CPU 1 → Thread B

Thread C → waiting
Thread D → waiting

C and D are runnable, but they aren’t currently executing.

Then the scheduler may decide:

CPU 0 → Thread C
CPU 1 → Thread D

and 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 → A

Parallelism

Multiple tasks literally execute at the same time on different CPU cores.

Core 1 → A
Core 2 → B
Core 3 → C

This distinction is very useful when discussing CPU performance.

What is Context Switching?

Now we reach the important part.

Suppose:

CPU Core 1
↓
Thread A

Thread 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 B

This 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 state

When 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 A

21. 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 → Zombie

22. Running / Runnable — R

R means the process is running or ready to run.

For example:

R

The 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 operation

It doesn’t need the CPU right now.

So:

Process
↓
Waiting
↓
Sleep

This 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
↓
Waiting

25. Stopped — T

A process can be stopped.

For example:

Ctrl + Z

can suspend a foreground process.

Then you might see:

T

The 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
│
└── exits

The child is finished.

But the parent still needs to call something like:

wait()

to collect the child’s exit status.

Until then:

Child → Zombie

It 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 removed

A 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
│
└── Child

The 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.com

The command occupies your terminal.

That’s a foreground process.

Terminal
↓
ping
↓
You wait

You can usually stop it with:

Ctrl + C

30. 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 → background

You can continue using your terminal.

You can see jobs with:

jobs

31. Ctrl + Z

Suppose you run:

ping google.com

Then press:

Ctrl + Z

The process is suspended.

You can check:

jobs

You may see:

[1]+  Stopped

Then:

bg

continues it in the background.

Or:

fg

brings 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
↑
Signal

You can send a signal with:

kill

Despite the name, kill does not always mean "immediately terminate."

It means:

Send a signal to a process.

33. SIGTERM

For example:

kill PID

normally sends:

SIGTERM

SIGTERM means roughly:

“Please terminate gracefully.”

The application can handle SIGTERM and perform cleanup.

For example:

Receive SIGTERM
↓
Close files
↓
Finish current work
↓
Cleanup
↓
Exit

This is extremely important in Docker and Kubernetes.

34. SIGKILL

Another signal is:

SIGKILL

You can send it with:

kill -9 PID

SIGKILL tells the kernel:

Terminate this process immediately.

The process cannot catch or ignore SIGKILL.

That’s why you shouldn’t immediately use:

kill -9

as your first option.

Normally try:

kill PID

first.

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
↓
CPU

More realistically:

             Program
python app.py
│
▼
Process
PID 500
│
┌────────┼────────┐
▼ ▼ ▼
Thread 1 Thread 2 Thread 3
│ │ │
└────────┼────────┘
▼
Linux Scheduler
│
┌────────┼────────┐
▼ ▼ ▼
CPU 1 CPU 2 CPU 3

36. 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
Cryptography

I/O-bound

The application spends a lot of time waiting for:

Disk
Network
Database

For Example,

Application
↓
Database request
↓
Waiting...
↓
Database response
↓
CPU processing

37. Now Let’s Understand ps aux

ps aux

Let’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.py

You can already understand:

USER

Who owns the process.

PID

Process ID.

%CPU

CPU usage.

%MEM

Memory usage.

STAT

Process state.

COMMAND

The program/process command.

So:

ubuntu  800  1.5  2.0  ...  R+  ... python app.py

means:

A process belonging to ubuntu, with PID 800, is using approximately 1.5% CPU and 2% memory, and its current process status includes R, 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

--

--

Oshadhi Wickramasinghe
Oshadhi Wickramasinghe

Writing about DevOps, AWS, Kubernetes, Terraform, GitOps, CI/CD, monitoring, automation, and lessons from production environments.