Project 4: Hello World Kernel Module

Build a series of increasingly sophisticated kernel modules—from “hello world” to modules that create entries in /proc and /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 /proc and /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:

  1. hello.ko - Basic hello world
  2. hello_params.ko - Module parameters
  3. procfs_example.ko - Creates /proc entries
  4. sysfs_example.ko - Creates /sys entries with read/write
  5. 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 value attribute
  • Create writable value attribute
  • 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

  1. Module Framework: License, author, init/exit functions
  2. Parameter System: Configurable options at load time
  3. procfs Interface: Virtual files in /proc
  4. sysfs Interface: Attributes in /sys
  5. 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:

  1. Kernel calls __init function
  2. Allocate resources, initialize state
  3. Register with subsystems (procfs, sysfs)
  4. Return 0 for success, negative error otherwise

Module Unloading:

  1. Kernel calls __exit function
  2. Unregister from subsystems
  3. Cancel timers, wait for callbacks
  4. Free all allocated memory
  5. 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

  1. 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
  2. What is GPL licensing for modules?
    • Why MODULE_LICENSE(“GPL”) matters
    • What symbols are GPL-only
    • Reference: Kernel licensing documentation
  3. 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:

  1. insmod opens the .ko file
  2. insmod calls init_module() syscall
  3. Kernel loads module into memory
  4. Kernel resolves symbols (finds addresses of printk, etc.)
  5. Kernel verifies module (signature, license)
  6. Kernel calls your __init function
  7. Your code runs with full kernel privileges
  8. You return 0 (success) or error code
  9. 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

  1. “What’s the difference between __init and __exit?”
    • __init: Function discarded after module loads
    • __exit: Function not included in built-in code
    • Memory optimization
  2. “Why can’t you use printf() in kernel code?”
    • No libc in kernel
    • Use printk() or pr_*() macros instead
    • Different log levels
  3. “What happens if you forget to free memory in exit?”
    • Memory leak
    • Can’t recover until reboot
    • Repeated load/unload exhausts memory
  4. “How do you debug a module that crashes on load?”
    • Check dmesg for oops message
    • Use pr_debug() extensively
    • Test in QEMU first
  5. “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

  1. Add statistics: Track call counts, bytes transferred
  2. Multiple proc files: Create a directory with multiple entries
  3. Module dependencies: Create module that requires another

Medium Extensions

  1. Debugfs interface: Use debugfs for debug-only files
  2. Netlink socket: Communicate with userspace via netlink
  3. Workqueue usage: Defer work to process context

Hard Extensions

  1. Character device: Create /dev entry (next project!)
  2. Interrupt handler: Handle hardware interrupts
  3. 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.rst
  • Documentation/core-api/printk-basics.rst
  • Documentation/filesystems/proc.rst
  • Documentation/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:

  1. hello.ko loads/unloads cleanly
  2. hello_params.ko accepts and uses parameters
  3. procfs_example.ko creates readable/writable /proc file
  4. sysfs_example.ko creates read/write /sys attributes
  5. timer_example.ko fires periodic timer
  6. All modules pass checkpatch.pl
  7. 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.