// 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.
nicevalues 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.psis not magic — it reads these directories and formats them. (Prove it in the lab withstrace.)/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/…/brightnesschanges 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.
Watch the one door. Run:
strace lsA torrent of syscalls prints. Don't read it all; hunt for the story: an
execveat the very top (the momentlsbegan — §2.2), a cluster ofopenatcalls on.sofiles (foreshadowing Module 9), anopenat("."), agetdents64(get directory entries — the actual "list files" call), and awrite(1, "…")near the end — the moment the listing hit your screen. This transcript is the entire kernel/userland relationship made visible.Sharpen it. Run
strace -c lsfor a syscall census (count and time per call). Then runstrace -e openat lsto filter to file-opens only. Keep-ein your pocket forever.Read kernel state as text.
cat /proc/cpuinfo | less cat /proc/meminfo | head -5 cat /proc/uptimeFrom
meminfo, answer: how much RAM does the machine have, and how much isMemAvailable? Fromuptime: the first number is seconds since boot — convert it to hours, roughly.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/$$/cwdYou 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.See the tree. Run
pstree -p | lessand find: PID 1 at the root (what is it?), your terminal emulator, your shell under it, andpstreeitself under that. Trace the ancestry chain out loud.Catch
psreading/proc. Run:strace -e openat ps | grep proc | head -20There it is —
psopening/proc/<pid>/statfiles one after another. No magic, just files.Modules in motion. Run
lsmod | head, and pick any module name you see; runmodinfo <name>and read its description. If you have any USB stick handy: rundmesg | tail -5, plug the stick in, wait two seconds, rundmesg | tail -15, and read the kernel's live narration of detecting it. Then checklsmod | grep usb_storage.
✅ Mastery Check — do not proceed until true
Answer out loud, without notes:
- Walk through what happens, step by step, when a program calls
read: registers, mode switch, permission check, and back. - Name six syscalls and what each asks the kernel to do.
- Your machine has 4 cores and 300 processes. Explain, with the words scheduler, time slice, context switch, and sleeping, why this works fine.
- What lie does virtual memory tell every process, and name three things the lie buys.
- What is the difference between a program and a process? What is PID 1, and what's special about it?
- What is a kernel module, and why can a buggy one crash the machine when a buggy application can't?
- What are
/procand/sys, and what does "virtual filesystem" mean about where their contents live? - How does
psfind out what processes exist? How could you prove your answer?
And perform cold:
- Use
strace -eto 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