Week 7 — Writing, Building, and Testing Kernel Modules¶
Goal¶
Write a kernel module from scratch, build it, load it into your QEMU guest, and interact with it. Then write a networking-related module that hooks into the packet path.
Why This Matters¶
Modules are how you'll test ideas without rebooting. They're also how most kernel contributions start — you change or add a module, test it, and submit a patch. Nearly all network drivers are modules.
Your First Module¶
Create a directory for your module work:
Create hello.c:
// SPDX-License-Identifier: GPL-2.0
/*
* hello.c - A minimal kernel module
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
pr_info("hello: module loaded\n");
return 0; // 0 = success, negative = error
}
static void __exit hello_exit(void)
{
pr_info("hello: module unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Carlos");
MODULE_DESCRIPTION("A minimal kernel module");
Key elements:
SPDX-License-Identifier— required by kernel coding style. GPL-2.0 for kernel code.__init— this function runs once at load, then its memory is freed.__exit— runs when the module is unloaded.module_init()/module_exit()— macros that register your init/exit functions.MODULE_LICENSE("GPL")— required. Non-GPL modules can't use many kernel symbols.
Create the Makefile:
obj-m += hello.o
KDIR ?= ~/linux
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Build it:
Expect: hello.ko is produced. This is your loadable module.
Loading and Testing¶
Copy the module to your QEMU guest (via 9p share, scp, or by mounting the disk image) and:
# Load the module
insmod hello.ko
# Check it loaded
lsmod | grep hello
# See the message
dmesg | tail -5
# Unload
rmmod hello
# See the exit message
dmesg | tail -5
Module Parameters¶
Make your module configurable at load time:
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/moduleparam.h>
static int count = 1;
static char *name = "world";
module_param(count, int, 0644); // readable/writable in sysfs
MODULE_PARM_DESC(count, "Number of greetings");
module_param(name, charp, 0444); // read-only in sysfs
MODULE_PARM_DESC(name, "Who to greet");
static int __init hello_init(void)
{
int i;
for (i = 0; i < count; i++)
pr_info("hello: Hello, %s!\n", name);
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello: Goodbye, %s!\n", name);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
Load with parameters:
insmod hello.ko count=3 name="Carlos"
# Parameters visible in sysfs
cat /sys/module/hello/parameters/count
cat /sys/module/hello/parameters/name
A Networking Module: Packet Counter¶
Now let's write something useful — a module that hooks into the network stack and counts packets using a Netfilter hook:
// SPDX-License-Identifier: GPL-2.0
/*
* pktcount.c - Count incoming packets using Netfilter
*/
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
static atomic_t pkt_count = ATOMIC_INIT(0);
static unsigned int pkt_hook(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
if (!skb)
return NF_ACCEPT;
iph = ip_hdr(skb);
atomic_inc(&pkt_count);
if (atomic_read(&pkt_count) % 100 == 0)
pr_info("pktcount: %d packets seen (last src: %pI4)\n",
atomic_read(&pkt_count), &iph->saddr);
return NF_ACCEPT; // Let the packet through
}
static struct nf_hook_ops pkt_hook_ops = {
.hook = pkt_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
};
static int __init pktcount_init(void)
{
int ret;
ret = nf_register_net_hook(&init_net, &pkt_hook_ops);
if (ret) {
pr_err("pktcount: failed to register hook: %d\n", ret);
return ret;
}
pr_info("pktcount: module loaded, counting packets\n");
return 0;
}
static void __exit pktcount_exit(void)
{
nf_unregister_net_hook(&init_net, &pkt_hook_ops);
pr_info("pktcount: module unloaded, total packets: %d\n",
atomic_read(&pkt_count));
}
module_init(pktcount_init);
module_exit(pktcount_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple packet counter using Netfilter");
Update the Makefile:
obj-m += hello.o
obj-m += pktcount.o
KDIR ?= ~/linux
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Test it:
insmod pktcount.ko
ping -c 200 10.0.2.2
dmesg | grep pktcount
rmmod pktcount
dmesg | tail -3 # See total count
What this teaches you:
- How Netfilter hooks work (you'll see this pattern everywhere in iptables/nftables)
- How
sk_buffrepresents a packet - How to safely access packet headers
- Atomic operations for concurrent access (packets arrive on multiple CPUs)
Creating a /proc Entry¶
Expose your packet count via /proc instead of just printk:
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int pktcount_show(struct seq_file *m, void *v)
{
seq_printf(m, "%d\n", atomic_read(&pkt_count));
return 0;
}
static int pktcount_open(struct inode *inode, struct file *file)
{
return single_open(file, pktcount_show, NULL);
}
static const struct proc_ops pktcount_proc_ops = {
.proc_open = pktcount_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
// In init:
proc_create("pktcount", 0444, NULL, &pktcount_proc_ops);
// In exit:
remove_proc_entry("pktcount", NULL);
Now you can read the count with:
Kernel Coding Style¶
Before writing more code, internalize the kernel coding style:
Key rules:
- Tabs for indentation (8 spaces wide)
- 80-column lines (soft limit, 100 for special cases)
- Opening brace on same line (except functions)
- No typedefs for structs
- Comments:
/* C style */, not//
Check your code:
This is the same tool used to review patches. Get used to its output now.
Exercises¶
- Write, build, and load the hello module. Verify with
dmesg. - Add a module parameter for the greeting count. Load with different values.
- Build and test the packet counter module. Ping the guest and verify counts.
- Add the
/proc/pktcountentry. Read it while generating traffic. - Run
checkpatch.plon your modules. Fix any warnings. - Modify the packet counter to also count TCP vs UDP packets separately. Expose both
counts via
/proc.
What's Next¶
Next week we dive into the networking architecture — how the kernel processes packets from NIC to socket, the role of sk_buff, and how protocol layers are organized.