内核模块开发基础
内核模块是可以动态加载到内核中的代码,无需重新编译整个内核即可扩展内核功能。
内核模块概述
模块的优势
动态加载/卸载
├─ 无需重启系统
├─ 按需加载驱动
└─ 便于开发调试
模块化设计
├─ 降低内核体积
├─ 提高可维护性
└─ 支持第三方驱动
模块生命周期
编写模块代码
↓
编译生成 .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
下一步:学习字符设备驱动开发 章节。