Feb 4

Built-In Kernel Code vs Loadable Modules: An Architectural Decision

Don't hesitate

Get the Professional Embedded Starter Kit: Production-ready templates and architectural cheat sheets for your firmware projects.

Built-In Kernel Code vs Loadable Modules: An Architectural Decision


1. Introduction

Linux is often described as a monolithic kernel, yet it supports dynamically loadable code. This apparent contradiction is resolved through kernel modules. Kernel modules allow code to be added to or removed from a running kernel without rebuilding or rebooting the system.

In embedded Linux systems, kernel modules are not a convenience feature. They are a structural tool used to manage hardware variability, development workflows, and long-term maintainability. Understanding what kernel modules are, and what they are not, is essential before discussing drivers or hardware support.

This article focuses on the architectural role of kernel modules. It intentionally avoids commands, build systems, and implementation details in order to explain why modules exist and how they behave inside the kernel.


2. What Runs Inside the Linux Kernel

The Linux kernel executes in a privileged CPU mode with unrestricted access to memory and hardware. Code running in this context is trusted by definition. There is no memory protection between different parts of the kernel.

Kernel code is responsible for core system functions such as process scheduling, memory management, interrupt handling, and hardware access. Errors in kernel code are not isolated. A fault in one component can crash or hang the entire system.

Because of this, the kernel is designed to be conservative. It provides a controlled environment where code must follow a strict set of rules regarding memory access, concurrency, and execution context.

Fig. 1 Kernel execution context and privilege boundaries


3. What a Kernel Module Is

A kernel module is a piece of kernel code that is compiled separately from the main kernel image but executes with the same privileges once loaded. From the CPU’s point of view, there is no distinction between built-in kernel code and module code.

A module is not a process and does not run independently. It does not have its own address space, scheduler context, or lifecycle in the way user space programs do. Once loaded, it becomes part of the kernel.

The purpose of a kernel module is not dynamic behavior but deferred inclusion. Modules exist so that certain kernel functionality can be added when needed rather than being permanently linked into the kernel image.

Fig. 2 Kernel image and loadable modules sharing a single execution environment


Struggling to implement this for a professional project? If you need to master full-scale firmware architecture, security, and build automation, join our 1-D or 4-Day Live Implemnetation Workshops. I'll show you the exact direct path to production-ready firmware without the trial and error.

4. Built-In Kernel Code vs Loadable Modules

Built-in kernel code is linked into the kernel image at build time. It is always present and initialized during early kernel startup. Loadable modules are stored separately and inserted into the kernel later.

From an execution standpoint, both behave identically once active. The difference lies in when they become available and how they are managed.

In embedded systems, built-in code is typically used for functionality required very early in boot or for hardware that must always be present. Modules are used where flexibility is required, such as optional hardware, multiple board variants, or staged bring-up during development.

Choosing between built-in code and modules is an architectural decision, not a performance optimization.

Fig. 3 Built-in kernel components versus dynamically loaded modules


5. Why Embedded Linux Uses Kernel Modules

Embedded Linux systems often target multiple hardware variants with a single software base. Kernel modules make this practical by allowing hardware support to be included only when needed.

Modules also simplify development and debugging. Reloading kernel code without rebooting the entire system reduces iteration time and makes fault isolation easier.

In deployed products, modules can support update strategies where hardware support evolves independently of the core kernel. This is particularly relevant when storage space, certification constraints, or long maintenance lifecycles are involved.

Fig. 4 Kernel modules enabling hardware variability in embedded platforms


6. The Lifecycle of a Kernel Module

A kernel module has a simple but strict lifecycle. It is loaded, initialized, used, and optionally unloaded.

A module exists as a binary object file on the target system. When it is inserted, the kernel links this code into the kernel address space and executes the module’s initialization routine. From that point onward, the module is indistinguishable from built-in kernel code.

In practice, insertion is an explicit operation. For example:

A kernel module has a simple but strict lifecycle. It is loaded, initialized, used, and optionally unloaded.

A module exists as a binary object file on the target system. When it is inserted, the kernel links this code into the kernel address space and executes the module’s initialization routine. From that point onward, the module is indistinguishable from built-in kernel code.

In practice, insertion is an explicit operation. For example:

insmod example_driver.ko

This command does not start a process or launch a service. It instructs the kernel to load the module’s code and run its initialization function. If initialization fails, the insertion fails and the module never becomes active.

Kernel feedback for this operation is reported through the kernel log. Immediately after insertion, messages generated by the module and by the kernel itself can be observed using dmesg:

[ 42.317845] example_driver: module loaded

[ 42.321102] example_driver: initializing hardware interface

These messages are produced from kernel context, not from user space. They reflect execution inside the kernel and are often the only visibility developers have into module behavior during early bring-up or failure analysis.

Once loaded, the module may register interfaces, callbacks, or hardware support with kernel subsystems. The kernel tracks the module’s state internally, including whether it is currently in use.

Unloading reverses this process. When removal is requested, the kernel verifies that the module is not actively referenced and then executes its cleanup logic:

rmmod example_driver

This transition is also reflected in the kernel log:

[ 58.904221] example_driver: module unloaded

If the module is still in use, removal is rejected and the cleanup code is not executed. In embedded systems, many modules are intentionally never unloaded to avoid complexity and to prevent destabilizing the system at runtime.

Importantly, successful insertion only means that the kernel accepted the code. It does not guarantee that hardware is present, correctly described, or functioning. The module lifecycle describes how code enters and leaves the kernel, not whether the system behaves correctly afterward.


7. Execution Context and Constraints

Kernel modules execute under the same constraints as the rest of the kernel. They must handle concurrency explicitly, respect interrupt context rules, and avoid operations that may block in inappropriate contexts.

There is no automatic protection against memory corruption or illegal access. A single invalid pointer dereference inside a module can crash the system.

Because of this, kernel modules must be written defensively and conservatively. The kernel assumes that module code is correct.

Fig. 6 Shared address space and failure impact of kernel modules


8. Conclusion

Kernel modules are a fundamental part of the Linux kernel’s architecture. They allow functionality to be included when needed while preserving a single, coherent kernel execution environment.

In embedded Linux systems, modules are a practical tool for managing hardware diversity, development workflows, and long-term maintenance.


Want to master this? Here are your next steps:



Created with