Topics Covered in This Tutorial
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.
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.
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.
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.
open("/mnt/usb/file.txt") or open("/proc/cpuinfo")VFS Layer
inode, dentry, file, superblockThe 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
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.
Page Cache
Block IO Layer
Storage Driver
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.
socket(), send(), recv()drivers/net/ethernet/intel/e1000e/)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.
SysV msgget()
SysV shmget()
SysV semget()
mkfifo(3)
socketpair()
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.
3. Additional Kernel Components
Beyond the major subsystems, the kernel has several other important components:
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.
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).
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.
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.
VFS | Network
IPC | Drivers
Sound | Security
✅ Simpler for kernel module development
⚠️ A driver bug can crash the whole kernel
+ Network server
+ Device drivers
Basic scheduler
Memory paging
✅ 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.
insmod mydriver.ko → loads module into kernelrmmod mydriver → unloads safely5. 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
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.
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
