内核模块

一、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

六、模块间的相互加载

七、模块签名(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 即可,无需手动清理。