Previous Lecture
Next Lecture
Required Headers
module_init / module_exit
Building with make
insmod / rmmod
dmesg output
Common Build Errors
Setup Requirements
Before you can write and load a kernel module, your development machine needs two things installed and working together:
sudo apt update
sudo apt install build-essential
build-essential pulls in gcc, g++, make, and other needed tools in one shot.
# Install headers matching your running kernel
sudo apt install linux-headers-$(uname -r)
# Verify installation:
ls /lib/modules/$(uname -r)/build
# You should see a directory — not "No such file or directory"
On Fedora/RHEL/CentOS systems, the equivalent commands are:
sudo dnf install gcc make kernel-devel kernel-headers
On Arch Linux:
sudo pacman -S base-devel linux-headers
Important note for kernel 6.x: Starting from kernel 6.3, several header files were reorganized. If you are on kernel 6.3 or newer and your module uses older header paths, you may get include errors. The module we write below uses paths that work from kernel 5.4 all the way through 6.x.
Create a new directory for your module and create the source file:
mkdir ~/hello_module
cd ~/hello_module
Now create a file called hello.c with the following content:
/*
* hello.c - A simple Hello World Linux Kernel Module
*
* Demonstrates the basic structure of every kernel module:
* - Required includes
* - Module metadata
* - Init function (called on insmod)
* - Exit function (called on rmmod)
*
* Tested on Linux kernel 5.15 through 6.x
*/
#include <linux/module.h> /* Required for all kernel modules */
#include <linux/kernel.h> /* Needed for KERN_INFO, pr_info() */
#include <linux/init.h> /* Needed for __init and __exit macros */
/* ---- Module Metadata ---- */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name <your@email.com>");
MODULE_DESCRIPTION("Hello World - First Kernel Module");
MODULE_VERSION("1.0");
/*
* hello_init - called when module is loaded with insmod
*
* Returns 0 on success, negative errno on failure.
* Returning non-zero aborts the load.
*/
static int __init hello_init(void)
{
pr_info("Hello World! Module loaded successfully.\n");
pr_info("I am now running inside the kernel!\n");
return 0; /* 0 = success */
}
/*
* hello_exit - called when module is removed with rmmod
*
* Must undo everything hello_init() did.
* No return value.
*/
static void __exit hello_exit(void)
{
pr_info("Goodbye! Module unloaded cleanly.\n");
}
/* Register our init and exit functions with the LKM framework */
module_init(hello_init);
module_exit(hello_exit);
Let us walk through every part of this code carefully.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
linux/module.h — The most important header for any kernel module. It defines module_init(), module_exit(), MODULE_LICENSE(), and the core structures the kernel uses to manage modules. You cannot write a module without this.
linux/kernel.h — Provides pr_info(), pr_err(), KERN_INFO, and many common macros and type definitions used throughout kernel code.
linux/init.h — Defines the __init and __exit section markers (explained below).
static int __init hello_init(void) { ... }
static void __exit hello_exit(void) { ... }
__init tells the compiler to place the init function in a special memory section called .init.text. After the module’s init function has run and the module is loaded, the kernel can free the memory occupied by that section because the init function will never be called again. This saves RAM — important for embedded systems.
__exit places the exit function in a .exit.text section. If the module is compiled as built-in (Y, not M), the exit function is never needed (you cannot unload built-in code), so the kernel can simply discard it at compile time.
static means these functions are private to this translation unit. They are not exported and cannot be called directly by other kernel code — the kernel only calls them through the registered function pointers.
pr_info("Hello World! Module loaded successfully.\n");
pr_info() is a convenience wrapper around printk() that automatically prepends the KERN_INFO log level. It is the preferred way to print informational messages in modern kernel code.
The output does not go to your terminal — it goes to the kernel ring buffer, which you read with dmesg. Part 5 of this series covers printk and log levels in full detail.
return 0; /* 0 = success, negative = failure */
The init function must return 0 to signal success. Any negative value signals failure and the kernel will not load the module. Standard error codes from <linux/errno.h> are used: -ENOMEM for memory allocation failure, -ENODEV for device not found, etc. Returning positive values is undefined behaviour in older kernels and should be avoided.
In the same directory as hello.c, create a file named exactly Makefile (capital M):
# Makefile for hello.c kernel module
# 'obj-m' tells the kernel build system to build hello.c as a module
obj-m += hello.o
# Path to the running kernel's build directory
KDIR := /lib/modules/$(shell uname -r)/build
# Default target: build the module
all:
make -C $(KDIR) M=$(PWD) modules
# Clean target: remove all build artifacts
clean:
make -C $(KDIR) M=$(PWD) clean
Critical: The lines under all: and clean: must start with a TAB character, not spaces. If you use spaces, make will give you a “missing separator” error.
Let us understand what this Makefile does:
obj-m += hello.o — Registers hello.o as an out-of-tree module to be built. The kernel build system knows it should compile hello.c and link it into hello.ko.KDIR := /lib/modules/$(shell uname -r)/build — Points to the kernel headers/build directory for your currently running kernel. The symlink here points to the actual kernel source or header tree.make -C $(KDIR) M=$(PWD) modules — Changes into the kernel build directory (-C), tells it this module is located at your current directory (M=$(PWD)), and requests a module build.Now let us go through the complete workflow step by step:
cd ~/hello_module
make
# Expected output (abbreviated):
# make -C /lib/modules/6.8.0-45-generic/build M=/home/user/hello_module modules
# make[1]: Entering directory '/usr/src/linux-headers-6.8.0-45-generic'
# CC [M] /home/user/hello_module/hello.o
# MODPOST /home/user/hello_module/Module.symvers
# CC [M] /home/user/hello_module/hello.mod.o
# LD [M] /home/user/hello_module/hello.ko
# make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-45-generic'
After a successful build, your directory will contain hello.ko — your kernel module file, ready to load.
sudo insmod hello.ko
# insmod gives no output on success.
# If it fails, check dmesg for the reason.
insmod (insert module) is the low-level command to load a .ko file. It does not resolve dependencies — it just loads the exact file you give it.
# Check if the module is listed in the running kernel:
lsmod | grep hello
# Output:
# hello 16384 0
# See the printk output from our init function:
dmesg | tail -5
# Output:
# [12345.678901] Hello World! Module loaded successfully.
# [12345.678910] I am now running inside the kernel!
sudo rmmod hello
# Check it is gone:
lsmod | grep hello
# (no output — module removed)
# See the exit function's message:
dmesg | tail -3
# Output:
# [12390.123456] Goodbye! Module unloaded cleanly.
make clean
# Removes: hello.o, hello.ko, hello.mod.c, hello.mod.o,
# Module.symvers, modules.order, .tmp_versions/
finit_module() system call (or the older init_module()).printk) by looking them up in the kernel’s exported symbol table (kallsyms). Unresolved symbols cause the load to fail./proc/modules and lsmod).Fix: Open the Makefile in a proper text editor, delete the whitespace before
make -C ... and press the Tab key once.Fix: Run
uname -r, ensure linux-headers-$(uname -r) is installed, then rebuild with make clean && make.Fix: Use
sudo insmod hello.ko. For Secure Boot, either disable it or sign the module.Fix:
sudo apt install linux-headers-$(uname -r)Fix: Check
lsmod — the third column shows the use count. Close any programs using the module, or remove dependent modules first.Previous Lecture
Next Lecture
