1. Overview

A kernel module provides a way to extend kernel functionality without rebuilding the entire kernel. Using the Buildroot toolchain ensures the module is ABI‑compatible with the kernel generated during your Buildroot build. QEMU then offers a convenient emulation environment to test modules without hardware.

Linux kernel modules are dynamically loadable pieces of code that extend the functionality of the kernel without requiring a reboot or recompilation. They are widely used for device drivers, filesystems, and various kernel extensions.

Modules can be loaded into the kernel using insmod or modprobe, and removed using rmmod.

The .ko file is the loadable kernel object file, which is an enhanced version of .o with kernel metadata.

2. Prepare the Workspace

Create a folder to store the module sources.

mkdir -p ~/project/kernel-study/0.hello-world/hello
cd ~/project/kernel-study/0.hello-world

3. Writing a Minimal Kernel Module

Create hello.c:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Rishav");
MODULE_DESCRIPTION("Simple Hello World Module");

static int hello_init(void)
{
    pr_info("Hello World from Buildroot module!");
    return 0;
}

static void hello_exit(void)
{
    pr_info("Goodbye from Buildroot module!");
}

module_init(hello_init);
module_exit(hello_exit);

Initialization function: called when the module is loaded, and will not be stored in kernel’s memory. Cleanup function: called when the module is removed.


4. Create the Makefile

Update the paths to match your Buildroot output directory.

obj-m := hello.o

BUILDROOT_SRC_DIR=~/project/Platform/buildroot
KDIR := $(BUILDROOT_SRC_DIR)/output/build/linux-*/
CROSS_COMPILE := $(BUILDROOT_SRC_DIR)/output/host/bin/aarch64-linux-

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

clean:
	$(MAKE) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) clean

KDIR must point to the kernel build directory created by Buildroot.


5. Build the Module

Run:

make

The output should include:

hello.ko

This is the loadable kernel module.

Pasted image 20251126001534


6. Start QEMU

Use the Buildroot-generated QEMU launch script with an added port forwarding option:

-netdev user,id=eth0,hostfwd=tcp::2222-:22
-device virtio-net-device,netdev=eth0

Using this, the port 22 from target/guest machine which is used for ssh can be accessed at port 2222 in host machine.

Now, transfer .ko file inside target machine

 scp -P 2222 hello.ko root@localhost:/root/

7. Insert the Kernel Module

Load the module:

insmod /mnt/host/hello.ko

Check the logs to see expected output:

$ dmesg | tail
Hello World from Buildroot module!

8. Remove the Module

Unload it:

rmmod hello
dmesg | tail

Expected output:

Goodbye from Buildroot module!
Pasted image 20251208201432

9. Summary: Understanding __init and __exit Macros

The Linux kernel provides special section markers:

__init

  • Marks functions that are only used at initialization (e.g., my_init).
  • Memory used by these functions is freed after initialization completes.

__exit

  • Marks functions used only at module removal (e.g., my_cleanup).
  • If the module is built into the kernel (not loadable), these functions are discarded because they are never used.

These macros are defined in <linux/init.h>


This setup is ideal for iterative development of kernel drivers, subsystems, and platform code without needing hardware.


References