Kernel Lab 1: Observing Process and VFS Structs

Kernel Lab 1: Observing Process and VFS Structs Primer on Kernel Data Structures 1. Process & Memory Management (MM) Struct Description Noteworthy Fields task_struct The kernel’s representation of a thread/process. mm (memory space), files (open files), fs (cwd/root), pid, comm (name). mm_struct The descriptor for an entire memory address space. mm_mt (the Maple Tree of VMAs), pgd (page global directory for hardware MMU). vm_area_struct A contiguous range of virtual memory with shared permissions (a VMA). vm_start, vm_end, vm_file (if it’s a memory-mapped file), vm_flags. 2. Virtual File System (VFS) The VFS bridges the gap between user-space file operations (like open() and read()) and the underlying hardware. ...

July 16, 2026 · 45 min

The Pipes

The Pipes File Descriptors To understand file descriptors (FDs), you have to look at one of the core design philosophies of Unix and Linux: “Everything is a file.” Whether a process is interacting with a regular text file, a directory, a hardware device (like a hard drive), a network socket, or an in-memory pipe, the kernel represents all of them as “files.” A file descriptor is simply the lightweight handle that user-space processes use to talk to these files. Here is the low-level architectural breakdown of how they work. ...

July 8, 2026 · 19 min

The Page Cache and Page Writeback

The Linux Page Cache & Page Writeback The Core Concept: Disk access is measured in milliseconds; RAM access is measured in nanoseconds. To bridge this massive performance gap, Linux dynamically uses free physical RAM to cache blocks of disk data. This relies on temporal locality: the computing principle that if data is accessed once, it is highly likely to be accessed again very soon. 1. Reading from Disk The cache is granular; Linux caches specific pages of files based on what you actually access, not whole files by default. ...

July 5, 2026 · 25 min

The Page Tables

Modern Linux Page Tables (4-Level Architecture) Modern 64-bit processors require much larger address spaces, and the Linux kernel adapted by shifting to a 4-level (and more recently, a 5-level) page table architecture. The Core Concept: Because a 64-bit address space is astronomically large and mostly empty, the kernel cannot use a single, massive translation array. Instead, it uses a hierarchical, multi-level tree of tables to map Virtual Addresses to Physical Addresses efficiently. ...

July 2, 2026 · 18 min

The Process Address Space

The Process Address Space — Intro Same flat (single contiguous range) address space model you already have from the article — nothing new conceptually. Key term to lock in: a memory area (this book’s name for what the article calls a VMA) is a permission-tagged interval within that address space. Access outside any valid area, or against an area’s permissions (write to read-only, execute non-executable) → segfault. The list of “what memory areas contain” is just a slightly different cut of the same segments from the article: text, data, bss, stack, shared library mappings, mmap’d files, shared memory, anonymous mappings (malloc). All non-overlapping — every valid address belongs to exactly one area. ...

June 26, 2026 · 32 min

The Virtual Filesystem

The Virtual Filesystem What is a Filesystem? Imagine a bare hard drive as a massive, empty warehouse. You can throw billions of bytes of data in there, but without a system, you will never find anything again. A filesystem is the specific set of rules, data structures, and “ledgers” (like the Inodes and Superblocks we discussed) used to organize, index, and retrieve that data. It dictates how large a file can be, how folders are nested, and how permissions are handled. ...

June 20, 2026 · 27 min

Memory Management in Kernel

Memory Management in Kernel Why Kernel Space Memory Management is Harder? Userspace can fail safely and wait patiently. Kernel-space cannot, making its allocation fundamentally harder. Key Differences in Kernel Memory Allocation Sleeping is often banned: Userspace can block while waiting for memory. Kernel contexts (interrupts, spinlocks) cannot, requiring instant success or failure flags like GFP_ATOMIC. Failure is catastrophic: App failures just kill the app; kernel allocation failures crash the entire system. The kernel must rely on emergency reserves, reclaim, and the OOM killer to survive. Dangerous recursion: The kernel manages memory using memory. A reclaim operation can trigger filesystem actions that need more memory, causing deadlocks (prevented by flags like GFP_NOFS). Strict physical constraints: Userspace only worries about virtual memory. The kernel must manage physical pages, DMA limits, and NUMA locality. Physical fragmentation matters: The kernel frequently requires physically contiguous pages, making fragmentation a critical, system-halting roadblock. Interrupt context is brutal: Interrupt handlers need immediate memory without sleeping or waiting on locks, relying heavily on per-CPU caches and lockless structures. High deadlock risk: Allocators interact with reclaim, writebacks, and system locks. GFP flags are essential to dictate exactly what an allocator is safely allowed to do. Predictable latency is required: Kernel subsystems (networking, real-time workloads) cannot tolerate unpredictable allocation pauses, necessitating highly optimized allocators like SLAB/SLUB. The Mental Shift ...

May 19, 2026 · 31 min

Rust Notes — Module 6

Rust Notes — Module 6: Error Handling 1. The Philosophy Language Error Mechanism Problem C Return codes (-1, NULL, errno) Easy to ignore, no enforcement Go (value, error) tuples Better, but still ignorable Rust Result<T, E> in the type system Impossible to ignore — compiler enforced If a function can fail, its return type says so. You cannot use the success value without handling the error case first. No hidden exceptions, no surprise crashes from ignored error codes. ...

March 29, 2026 · 7 min

Rust Notes — Module 7

Rust Notes — Module 7: Packages, Crates & Modules 1. How Rust Compilation Works In C, you hand the compiler a list of files. Each file compiles independently, and a linker stitches the object files together. The problem — the compiler has no idea which files depend on which, so you need a Makefile to track what needs recompiling when something changes. Rust takes a different approach. You hand the compiler one root file (main.rs or lib.rs). The compiler follows mod declarations to find every other file that’s part of the crate. Because Rust knows the full dependency graph from the start, it handles incremental recompilation internally — no Makefile needed. ...

March 29, 2026 · 8 min

Rust Notes — Module 5

Rust Notes — Module 5: Generics, Closures & Iterators 1. Generics Generics let you write code that works for any type, with the compiler generating specialized versions at compile time. Zero runtime overhead — same as C++ templates. Generic Functions // T is a type parameter — placeholder for any concrete type fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } // works for any type that implements PartialOrd let nums = vec![1, 5, 3, 2]; let chars = vec!['a', 'z', 'm']; println!("{}", largest(&nums)); // 5 println!("{}", largest(&chars)); // z Generic Structs struct Stack<T> { elements: Vec<T>, } // impl block also needs <T> impl<T> Stack<T> { fn new() -> Self { Stack { elements: Vec::new() } } fn push(&mut self, item: T) { self.elements.push(item); } fn pop(&mut self) -> Option<T> { self.elements.pop() } fn peek(&self) -> Option<&T> { self.elements.last() } fn is_empty(&self) -> bool { self.elements.is_empty() } } // type parameter inferred from usage let mut int_stack = Stack::new(); int_stack.push(1); int_stack.push(2); // or explicit let mut move_stack: Stack<Move> = Stack::new(); Generic Enums You’ve already used these — Option<T> and Result<T, E> are generic enums: ...

March 22, 2026 · 10 min