L30: Kernel Module Installation & initramfs

 

Kernel Module Installation & initramfs

Linux Kernel Programming — Chapter 3, Lecture 30  |  Free Course by EmbeddedPathashala

📚
Chapter 3

Linux 6.x
🎓
Beginner
🔍
FAQ Inside

What You Will Learn in This Lecture

make modules_install
depmod utility
/lib/modules/ layout
INSTALL_MOD_PATH
initramfs vs initrd
Linux boot process
GRUB bootloader
make install
dracut & update-initramfs
modprobe vs insmod
switch_root
Embedded Linux cross-build

Introduction

In the previous lecture you compiled the Linux kernel and produced a bzImage. But that alone is not enough to boot your system. Two critical steps must still happen before you reboot:

 

Step 5 — Install kernel modules so the running kernel can find and load them from the right location on disk.

 

Step 6 — Generate initramfs and update GRUB so the bootloader knows about your new kernel and the early-boot environment has what it needs.

 

Skipping either step almost always ends with a kernel that either panics on boot or silently loses device support. This lecture walks through both steps in plain language, updated for Linux 6.x kernels.

📷 Suggested Image Here
Screenshot of a terminal running make modules_install showing INSTALL lines followed by the DEPMOD line. Label it: “Terminal output of make modules_install on Linux 6.x”. Alt text: make modules_install output showing kernel module installation and depmod step.

Step 5 — Installing Kernel Modules

What Is a Kernel Module?

When you run make menuconfig, each kernel feature can be set to one of three states:

Config Value Meaning Where It Goes
Y (built-in) Feature is compiled permanently into the kernel Inside vmlinux / bzImage
M (module) Feature is compiled as a loadable object Separate .ko file on disk
N (disabled) Feature is not compiled at all Not present anywhere

A .ko (kernel object) file is not a normal program. It is a chunk of kernel code that can be dynamically inserted into a running kernel and removed when no longer needed. This keeps the core kernel image small while still supporting a huge range of hardware and features.

✅ Real-world example
Your Wi-Fi card driver is almost certainly a .ko module. It loads when the card is detected and can be unloaded if the card is removed. The kernel does not need to carry driver code for every possible Wi-Fi chip permanently inside its image.

How Kernel Build Output Is Organized

🛠 make -j$(nproc)
vmlinux / bzImage
Built-in features (Y)
Always present at boot
.ko files (modules)
e1000e.ko   ext4.ko
nvme.ko   snd-hda-intel.ko
…hundreds more…
📁 make modules_install
/lib/modules/6.6.30-mykernel/

├─ kernel/drivers/
├─ kernel/net/
├─ kernel/fs/
├─ kernel/sound/
├─ modules.dep
└─ modules.alias

Why Modules Need to Be Installed

After the build, all .ko files live inside your kernel source directory (for example /usr/src/linux-6.6.30/). The running kernel and userspace tools like modprobe never look there. They look in one specific location:

/lib/modules/$(uname -r)/

The $(uname -r) part expands to the running kernel’s version string, for example 6.6.30-mykernelbuild. Every kernel version on the machine gets its own subdirectory here, so multiple kernels can coexist safely without their modules colliding.

Running make modules_install

From inside your kernel source directory run:

cd /path/to/linux-6.6.30
sudo make modules_install

This command does exactly two things:

1 — Copy .ko files

Every compiled .ko file is copied from the source tree into /lib/modules/<version>/kernel/, keeping the same subdirectory structure (drivers/, fs/, net/, sound/, etc.).

2 — Run depmod automatically

After copying, the build system calls depmod on the new module directory. This generates the dependency metadata files that modprobe needs to load modules in the right order.

Sample output looks like this:

INSTALL arch/x86/crypto/aesni-intel.ko
INSTALL drivers/net/ethernet/intel/e1000e/e1000e.ko
INSTALL fs/ext4/ext4.ko
INSTALL sound/pci/hda/snd-hda-intel.ko
...
DEPMOD 6.6.30-mykernelbuild
⚠️ Why sudo is required
The directory /lib/modules/ is only writable by root. You need sudo. The only exception is when you redirect the install to a path you own using INSTALL_MOD_PATH (covered next).

Understanding depmod

Some modules depend on other modules. For example a USB audio driver needs the USB core module loaded first, and the USB core might need the USB host controller driver. depmod figures all of this out automatically.

How depmod Maps Module Dependencies

You want
snd-usb-audio.ko
→ needs
which needs
snd-usbmidi-lib.ko
→ needs
which needs
snd.ko
depmod writes this to modules.dep:
snd-usb-audio.ko: snd-usbmidi-lib.ko snd.komodprobe reads modules.dep and loads in correct order:
snd.kosnd-usbmidi-lib.kosnd-usb-audio.ko

The metadata files depmod generates:

File Purpose
modules.dep Lists every module and the modules it depends on
modules.alias Maps hardware IDs (PCI, USB) to module names so the kernel can auto-load drivers
modules.symbols Maps kernel symbol names to the modules that export them
modules.builtin Lists all features compiled directly into the kernel (not as modules)

INSTALL_MOD_PATH — Critical for Embedded Development

If you are cross-compiling a kernel for an embedded target (ARM Cortex-A, RISC-V, MIPS, etc.) on your x86 laptop, you must never install the target’s modules into your laptop’s /lib/modules/. That would destroy your laptop’s own module setup.

The INSTALL_MOD_PATH variable redirects the install to any directory you choose:

# Create a staging directory (no sudo needed — you own it)
export INSTALL_MOD_PATH=/home/ravi/staging/rootfs

# Install target modules into staging directory
make INSTALL_MOD_PATH=${INSTALL_MOD_PATH} modules_install

# Result: /home/ravi/staging/rootfs/lib/modules/6.6.30-arm/
#   kernel/drivers/ ...
#   modules.dep ...
# Then copy this to the target's SD card or flash

Embedded Cross-Compile Module Install Workflow

💻 Host Machine (x86_64)
Cross compile kernel for ARM
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-Install modules to staging:
make INSTALL_MOD_PATH=/staging modules_install

/staging/lib/modules/6.6.30-arm/
├─ kernel/drivers/
├─ modules.dep
└─ modules.alias
⚙ Target Device (ARM)

Copy /staging/ to SD card or rootfs

/lib/modules/6.6.30-arm/
Modules ready to use
modprobe works correctly
Boot succeeds
⚠️ Never forget INSTALL_MOD_PATH when cross-compiling
Running sudo make modules_install without it during a cross-compile will silently overwrite your host machine’s kernel modules. Your host system may become unbootable. Always set INSTALL_MOD_PATH to a safe staging directory.
Scenario Command Install Destination
Native build (desktop / server) sudo make modules_install /lib/modules/<version>/
Cross-compile for embedded make INSTALL_MOD_PATH=/staging modules_install /staging/lib/modules/<version>/
Custom rootfs build make INSTALL_MOD_PATH=/my/rootfs modules_install /my/rootfs/lib/modules/<version>/

📷 Suggested Image Here
A split-screen diagram showing a laptop (host) on the left with an arrow pointing to an embedded board (target) on the right, with an SD card in between. Label it: “Cross-compile module install using INSTALL_MOD_PATH”. Alt text: Embedded Linux cross-compilation kernel module install workflow with INSTALL_MOD_PATH.

Step 6 — Generating initramfs and Updating the Bootloader

The Boot Problem initramfs Solves

Here is a situation that comes up on almost every modern Linux machine. The kernel needs to mount the root filesystem to continue booting. But the root filesystem might live on a device that requires a kernel module to work. For example:

The Chicken-and-Egg Boot Problem

❓ What the kernel needs

Root on NVMe SSD → needs nvme.ko
Root on LVM → needs dm-mod.ko
Root on RAID → needs md-mod.ko
Root encrypted → needs dm-crypt.ko

❌ The problem

To load .ko modules you need a filesystem to read them from.

But to read the filesystem you need the modules loaded first.

Classic deadlock.

✓ Solution: initramfs — a tiny filesystem loaded entirely into RAM before the disk is needed

What initramfs Is

initramfs (initial RAM filesystem) is a small compressed archive — think of it like a tiny ZIP file — that the bootloader loads into RAM alongside the kernel image. The kernel unpacks it into a temporary in-memory filesystem and immediately runs the /init script inside it.

That init script is responsible for loading the early boot modules, setting up the root device, and then handing off to the real filesystem on disk.

What Is Inside a Typical initramfs Archive

📦 initrd.img-6.6.30-mykernelbuild (compressed cpio archive, lives in /boot)

🔨 Early Tools

/bin/busybox
/sbin/mount
/sbin/modprobe
/sbin/fsck

💾 Essential Modules

nvme.ko
ext4.ko
dm-crypt.ko
virtio-blk.ko

⚙ Init Script

/init
Loads modules
Mounts real root
Calls switch_root

💡 initrd vs initramfs — what is the difference?
initrd (initial ramdisk) was the old approach — a block device image the kernel mounted as a virtual disk. initramfs is the modern replacement — a cpio archive the kernel extracts directly into memory (tmpfs). initramfs is simpler, boots faster, and needs less code in the kernel. Modern distros use initramfs internally even when they still name the file initrd.img for historical reasons.

The Complete Linux Boot Flow

Linux Boot Sequence with initramfs (Linux 6.x)

⚡ Power ON / UEFI Firmware

Initializes CPU and RAM, locates boot device

📄 GRUB2 Bootloader

Reads /boot/grub/grub.cfg → loads vmlinuz-6.6.30 + initrd.img-6.6.30 into RAM

🛠 Kernel Starts

Decompresses itself, sets up memory management, extracts initramfs into tmpfs

💾 /init Script Runs (inside initramfs)

Loads nvme.ko, ext4.ko, etc. → mounts real root at /sysroot → calls switch_root

💻 Real Root Filesystem Active

initramfs RAM freed, /sbin/init (systemd) starts, all services launch

💻 Login Prompt

System fully booted

How to Generate initramfs — By Distribution

On Ubuntu/Debian the easiest method is a single command run from the kernel source directory:

sudo make install

This copies bzImage to /boot/vmlinuz-<version>, calls update-initramfs to build the initramfs, and runs update-grub to refresh the boot menu — all in one step.

On other distributions you may need to do this manually:

# Ubuntu / Debian
sudo update-initramfs -c -k 6.6.30-mykernelbuild
sudo update-grub

# Fedora / RHEL 9 / AlmaLinux / Rocky Linux (uses dracut)
sudo dracut --force /boot/initramfs-6.6.30-mykernelbuild.img 6.6.30-mykernelbuild
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

# Arch Linux / Manjaro (uses mkinitcpio)
sudo mkinitcpio -k 6.6.30-mykernelbuild -g /boot/initramfs-6.6.30-mykernelbuild.img
sudo grub-mkconfig -o /boot/grub/grub.cfg
Distribution initramfs Tool GRUB Update Command
Ubuntu / Debian update-initramfs sudo update-grub
Fedora / RHEL 9+ / AlmaLinux dracut sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Arch / Manjaro mkinitcpio sudo grub-mkconfig -o /boot/grub/grub.cfg
openSUSE dracut sudo grub2-mkconfig -o /boot/grub2/grub.cfg
✅ Linux 6.x Update: dracut is now the standard on most distros
Ubuntu 22.04+ and Debian 12+ still use initramfs-tools. Fedora 38+, RHEL 9+, AlmaLinux 9, and Rocky Linux 9 use dracut by default. Arch Linux uses mkinitcpio. All three tools produce the same end result — an initramfs archive in /boot/.

Complete Kernel Build Checklist

End-to-End Kernel Build Steps

1
Configure   make menuconfig — select features and modules
2
Build   make -j$(nproc) — compiles vmlinux, bzImage, and all .ko files
3
Install modules   sudo make modules_install — copies .ko files and runs depmod
4
Install kernel + generate initramfs   sudo make install — copies bzImage, builds initramfs, updates GRUB
5
Reboot and verify   sudo reboot — select new kernel from GRUB menu, confirm with uname -r

Verifying After Reboot

# Confirm which kernel is running
uname -r
# Expected: 6.6.30-mykernelbuild

# Confirm module directory exists
ls /lib/modules/$(uname -r)/

# List all currently loaded modules
lsmod

# Check if a specific module is loaded
lsmod | grep ext4

# Load a module manually
sudo modprobe e1000e

# Get details about a module
modinfo e1000e

📷 Suggested Image Here
Screenshot of GRUB boot menu showing two kernel entries — the original distro kernel and the new custom-built kernel. Label it: “GRUB boot menu showing newly installed Linux 6.x kernel”. Alt text: GRUB2 boot menu with custom built Linux kernel entry after make install.

Frequently Asked Questions (FAQ)

What happens if I forget make modules_install before rebooting?

The new kernel will look for modules in /lib/modules/6.6.30-mykernelbuild/ and find nothing there. If the filesystem or disk driver is a module, the system may drop to an emergency shell or fail to boot entirely. Even if it boots, networking, audio, and many devices will not work because their drivers are missing.

Can I run depmod manually if I need to?

Yes. If for any reason you need to regenerate the module dependency files without doing a full install, run: sudo depmod -a 6.6.30-mykernelbuild. The -a flag tells depmod to consider all modules. This is sometimes useful after manually copying extra .ko files into the module directory.

Do I need initramfs if my root filesystem driver is built-in?

Strictly speaking, no — if every driver needed at boot time is compiled directly into the kernel (Y, not M), and you do not use LVM, RAID, or encryption, the kernel can mount the root filesystem directly. But most distribution kernels compile drivers as modules for flexibility, so initramfs becomes necessary. Even if you technically don’t need it, most distros still generate one as a safety net.

How do I add custom modules to the initramfs?

On Ubuntu/Debian, add the module name to /etc/initramfs-tools/modules and run sudo update-initramfs -u -k $(uname -r). On Fedora/RHEL using dracut, add add_drivers+=" mymodule " to a file in /etc/dracut.conf.d/ and regenerate. On Arch with mkinitcpio, add the module to the MODULES array in /etc/mkinitcpio.conf and run sudo mkinitcpio -P.

What is the difference between modprobe and insmod?

insmod loads a single module from a file path you give it. It does not handle dependencies — if a required module is missing, it fails. modprobe reads the modules.dep file and automatically loads all dependencies first, then loads your target module. For all normal use, modprobe is the right tool. insmod is mainly used during kernel module development when you want to load a specific file directly.

What is switch_root and why does the initramfs init script call it?

Once the initramfs init script has loaded necessary modules and mounted the real root filesystem at /sysroot, it calls switch_root /sysroot /sbin/init. This atomically swaps the current root (the tmpfs initramfs) for the real disk root, releases the RAM used by initramfs, and exec-replaces itself with the real init process (systemd on modern systems). After this point the system runs entirely from the real disk.

🎓 Interview Questions & Answers

Q1. What exactly does make modules_install do under the hood?
It iterates over all compiled .ko files in the kernel build tree and copies them to /lib/modules/<version>/kernel/, preserving the subdirectory structure. After the copy it invokes depmod -a <version> which scans all the installed modules, resolves inter-module dependencies, and writes the dependency information into metadata files (modules.dep, modules.alias, modules.symbols). Without this step the kernel and tools like modprobe cannot locate or correctly load any modules at runtime.
Q2. Explain depmod and what files it produces.
depmod scans every .ko file in the module directory, reads the symbol imports and exports from each, and figures out which modules depend on which. It writes this to modules.dep (the dependency graph), modules.alias (hardware ID to module name mappings used for auto-loading by udev), and modules.symbols (symbol to module mappings). Together these allow modprobe to load modules and their full dependency chain with a single command.
Q3. What problem does initramfs solve?
It solves the chicken-and-egg boot problem. The kernel needs to mount the root filesystem to continue booting. But on modern systems the root filesystem may sit on an NVMe drive, a RAID array, an LVM volume, or an encrypted partition — all of which require kernel modules to be loaded first. Those modules are files on the root filesystem, which is not yet mounted. initramfs breaks the deadlock by providing a tiny in-RAM filesystem that is already available when the kernel starts. The kernel runs an init script from it to load the needed modules and mount the real root, then switches over to the real filesystem.
Q4. What is INSTALL_MOD_PATH and when is it essential?
INSTALL_MOD_PATH redirects make modules_install to install kernel modules into a directory you specify instead of the system /lib/modules/. It is essential during cross-compilation for embedded targets. If you are building a kernel for an ARM board on an x86 laptop and run sudo make modules_install without it, the target’s modules get installed into your laptop’s /lib/modules/ — potentially making your laptop unbootable. Setting INSTALL_MOD_PATH to a staging directory keeps the host safe and makes it easy to copy the modules to the target device.
Q5. What is the difference between initrd and initramfs?
initrd (initial ramdisk) was a block device image that the kernel mounted using a ramdisk driver, just like a virtual floppy disk in RAM. initramfs is a cpio archive that the kernel can unpack directly into tmpfs — no block device emulation needed. initramfs is simpler (fewer kernel code paths), boots faster, and the kernel can free the RAM immediately after switching to the real root. All modern Linux kernels use initramfs. Many tools and filenames still say initrd for historical compatibility, but the content is initramfs format.
Q6. Walk through the Linux boot sequence from power-on to login prompt.
UEFI or BIOS runs first and initializes hardware, then hands off to GRUB2. GRUB reads its config from /boot/grub/grub.cfg and loads the chosen kernel image (vmlinuz) and the initramfs image (initrd.img) into RAM. The kernel decompresses itself, sets up memory management and CPU state, then extracts the initramfs into a tmpfs. The kernel executes /init from the initramfs. That script loads required early-boot modules (NVMe, ext4, dm-crypt, etc.), mounts the real root filesystem, and calls switch_root to atomically swap to the real root and exec systemd. Systemd then brings up all services and eventually presents the login prompt.
Q7. What dracut command regenerates initramfs on Fedora/RHEL?
sudo dracut --force /boot/initramfs-<version>.img <version>. The --force flag overwrites an existing file. The version string must match exactly what uname -r would return for that kernel. After regenerating, run sudo grub2-mkconfig -o /boot/grub2/grub.cfg to update the boot menu.
Q8. What does modinfo show and why is it useful?
modinfo <module-name> displays metadata embedded in the .ko file: the filename, description, author, license, version, supported kernel version, and most importantly the list of parameters the module accepts. It also shows alias patterns and firmware files the module may need. It is useful for understanding what a module does before loading it, for finding configurable parameters, and for verifying the module was compiled for the correct kernel.

 

Leave a Reply

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