Linux Kernel Modules: How to Build and Load Your First Module

Linux kernel modules are pieces of code that can be loaded into or removed from the Linux kernel without rebuilding the entire kernel. They are widely used for device drivers, hardware support, networking, and other kernel-level functionality. In this tutorial, you will learn how to write a simple kernel module in C, compile it using a Makefile, load it into the Linux kernel, verify its output, and safely unload it.

Linux is widely used in embedded systems, automotive electronics, networking devices, industrial controllers, and Internet of Things (IoT) applications. One of the reasons for its popularity is its ability to support different hardware devices and system features through a flexible kernel architecture. At the heart of Linux is the Linux kernel, which manages hardware resources, memory, processes, and communication between software and hardware. However, the kernel does not need to contain every possible driver or feature permanently. Many components can be added when required through loadable kernel modules. For example, imagine developing an embedded Linux device that uses a particular sensor, communication interface, or hardware controller. Instead of rebuilding the entire kernel whenever you need to add compatible functionality, you may be able to develop and load a suitable kernel module. Learning Linux kernel module programming is therefore an important step for students interested in embedded Linux, device driver development, operating systems, and low-level C programming. In this Linux kernel module tutorial, we will build a simple module that prints a message when loaded and another message when unloaded. You will also learn how to compile the module, inspect kernel logs, and troubleshoot common errors.

Linux Kernel Modules_ Build and Load Your First Module

1. What Are Linux Kernel Modules?

A Linux kernel module is a piece of code that extends the functionality of the Linux kernel. It can be loaded into kernel memory when needed and removed when it is no longer required, subject to the module’s dependencies and whether it is currently in use.

Modules are commonly used for:

  • Device drivers for supported hardware.
  • Network interface drivers and networking functionality.
  • Filesystem support.
  • Hardware-specific features in embedded Linux systems.
  • Other kernel functionality that can be built as a loadable component.

A compiled loadable kernel module commonly uses the .ko file extension, which stands for kernel object.

How does a kernel module work?

Consider a simple example. You write a module in C, compile it against the appropriate kernel build files, and produce a file named hello.ko. You then ask Linux to load it into the running kernel.

The module’s initialization function runs when the module loads successfully. When the module is removed, its exit function runs, allowing it to release resources or perform cleanup.

The process looks like this:

  1. Write the module source code in C.
  2. Create a Makefile to describe the build.
  3. Compile the source code into a kernel module.
  4. Load the module into the running kernel.
  5. Check the kernel log for the expected message.
  6. Unload the module and verify its cleanup message.

Kernel modules vs ordinary programs

FeatureOrdinary user-space programLinux kernel module
Execution locationUser spaceKernel space
Hardware accessUsually through system interfaces and driversCan implement kernel-level hardware functionality
Failure impactOften limited to the processMay affect the entire operating system
Typical outputTerminal or application logsKernel log
Loading methodRun an executableLoad through kernel module tools
Programming interfacesStandard user-space libraries and APIsKernel APIs and headers
A kernel module is not simply a C program that you execute from the terminal. It must follow the kernel’s programming rules and be compatible with the target kernel.

 

registor_now_P

 

2. Prerequisites for Linux Kernel Module Development

Before creating your first Linux kernel module, prepare a Linux environment with the required development tools.

You will need:

  • A Linux machine or virtual machine.
  • Basic knowledge of C programming.
  • Access to a terminal.
  • GCC or another supported compiler toolchain.
  • GNU Make.
  • Kernel headers or the appropriate kernel build directory for the running kernel.
  • Administrative privileges to load and unload the module.

For beginners, Ubuntu running in a virtual machine is a practical choice. A virtual machine also helps reduce the risk of disrupting your primary operating system while experimenting with kernel code.

Step 1: Check the Linux kernel version

Open a terminal and execute:

uname -r

This command displays the version and build identifier of the running kernel.

For example, the output might resemble:

6.x.x-xx-generic

Your actual output will depend on your Linux distribution and installed kernel.

Why does this matter? Kernel modules must be built for a compatible target kernel. The running kernel’s headers and build configuration are usually the correct starting point when building a module for that system.

Step 2: Install development tools

On Ubuntu or Debian-based systems, run:

sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)

The build-essential package provides commonly used compilation tools, including GCC and Make. The kernel headers package supplies files required to build external modules for the matching kernel.

Check that the kernel build directory exists:

ls -ld /lib/modules/$(uname -r)/build

If the directory is missing, your distribution may not provide matching headers for the running kernel. You may need to install the appropriate kernel development package or boot into a kernel version for which matching build files are available.

Important: Installing the headers for a different kernel version does not automatically make a module compatible with the kernel you are currently running.

3. How to Create Your First Linux Kernel Module

Now let’s create a simple Linux kernel module example using C.

The module will print a message when it is loaded and another message when it is unloaded. It will not control hardware or modify system settings, making it a useful first exercise.

Step 1: Create a project directory

Run:

mkdir linux-kernel-module
cd linux-kernel-module

Create a source file named hello.c:

nano hello.c

Add the following code:

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("IIES Student");
MODULE_DESCRIPTION("A simple Linux kernel module");

static int __init hello_init(void)
{
    pr_info("Hello, Linux kernel module loaded!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    pr_info("Goodbye, Linux kernel module unloaded!\n");
}

module_init(hello_init);
module_exit(hello_exit);

Save the file and exit the editor.

Let’s understand the important parts of this code.

Understanding the kernel module code

1. Header files

#include <linux/init.h>
#include <linux/module.h>

The linux/init.h header provides initialization and cleanup annotations, including __init and __exit. The linux/module.h header provides declarations used for kernel modules, including module metadata and registration macros.

2. Module metadata

MODULE_LICENSE("GPL");
MODULE_AUTHOR("IIES Student");
MODULE_DESCRIPTION("A simple Linux kernel module");

These macros describe the module. The license declaration is particularly important because the kernel uses module licensing information when determining whether certain kernel interfaces are available.

Use accurate licensing information for your actual code; do not declare a license that does not apply to your module.

3. Initialization function

static int __init hello_init(void)
{
    pr_info("Hello, Linux kernel module loaded!\n");
    return 0;
}

The hello_init() function is called when the module is successfully initialized.

The pr_info() macro writes an informational message to the kernel log. It does not print directly to the terminal like printf() normally would in a user-space C program.

Returning 0 indicates successful initialization. A negative error code should be returned if initialization fails.

The __init annotation identifies initialization code that the kernel can discard after initialization has completed.

4. Cleanup function

static void __exit hello_exit(void)
{
    pr_info("Goodbye, Linux kernel module unloaded!\n");
}

The hello_exit() function is called when the module is unloaded normally.

In a real device driver, this function might unregister a device, release resources, or stop operations started during initialization.

The __exit annotation identifies cleanup code. For a module built specifically as a loadable module, the exit function is used during normal unloading.

5. Registering the functions

module_init(hello_init);
module_exit(hello_exit);

module_init() identifies the initialization function, while module_exit() identifies the cleanup function for a loadable module.

Without the appropriate registration, the kernel would not know which functions to invoke for the module’s initialization and cleanup lifecycle.

4. How to Create a Linux Kernel Module Makefile

Writing the C source file is only part of Linux kernel module development. You must also build it using the kernel’s external-module build system.

Create a file named Makefile in the same directory:

nano Makefile

Add the following:

obj-m += hello.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
Important: The command lines under all: and clean: must begin with a real tab character, not spaces.

How does the Makefile work?

obj-m += hello.o — This tells the kernel build system to build hello.o as an external module. The resulting loadable module will be named hello.ko.

KDIR — KDIR := /lib/modules/$(shell uname -r)/build points to the build directory for the currently running kernel.

M=$(PWD) — This identifies the directory containing your external module source and Makefile.

modules — This invokes the kernel build system’s module-building target.

clean — This removes generated build artifacts associated with the module build.

Using the kernel’s own build system is preferable to manually invoking GCC because kernel code depends on the target kernel’s configuration, compiler options, headers, and build conventions.

 

Explore Courses - Learn More

 

5. How to Compile a Linux Kernel Module

Once hello.c and Makefile are ready, run this command from the project directory:

make

The Makefile invokes the kernel build system and compiles your source file.

If the build succeeds, the directory should contain a file named hello.ko.

You can check the generated files using:

ls -l

Depending on the kernel and build configuration, you may also see intermediate files such as hello.o, hello.mod, hello.mod.c, or Module.symvers.

What happens during compilation?

The kernel build process generally performs these steps:

  1. Processes the module source code.
  2. Compiles the source into an object file.
  3. Resolves the required kernel symbols and module metadata.
  4. Links the module into a loadable kernel object.
  5. Produces the .ko file.

If compilation fails, read the first meaningful error in the output rather than focusing only on the final make failure message.

Common causes include missing kernel headers, a malformed Makefile, incorrect indentation, incompatible source code, and build files that do not match the intended kernel.

6. How to Load a Linux Kernel Module

After compiling the module successfully, the next step is to load it into the running kernel.

Warning: Kernel modules execute with kernel-level privileges. Incorrect code can crash the system or compromise its security. Test only code you understand, preferably inside a disposable virtual machine, and never load untrusted modules.

Step 1: Load the module using insmod

Run:

sudo insmod hello.ko

The insmod command attempts to insert the specified module into the running kernel.

If the command succeeds, it commonly produces no terminal output. That does not mean nothing happened; it means the insertion completed without reporting an error.

If the module cannot be loaded, inspect the error message and kernel logs.

Step 2: Verify the module using lsmod

Execute:

lsmod | grep hello

If the module is loaded, you should see an entry beginning with hello, followed by fields such as its size and usage count.

The lsmod command displays currently loaded modules. The grep hello portion filters the output to show lines containing the module name.

If nothing appears, check whether insertion failed, whether the module name differs from the expected name, or whether you are inspecting the correct system.

Step 3: Check the kernel log using dmesg

Run:

sudo dmesg | tail -n 20

Look for a message similar to:

Hello, Linux kernel module loaded!

The precise output depends on your system, and the message may not be visible at the end if other kernel messages were written afterward.

The dmesg command displays kernel ring-buffer messages. On some systems, access to these messages is restricted, which is why sudo may be needed.

You have now compiled and loaded your first Linux kernel module.

7. How to Unload a Linux Kernel Module

A good kernel module tutorial should cover both loading and unloading. Cleanup is an essential part of kernel development because modules may allocate memory, register devices, or start operations that must be stopped before removal.

To remove the example module, execute:

sudo rmmod hello

The rmmod command removes the module named hello, provided that the kernel permits its removal.

Check the kernel log again:

sudo dmesg | tail -n 20

You should find a message similar to:

Goodbye, Linux kernel module unloaded!

You can also check whether the module remains loaded:

lsmod | grep hello

If the module was removed successfully, the command should return no matching entry.

What is the difference between insmod, rmmod, lsmod, and modprobe?

These commands are frequently used in Linux kernel module programming.

CommandPurpose
insmod hello.koInserts the specified module file into the kernel
rmmod helloRequests removal of a loaded module by name
lsmodLists modules currently loaded in the kernel
modprobe module_nameLoads a module by name and handles dependencies using the system’s module database
modprobe -r module_nameRequests removal of a module and handles applicable dependency relationships
dmesgDisplays kernel log messages, including messages produced by the example module

One important distinction is that insmod expects a module file path, whereas modprobe typically resolves a module by name using the installed module tree and dependency information.

For modules installed in the system’s standard module directories, modprobe is generally more convenient because it can handle dependencies. For a freshly built external module in your current directory, insmod hello.ko is a straightforward demonstration.

8. Common Linux Kernel Module Errors and Solutions

Beginners often encounter errors when building or loading their first kernel module. Understanding the cause of each error is more useful than repeatedly running the same command.

Error 1: Kernel headers are missing

You may see an error indicating that the kernel build directory cannot be found.
Possible cause: The headers or build files for the running kernel are not installed.
Solution on Ubuntu or Debian:

sudo apt update
sudo apt install linux-headers-$(uname -r)

Then check:

ls -ld /lib/modules/$(uname -r)/build

If the package is unavailable, check whether your distribution provides a matching kernel development package or whether you need to boot into a supported kernel version.

Error 2: Invalid module format

You may encounter an error such as:

Invalid module format

Possible cause: The module was built for a different kernel version, uses incompatible kernel configuration settings, or has another compatibility problem.
Solution: Rebuild the module against the correct kernel build files. Check the running kernel version and inspect the kernel log for a more specific explanation:

uname -r
sudo dmesg | tail -n 30

The kernel’s version magic and other compatibility checks can prevent a module built for an incompatible kernel from loading.

Error 3: Operation not permitted

The kernel may refuse to load a module even when you use sudo.

Possible causes include:

  • Secure Boot or kernel module signature enforcement.
  • Kernel lockdown policies.
  • System security restrictions.
  • Other platform-specific module-loading rules.

Solution: Inspect the kernel log and your distribution’s Secure Boot documentation. Some environments require modules to be signed with a trusted key. Do not disable security features without understanding the consequences.

Error 4: Module is in use

When running rmmod, you may see an error indicating that the module is in use.
Possible cause: Another component depends on the module, or the module’s reference count prevents removal.
Solution: Inspect the module’s users and dependencies before attempting removal. Stop the dependent activity safely, and avoid forcing removal of a module that is actively being used.

Error 5: The expected message does not appear

You may load the module successfully but fail to find the message in your terminal.
Possible cause: The message was written to the kernel log rather than standard output.
Solution: Run:

sudo dmesg | tail -n 50

Also verify that the module appears in lsmod. If your system restricts kernel-log access or uses a different logging configuration, consult its system logs.

9. Practical Exercises for Embedded Systems Students

Once the basic module works, try the following exercises to strengthen your understanding of Linux kernel module development.

Exercise 1: Change the log message

Replace the initialization message with:

pr_info("IIES Linux kernel module is running!\n");

Rebuild and reload the module. Remember to unload the previous version before inserting the new one.

Exercise 2: Add a module parameter

Linux modules can accept parameters when loaded. Research the kernel’s module_param() and MODULE_PARM_DESC() macros, then add a simple integer parameter and print its value in the initialization function.

Use appropriate parameter permissions and types, and validate values when necessary. This exercise introduces configurable module behavior without requiring hardware.

Exercise 3: Understand initialization and cleanup

Add a second log message to the initialization function and observe the order in which the messages appear. Then unload the module and confirm that the cleanup function runs.

Exercise 4: Explore device driver development

Research how Linux device drivers interact with hardware through kernel APIs. Study concepts such as device registration, memory-mapped I/O, interrupts, and synchronization before attempting a hardware driver.

Do not access hardware registers directly without understanding the target device, its memory map, and the relevant kernel interfaces.

Exercise 5: Explore embedded Linux

Investigate how external modules are built for a development board running embedded Linux. In cross-compilation environments, the build process must use the appropriate target kernel source or build tree, configuration, toolchain, and architecture.

A module built for your desktop Linux system should not be assumed to work on a different embedded board.

10. Why Linux Kernel Module Programming Matters in Embedded Systems

Linux kernel modules are relevant to embedded development because many embedded devices run Linux while relying on specialized hardware and configurable kernel functionality.

Examples include:

  • IoT gateways: Supporting networking interfaces and compatible peripheral devices.
  • Industrial systems: Integrating supported hardware and device interfaces.
  • Automotive development: Working with Linux-based platforms and device driver stacks.
  • Embedded networking: Developing and testing kernel-level networking components.
  • Edge computing: Supporting hardware interfaces used by computing and communication platforms.

Learning to build and load a kernel module helps you understand the boundary between user-space applications and kernel-space functionality.

However, not every hardware feature requires a custom module. Linux already provides many drivers, and some devices are best accessed through existing kernel interfaces, user-space APIs, or established driver frameworks.

For students aiming for embedded Linux or device driver roles, the next learning steps include Linux system programming, kernel APIs, character device drivers, device trees, interrupts, synchronization, and cross-compilation.

Conclusion

Linux kernel modules provide a flexible way to extend kernel functionality without rebuilding the entire kernel for every supported feature. By creating a simple module in C, writing a Makefile, compiling it, loading it with insmod, inspecting its output using dmesg, and removing it with rmmod, you have learned the fundamentals of the module development workflow.

The most important lesson is that kernel programming differs from ordinary C programming. Kernel modules run with elevated privileges, depend on kernel interfaces, and require careful resource management and compatibility checks.

Practice the example in a virtual machine, understand each command, and gradually move toward module parameters and device driver development. These fundamentals will help you build a stronger foundation in Linux kernel programming and embedded Linux engineering.

insmod inserts a specified module file directly, while modprobe normally loads a module by name using the installed module database and handles dependencies. modprobe is generally more convenient for managing modules installed in the standard system module directories.

 

Talk to Academic Advisor

Frequently Asked Questions

A Linux kernel module is a piece of code that extends the functionality of the Linux kernel. It can be loaded when needed and removed when no longer required, making it useful for device drivers, networking, and other supported kernel features.

Write the module in C, create a Makefile using the kernel build system, and run make to generate the .ko file. You can then load it using sudo insmod hello.ko, verify it with lsmod, and inspect its output using dmesg.

insmod inserts a specified module file directly, while modprobe normally loads a module by name using the installed module database and handles dependencies. modprobe is generally more convenient for managing modules installed in the standard system module directories.

Author

Embedded Systems and IOT Trainer– IIES

Updated On: 17-10-26


10+ years of hands-on experience delivering practical training in Embedded Systems and it's design