Linux Kernel Module

Exercise: Linux Kernel Module Development

Difficulty - Advanced

Estimated Time - 10-12 hours

Learning Objectives

  • Understand Linux kernel architecture and module system
  • Write kernel modules that interact with hardware and drivers
  • Implement character devices with file operations
  • Master kernel-space programming and debugging techniques
  • Build a custom system call or netfilter hook
  • Use Go to interact with kernel modules via syscalls and ioctl

Problem Statement

Build a custom Linux kernel module that implements a character device driver, and create a Go userspace program that interacts with it. The kernel module will provide a virtual device that can be read from and written to, demonstrating kernel-space programming while using Go for the userspace interface.

Kernel module development is essential for device drivers, system extensions, security tools, and performance optimization. Understanding kernel programming helps you build low-level tools, debug system issues, and understand how operating systems work.

Real-World Scenario:

 1// Go userspace program interacting with kernel module
 2func main() {
 3    // Open custom kernel device
 4    dev, err := os.OpenFile("/dev/mydevice", os.O_RDWR, 0)
 5    if err != nil {
 6        log.Fatal(err)
 7    }
 8    defer dev.Close()
 9
10    // Write to kernel device
11    message := "Hello from userspace!"
12    dev.Write([]byte(message))
13
14    // Read from kernel device
15    buffer := make([]byte, 256)
16    n, _ := dev.Read(buffer)
17    fmt.Printf("Kernel says: %s\n", buffer[:n])
18
19    // Use ioctl to control device
20    const IOCTL_GET_STATUS = 0x8001
21    status, _ := ioctl(dev.Fd(), IOCTL_GET_STATUS)
22    fmt.Printf("Device status: %d\n", status)
23}
24
25// Result: Direct communication with kernel!

Requirements

Functional Requirements

  1. Kernel Module: Loadable kernel module with init/exit functions
  2. Character Device: Implement char device with file operations
  3. Device Registration: Register device with major/minor numbers
  4. Proc Entry: Create /proc entry for status information
  5. Kernel Logging: Use printk for kernel log messages
  6. Go Userspace: Go program using syscalls to interact with module
  7. Ioctl Interface: Control device behavior via ioctl calls
  8. Interrupt Handling: Handle hardware interrupts

Non-Functional Requirements

  • Module loads/unloads cleanly without system crashes
  • Proper error handling in both kernel and userspace
  • Thread-safe kernel code
  • Memory safety
  • Documentation for building and loading module

Background and Theory

Linux Kernel Modules

Kernel modules are pieces of code that can be loaded into the kernel dynamically without rebooting. They extend kernel functionality for:

  • Device drivers
  • Filesystems
  • Security modules
  • Network filters

Module Lifecycle:

Load: insmod/modprobe
  ↓
module_init() function runs
  ↓
Module active in kernel
  ↓
module_exit() function runs
  ↓
Unload: rmmod

Character Devices

Character devices are accessed sequentially as a stream of bytes. Examples: /dev/null, /dev/random, /dev/tty

File Operations Structure:

1struct file_operations {
2    struct module *owner;
3    loff_t;
4    ssize_t;
5    ssize_t;
6    long;
7    int;
8    int;
9};

Kernel Space vs User Space

User Space:
- Applications run here
- Limited privileges
- Can be preempted
- Uses virtual memory

System Call Interface
──────────────────────

Kernel Space:
- Kernel and drivers
- Full privileges
- Direct hardware access
- Non-preemptible critical sections

Kernel Debugging

printk: Kernel's printf equivalent

1printk(KERN_INFO "Hello from kernel\n");

Levels: KERN_EMERG, KERN_ALERT, KERN_CRIT, KERN_ERR, KERN_WARNING, KERN_NOTICE, KERN_INFO, KERN_DEBUG

dmesg: View kernel log

1dmesg | tail

GDB: Debug kernel with kgdb
ftrace: Trace kernel functions
SystemTap: Dynamic tracing

Use Cases

1. Device Drivers

  • GPU drivers
  • Network cards
  • Storage controllers

2. Security Tools

  • Rootkit detection
  • System call monitoring
  • Network packet inspection

3. Performance Tools

  • eBPF programs
  • Profiling tools
  • Custom schedulers

4. System Extensions

  • Custom filesystems
  • Virtual devices
  • Network protocols

Implementation Challenges

Challenge 1: Kernel API Changes

Issue: Kernel APIs change between versions.

Solution: Use compatibility checks:

1#include <linux/version.h>
2
3#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
4    // New API
5#else
6    // Old API
7#endif

Challenge 2: Memory Management

Issue: Kernel has no malloc/free, different allocators.

Solution: Use kmalloc/kfree:

1void *ptr = kmalloc(size, GFP_KERNEL);
2if {
3    return -ENOMEM;
4}
5
6// Use ptr...
7
8kfree(ptr);

GFP Flags:

  • GFP_KERNEL: Normal allocation, may sleep
  • GFP_ATOMIC: Cannot sleep
  • GFP_DMA: For DMA-capable memory

Challenge 3: Synchronization

Issue: Kernel is preemptible, need proper locking.

Solution: Use mutexes or spinlocks:

 1// Mutex
 2DEFINE_MUTEX(my_mutex);
 3mutex_lock(&my_mutex);
 4// Critical section
 5mutex_unlock(&my_mutex);
 6
 7// Spinlock
 8spinlock_t my_lock;
 9spin_lock_init(&my_lock);
10spin_lock(&my_lock);
11// Critical section
12spin_unlock(&my_lock);

Challenge 4: User/Kernel Data Transfer

Issue: Cannot directly access user-space pointers from kernel.

Solution: Use copy_to_user/copy_from_user:

1// Kernel to user
2if) {
3    return -EFAULT;
4}
5
6// User to kernel
7if) {
8    return -EFAULT;
9}

Hints

Hint 1: Start with Minimal Module

Create a simple "Hello World" kernel module:

 1#include <linux/module.h>
 2#include <linux/kernel.h>
 3#include <linux/init.h>
 4
 5MODULE_LICENSE("GPL");
 6MODULE_AUTHOR("Your Name");
 7MODULE_DESCRIPTION("A simple Hello World module");
 8
 9static int __init hello_init(void) {
10    printk(KERN_INFO "Hello, Kernel!\n");
11    return 0;
12}
13
14static void __exit hello_exit(void) {
15    printk(KERN_INFO "Goodbye, Kernel!\n");
16}
17
18module_init(hello_init);
19module_exit(hello_exit);

Makefile:

1obj-m += hello.o
2
3all:
4	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
5
6clean:
7	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Load: sudo insmod hello.ko
Unload: sudo rmmod hello

Hint 2: Register Character Device

Use misc device for easy registration:

 1#include <linux/miscdevice.h>
 2
 3static struct file_operations fops = {
 4    .owner = THIS_MODULE,
 5    .read = device_read,
 6    .write = device_write,
 7    .open = device_open,
 8    .release = device_release,
 9};
10
11static struct miscdevice my_device = {
12    .minor = MISC_DYNAMIC_MINOR,
13    .name = "mydevice",
14    .fops = &fops,
15};
16
17static int __init my_init(void) {
18    return misc_register(&my_device);
19}
20
21static void __exit my_exit(void) {
22    misc_deregister(&my_device);
23}
Hint 3: Implement Read/Write

Handle read and write operations:

 1static ssize_t device_read(struct file *file, char __user *buf,
 2                          size_t count, loff_t *offset) {
 3    const char *message = "Hello from kernel";
 4    size_t len = strlen(message);
 5
 6    if
 7        return 0;
 8
 9    if
10        count = len - *offset;
11
12    if)
13        return -EFAULT;
14
15    *offset += count;
16    return count;
17}
18
19static ssize_t device_write(struct file *file, const char __user *buf,
20                           size_t count, loff_t *offset) {
21    char kernel_buf[256];
22    size_t to_copy = min(count, sizeof(kernel_buf) - 1);
23
24    if)
25        return -EFAULT;
26
27    kernel_buf[to_copy] = '\0';
28    printk(KERN_INFO "Received from user: %s\n", kernel_buf);
29
30    return to_copy;
31}
Hint 4: Go Ioctl Interface

Call ioctl from Go:

 1import (
 2    "syscall"
 3    "unsafe"
 4)
 5
 6const (
 7    IOCTL_GET_STATUS = 0x8001
 8    IOCTL_SET_MODE   = 0x8002
 9)
10
11func ioctl(fd uintptr, request uint, arg uintptr) {
12    r, _, errno := syscall.Syscall(
13        syscall.SYS_IOCTL,
14        fd,
15        uintptr(request),
16        arg,
17    )
18    if errno != 0 {
19        return 0, errno
20    }
21    return r, nil
22}
23
24// Usage
25status, err := ioctl(dev.Fd(), IOCTL_GET_STATUS, 0)
Hint 5: Create Proc Entry

Add /proc entry for status:

 1#include <linux/proc_fs.h>
 2#include <linux/seq_file.h>
 3
 4static int my_proc_show(struct seq_file *m, void *v) {
 5    seq_printf(m, "Device Status: Active\n");
 6    seq_printf(m, "Buffer Size: %d\n", buffer_size);
 7    return 0;
 8}
 9
10static int my_proc_open(struct inode *inode, struct file *file) {
11    return single_open(file, my_proc_show, NULL);
12}
13
14static const struct proc_ops my_proc_ops = {
15    .proc_open = my_proc_open,
16    .proc_read = seq_read,
17    .proc_lseek = seq_lseek,
18    .proc_release = single_release,
19};
20
21static int __init my_init(void) {
22    proc_create("mydevice", 0, NULL, &my_proc_ops);
23    // ...
24}

Solution

Show Complete Solution

Approach

We'll create:

  1. Kernel Module - Character device driver in C
  2. Go Userspace - Program to interact with the module
  3. Proc Interface - Status information in /proc
  4. Ioctl Commands - Control device behavior

Kernel Module Implementation

  1// mydevice.c - Kernel module
  2
  3#include <linux/module.h>
  4#include <linux/kernel.h>
  5#include <linux/init.h>
  6#include <linux/fs.h>
  7#include <linux/uaccess.h>
  8#include <linux/slab.h>
  9#include <linux/mutex.h>
 10#include <linux/miscdevice.h>
 11#include <linux/proc_fs.h>
 12#include <linux/seq_file.h>
 13
 14MODULE_LICENSE("GPL");
 15MODULE_AUTHOR("Go Tutorial");
 16MODULE_DESCRIPTION("Example character device driver");
 17MODULE_VERSION("1.0");
 18
 19#define DEVICE_NAME "mydevice"
 20#define BUFFER_SIZE 1024
 21
 22// IOCTL commands
 23#define IOCTL_GET_STATUS _IOR('M', 1, int)
 24#define IOCTL_SET_MODE   _IOW('M', 2, int)
 25#define IOCTL_RESET      _IO('M', 3)
 26
 27// Device state
 28static char *device_buffer;
 29static size_t buffer_size = 0;
 30static size_t read_offset = 0;
 31static int device_mode = 0;
 32static int open_count = 0;
 33static DEFINE_MUTEX(device_mutex);
 34
 35// Statistics
 36static unsigned long read_count = 0;
 37static unsigned long write_count = 0;
 38
 39// Device open
 40static int device_open(struct inode *inode, struct file *file) {
 41    mutex_lock(&device_mutex);
 42
 43    if {
 44        mutex_unlock(&device_mutex);
 45        return -EBUSY;  // Device busy
 46    }
 47
 48    open_count++;
 49    mutex_unlock(&device_mutex);
 50
 51    printk(KERN_INFO "mydevice: Device opened\n");
 52    return 0;
 53}
 54
 55// Device close
 56static int device_release(struct inode *inode, struct file *file) {
 57    mutex_lock(&device_mutex);
 58    open_count--;
 59    read_offset = 0;
 60    mutex_unlock(&device_mutex);
 61
 62    printk(KERN_INFO "mydevice: Device closed\n");
 63    return 0;
 64}
 65
 66// Device read
 67static ssize_t device_read(struct file *file, char __user *buf,
 68                          size_t count, loff_t *offset) {
 69    size_t to_read;
 70
 71    mutex_lock(&device_mutex);
 72
 73    if {
 74        mutex_unlock(&device_mutex);
 75        return 0;  // EOF
 76    }
 77
 78    to_read = min(count, buffer_size -*offset);
 79
 80    if) {
 81        mutex_unlock(&device_mutex);
 82        return -EFAULT;
 83    }
 84
 85    *offset += to_read;
 86    read_count++;
 87
 88    mutex_unlock(&device_mutex);
 89
 90    printk(KERN_INFO "mydevice: Read %zu bytes\n", to_read);
 91    return to_read;
 92}
 93
 94// Device write
 95static ssize_t device_write(struct file *file, const char __user *buf,
 96                           size_t count, loff_t *offset) {
 97    char *new_buffer;
 98
 99    mutex_lock(&device_mutex);
100
101    // Allocate new buffer if needed
102    if {
103        count = BUFFER_SIZE;
104    }
105
106    new_buffer = kmalloc(count, GFP_KERNEL);
107    if {
108        mutex_unlock(&device_mutex);
109        return -ENOMEM;
110    }
111
112    if) {
113        kfree(new_buffer);
114        mutex_unlock(&device_mutex);
115        return -EFAULT;
116    }
117
118    // Replace old buffer
119    if {
120        kfree(device_buffer);
121    }
122    device_buffer = new_buffer;
123    buffer_size = count;
124    read_offset = 0;
125    write_count++;
126
127    mutex_unlock(&device_mutex);
128
129    printk(KERN_INFO "mydevice: Wrote %zu bytes\n", count);
130    return count;
131}
132
133// Device ioctl
134static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
135    int status;
136    int mode;
137
138    switch {
139    case IOCTL_GET_STATUS:
140        status = ? 1 : 0;
141        ifarg)) {
142            return -EFAULT;
143        }
144        printk(KERN_INFO "mydevice: IOCTL_GET_STATUS = %d\n", status);
145        return 0;
146
147    case IOCTL_SET_MODE:
148        ifarg)) {
149            return -EFAULT;
150        }
151        mutex_lock(&device_mutex);
152        device_mode = mode;
153        mutex_unlock(&device_mutex);
154        printk(KERN_INFO "mydevice: IOCTL_SET_MODE = %d\n", mode);
155        return 0;
156
157    case IOCTL_RESET:
158        mutex_lock(&device_mutex);
159        if {
160            kfree(device_buffer);
161            device_buffer = NULL;
162        }
163        buffer_size = 0;
164        read_offset = 0;
165        device_mode = 0;
166        mutex_unlock(&device_mutex);
167        printk(KERN_INFO "mydevice: IOCTL_RESET\n");
168        return 0;
169
170    default:
171        return -EINVAL;
172    }
173}
174
175// File operations
176static struct file_operations fops = {
177    .owner = THIS_MODULE,
178    .open = device_open,
179    .release = device_release,
180    .read = device_read,
181    .write = device_write,
182    .unlocked_ioctl = device_ioctl,
183};
184
185// Misc device
186static struct miscdevice my_device = {
187    .minor = MISC_DYNAMIC_MINOR,
188    .name = DEVICE_NAME,
189    .fops = &fops,
190    .mode = 0666,  // rw-rw-rw-
191};
192
193// Proc file operations
194static int proc_show(struct seq_file *m, void *v) {
195    seq_printf(m, "Device Status:\n");
196    seq_printf(m, "  Buffer Size: %zu bytes\n", buffer_size);
197    seq_printf(m, "  Mode: %d\n", device_mode);
198    seq_printf(m, "  Open Count: %d\n", open_count);
199    seq_printf(m, "  Read Count: %lu\n", read_count);
200    seq_printf(m, "  Write Count: %lu\n", write_count);
201
202    if {
203        seq_printf(m, "  Buffer Content: ");
204        seq_write(m, device_buffer, min((size_t)64, buffer_size));
205        seq_printf(m, "\n");
206    }
207
208    return 0;
209}
210
211static int proc_open(struct inode *inode, struct file *file) {
212    return single_open(file, proc_show, NULL);
213}
214
215#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
216static const struct proc_ops proc_fops = {
217    .proc_open = proc_open,
218    .proc_read = seq_read,
219    .proc_lseek = seq_lseek,
220    .proc_release = single_release,
221};
222#else
223static const struct file_operations proc_fops = {
224    .owner = THIS_MODULE,
225    .open = proc_open,
226    .read = seq_read,
227    .llseek = seq_lseek,
228    .release = single_release,
229};
230#endif
231
232static struct proc_dir_entry *proc_entry;
233
234// Module initialization
235static int __init mydevice_init(void) {
236    int ret;
237
238    printk(KERN_INFO "mydevice: Initializing module\n");
239
240    // Register misc device
241    ret = misc_register(&my_device);
242    if {
243        printk(KERN_ERR "mydevice: Failed to register device\n");
244        return ret;
245    }
246
247    // Create proc entry
248    proc_entry = proc_create("mydevice", 0444, NULL, &proc_fops);
249    if {
250        printk(KERN_ERR "mydevice: Failed to create proc entry\n");
251        misc_deregister(&my_device);
252        return -ENOMEM;
253    }
254
255    printk(KERN_INFO "mydevice: Device registered at /dev/%s\n", DEVICE_NAME);
256    printk(KERN_INFO "mydevice: Proc entry at /proc/mydevice\n");
257
258    return 0;
259}
260
261// Module cleanup
262static void __exit mydevice_exit(void) {
263    printk(KERN_INFO "mydevice: Cleaning up module\n");
264
265    // Remove proc entry
266    if {
267        proc_remove(proc_entry);
268    }
269
270    // Free buffer
271    mutex_lock(&device_mutex);
272    if {
273        kfree(device_buffer);
274        device_buffer = NULL;
275    }
276    mutex_unlock(&device_mutex);
277
278    // Unregister device
279    misc_deregister(&my_device);
280
281    printk(KERN_INFO "mydevice: Module removed\n");
282}
283
284module_init(mydevice_init);
285module_exit(mydevice_exit);

Makefile:

 1obj-m += mydevice.o
 2
 3KDIR := /lib/modules/$(shell uname -r)/build
 4PWD := $(shell pwd)
 5
 6all:
 7	make -C $(KDIR) M=$(PWD) modules
 8
 9clean:
10	make -C $(KDIR) M=$(PWD) clean
11
12install:
13	sudo insmod mydevice.ko
14
15uninstall:
16	sudo rmmod mydevice
17
18reload: uninstall install
19
20test:
21	@echo "Loading module..."
22	@sudo insmod mydevice.ko
23	@echo "Module loaded. Check dmesg for messages."
24	@dmesg | tail -5

Go Userspace Implementation

  1// main.go - Userspace program
  2
  3package main
  4
  5import (
  6    "fmt"
  7    "io/ioutil"
  8    "log"
  9    "os"
 10    "syscall"
 11    "unsafe"
 12)
 13
 14const (
 15    DEVICE_PATH = "/dev/mydevice"
 16    PROC_PATH   = "/proc/mydevice"
 17)
 18
 19// IOCTL commands
 20const (
 21    IOCTL_GET_STATUS = 0x80044D01 // _IOR('M', 1, int)
 22    IOCTL_SET_MODE   = 0x40044D02 // _IOW('M', 2, int)
 23    IOCTL_RESET      = 0x00004D03 // _IO('M', 3)
 24)
 25
 26// ioctl performs an ioctl system call
 27func ioctl(fd uintptr, request uint, arg uintptr) {
 28    r, _, errno := syscall.Syscall(
 29        syscall.SYS_IOCTL,
 30        fd,
 31        uintptr(request),
 32        arg,
 33    )
 34    if errno != 0 {
 35        return 0, errno
 36    }
 37    return r, nil
 38}
 39
 40func main() {
 41    fmt.Println("=== Kernel Module Interaction Demo ===\n")
 42
 43    // Open device
 44    dev, err := os.OpenFile(DEVICE_PATH, os.O_RDWR, 0)
 45    if err != nil {
 46        log.Fatalf("Failed to open device: %v\n", err)
 47    }
 48    defer dev.Close()
 49
 50    fmt.Println("1. Writing to device...")
 51    message := "Hello from Go userspace!"
 52    n, err := dev.Write([]byte(message))
 53    if err != nil {
 54        log.Fatalf("Write failed: %v\n", err)
 55    }
 56    fmt.Printf("   Wrote %d bytes\n", n)
 57
 58    // Seek to beginning
 59    dev.Seek(0, 0)
 60
 61    fmt.Println("\n2. Reading from device...")
 62    buffer := make([]byte, 256)
 63    n, err = dev.Read(buffer)
 64    if err != nil {
 65        log.Fatalf("Read failed: %v\n", err)
 66    }
 67    fmt.Printf("   Read %d bytes: %s\n", n, buffer[:n])
 68
 69    fmt.Println("\n3. Using IOCTL commands...")
 70
 71    // Get status
 72    var status int32
 73    _, err = ioctl(dev.Fd(), IOCTL_GET_STATUS, uintptr(unsafe.Pointer(&status)))
 74    if err != nil {
 75        log.Printf("IOCTL_GET_STATUS failed: %v\n", err)
 76    } else {
 77        fmt.Printf("   Device status: %d\n", status)
 78    }
 79
 80    // Set mode
 81    mode := int32(42)
 82    _, err = ioctl(dev.Fd(), IOCTL_SET_MODE, uintptr(unsafe.Pointer(&mode)))
 83    if err != nil {
 84        log.Printf("IOCTL_SET_MODE failed: %v\n", err)
 85    } else {
 86        fmt.Printf("   Set mode to: %d\n", mode)
 87    }
 88
 89    fmt.Println("\n4. Reading proc entry...")
 90    procData, err := ioutil.ReadFile(PROC_PATH)
 91    if err != nil {
 92        log.Printf("Failed to read proc: %v\n", err)
 93    } else {
 94        fmt.Printf("%s\n", procData)
 95    }
 96
 97    fmt.Println("5. Resetting device...")
 98    _, err = ioctl(dev.Fd(), IOCTL_RESET, 0)
 99    if err != nil {
100        log.Printf("IOCTL_RESET failed: %v\n", err)
101    } else {
102        fmt.Println("   Device reset successfully")
103    }
104
105    fmt.Println("\n=== Demo Complete ===")
106    fmt.Println("\nCheck kernel log with: sudo dmesg | tail")
107}

Build and Run Instructions

Build kernel module:

1make

Load module:

1sudo insmod mydevice.ko

Check module loaded:

1lsmod | grep mydevice

Build Go program:

1go build -o client main.go

Run Go program:

1./client

Check kernel log:

1sudo dmesg | tail -20

Unload module:

1sudo rmmod mydevice

Expected Output

Go program output:

=== Kernel Module Interaction Demo ===

1. Writing to device...
   Wrote 23 bytes

2. Reading from device...
   Read 23 bytes: Hello from Go userspace!

3. Using IOCTL commands...
   Device status: 1
   Set mode to: 42

4. Reading proc entry...
Device Status:
  Buffer Size: 23 bytes
  Mode: 42
  Open Count: 1
  Read Count: 1
  Write Count: 1
  Buffer Content: Hello from Go userspace!

5. Resetting device...
   Device reset successfully

=== Demo Complete ===

Kernel log:

[12345.678] mydevice: Initializing module
[12345.679] mydevice: Device registered at /dev/mydevice
[12345.679] mydevice: Proc entry at /proc/mydevice
[12346.123] mydevice: Device opened
[12346.124] mydevice: Wrote 23 bytes
[12346.125] mydevice: Read 23 bytes
[12346.126] mydevice: IOCTL_GET_STATUS = 1
[12346.127] mydevice: IOCTL_SET_MODE = 42
[12346.128] mydevice: IOCTL_RESET
[12346.129] mydevice: Device closed

Testing Your Solution

Test these scenarios:

  1. Module Loading: Load and unload module successfully
  2. Device Creation: /dev/mydevice appears and is accessible
  3. Read/Write: Basic file operations work correctly
  4. Ioctl Commands: Control device behavior
  5. Proc Entry: Status information is accurate
  6. Concurrent Access: Multiple processes accessing device
  7. Error Handling: Invalid operations return proper errors
  8. Memory Leaks: No kernel memory leaks after unload

Verification Checklist:

  • Module loads without errors
  • Device file created at /dev/mydevice
  • Proc entry created at /proc/mydevice
  • Write operation stores data correctly
  • Read operation retrieves stored data
  • Ioctl commands execute successfully
  • Module unloads cleanly
  • No memory leaks

Bonus Challenges

  1. Sysfs Interface: Add sysfs attributes for device configuration
  2. Interrupt Handling: Handle hardware interrupts
  3. DMA: Implement direct memory access for bulk transfers
  4. Netlink Socket: Create netlink interface for kernel-user communication
  5. Netfilter Hook: Intercept and modify network packets
  6. System Call: Add a new custom system call
  7. Kernel Thread: Create kernel thread for background work
  8. Workqueue: Use workqueues for deferred work
  9. Block Device: Implement a simple block device driver
  10. Virtual Filesystem: Create a simple in-memory filesystem

Key Takeaways

  • Kernel modules extend kernel functionality without rebooting
  • Character devices provide sequential byte stream access via standard file operations
  • Kernel space has no safety net - bugs cause kernel panics
  • Memory management is explicit - use kmalloc/kfree, never malloc
  • Synchronization is critical - use mutexes and spinlocks appropriately
  • User/kernel data transfer requires special functions - copy_to_user/copy_from_user
  • Ioctl provides custom device control beyond read/write
  • Go can interact with kernel modules using syscalls and ioctl
  • Debugging is harder in kernel space - use printk and /proc for visibility
  • Always test in a VM - kernel bugs can crash the system

Kernel module development is one of the most advanced topics in systems programming, requiring deep understanding of operating system internals, memory management, and hardware interfaces.

References