Project 4: Hello World Kernel Module
Build a series of increasingly sophisticated kernel modules—from “hello world” to modules that create entries in
/procand/sys.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Intermediate |
| Time Estimate | 1 week |
| Language | C (Rust emerging for kernel) |
| Prerequisites | Project 3 (kernel build environment) |
| Key Topics | Module lifecycle, procfs, sysfs, kernel APIs |
1. Learning Objectives
By completing this project, you will:
- Understand the loadable kernel module lifecycle (init/exit)
- Learn essential kernel APIs (printk, kmalloc, etc.)
- Create user-visible interfaces via
/procand/sys - Master kernel resource management and cleanup
2. Theoretical Foundation
2.1 Core Concepts
Loadable Kernel Module (LKM): Code that can be loaded into the running kernel without rebooting. Modules extend kernel functionality dynamically—most device drivers are modules.
Module Lifecycle: Every module has an init function (called on load) and an exit function (called on unload). Proper cleanup is critical—the kernel won’t do it for you.
Module Lifecycle:
┌─────────────────────────────────────────────────────────────┐
│ insmod mymodule.ko │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Module Loading │ │
│ │ 1. Kernel loads .ko file into memory │ │
│ │ 2. Resolves symbols (printk, kmalloc, etc.) │ │
│ │ 3. Calls module_init function │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Module Running │ │
│ │ - Registered with kernel subsystems │ │
│ │ - Handles requests (device I/O, proc reads, etc.) │ │
│ │ - Module code remains in kernel memory │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ rmmod mymodule │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Module Unloading │ │
│ │ 1. Calls module_exit function │ │
│ │ 2. Module must clean up ALL resources │ │
│ │ 3. Kernel removes module from memory │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 Why This Matters
Kernel modules are the gateway to kernel development:
- Low barrier: No kernel rebuild needed for each change
- Real kernel code: Same APIs, same constraints
- Essential for drivers: Most hardware support is modules
- Learning platform: Experiment safely without breaking the kernel
2.3 Historical Context
Loadable modules have been in Linux since version 1.2 (1995). They were controversial—some argued all code should be compiled in—but the flexibility won. Today, a typical Linux system has 100+ loaded modules.
2.4 Common Misconceptions
- “Modules are slower than built-in” - Once loaded, there’s no performance difference.
- “Modules are sandboxed” - No! Module code runs with full kernel privileges.
- “I can use any library function” - No. Only kernel-exported symbols are available.
3. Project Specification
3.1 What You Will Build
A progression of kernel modules:
- hello.ko - Basic hello world
- hello_params.ko - Module parameters
- procfs_example.ko - Creates /proc entries
- sysfs_example.ko - Creates /sys entries with read/write
- timer_example.ko - Kernel timers
3.2 Functional Requirements
Module 1 - hello.ko:
- Print message on load
- Print message on unload
- Display current CPU and PID
Module 2 - hello_params.ko:
- Accept debug_level parameter
- Accept message string parameter
- Print parameters on load
Module 3 - procfs_example.ko:
- Create /proc/kernel_explorer/info file
- Track read count
- Display module information
Module 4 - sysfs_example.ko:
- Create /sys/kernel/my_module/ directory
- Create readable
valueattribute - Create writable
valueattribute - Persist value between reads/writes
Module 5 - timer_example.ko:
- Schedule periodic timer
- Print message every N seconds
- Allow interval to be configured
3.3 Non-Functional Requirements
- Clean unload (no memory leaks)
- Proper error handling
- Follow kernel coding style
3.4 Example Usage / Output
# Module 1: Basic hello
$ sudo insmod hello.ko
$ dmesg | tail -3
[12345.678] hello: module loaded
[12345.678] hello: Hello from kernel space!
[12345.678] hello: Running on CPU 0, PID 1234 (insmod)
$ sudo rmmod hello
$ dmesg | tail -1
[12346.789] hello: Goodbye from kernel space!
# Module 2: With parameters
$ sudo insmod hello_params.ko debug_level=2 message="Custom greeting"
$ dmesg | tail
[12350.123] hello_params: Debug level set to 2
[12350.123] hello_params: Message: Custom greeting
# Module 3: procfs interface
$ sudo insmod procfs_example.ko
$ cat /proc/kernel_explorer/info
Module: procfs_example
Loaded at: 1234567890
Read count: 1
$ cat /proc/kernel_explorer/info
Module: procfs_example
Loaded at: 1234567890
Read count: 2
# Module 4: sysfs interface
$ sudo insmod sysfs_example.ko
$ ls /sys/kernel/my_module/
value description
$ cat /sys/kernel/my_module/value
42
$ echo 100 > /sys/kernel/my_module/value
$ cat /sys/kernel/my_module/value
100
# Module 5: Timer
$ sudo insmod timer_example.ko interval=5
$ dmesg | tail -5
[12400.000] timer_example: Timer scheduled for 5 second intervals
[12405.001] timer_example: Timer fired! Count: 1
[12410.002] timer_example: Timer fired! Count: 2
[12415.003] timer_example: Timer fired! Count: 3
3.5 Real World Outcome
You’ll have mastered the fundamentals of kernel module development—the same skills used to write device drivers, filesystem modules, and network filters.
4. Solution Architecture
4.1 High-Level Design
┌─────────────────────────────────────────────────────────────┐
│ Kernel Module │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Module Metadata │ │
│ │ MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION│ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Module Parameters │ │
│ │ module_param(name, type, permissions) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Init Function │ │
│ │ static int __init my_init(void) { ... } │ │
│ │ module_init(my_init); │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Operational Functions │ │
│ │ - procfs read/write handlers │ │
│ │ - sysfs show/store handlers │ │
│ │ - timer callbacks │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Exit Function │ │
│ │ static void __exit my_exit(void) { ... } │ │
│ │ module_exit(my_exit); │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 Key Components
- Module Framework: License, author, init/exit functions
- Parameter System: Configurable options at load time
- procfs Interface: Virtual files in /proc
- sysfs Interface: Attributes in /sys
- Timer System: Periodic callbacks
4.3 Data Structures
// Module-level state
struct my_module_state {
int debug_level;
char message[256];
unsigned long load_time;
atomic_t read_count;
struct timer_list my_timer;
struct kobject *kobj;
};
// procfs operations
static const struct proc_ops my_proc_ops = {
.proc_read = my_proc_read,
.proc_write = my_proc_write,
};
// sysfs attribute
static struct kobj_attribute value_attr =
__ATTR(value, 0644, value_show, value_store);
4.4 Algorithm Overview
Module Loading:
- Kernel calls
__initfunction - Allocate resources, initialize state
- Register with subsystems (procfs, sysfs)
- Return 0 for success, negative error otherwise
Module Unloading:
- Kernel calls
__exitfunction - Unregister from subsystems
- Cancel timers, wait for callbacks
- Free all allocated memory
- Module code removed from kernel
5. Implementation Guide
5.1 Development Environment Setup
# You need kernel headers for your running kernel
sudo apt install linux-headers-$(uname -r)
# Or use your custom kernel from Project 3
cd /path/to/your/linux
make modules_prepare
5.2 Project Structure
modules/
├── Makefile # Main Makefile
├── hello/
│ ├── Makefile
│ └── hello.c
├── hello_params/
│ ├── Makefile
│ └── hello_params.c
├── procfs_example/
│ ├── Makefile
│ └── procfs_example.c
├── sysfs_example/
│ ├── Makefile
│ └── sysfs_example.c
└── timer_example/
├── Makefile
└── timer_example.c
5.3 The Core Question You’re Answering
“How does kernel code differ from userspace code, and how do modules integrate with the running kernel?”
5.4 Concepts You Must Understand First
- What makes kernel code different?
- No libc, only kernel APIs
- No memory protection—bugs crash the system
- Must handle concurrency
- Reference: “Linux Device Drivers, 3rd Edition” Chapter 2
- What is GPL licensing for modules?
- Why MODULE_LICENSE(“GPL”) matters
- What symbols are GPL-only
- Reference: Kernel licensing documentation
- What is the difference between procfs and sysfs?
- When to use each
- Design philosophy differences
- Reference: “Linux Kernel Programming” Chapter 7
5.5 Questions to Guide Your Design
Resource Management:
- What happens if init fails partway through?
- How do you ensure cleanup happens in reverse order?
Concurrency:
- Can multiple processes read /proc at once?
- What locking is needed for shared state?
Error Handling:
- What should init return on failure?
- How do you report errors to userspace?
5.6 Thinking Exercise
Before writing code, trace what happens when you run insmod hello.ko:
insmodopens the .ko fileinsmodcallsinit_module()syscall- Kernel loads module into memory
- Kernel resolves symbols (finds addresses of
printk, etc.) - Kernel verifies module (signature, license)
- Kernel calls your
__initfunction - Your code runs with full kernel privileges
- You return 0 (success) or error code
- Module is registered in
/proc/modules
Question: What if your __init function never returns?
5.7 Hints in Layers
Hint 1 - Basic Module Structure:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My first kernel module");
static int __init hello_init(void)
{
pr_info("Hello, kernel world!\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, kernel world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
Hint 2 - Module Makefile:
obj-m += hello.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
Hint 3 - Module Parameters:
static int debug_level = 0;
static char *message = "default message";
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug level (0-3)");
module_param(message, charp, 0644);
MODULE_PARM_DESC(message, "Message to print");
Hint 4 - procfs Entry:
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static struct proc_dir_entry *proc_dir;
static struct proc_dir_entry *proc_file;
static int my_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Hello from proc!\n");
return 0;
}
static int my_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, my_proc_show, NULL);
}
static const struct proc_ops my_proc_ops = {
.proc_open = my_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
// In init:
proc_dir = proc_mkdir("my_module", NULL);
proc_file = proc_create("info", 0444, proc_dir, &my_proc_ops);
// In exit:
proc_remove(proc_file);
proc_remove(proc_dir);
5.8 The Interview Questions They’ll Ask
- “What’s the difference between
__initand__exit?”__init: Function discarded after module loads__exit: Function not included in built-in code- Memory optimization
- “Why can’t you use printf() in kernel code?”
- No libc in kernel
- Use
printk()orpr_*()macros instead - Different log levels
- “What happens if you forget to free memory in exit?”
- Memory leak
- Can’t recover until reboot
- Repeated load/unload exhausts memory
- “How do you debug a module that crashes on load?”
- Check dmesg for oops message
- Use pr_debug() extensively
- Test in QEMU first
- “What is the kernel coding style?”
- Tabs for indentation (8 spaces wide)
- Opening brace on same line (except functions)
- 80-column limit (relaxed to 100)
- Run checkpatch.pl
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Module basics | Linux Device Drivers, 3rd Ed | Chapter 2 |
| procfs interface | Linux Kernel Programming | Chapter 7 |
| Memory allocation | Understanding the Linux Kernel | Chapter 8 |
| Kernel coding style | Kernel Documentation | coding-style.rst |
5.10 Implementation Phases
Phase 1: hello.ko (Day 1)
- Basic module structure
- printk on load/unload
- Build and test
Phase 2: hello_params.ko (Day 2)
- Add module parameters
- Test different parameter values
- Verify in /sys/module/
Phase 3: procfs_example.ko (Days 3-4)
- Create /proc directory
- Implement read handler
- Add write handler
Phase 4: sysfs_example.ko (Days 5-6)
- Create kobject
- Add attributes
- Implement show/store
Phase 5: timer_example.ko (Day 7)
- Set up kernel timer
- Handle timer callback
- Proper cleanup on exit
5.11 Key Implementation Decisions
Decision 1: pr_info vs printk
- Use pr_* macros (pr_info, pr_err, pr_debug)
- They include module name automatically
Decision 2: Static vs dynamic allocation
- Module-level state: static or kmalloc in init
- Per-use state: kmalloc
Decision 3: Error handling approach
- Use goto for cleanup on partial init failure
- Return appropriate -ERRNO values
6. Testing Strategy
Module Loading
# Check module builds
make
ls *.ko
# Load and verify
sudo insmod hello.ko
lsmod | grep hello
dmesg | tail
# Unload and verify
sudo rmmod hello
lsmod | grep hello # Should be empty
dmesg | tail
Memory Leak Detection
# Check before loading
grep -i "MemFree\|Slab" /proc/meminfo
# Load/unload 100 times
for i in {1..100}; do
sudo insmod hello.ko
sudo rmmod hello
done
# Check after - should be similar
grep -i "MemFree\|Slab" /proc/meminfo
procfs/sysfs Testing
# Verify files exist
ls -la /proc/kernel_explorer/
ls -la /sys/kernel/my_module/
# Test reading
cat /proc/kernel_explorer/info
# Test writing
echo "test" > /sys/kernel/my_module/value
cat /sys/kernel/my_module/value
7. Common Pitfalls & Debugging
| Problem | Symptom | Root Cause | Fix |
|---|---|---|---|
| Module won’t load | “Invalid format” | Wrong kernel version | Rebuild for current kernel |
| GPL symbol error | “Unknown symbol” | Using GPL symbol without license | Add MODULE_LICENSE(“GPL”) |
| Crash on unload | Oops in dmesg | Using freed memory | Check cleanup order |
| proc file not visible | Empty /proc/mydir | proc_create failed | Check return value |
| Memory leak | Slab grows | Forgot kfree | Add proper cleanup |
Quick Verification
# Check module info
modinfo hello.ko
# Check loaded modules
lsmod | grep hello
# Check module parameters
cat /sys/module/hello_params/parameters/debug_level
# Check for kernel warnings
dmesg | grep -i "warning\|error"
8. Extensions & Challenges
Easy Extensions
- Add statistics: Track call counts, bytes transferred
- Multiple proc files: Create a directory with multiple entries
- Module dependencies: Create module that requires another
Medium Extensions
- Debugfs interface: Use debugfs for debug-only files
- Netlink socket: Communicate with userspace via netlink
- Workqueue usage: Defer work to process context
Hard Extensions
- Character device: Create /dev entry (next project!)
- Interrupt handler: Handle hardware interrupts
- Memory-mapped I/O: Interact with hardware registers
9. Real-World Connections
How Real Drivers Work
Real device drivers are just more complex modules:
- Network drivers: Same init/exit, more callbacks
- Block drivers: Register with block layer
- USB drivers: Register with USB core
The patterns you learn here apply everywhere.
Industry Usage
- Out-of-tree drivers: NVIDIA driver is a module
- Security modules: SELinux, AppArmor are modules
- Filesystem modules: FUSE, ZFS are modules
- Network filters: iptables uses netfilter modules
10. Resources
Essential Documentation
Documentation/process/coding-style.rstDocumentation/core-api/printk-basics.rstDocumentation/filesystems/proc.rstDocumentation/filesystems/sysfs.rst
Code References
- Kernel source:
samples/kprobes/- Example modules - Kernel source:
fs/proc/- procfs implementation - Kernel source:
fs/sysfs/- sysfs implementation
Online Resources
11. Self-Assessment Checklist
Before moving to the next project, verify:
- I can write, build, and load a kernel module
- I understand MODULE_LICENSE and why it matters
- I can use module_param for configurable options
- I can create /proc entries with read/write handlers
- I can create /sys attributes with show/store
- My modules unload cleanly with no leaks
- I handle errors properly in init (goto cleanup pattern)
- I’ve run checkpatch.pl on my code
12. Submission / Completion Criteria
Your project is complete when:
- hello.ko loads/unloads cleanly
- hello_params.ko accepts and uses parameters
- procfs_example.ko creates readable/writable /proc file
- sysfs_example.ko creates read/write /sys attributes
- timer_example.ko fires periodic timer
- All modules pass checkpatch.pl
- No memory leaks after repeated load/unload
Verification Commands
# Build all
make
# Test each module
for mod in hello hello_params procfs_example sysfs_example timer_example; do
sudo insmod ${mod}/${mod}.ko
sleep 1
sudo rmmod ${mod}
done
# Check for issues
dmesg | grep -i "error\|warning\|oops"
Next Project: P05 - Character Device Driver - Create a device in /dev with full file operations.