In Part 1, I started from the bottom:
JavaScript → V8 → machine instructions → CPU → memory
But there was an obvious missing piece.
If multiple applications are running on the same machine, who decides:
- which process gets CPU time?
- where its memory lives?
- which files it can access?
- how it talks to hardware?
The answer is the Operating System.
And this turned out to be one of the most important layers to understand.
A Program Isn't a Process
A program is code sitting on disk.
A process is a running instance of that program, along with the resources and state the OS associates with it.
When I run:
node server.js
Linux creates a process for Node.
It gets a PID (Process ID), and the process has things like:
Process
├── Virtual address space
├── Code
├── Heap
├── Stack
├── CPU state
├── Open files
└── Network sockets
I can inspect running processes with:
ps aux
ps -ef
And this is where PIDs, parent/child processes, process states, signals, and exit codes start becoming useful rather than just Linux terminology.
For example:
kill <PID>
normally sends SIGTERM — essentially asking the process to shut down gracefully.
Whereas:
kill -9 <PID>
sends SIGKILL, which the process cannot catch or handle.
Processes vs Threads
A process can contain multiple threads:
Process
├── Thread 1
├── Thread 2
└── Thread 3
Threads within the same process share its memory, while each thread has its own execution state and stack.
This makes threads useful for concurrent work, but shared memory creates a new class of problems.
For example:
Thread A Thread B
READ counter READ counter
ADD 1 ADD 1
WRITE counter WRITE counter
Both might read 0 and both write 1.
That's a race condition.
We need synchronization mechanisms such as mutexes to control access to shared state. But synchronization itself can introduce problems such as deadlocks.
This is why concurrency isn't simply:
"Run multiple things at once."
It's about correctly coordinating things that can execute independently.
Virtual Memory
A process doesn't simply get handed a physical chunk of RAM.
Instead, it gets a virtual address space.
Process A Process B
Virtual Memory Virtual Memory
↓ ↓
Page Tables Page Tables
↓ ↓
└──────────┬─────────────┘
↓
Physical RAM
The CPU generates virtual addresses, and the MMU (Memory Management Unit) translates them using page tables into physical memory locations.
Memory is managed in chunks called pages.
This abstraction gives every process the illusion that it has its own private memory space.
That's why Process A can't simply access Process B's memory.
The OS and hardware enforce memory mappings and permissions.
Page Faults
Virtual memory also means that a memory access can trigger a page fault.
This doesn't automatically mean something went wrong.
A page fault can happen when the required page isn't currently available in the expected physical mapping, and the OS needs to handle the situation.
Depending on the circumstances, it might:
- establish a mapping
- allocate memory
- bring data into RAM
- or reject an invalid access
Under heavy memory pressure, the system may also need to move pages between RAM and storage, which can be extremely expensive compared with normal RAM access.
System Calls: Where Applications Meet the OS
This is probably the most useful concept for backend engineers.
My Node.js application cannot simply reach into the SSD and read bytes.
When I write:
const data = fs.readFileSync("data.txt");
the simplified mental model is:
Node.js
↓
System Call
↓
Linux Kernel
↓
Filesystem
↓
Storage
A system call is a controlled interface through which an application requests services from the OS kernel.
Examples include operations such as:
read()
write()
open()
close()
socket()
This is also where user space vs kernel space becomes important.
┌──────────────────────┐
│ User Space │
│ │
│ Node.js │
└──────────┬───────────┘
│
System Call
│
┌──────────▼───────────┐
│ Kernel Space │
│ │
│ Memory / Files / │
│ Networking / Devices │
└──────────────────────┘
The kernel has the privileged access needed to manage hardware and system resources.
Watching the OS Work
One thing I found much more useful than reading definitions was actually watching the OS.
For example:
ps aux
top
lsof -p <PID>
vmstat 1
strace node app.js
strace is particularly interesting.
If my Node program reads a file, I can see system calls such as:
openat(...)
read(...)
close(...)
Suddenly, "read a file" isn't just an API call anymore.
There's an entire OS-level operation happening underneath it.
The Mental Model
After Part 1, I had:
JavaScript
↓
V8
↓
Machine Instructions
↓
CPU
↓
Registers / Cache / RAM
Now I can add the OS:
Application
↓
Process
↓
Virtual Memory
↓
System Calls
↓
Linux Kernel
↓
CPU / RAM / Disk / Network
The important realization for me is:
My application doesn't directly control the machine. It runs inside an environment managed by the operating system.
And once I understood that, things like processes, memory isolation, file I/O, signals, and system calls stopped feeling like unrelated concepts.
They became different parts of the same system.
The question I'm taking into Part 3
So far, we've mostly stayed inside one machine.
But what happens when I write:
const response = await fetch("https://example.com");
The data isn't sitting in my RAM or SSD anymore.
It has to travel to another machine.
That's where things get interesting.
Top comments (0)