内核模块
一、Hello World 模块
1. 源码 hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
pr_info("Hello, kernel!\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("You");
MODULE_DESCRIPTION("Hello module");
2. 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
Makefile 必须用 TAB!
3. 编译、加载、卸载
make
sudo insmod hello.ko # 加载
dmesg | tail # 看输出
sudo rmmod hello # 卸载
注意:模块必须与运行内核版本一致(
uname -r)
二、模块参数
#include <linux/moduleparam.h>
static int my_int = 42;
static char *my_str = "default";
static int my_arr[4] = { 1, 2, 3, 4 };
static int arr_argc;
module_param(my_int, int, 0644);
MODULE_PARM_DESC(my_int, "An integer");
module_param(my_str, charp, 0644);
module_param_array(my_arr, int, &arr_argc, 0644);
加载时传参:
sudo insmod hello.ko my_int=100 my_str=hello my_arr=10,20,30,40
三、导出符号
// foo.c
int my_helper(int x) { return x * 2; }
EXPORT_SYMBOL_GPL(my_helper);
// bar.c(同一模块或不同模块)
extern int my_helper(int);
int call_helper(void) { return my_helper(21); }
四、许可与依赖
MODULE_LICENSE("GPL"); // GPL only
MODULE_LICENSE("GPL v2");
MODULE_LICENSE("GPL and additional rights");
MODULE_LICENSE("Dual BSD/GPL"); // 兼容 BSD
MODULE_LICENSE("Proprietary"); // 不能用 GPL 符号(污染内核)
五、字符设备驱动基础骨架
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEV_NAME "mychardev"
#define BUF_SIZE 1024
static dev_t devno;
static struct cdev my_cdev;
static struct class *my_class;
static char device_buffer[BUF_SIZE];
static size_t data_len;
static int my_open(struct inode *inode, struct file *filp)
{
pr_info("open\n");
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
pr_info("release\n");
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
size_t to_copy = min(count, data_len);
if (copy_to_user(buf, device_buffer, to_copy))
return -EFAULT;
return to_copy;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
size_t to_copy = min(count, (size_t)BUF_SIZE);
if (copy_from_user(device_buffer, buf, to_copy))
return -EFAULT;
data_len = to_copy;
return to_copy;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
static int __init my_init(void)
{
alloc_chrdev_region(&devno, 0, 1, DEV_NAME);
cdev_init(&my_cdev, &my_fops);
cdev_add(&my_cdev, devno, 1);
my_class = class_create(THIS_MODULE, DEV_NAME);
device_create(my_class, NULL, devno, NULL, DEV_NAME);
pr_info("loaded: %s (major=%d minor=%d)\n",
DEV_NAME, MAJOR(devno), MINOR(devno));
return 0;
}
static void __exit my_exit(void)
{
device_destroy(my_class, devno);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(devno, 1);
pr_info("unloaded\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
测试:
sudo insmod mychardev.ko
ls /dev/mychardev
echo "hello" > /dev/mychardev
cat /dev/mychardev
sudo rmmod mychardev
六、模块间的相互加载
- 模块 A 导出函数
- 模块 B 调用(需
EXPORT_SYMBOL) - A 必须先加载,B 才能 load(否则
-EAGAIN) - 用
request_module("A")自动加载
七、模块签名(Secure Boot)
现代内核启用 Secure Boot 后只允许签名模块加载。
# 内核配置
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"
# 用户自己签
openssl req -new -x509 -newkey rsa:2048 -nodes \
-keyout MOK.key -out MOK.crt -subj "/CN=Module/"
sudo kmodsign sha512 MOK.key MOK.crt mymodule.ko
八、模块调试
# modinfo
modinfo hello.ko
# 看依赖
lsmod
modprobe --show-depends hello
# 强制加载(危险!)
sudo insmod --force hello.ko
# 看模块符号
cat /proc/kallsyms | grep my_helper
九、模块资源管理(devm_*)
推荐使用 managed API,离开时自动释放:
devm_kmalloc(dev, size, GFP_KERNEL);
devm_kzalloc(dev, size, GFP_KERNEL);
devm_request_irq(dev, irq, handler, flags, name, dev);
devm_ioremap(dev, addr, size);
错误时 return ret 即可,无需手动清理。