skip to content

On Linux, what is a loadable kernel module, and how does a driver compiled as a module (=m) differ at runtime from the same driver built into the kernel image (=y)?

level: juniorimportance: must knowfreq 68%

answer

  1. separate .ko file, not inside vmlinuz
  2. linked into the running kernel
  3. same privileges, no sandbox
  4. =y cannot be unloaded or blacklisted
  5. per-kernel tree under /lib/modules

basics

~20 s

A loadable kernel module is kernel code — typically a driver or filesystem — shipped as a separate .ko file and inserted into the running kernel on demand. Code built in with =y is part of the kernel image itself and can never be unloaded.

solid answer

~50 s

A loadable kernel module is a piece of kernel code — usually a device driver, a filesystem, or a network protocol — compiled into its own `.ko` object and linked into the *running* kernel at load time by `modprobe`. Once loaded it is indistinguishable from the rest of the kernel: same address space, same privileges, no sandbox, so a bad module panics the machine. Building it as `=m` is what lets a distribution support thousands of devices without a gigantic image: the module file sits under `/lib/modules/<kernel version>/`, and only the ones you actually need get loaded, usually automatically when a device appears. Building the same code `=y` links it into `vmlinuz`, so it is always present from the first instant of boot, needs no `/lib/modules` tree, and cannot be unloaded, blacklisted, or given parameters at load time — which is why the driver for the root filesystem is either built in or carried in the initramfs.

code

bash · 5 lines
bash
# Is this driver present as a loadable module right now?
lsmod | awk '$1 == "overlay"'

# Or was it compiled into the kernel image instead?
grep overlay /lib/modules/$(uname -r)/modules.builtin

go deeper

for a junior

Be able to say plainly that a module is kernel code in a .ko file that can be loaded and unloaded while the system runs, and that built-in code is inside the kernel image and stays there. Know that modules live under /lib/modules and are usually loaded automatically.

for a middle

Explain the =y/=m/=n build choice, the per-kernel-release module tree, and how a device's modalias string ends up selecting a module. Be ready to say why the root filesystem driver is special.

for a senior

Show judgment about what belongs built in versus modular on a fleet: boot-critical drivers, attack surface, the fact that a loadable module is a code-injection path into the kernel, and why you would disable module loading entirely on a hardened appliance.

for a principal

Own the tradeoff between a single broad distribution kernel and a trimmed custom build: support burden, security posture, out-of-tree driver lifecycle, and the cost of owning a kernel your vendor will not debug because it is tainted or modified.

## The kernel is not one monolithic binary in practice The Linux kernel is architecturally monolithic — everything runs in one privileged address space, unlike a microkernel where drivers are separate user-space servers. But it is *not* one indivisible file. Most driver, filesystem and protocol code is compiled into separate object files with a `.ko` extension ("kernel object") that can be linked into the running kernel and later unlinked, without a reboot. Every kernel config symbol that supports it has three states: `n` (not built), `m` (built as a loadable module), `y` (built into the kernel image). The *code* is identical; only the packaging differs. ## Where modules live Modules for a given kernel live under `/lib/modules/<kernel release>/`, where the release string is what `uname -r` prints. Inside it there is a `kernel/` directory tree mirroring the kernel source layout (`kernel/fs/`, `kernel/drivers/net/`, …), plus generated index files such as `modules.dep`, `modules.alias` and `modules.builtin`. That last file is worth knowing: it lists what was compiled in with `=y`, which is how `modprobe` can be asked for a built-in feature and quietly report success instead of an error. Because the tree is keyed by kernel release, every installed kernel has its own set of modules. A module built for one kernel is not visible to another. ## Loading is not a user-space thing Once inserted, a module is kernel code in kernel mode. It has no memory protection from the rest of the kernel, can touch any physical address, and a null-pointer dereference in it is an oops or a panic, not a crashed process. That is why loading requires the `CAP_SYS_MODULE` capability — being able to load a module is, in practice, being able to do anything. Modules also carry a licence declaration. Loading a module that is not GPL-compatible, or one that was built outside the kernel tree, sets a *taint* flag in the kernel that persists until reboot; distributions and upstream developers use taint to decide whether a bug report is theirs to fix. ## Who actually types `modprobe`? Usually nobody In day-to-day operation, modules are loaded on demand. Two mechanisms dominate: - **Device-driven autoload.** When the kernel discovers hardware it exports a `modalias` string describing it (bus type, vendor and device IDs). The device manager matches that string against the `modules.alias` index and loads the module that claims it. - **Feature-driven autoload.** When the kernel needs a capability it does not have — mounting a filesystem type, using a network protocol — it asks user space to load the module registered under a well-known alias for that feature. That is why a freshly booted machine usually shows a few dozen loaded modules that nobody asked for by name. ```bash lsmod # modules loaded right now, with reference counts modinfo overlay # file path, licence, parameters, vermagic ls /lib/modules/$(uname -r)/kernel/fs # the on-disk module tree for this kernel grep overlay /lib/modules/$(uname -r)/modules.builtin # or was it compiled in? ``` ## What `=y` buys and what it costs Built-in code is present before any filesystem is mounted, which matters for anything needed to *reach* `/lib/modules` in the first place: the disk controller, the root filesystem driver, sometimes the crypto for an encrypted root. Distributions solve that differently — they keep those as modules and ship them inside the initramfs, a small archive the boot loader hands to the kernel — but embedded and appliance kernels frequently just build them in. The costs of `=y` are real: - It cannot be unloaded. `rmmod` has nothing to remove; the code is not a module and does not appear in `/proc/modules`. - It cannot be blacklisted. Configuration that suppresses module autoloading has no effect on code already inside the image; disabling it needs a kernel command-line switch the driver itself implements, or a different kernel. - Its parameters can only be given on the kernel command line, not at load time, because there is no load time. - It occupies memory whether or not the hardware exists. ## The distinction interviewers are testing The wrong mental model — "a module is a plugin that runs in a safe sandbox" or "a module is a background service" — leads to bad reasoning about everything downstream: why an unsigned module is a security event, why a driver bug takes the whole box down, why a module built for another kernel refuses to load. Get the model right first: a module is the *same* kernel code, delivered late.

  • If modules are just kernel code, why does the kernel bother supporting them at all instead of building everything in?
    Distributions ship one kernel for millions of hardware combinations. Building every driver in would produce a huge image that must all be loaded into memory at boot. Modules let the same kernel carry thousands of drivers on disk and pay for only the handful the machine actually uses, and they let third-party or rarely-updated drivers ship separately from the kernel package.
  • Why does loading a module require CAP_SYS_MODULE rather than ordinary root permissions on a file?
    Because a module is not data being read, it is code being linked into the kernel with full privileges. Anyone able to load a module can patch syscalls, read any memory and disable every security control, so it is effectively equivalent to owning the kernel. Container runtimes drop the capability by default for exactly that reason.
  • What does it mean when someone says the kernel is "tainted", and does it change behaviour?
    Taint is a bitmask the kernel sets when something happened that upstream cannot support — a proprietary-licence module, an out-of-tree module, an unsigned module, a forced load, a prior oops. It is recorded and printed with every oops. It does not change how the kernel runs; it tells whoever reads the crash whether the report is actionable.

saying these in an interview costs you the question

  • Says modules run in user space so a crash is contained
  • Calls a kernel module a background daemon or a library
  • Claims a built-in (=y) driver can be removed with rmmod
  • Assumes a .ko built for one kernel loads on any kernel
  • Thinks any user can load a module if the file is readable

context