Operating Systems

Operating Systems - Basic

Page 2 of 3

Virtual Memory and Page Faults

Every process on a modern OS believes it has the entire address space of the machine to itself, starting near address zero and running up to some enormous number, with nothing else in the way. That's a carefully maintained illusion called virtual memory. What's actually happening is that the kernel keeps a translation table (a page table) per process, mapping the virtual addresses that process sees to real, physical locations in RAM. No two processes' virtual addresses collide with each other even though the physical RAM underneath is one shared, finite resource.

This illusion is what makes processes memory-safe from each other by default: process A literally cannot address process B's memory, because A's addresses only ever get translated through A's own page table. It's also why a program compiled once can run at the same-looking memory addresses on wildly different machines with different amounts of physical RAM.

cat /proc/1022/status | grep -i vm
# VmSize:    12104 kB   # total virtual memory this process has mapped
# VmRSS:      3220 kB   # how much of that is actually resident in physical RAM right now

That gap between VmSize and VmRSS is the point: a process can map far more virtual memory than is actually backed by real RAM at any given moment. Memory only gets connected to real physical pages as it's actually touched, and when a process accesses a virtual address that isn't currently mapped, the CPU raises a page fault, handing control to the kernel, which either fetches the right page in (from disk, or by zeroing out fresh memory) or, if the process has no legitimate business touching that address, kills it with a segmentation fault. Page faults aren't inherently bad; a huge number of them are just a normal part of a process starting up and touching memory for the first time. They become a performance problem specifically when the kernel has to go all the way to disk to satisfy one, which is what swapping (covered in the medium tier) actually costs you.


Process States and the Zombie

At any given moment, a process is in one of a handful of states, and ps will show you which one with a single letter.

ps -eo pid,stat,cmd
#   PID STAT CMD
#  1022 S    -bash
#  1099 R    stress --cpu 1
#  1150 D    dd if=/dev/sda of=/dev/null
#  1201 T    sleep 100
#  1250 Z    [worker] <defunct>

R is running (actually on a CPU, or ready and waiting its turn). S is sleeping, by far the most common state you'll see, meaning the process is idle, waiting for something like input or a timer, and using no CPU in the meantime. D is uninterruptible sleep, usually waiting on disk I/O; a process stuck here can't even be killed with a signal until the I/O completes, which makes a pile of D-state processes a strong hint that storage, not CPU, is your actual bottleneck. T is stopped, paused by a signal (like when you hit Ctrl+Z) rather than by its own choice. And Z is a zombie: a process that has already finished running, but whose exit status hasn't been collected yet by its parent, so the kernel keeps a small bookkeeping entry around until the parent reads it. A zombie isn't actually consuming CPU or real memory, it's just an entry in the process table, but a parent that never cleans up after its children can accumulate enough zombies to exhaust that table.


Signals: SIGTERM, SIGKILL, and Graceful Shutdown

A signal is a small, asynchronous notification the kernel (or another process) sends to a process, interrupting whatever it's doing. There are dozens of them, but a handful matter enormously for how you run services in production.

kill -TERM 1099    # ask the process to shut down gracefully (the default signal kill sends)
kill -HUP 1099      # historically "hang up"; many daemons treat this as "reload your config"
kill -KILL 1099     # the kernel terminates it immediately, no cleanup possible
kill -9 1099           # identical to the line above, just written by signal number instead of name

is a request, not an order: it tells a process "please shut down," and a well-behaved program catches it, finishes in-flight work, closes its database connections, flushes any buffers, and then exits on its own. This is what orchestrators like Docker and Kubernetes send first when stopping a container. is not a request; the process gets no chance to run any cleanup code at all, the kernel simply removes it. This is why kill -9 is a genuinely different, riskier action than a plain kill: a database killed with -9 mid-write can leave corrupted files behind, where the same database given a SIGTERM gets to close everything cleanly first. SIGHUP originally meant a terminal hung up on a process, but plenty of long-running daemons have repurposed it as "reload your configuration without restarting," which is why you'll see it used deliberately, not just as an accident of history.

The practical rule that falls out of this: always give a service a chance to handle SIGTERM before reaching for SIGKILL, and if a container orchestrator is force-killing your process on every deploy, check whether your app handles SIGTERM at all. A process that doesn't explicitly handle a signal often just dies immediately anyway, skipping any cleanup you assumed would happen.


    Welcome to OpsQuiz!

    Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.