DEV Community

Cover image for Part 2 — Operating Systems
Alok Kumar
Alok Kumar

Posted on

Part 2 — Operating Systems

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

I can inspect running processes with:

ps aux
ps -ef
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

normally sends SIGTERM — essentially asking the process to shut down gracefully.

Whereas:

kill -9 <PID>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

the simplified mental model is:

Node.js
   ↓
System Call
   ↓
Linux Kernel
   ↓
Filesystem
   ↓
Storage
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

This is also where user space vs kernel space becomes important.

┌──────────────────────┐
│      User Space      │
│                      │
│      Node.js         │
└──────────┬───────────┘
           │
      System Call
           │
┌──────────▼───────────┐
│     Kernel Space     │
│                      │
│ Memory / Files /     │
│ Networking / Devices │
└──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

strace is particularly interesting.

If my Node program reads a file, I can see system calls such as:

openat(...)
read(...)
close(...)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now I can add the OS:

Application
    ↓
  Process
    ↓
Virtual Memory
    ↓
System Calls
    ↓
Linux Kernel
    ↓
CPU / RAM / Disk / Network
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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)