内核模块开发基础

内核模块是可以动态加载到内核中的代码,无需重新编译整个内核即可扩展内核功能。

内核模块概述

模块的优势

动态加载/卸载
├─ 无需重启系统
├─ 按需加载驱动
└─ 便于开发调试

模块化设计
├─ 降低内核体积
├─ 提高可维护性
└─ 支持第三方驱动

模块生命周期

编写模块代码
    ↓
编译生成 .ko 文件
    ↓
insmod/modprobe 加载
    ↓
模块初始化函数执行
    ↓
模块运行(提供功能)
    ↓
rmmod 卸载
    ↓
模块清理函数执行
    ↓
从内核移除

Hello World 模块

最简模块

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World module");
MODULE_VERSION("1.0");

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Kernel!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel!\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile

# Makefile
obj-m += hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

install:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install

load:
	sudo insmod hello.ko

unload:
	sudo rmmod hello

test: load
	dmesg | tail
	$(MAKE) unload

编译和使用

# 编译模块
make

# 查看生成的文件
ls -lh hello.ko

# 查看模块信息
modinfo hello.ko

# 加载模块
sudo insmod hello.ko

# 查看内核日志
dmesg | tail

# 查看已加载模块
lsmod | grep hello

# 卸载模块
sudo rmmod hello

# 清理
make clean

模块参数

定义和使用参数

// param_demo.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");

// 定义模块参数
static int count = 1;
static char *name = "world";
static int arr[3] = {1, 2, 3};
static int arr_count = 3;

// 声明参数
module_param(count, int, S_IRUGO);
MODULE_PARM_DESC(count, "Number of times to print");

module_param(name, charp, S_IRUGO);
MODULE_PARM_DESC(name, "Name to print");

module_param_array(arr, int, &arr_count, S_IRUGO);
MODULE_PARM_DESC(arr, "Array of integers");

static int __init param_init(void)
{
    int i;
    
    printk(KERN_INFO "Module loaded with parameters:\n");
    printk(KERN_INFO "count = %d\n", count);
    printk(KERN_INFO "name = %s\n", name);
    
    for (i = 0; i < arr_count; i++) {
        printk(KERN_INFO "arr[%d] = %d\n", i, arr[i]);
    }
    
    for (i = 0; i < count; i++) {
        printk(KERN_INFO "Hello, %s!\n", name);
    }
    
    return 0;
}

static void __exit param_exit(void)
{
    printk(KERN_INFO "Module unloaded\n");
}

module_init(param_init);
module_exit(param_exit);

使用参数

# 加载时指定参数
sudo insmod param_demo.ko count=3 name="Linux"
sudo insmod param_demo.ko arr=10,20,30

# 查看参数
ls /sys/module/param_demo/parameters/
cat /sys/module/param_demo/parameters/count

# 运行时修改参数(如果权限允许)
echo 5 | sudo tee /sys/module/param_demo/parameters/count

导出符号

符号导出和使用

// export_module.c
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");

// 定义要导出的函数
int my_function(int x, int y)
{
    return x + y;
}

// 导出符号
EXPORT_SYMBOL(my_function);
// 或仅对 GPL 模块导出
// EXPORT_SYMBOL_GPL(my_function);

static int __init export_init(void)
{
    printk(KERN_INFO "Export module loaded\n");
    return 0;
}

static void __exit export_exit(void)
{
    printk(KERN_INFO "Export module unloaded\n");
}

module_init(export_init);
module_exit(export_exit);
// import_module.c
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");

// 声明外部函数
extern int my_function(int x, int y);

static int __init import_init(void)
{
    int result = my_function(3, 4);
    printk(KERN_INFO "Result: %d\n", result);
    return 0;
}

static void __exit import_exit(void)
{
    printk(KERN_INFO "Import module unloaded\n");
}

module_init(import_init);
module_exit(import_exit);

模块依赖

# 加载导出模块
sudo insmod export_module.ko

# 加载导入模块(依赖 export_module)
sudo insmod import_module.ko

# 查看模块依赖
lsmod | grep -E "export|import"

# 查看符号表
cat /proc/kallsyms | grep my_function

内核 API

内存分配

#include <linux/slab.h>

// kmalloc - 连续物理内存
void *ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
    printk(KERN_ERR "kmalloc failed\n");
    return -ENOMEM;
}
kfree(ptr);

// vmalloc - 连续虚拟内存
void *vptr = vmalloc(large_size);
vfree(vptr);

// 内存分配标志
GFP_KERNEL    // 可能睡眠,用于进程上下文
GFP_ATOMIC    // 不能睡眠,用于中断上下文
GFP_DMA       // DMA 内存
GFP_NOWAIT    // 不等待

字符串操作

#include <linux/string.h>

// 复制字符串
strncpy(dest, src, len);

// 比较字符串
result = strcmp(str1, str2);

// 字符串长度
len = strlen(str);

// 格式化字符串
snprintf(buf, size, "Value: %d\n", value);

时间和延迟

#include <linux/delay.h>
#include <linux/jiffies.h>

// 延迟(忙等待)
udelay(10);      // 微秒
mdelay(10);      // 毫秒

// 睡眠(让出 CPU)
msleep(1000);    // 毫秒
ssleep(1);       // 秒

// jiffies(时钟节拍)
unsigned long start = jiffies;
// 等待 5 秒
while (time_before(jiffies, start + 5*HZ));

// 定时器
#include <linux/timer.h>

struct timer_list my_timer;

void timer_callback(struct timer_list *t)
{
    printk(KERN_INFO "Timer expired\n");
}

// 初始化定时器
timer_setup(&my_timer, timer_callback, 0);
my_timer.expires = jiffies + HZ;  // 1 秒后
add_timer(&my_timer);

// 删除定时器
del_timer(&my_timer);

链表操作

#include <linux/list.h>

// 定义链表头
LIST_HEAD(my_list);

// 定义链表节点
struct my_node {
    int data;
    struct list_head list;
};

// 分配并添加节点
struct my_node *node = kmalloc(sizeof(*node), GFP_KERNEL);
node->data = 42;
list_add(&node->list, &my_list);

// 尾部添加
list_add_tail(&node->list, &my_list);

// 遍历链表
struct my_node *pos;
list_for_each_entry(pos, &my_list, list) {
    printk(KERN_INFO "Data: %d\n", pos->data);
}

// 安全遍历(允许删除)
struct my_node *n;
list_for_each_entry_safe(pos, n, &my_list, list) {
    list_del(&pos->list);
    kfree(pos);
}

同步机制

自旋锁

#include <linux/spinlock.h>

// 定义自旋锁
DEFINE_SPINLOCK(my_lock);

// 获取锁
spin_lock(&my_lock);
// 临界区代码
spin_unlock(&my_lock);

// 中断安全版本
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
// 临界区代码
spin_unlock_irqrestore(&my_lock, flags);

// 读写自旋锁
DEFINE_RWLOCK(my_rwlock);

// 读锁
read_lock(&my_rwlock);
// 读操作
read_unlock(&my_rwlock);

// 写锁
write_lock(&my_rwlock);
// 写操作
write_unlock(&my_rwlock);

信号量

#include <linux/semaphore.h>

// 定义信号量
struct semaphore my_sem;

// 初始化(计数为 1,用作互斥锁)
sema_init(&my_sem, 1);

// 获取信号量(可能睡眠)
if (down_interruptible(&my_sem)) {
    return -ERESTARTSYS;
}
// 临界区代码
up(&my_sem);

// 尝试获取(不阻塞)
if (down_trylock(&my_sem) == 0) {
    // 获取成功
    up(&my_sem);
}

互斥锁

#include <linux/mutex.h>

// 定义互斥锁
DEFINE_MUTEX(my_mutex);

// 获取锁
mutex_lock(&my_mutex);
// 临界区代码
mutex_unlock(&my_mutex);

// 可中断版本
if (mutex_lock_interruptible(&my_mutex)) {
    return -ERESTARTSYS;
}
mutex_unlock(&my_mutex);

// 尝试获取
if (mutex_trylock(&my_mutex)) {
    // 获取成功
    mutex_unlock(&my_mutex);
}

错误处理

错误码

#include <linux/errno.h>

// 常用错误码
-ENOMEM     // 内存不足
-EINVAL     // 无效参数
-EFAULT     // 内存访问错误
-EIO        // I/O 错误
-EBUSY      // 设备忙
-EAGAIN     // 重试
-ENODEV     // 设备不存在

// 返回错误
if (error_condition) {
    return -EINVAL;
}

// 指针错误
void *ptr = some_function();
if (IS_ERR(ptr)) {
    return PTR_ERR(ptr);
}

// 创建错误指针
return ERR_PTR(-ENOMEM);

调试技巧

// printk 日志级别
printk(KERN_EMERG   "Emergency\n");    // 0
printk(KERN_ALERT   "Alert\n");        // 1
printk(KERN_CRIT    "Critical\n");     // 2
printk(KERN_ERR     "Error\n");        // 3
printk(KERN_WARNING "Warning\n");      // 4
printk(KERN_NOTICE  "Notice\n");       // 5
printk(KERN_INFO    "Info\n");         // 6
printk(KERN_DEBUG   "Debug\n");        // 7

// 动态调试
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

pr_info("Module loaded\n");
pr_err("Error occurred: %d\n", error);
pr_debug("Debug info: %s\n", str);

// 断言
BUG_ON(condition);     // 触发内核 panic
WARN_ON(condition);    // 打印警告但继续运行

// 打印函数名和行号
pr_info("%s:%d: Message\n", __func__, __LINE__);

完整示例:计数器模块

// counter.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/mutex.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple counter module");

static struct proc_dir_entry *proc_entry;
static unsigned long counter = 0;
static DEFINE_MUTEX(counter_mutex);

static int counter_show(struct seq_file *m, void *v)
{
    mutex_lock(&counter_mutex);
    seq_printf(m, "Counter: %lu\n", counter);
    mutex_unlock(&counter_mutex);
    return 0;
}

static ssize_t counter_write(struct file *file, const char __user *buffer,
                             size_t count, loff_t *pos)
{
    char input[32];
    unsigned long value;
    
    if (count >= sizeof(input))
        return -EINVAL;
    
    if (copy_from_user(input, buffer, count))
        return -EFAULT;
    
    input[count] = '\0';
    
    if (kstrtoul(input, 10, &value))
        return -EINVAL;
    
    mutex_lock(&counter_mutex);
    counter = value;
    mutex_unlock(&counter_mutex);
    
    return count;
}

static int counter_open(struct inode *inode, struct file *file)
{
    return single_open(file, counter_show, NULL);
}

static const struct proc_ops counter_fops = {
    .proc_open = counter_open,
    .proc_read = seq_read,
    .proc_write = counter_write,
    .proc_lseek = seq_lseek,
    .proc_release = single_release,
};

static int __init counter_init(void)
{
    proc_entry = proc_create("counter", 0666, NULL, &counter_fops);
    if (!proc_entry) {
        pr_err("Failed to create /proc/counter\n");
        return -ENOMEM;
    }
    
    pr_info("Counter module loaded\n");
    return 0;
}

static void __exit counter_exit(void)
{
    proc_remove(proc_entry);
    pr_info("Counter module unloaded (final count: %lu)\n", counter);
}

module_init(counter_init);
module_exit(counter_exit);

使用示例

# 编译加载
make
sudo insmod counter.ko

# 读取计数
cat /proc/counter

# 写入计数
echo 100 | sudo tee /proc/counter
cat /proc/counter

# 卸载
sudo rmmod counter
dmesg | tail

调试工具

使用 dmesg

# 查看所有内核消息
dmesg

# 实时查看
dmesg -w

# 清空缓冲区
sudo dmesg -C

# 按级别过滤
dmesg -l err,warn

使用 ftrace

# 启用函数跟踪
echo function > /sys/kernel/debug/tracing/current_tracer

# 设置要跟踪的模块
echo ':mod:hello' > /sys/kernel/debug/tracing/set_ftrace_filter

# 启动跟踪
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 查看跟踪结果
cat /sys/kernel/debug/tracing/trace

# 停止跟踪
echo 0 > /sys/kernel/debug/tracing/tracing_on

下一步:学习字符设备驱动开发 章节。