Linux Kernel Space — Inside the Engine-Free Linux kernel development course

Previous Lecture
Next Lecture

Linux Kernel Space — Inside the Engine
Chapter 4, Part 2 — Kernel Subsystems, Components & the Monolithic Architecture
🏗️ Architecture Deep Dive
⚡ Kernel 6.x Updated
🆓 100% Free
💡 Interview Q&A Included

Topics Covered in This Tutorial

Core Kernel
Memory Management
VFS
Block IO
Network Stack
IPC
Device Drivers
KVM Virtualization
Monolithic Architecture
Security Frameworks

Picking Up From Part 1

In Part 1, we saw how user space and kernel space are separated, and how system calls are the controlled bridge between them. Now let us step inside kernel space and understand what actually lives there.

The Linux kernel is often described as a large and complex piece of software — and that is true. But it is not a single blob of undifferentiated code. It is organised into well-defined subsystems and components, each with a clear responsibility. Understanding this organisation is essential for any kernel programmer, driver developer, or embedded systems engineer.

1. The Linux Kernel — Organised Complexity

The Linux kernel source tree (available at kernel.org) is massive — the 6.x kernel contains over 30 million lines of code spread across thousands of files. Yet it is structured. Every directory at the top level of the kernel source corresponds to a major subsystem or component.

# Quick look at the top-level kernel source structure (kernel 6.x)
ls /usr/src/linux-6.x/

# You will see directories like:
# arch/       — CPU architecture-specific code (x86, arm64, riscv, mips...)
# kernel/     — Core kernel (scheduler, signals, timers, namespaces)
# mm/         — Memory management subsystem
# fs/         — Filesystems (ext4, btrfs, proc, sysfs, nfs...)
# net/        — Networking stack (TCP/IP, Bluetooth, WiFi drivers)
# drivers/    — Device drivers (thousands of them)
# ipc/        — Inter-process communication
# block/      — Block I/O layer
# sound/      — Audio subsystem (ALSA)
# virt/       — Virtualization (KVM)
# security/   — Security frameworks (SELinux, AppArmor)
# lib/        — Common utility functions used across the kernel
# include/    — Kernel header files

Each directory is not just a folder — it maps to a living, maintained subsystem with its own maintainers, mailing lists, and development roadmap. When you write a device driver, your code will typically live inside the drivers/ directory under the appropriate category.

2. The Major Kernel Subsystems — One by One

Let us walk through each major kernel subsystem, understand what problem it solves, and see how it fits into the bigger picture.

2.1 Core Kernel

This is the heart of the operating system — the code that makes Linux act like an operating system. It does not handle any specific hardware or file format; instead it provides the fundamental services every other part of the OS depends on.

Core Kernel — What Lives Here
⏱️
Process Scheduler
CFS, RT, Deadline
🔔
Signals
SIGKILL, SIGTERM, handlers
Timers
hrtimer, jiffies, NOHZ
🔐
Synchronisation
spinlock, mutex, RCU
🧊
Namespaces
pid, net, user, mount
📦
cgroups
CPU, memory limits per group
🧩
Module Support
insmod, rmmod, .ko files
🛡️
Interrupt Handling
IRQ, softirq, tasklets

One thing that is critical to understand for kernel module development: the module support subsystem. When you write a Linux kernel module (a .ko file), it is loaded into this core kernel space at runtime using insmod or modprobe. Your module code runs at Ring 0 with full kernel privileges. This is both what makes kernel modules powerful and what makes a bug in them potentially system-crashing.

⚙️ Kernel 6.x Scheduler Note: Linux uses the Completely Fair Scheduler (CFS) as its default scheduler for normal tasks, alongside the Deadline scheduler (for real-time tasks with strict timing requirements) and the RT scheduler. In kernel 6.6+, a new experimental scheduler called EEVDF (Earliest Eligible Virtual Deadline First) was merged as an improvement over CFS. This is an active area of kernel development.

2.2 Memory Management (MM)

Memory management is arguably the most complex and important subsystem in the kernel. Its job is to manage every byte of RAM in the system — how it is divided, mapped, shared, and reclaimed.

Virtual Address Space — Each Process Gets Its Own View of Memory

Process P1 VAS
Kernel Space
0xFFFF… (mapped but inaccessible)
Stack
grows downward ↓
↕ free space
Heap
grows upward ↑
BSS (uninit data)
Data Segment
Text (code)
0x400000 start

Process P2 VAS
Kernel Space
same physical kernel
Stack
↕ free space
Heap
BSS
Data
Text (code)

Both processes see addresses starting from 0 — but these are virtual addresses. The MM subsystem maps them to different physical pages transparently.

Key responsibilities of the MM subsystem include:

  • Page allocator: Managing physical memory pages (typically 4KB each). The buddy allocator handles large-scale allocations.
  • Slab allocator (SLUB in modern kernels): Efficient allocation of small, frequently-needed objects like task_struct, inode, etc.
  • Virtual memory: Setting up page tables, handling page faults (including demand paging and copy-on-write).
  • OOM Killer: When the system truly runs out of memory, the Out-Of-Memory killer selects and terminates a process to free RAM.
  • Huge pages & THP: Transparent Huge Pages allow the kernel to use 2MB or 1GB pages to reduce TLB pressure on workloads with large memory footprints.

2.3 VFS — Virtual Filesystem Switch

How does a single open("/home/ravi/notes.txt", O_RDONLY) call work whether the file is on an ext4 partition, an NFS network share, a USB FAT32 drive, or a virtual /proc file? The answer is the Virtual Filesystem Switch (VFS).

VFS is an abstraction layer. It defines a uniform set of interfaces (open, read, write, stat, mkdir, etc.) that all filesystems must implement. When you call open(), the kernel calls the VFS layer, which then dispatches to the correct filesystem driver based on where the file actually lives.

VFS — One Interface, Many Filesystems

Application: open("/mnt/usb/file.txt") or open("/proc/cpuinfo")
↓ system call

VFS Layer

Unified objects: inode, dentry, file, superblock
↓ dispatches to correct driver

ext4
Local disk
btrfs
Local disk
FAT32/vfat
USB drives
proc
Virtual FS
sysfs
Device info
NFS
Network FS

The key VFS data structures you will encounter when writing kernel code or device drivers are:

  • struct inode — represents a file or directory on disk (metadata: size, permissions, timestamps)
  • struct dentry — represents a directory entry (the name-to-inode mapping in the path cache)
  • struct file — represents an open file (per-process file descriptor state)
  • struct super_block — represents a mounted filesystem
⚙️ Kernel 6.x Note: In modern Linux, io_uring (introduced in 5.1) is now a widely-used alternative to traditional blocking I/O syscalls. It allows user space to submit I/O requests asynchronously through a shared ring buffer with the kernel, dramatically reducing syscall overhead for high-throughput I/O workloads (databases, web servers). This works on top of the VFS layer but bypasses some of the traditional system call path overhead.

2.4 Block IO Layer

When you write data to a file on an ext4 partition, the VFS eventually needs to get that data onto actual physical storage — an NVMe SSD, a SATA hard drive, or an eMMC chip. The Block IO layer manages this journey.

Between the filesystem and the storage hardware, the block layer does important work: it merges multiple small write requests into larger ones (to reduce head movement on HDDs), reorders requests for optimal performance, enforces I/O scheduling policies, and handles error recovery.

Block IO Path — From Filesystem to Hardware
VFS / Filesystem (ext4, btrfs…)

Page Cache

Linux caches disk reads in RAM here
↓ (on write or cache miss)

Block IO Layer

I/O scheduler: mq-deadline, kyber, bfq

Storage Driver

NVMe driver, SATA AHCI, MMC driver
Physical Storage Hardware
⚙️ Kernel 6.x Note: The older single-queue block layer was replaced with the multi-queue block layer (blk-mq) starting from kernel 3.13, and it became the only option from kernel 5.0. blk-mq maps hardware queues (NVMe SSDs can have 32+ queues) directly to software queues, dramatically improving throughput on modern NVMe storage. If you are writing a block device driver today, you work with blk-mq APIs only.

2.5 Network Protocol Stack

Linux is famous in the networking world for the quality and correctness of its TCP/IP implementation. The network stack in the kernel implements protocols at multiple layers — from raw Ethernet frames at the bottom to TCP/UDP sockets at the top that applications use.

Linux Network Stack — Layers Inside the Kernel
Application — uses socket(), send(), recv()
↓ via system calls
Socket Layer (BSD Sockets API)
Transport Layer — TCP, UDP, SCTP, QUIC (via XDP)
Network Layer — IPv4, IPv6, routing, netfilter/iptables
Link Layer — Ethernet, WiFi (mac80211), bonding
NIC Driver (e.g., drivers/net/ethernet/intel/e1000e/)
⚙️ Kernel 6.x Note: XDP (eXpress Data Path) and eBPF are now deeply integrated into the Linux network stack. XDP allows you to run eBPF programs in the NIC driver context (before the packet even enters the main network stack), achieving millions of packets per second processing. This is used in production for load balancers (Facebook’s Katran), firewalls, and DDoS mitigation. If you are doing network kernel development today, familiarity with eBPF/XDP is important.

2.6 Inter-Process Communication (IPC)

Processes running in user space cannot directly access each other’s memory (by design — isolation is the whole point). But they often need to cooperate and exchange data. The IPC subsystem provides the mechanisms to do this safely.

IPC Mechanisms Available in Linux Kernel
📨
Message Queues
POSIX mq_open()
SysV msgget()
🗄️
Shared Memory
POSIX shm_open()
SysV shmget()
🚦
Semaphores
POSIX sem_open()
SysV semget()
🔗
Pipes / FIFOs
pipe(2)
mkfifo(3)
🔌
Unix Sockets
AF_UNIX domain
socketpair()
Signals
kill(2)
sigaction(2)

When should you use which? A quick rule of thumb: use pipes for simple parent-child data flow, shared memory for high-throughput data sharing between related processes, message queues for structured message passing, and Unix domain sockets for bidirectional communication between unrelated processes on the same machine.

2.7 Sound Support & Virtualization (KVM)

Sound support: The Linux audio subsystem is called ALSA (Advanced Linux Sound Architecture). It manages everything from loading firmware for audio hardware to mixing audio streams and exposing device nodes like /dev/snd/pcmC0D0p. ALSA replaced the older OSS (Open Sound System) which is now obsolete. PulseAudio and PipeWire (the modern replacement) run in user space on top of ALSA kernel drivers.

Virtualization (KVM): KVM stands for Kernel-based Virtual Machine. It is a virtualization module built directly into the Linux kernel. KVM turns the Linux kernel itself into a Type-1 hypervisor, using the hardware virtualisation extensions present in modern CPUs (Intel VT-x, AMD-V). This is why Linux is the dominant platform for cloud computing — KVM powers billions of virtual machines in data centres from AWS, Google Cloud, Azure, and others.

⚙️ Kernel 6.x Note: In kernel 6.x, KVM has been enhanced significantly for ARM64 (used in AWS Graviton instances), RISC-V (gaining virtualization support from kernel 6.2+), and has received substantial security hardening through Protected KVM (pKVM) on Android which isolates the hypervisor from the host kernel. For embedded systems engineers, understanding KVM is increasingly important as even embedded devices now run virtualized workloads.

3. Additional Kernel Components

Beyond the major subsystems, the kernel has several other important components:

🏛️ Arch-Specific Code

Located in arch/, this handles CPU-specific details: boot process, interrupt handling, context switching, system call entry, and hardware-specific optimisations. Each supported architecture (x86, arm64, riscv, mips, powerpc…) has its own directory here.

🚀 Kernel Initialisation

The code that runs when the kernel first boots — start_kernel() in init/main.c — is the entry point for kernel initialization. It sets up the memory subsystem, initialises each subsystem in order, mounts the root filesystem, and then launches init (PID 1, typically systemd).

🔒 Security Frameworks

Linux supports pluggable security modules via the LSM (Linux Security Module) framework. SELinux, AppArmor, SMACK, and Yama are all LSM implementations. They hook into kernel operations to enforce mandatory access controls beyond the standard Unix permission model. In kernel 6.x, Landlock (a sandboxing LSM that unprivileged users can use) is also available.

🔧 Device Drivers

The drivers/ directory is the largest in the kernel source, containing drivers for thousands of hardware devices — network cards, GPUs, USB devices, sensors, buses (I2C, SPI, UART), storage, input devices, and more. As an embedded systems engineer, this is where most of your kernel work will live.

4. The Monolithic Architecture — Why Linux is Designed This Way

The Linux kernel uses a monolithic architecture. This is one of the most discussed architectural decisions in operating systems — and one of the most commonly asked interview topics.

In a monolithic design, all kernel components — the scheduler, memory manager, filesystem drivers, network stack, device drivers — live together in a single, shared kernel address space. They all run at the same privilege level (Ring 0) and can call each other’s functions directly.

Monolithic Kernel vs Microkernel — The Core Difference

Monolithic (Linux)
User Processes
— System Call Interface —
KERNEL SPACE
Scheduler  |  MM
VFS  |  Network
IPC  |  Drivers
Sound  |  Security
All share one kernel address space
✅ Direct function calls between subsystems = fast
✅ Simpler for kernel module development
⚠️ A driver bug can crash the whole kernel

vs

Microkernel (e.g., QNX, Mach)
User Processes
+ FS server
+ Network server
+ Device drivers
— Message Passing (IPC) —
TINY KERNEL
IPC only
Basic scheduler
Memory paging
✅ Driver crash does not kill kernel
✅ More isolated, potentially more secure
⚠️ IPC overhead between components = slower

Why Does Linux Use Monolithic?

The monolithic design was chosen for performance. When the scheduler needs to tell the memory manager to map pages for a new process, or when the VFS calls a filesystem driver, these are just direct C function calls — microseconds fast. In a microkernel, the same operations would require sending messages through IPC mechanisms, adding significant latency.

Linux mitigates the main downside (all-or-nothing crash risk) through kernel modules. Device drivers and other components can be compiled as loadable modules (.ko files) that are loaded into the kernel address space only when needed. This gives some of the flexibility benefits of a microkernel while retaining monolithic performance. However, once a module is loaded, it still runs with full kernel privileges — isolation is not enforced between modules.

Loadable Kernel Modules — Extending the Monolithic Kernel at Runtime
KERNEL ADDRESS SPACE (Ring 0)
Core Kernel
Built-in (vmlinux)
WiFi Driver
Loaded module (.ko)
Your Driver
insmod mydrv.ko
USB Driver
Loaded module (.ko)
All modules share the same kernel address space — direct function calls between them are possible
insmod mydriver.ko → loads module into kernel
rmmod mydriver → unloads safely

5. Hands-On — Exploring the Running Kernel

The best way to understand the kernel’s components is to poke around while it is running. Linux exposes a huge amount of its internal state through virtual filesystems that require no special tools — just basic shell commands.

# See all currently loaded kernel modules
lsmod

# Get detailed info about a specific module
modinfo bluetooth

# See kernel messages (ring buffer) — full of useful diagnostic info
dmesg | head -50

# See loaded module list with memory addresses (needs root)
cat /proc/modules

# Explore the kernel's view of the filesystem hierarchy
ls /proc/
ls /sys/

# See how much memory each kernel subsystem uses
cat /proc/meminfo

# Check current I/O scheduler for a disk
cat /sys/block/sda/queue/scheduler

# See all system calls available (via /proc)
cat /proc/kallsyms | grep "sys_" | head -20

# Check kernel version and build info
uname -r      # e.g., 6.8.0-45-generic
cat /proc/version
✅ Exercise: Run lsmod | wc -l to see how many modules are loaded on your system. Then run lsmod | sort -k2 -rn | head -10 to find the 10 largest modules by size. Can you identify which devices they correspond to?

📋 Interview Questions & Answers

Frequently asked in Linux kernel, embedded software, and device driver engineering interviews.

Q1. What are the major subsystems of the Linux kernel?
The Linux kernel’s major subsystems are: Core Kernel (scheduler, signals, timers, namespaces, cgroups, module support, crypto), Memory Management (physical and virtual memory, page allocator, slab allocator, OOM killer), VFS (abstraction layer over all filesystems), Block IO (I/O scheduling and block device layer), Network Protocol Stack (TCP/IP, Bluetooth, WiFi through mac80211), IPC (message queues, shared memory, semaphores, pipes, Unix sockets), Sound (ALSA), and Virtualization (KVM). Additionally there are arch-specific code, device drivers, security frameworks (LSM), and kernel initialization code.
Q2. What is the difference between a monolithic kernel and a microkernel? Which does Linux use?
In a monolithic kernel, all OS services (scheduler, filesystems, networking, drivers) run together in a single kernel address space at the highest privilege level. They communicate via direct function calls — very fast. In a microkernel, only essential services (IPC, basic scheduling, memory paging) run in kernel space; everything else (filesystem servers, network servers, device drivers) runs as user-space processes and communicates via message passing — safer but slower due to IPC overhead. Linux uses the monolithic architecture. It compensates for the flexibility limitation through loadable kernel modules (LKMs), allowing drivers and subsystems to be loaded/unloaded at runtime without rebooting. Examples of true microkernels include QNX, Mach (used in macOS XNU), and L4.
Q3. What is the VFS and why is it important?
The VFS (Virtual Filesystem Switch) is an abstraction layer in the Linux kernel that provides a uniform interface for all filesystems. It defines a set of standard operations (open, read, write, stat, mkdir, etc.) that any filesystem driver must implement. This means application code and upper kernel layers do not need to know whether they are dealing with ext4, btrfs, NFS, FAT32, or a virtual filesystem like /proc — they all look the same through the VFS interface. The key VFS data structures are struct inode, struct dentry, struct file, and struct super_block. When writing a filesystem driver or a pseudo-filesystem, you implement the VFS operations defined in struct file_operations and struct inode_operations.
Q4. What is a kernel module? How does it differ from a built-in kernel feature?
A kernel module (LKM — Loadable Kernel Module) is a piece of kernel code compiled as a separate .ko (kernel object) file that can be loaded into the running kernel at any time without rebooting, and unloaded when no longer needed. A built-in feature is compiled directly into the kernel image (vmlinux) and is always present. In menuconfig, y means built-in and m means compiled as a module. Modules are useful for: device drivers (so you do not need to reboot to add hardware support), experimental features (load and test without risking a permanent change), and reducing kernel memory footprint (only load what you need). The key limitation is that modules share the same address space as the kernel — a buggy module can corrupt kernel memory just as easily as built-in code. Commands: insmod (load), rmmod (remove), modprobe (load with dependency resolution), lsmod (list), modinfo (inspect).
Q5. What is the difference between POSIX IPC and SysV IPC in Linux?
Both SysV IPC and POSIX IPC provide the same three mechanisms: shared memory, message queues, and semaphores. The differences are in API style and behaviour. SysV IPC is the older interface: it uses numeric keys (key_t) to identify objects, and the APIs are shmget/shmat, msgget/msgsnd, semget/semop. SysV IPC objects persist until explicitly deleted or the system reboots, even if no process has them open. POSIX IPC is newer and cleaner: it uses names (like filesystem paths, e.g. /myshm) and file-descriptor-like handles. APIs are shm_open/mmap, mq_open/mq_send, sem_open/sem_post. POSIX shared memory objects appear under /dev/shm/. In new code, POSIX IPC is preferred because the API is simpler and better integrated with POSIX standards. Both coexist in the Linux kernel.
Q6. What is KVM and how does it relate to the Linux kernel?
KVM (Kernel-based Virtual Machine) is a virtualisation subsystem built into the Linux kernel since version 2.6.20 (2007). It exposes a device file (/dev/kvm) that user-space hypervisors (like QEMU) use to create and manage virtual machines. KVM itself handles the privileged operations — setting up and switching virtual CPU state, handling VM exits (when the guest tries to access hardware), and managing memory mappings (EPT/NPT for nested page tables). It relies on hardware virtualisation extensions: Intel VT-x and AMD-V on x86, and ARM’s EL2 on 64-bit ARM. Because KVM is part of the kernel, a Linux host with KVM is effectively a Type-1 hypervisor, giving it near-native VM performance. This is why KVM powers most public cloud infrastructure.
Q7. What happens during kernel boot — in which order do subsystems initialise?
The kernel boot sequence (simplified) after the bootloader hands control to the kernel: (1) Architecture-specific early setup — CPU mode switch, page table setup, stack initialisation. (2) start_kernel() is called in init/main.c — this is the first C function. (3) setup_arch() — architecture-specific initialisation (memory map, command line parsing). (4) Memory management is initialised — buddy allocator, slab allocator. (5) Interrupt handling — IDT/GDT setup, interrupt controllers. (6) Scheduler initialisation. (7) VFS initialisation. (8) Each subsystem initialises in order defined by initcall levels (early, core, postcore, arch, subsys, fs, device, late). (9) Root filesystem is mounted. (10) init process (PID 1, typically systemd) is executed as the first user-space process. Everything after this is in user space.

You Now Know the Kernel’s Architecture

You have now seen both the user side and the kernel side of Linux. In the next chapters, we go hands-on: writing actual kernel modules, understanding the kernel’s memory model, working with kernel data structures, and eventually writing device drivers.

EmbeddedPathashala — Free Linux Kernel Programming | Free Embedded Systems Course | Free Linux Device Drivers Course

Previous Lecture
Next Lecture

Leave a Reply

Your email address will not be published. Required fields are marked *