// linux fundamentals — module 02

Module 2 — The Kernel: The Thing in Charge

Thesis: The kernel hands out the machine's resources — CPU time, memory, devices — to everyone else, and the only way to ask it for anything is a system call. This module makes that stop being a sentence you've memorized and start being something you can watch happening.

Prerequisite: Module 1 — The Shell and the Command Line — every lab here is done through the shell, fluently.


2.1 The boundary, revisited with teeth

Module 0 gave you the picture: user space above, kernel below, hardware-enforced line between. Now make it precise.

When your machine boots (full story in Module 6), the kernel is loaded into a protected region of memory and the CPU is configured so that region — and all privileged instructions — are untouchable from user mode. From that moment, every other program on the system runs in user mode, inside a sandbox the kernel built for it. The sandbox has exactly one door.

2.2 System calls: the one door

A system call (syscall) is a request from a user-space program to the kernel. Mechanically: the program places a syscall number and its arguments in agreed-upon CPU registers, then executes a special instruction that hands control to the kernel. The CPU switches to kernel mode, the kernel looks up the number, checks permissions (Module 4 is entirely about those checks), does the work, puts the result in a register, and drops the CPU back to user mode in your program. Your program experiences it as a function call that happens to be answered by the kernel.

Linux has ~300–400 syscalls, but a small cast does most of the work, and you should know these by name — they'll recur through the whole course:

Syscall What it asks
open / openat "Give me access to this file" (returns a file descriptor — Module 5)
read / write "Move bytes from/to that descriptor"
close "I'm done with it"
stat "Tell me about this file — size, owner, permissions" (Module 3's inodes)
fork (clone) "Duplicate me into a new process" (Module 5's big idea)
execve "Replace me with a different program" (the other half of Module 5's big idea)
wait4 "Pause me until my child process finishes"
exit "I'm finished"
kill "Send a signal to a process" (despite the name: any signal, not just fatal ones)
mmap / brk "Give me more memory"
socket, connect, bind Networking (Module 8)
ioctl The kitchen drawer: device-specific requests

One layer of honesty: programs almost never invoke syscalls by hand. They call functions in the C library (libc)printf() eventually calls write, fopen() calls openat — and libc performs the actual syscall. This is why libc is the one library everything depends on, a fact that becomes a plot point in Module 9.

The tool that changes everything: strace. It runs a program and prints every syscall it makes, live. When you strace ls in the lab, you'll see the abstract boundary as a concrete transcript — every request ls made, the kernel's every answer. From today onward, strace is also a debugging superpower: a program failing mysteriously will show you the failing syscall (usually an open on a file that isn't there, or a permission denial).

2.3 What the kernel manages: CPU time

There are more running programs than CPU cores — right now your machine likely has hundreds of processes and eight-ish cores. The kernel's scheduler resolves this by rapid turn-taking: it runs one process on a core for a slice of time (on the order of milliseconds), then interrupts it — using a hardware timer that only kernel mode can control — saves its complete CPU state, restores another process's saved state, and lets that one run. This swap is a context switch, and it happens thousands of times per second.

Consequences worth internalizing:

  • Multitasking is an illusion built from speed. Nothing runs "simultaneously" beyond the number of physical cores; everything else is fast turn-taking.
  • Processes can't hog by refusing to yield — preemption is enforced by the timer interrupt, which user mode cannot touch. (This is one of §0.1's "sharing" and "protection" jobs, delivered.)
  • Most processes are asleep most of the time. A process waiting for input, a network packet, or a timer is sleeping: the scheduler skips it entirely until the event arrives. A healthy system with 300 processes might have 2 actually runnable. This is why "load" and "number of processes" are different ideas.
  • nice values exist: a per-process politeness score (−20 to 19, lower = higher priority) that biases the scheduler. You'll rarely need it; you should know it exists.

2.4 What the kernel manages: memory

Every process believes it has an enormous, private, contiguous memory all to itself, starting at address zero. This is a lie, and it's called virtual memory — arguably the most consequential fiction in computing.

The mechanism: memory is managed in pages (4 KiB each). The kernel keeps, per process, a page table — a map from the process's virtual addresses to actual physical RAM pages. The CPU's memory-management unit (MMU) translates every single memory access through this map, in hardware, for free. What the fiction buys:

  • Isolation. Your process's page table simply contains no entries pointing at another process's memory. It's not that reading someone else's memory is forbidden — it's that in your universe of addresses, their memory doesn't exist. Protection by construction.
  • The illusion of abundance. The kernel only assigns physical pages when they're actually touched, so processes can "have" far more address space than the machine has RAM.
  • Swap. Under pressure, the kernel can write rarely-used pages out to disk and reclaim the RAM, pulling them back on demand (each pull is a page fault — slow, which is why a swapping machine crawls).
  • Shared pages without shared risk. Two processes running the same program or library map the same physical pages read-only — one copy of libc in RAM serves every process on the system (Module 9 will lean on this).

One famous kernel behavior to know: if memory truly runs out, the OOM killer (out-of-memory killer) picks a process — heuristically, a big and unimportant-looking one — and kills it to save the system. When a huge program dies silently, this is a suspect; the kernel logs it (you'll find such logs with dmesg and, later, journalctl).

2.5 Processes: the kernel's unit of "a running program"

You've been using the word; now define it. A process is the kernel's bookkeeping for one running instance of a program:

  • its virtual memory (page table, from §2.4),
  • its scheduling state (running / runnable / sleeping — §2.3),
  • its identity (which user it's running as — Module 4 makes this the linchpin of all security),
  • its open files (file descriptors — Module 5),
  • and a unique PID (process ID), a number.

A program is a file on disk; a process is that program in motion, with state. One program can be running as fifty simultaneous processes (fifty shells, one /bin/bash).

Every process is created by another process — a parent — so all processes form a single tree. At the root sits PID 1, the first user-space process, started by the kernel at boot, ancestor of everything. On your machine PID 1 is systemd, and Module 7 is devoted to it. For now, run pstree once in the lab and see the tree: systemd at the root, your terminal emulator somewhere in the middle, your shell below it, and pstree itself as a leaf — a family portrait including the photographer.

How processes are created (the fork/exec two-step) and controlled (signals) is deliberately deferred to Module 5 — you'll want file descriptors in hand first. This module only needs you to know what a process is and that the kernel schedules them.

2.6 Kernel modules: extending the kernel at runtime

The kernel needs a driver for every piece of hardware it touches — and there are tens of thousands of devices in the world. Compiling every driver into one gargantuan kernel image would be absurd, so Linux makes the kernel modular: a kernel module is a chunk of kernel code (a .ko file — kernel object) that can be loaded into the running kernel when needed and removed when not.

The critical nuance: a loaded module becomes part of the kernel — it runs in kernel mode with total power. It is not an "app for the kernel"; it's a transplant. (This is why a buggy driver can crash the whole machine when a buggy application can't, and why the kernel refuses modules built for a different kernel version.)

The tooling:

lsmod                     # list currently loaded modules
modprobe <name>           # load a module (plus everything it depends on)
modprobe -r <name>        # unload one
modinfo <name>            # describe one: what it's for, its parameters, its file

In practice you almost never run modprobe by hand: when hardware appears, the kernel announces it, and userland (a systemd component called udev — Module 7's family) loads the right module automatically. Plug in a USB drive, and within milliseconds the storage modules are in. You'll watch exactly this in the lab.

Most drivers are modules; some are built in to the kernel image at compile time (essential ones like the disk driver needed for booting — a choice that becomes interesting in Module 6's initramfs discussion).

2.7 /proc and /sys: reading the kernel's mind

How do tools like ps learn what processes exist? There's no secret channel. The kernel publishes its internal state as files, in two virtual filesystems:

  • /proc — process and system state. /proc/cpuinfo, /proc/meminfo, /proc/uptime; and one directory per PID: /proc/1234/ contains that process's command line, environment, open files, memory map. ps is not magic — it reads these directories and formats them. (Prove it in the lab with strace.)
  • /sys — the device and driver universe, one directory per device, with attributes as files. Some are even writable: on many laptops, writing a number to /sys/class/backlight/…/brightness changes the screen brightness — configuring hardware with a text edit.

"Virtual" means: these files exist nowhere on disk and occupy no space. Reading one is a syscall like any other read — the kernel just fabricates the content on the spot from live internal data. This is §0.6's Unix philosophy applied to the kernel itself: expose everything as text files, and every text tool ever written becomes a system-inspection tool.

Also meet dmesg: the kernel's own log — hardware detected, drivers loaded, warnings, OOM kills. dmesg | less right after something odd happens is a fundamental diagnostic move (root may be required on some distros).

2.8 → NixOS

The kernel is the least distro-specific component there is — NixOS runs the same Linux kernel as Debian. What NixOS changes is how the kernel and its modules are selected and assembled: in your configuration you'll write things like boot.kernelPackages = pkgs.linuxPackages_latest; and boot.kernelModules = [ "kvm-amd" ];, and the system builds itself accordingly — coherently, atomically, and reversibly. Those lines are copy-paste runes to someone who doesn't know what a kernel version or a module is. After this module, they're plain statements of fact about things you've listed with uname and lsmod.


Lab 2

You may need to install strace (and possibly pstree, packaged as psmisc) — installing a package is fine to do by recipe now; Module 9 explains what actually happened when you did.

  1. Watch the one door. Run:

    strace ls

    A torrent of syscalls prints. Don't read it all; hunt for the story: an execve at the very top (the moment ls began — §2.2), a cluster of openat calls on .so files (foreshadowing Module 9), an openat("."), a getdents64 (get directory entries — the actual "list files" call), and a write(1, "…") near the end — the moment the listing hit your screen. This transcript is the entire kernel/userland relationship made visible.

  2. Sharpen it. Run strace -c ls for a syscall census (count and time per call). Then run strace -e openat ls to filter to file-opens only. Keep -e in your pocket forever.

  3. Read kernel state as text.

    cat /proc/cpuinfo | less
    cat /proc/meminfo | head -5
    cat /proc/uptime

    From meminfo, answer: how much RAM does the machine have, and how much is MemAvailable? From uptime: the first number is seconds since boot — convert it to hours, roughly.

  4. Inspect one process's kernel file. Run echo $$ — the shell substitutes its own PID (one to remember). Then:

    ls /proc/$$/
    cat /proc/$$/cmdline
    ls -l /proc/$$/cwd

    You are reading the kernel's live bookkeeping about your own shell — including a link to its current working directory, which is pwd's data source.

  5. See the tree. Run pstree -p | less and find: PID 1 at the root (what is it?), your terminal emulator, your shell under it, and pstree itself under that. Trace the ancestry chain out loud.

  6. Catch ps reading /proc. Run:

    strace -e openat ps | grep proc | head -20

    There it is — ps opening /proc/<pid>/stat files one after another. No magic, just files.

  7. Modules in motion. Run lsmod | head, and pick any module name you see; run modinfo <name> and read its description. If you have any USB stick handy: run dmesg | tail -5, plug the stick in, wait two seconds, run dmesg | tail -15, and read the kernel's live narration of detecting it. Then check lsmod | grep usb_storage.


✅ Mastery Check — do not proceed until true

Answer out loud, without notes:

  1. Walk through what happens, step by step, when a program calls read: registers, mode switch, permission check, and back.
  2. Name six syscalls and what each asks the kernel to do.
  3. Your machine has 4 cores and 300 processes. Explain, with the words scheduler, time slice, context switch, and sleeping, why this works fine.
  4. What lie does virtual memory tell every process, and name three things the lie buys.
  5. What is the difference between a program and a process? What is PID 1, and what's special about it?
  6. What is a kernel module, and why can a buggy one crash the machine when a buggy application can't?
  7. What are /proc and /sys, and what does "virtual filesystem" mean about where their contents live?
  8. How does ps find out what processes exist? How could you prove your answer?

And perform cold:

  • Use strace -e to show which files any given command opens.
  • Find any process's command line and working directory using only /proc.
  • List loaded kernel modules and get the description of one.

When all of that is effortless: Module 3 — Everything Is a File