Part 5:module_init / module_exit / printk / MODULE_LICENSE 核心机制
这一节把 hello 模块里的几个核心宏讲透。目标是:
以后你看到一个内核模块,能立刻看出它的入口、出口、日志和许可证。
资料里的学习路线也把 module_init / module_exit / printk / MODULE_LICENSE 放在 hello 模块之后,属于必须掌握的核心部分。
1. 先看代码位置
还是这段最小模块:
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk("hello module init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("hello module exit\n");
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
这一节只看 4 个东西:
module_init
module_exit
printk
MODULE_LICENSE
2. module_init:注册加载入口
module_init(hello_init);
它的作用是:
告诉内核:这个模块被加载时,调用
hello_init()。
也就是:
sudo insmod hello.ko
↓
内核加载模块
↓
调用 hello_init()
所以 hello_init() 就是模块的加载入口。
注意:
内核模块没有 main 函数。
用户态程序靠:
int main()
内核模块靠:
module_init(hello_init);
注册入口。
hello_init 返回值有什么意义
static int __init hello_init(void)
{
printk("hello module init\n");
return 0;
}
返回值:
| 返回值 | 含义 |
|---|---|
0 | 初始化成功,模块加载成功 |
| 非 0 | 初始化失败,模块加载失败 |
比如项目里,如果初始化阶段注册字符设备失败,就应该返回错误码:
return -ENOMEM; // 内存不足
return -EINVAL; // 参数非法
return -EBUSY; // 资源忙
面试里可以说:
module_init注册的是模块加载入口,返回 0 表示加载成功,返回负错误码表示初始化失败。
3. module_exit:注册卸载出口
module_exit(hello_exit);
它的作用是:
告诉内核:这个模块被卸载时,调用
hello_exit()。
也就是:
sudo rmmod hello
↓
内核准备卸载模块
↓
调用 hello_exit()
hello_exit() 主要负责释放资源。
static void __exit hello_exit(void)
{
printk("hello module exit\n");
}
hello 模块只打印日志,所以 exit 里只有 printk。
但真实项目里,exit 里通常要做:
注销字符设备
销毁 class / device
释放内核缓冲区
停止定时器
停止内核线程
删除 proc/debugfs 节点
对你的性能监控项目来说,应该这样理解:
module_init:
注册字符设备
初始化 mmap 缓冲区
启动 CPU / softirq 采集逻辑
module_exit:
停止采集
注销字符设备
释放 mmap 缓冲区
清理内核资源
核心原则:
init 里申请什么,exit 里就要释放什么。
4. printk:内核态日志打印
用户态程序打印:
printf("hello\n");
内核模块打印:
printk("hello module init\n");
printk 的输出不会像 printf 一样直接出现在终端,它会写入内核日志缓冲区。
查看日志:
dmesg | tail
常用流程:
sudo dmesg -C
sudo insmod hello.ko
dmesg | tail
sudo rmmod hello
dmesg | tail
printk 可以带日志级别
更标准的写法是:
printk(KERN_INFO "hello module init\n");
常见级别:
| 级别 | 含义 |
|---|---|
KERN_EMERG | 系统不可用 |
KERN_ALERT | 必须立即处理 |
KERN_CRIT | 严重错误 |
KERN_ERR | 普通错误 |
KERN_WARNING | 警告 |
KERN_NOTICE | 需要注意 |
KERN_INFO | 普通信息 |
KERN_DEBUG | 调试信息 |
速成阶段你主要记:
KERN_INFO:普通信息
KERN_ERR:错误信息
KERN_DEBUG:调试信息
项目里可以这样用:
printk(KERN_INFO "cpu_stat_collector: module loaded\n");
printk(KERN_ERR "cpu_stat_collector: failed to register char device\n");
5. MODULE_LICENSE("GPL"):许可证声明
MODULE_LICENSE("GPL");
它声明模块许可证。
它有三个作用:
1. 告诉内核模块的许可证类型
2. 避免内核 warning
3. 影响能否使用 GPL-only 内核符号
如果不写,加载时可能看到类似 warning:
module license 'unspecified' taints kernel
也就是:
内核认为这个模块许可证不明确,会标记内核被 taint。
什么是 taint
taint 可以理解为:
内核被非标准或不可信模块污染了状态。
比如你加载一个闭源模块、强制加载不匹配模块,内核会标记 taint。
这不一定导致系统立刻崩,但它表示:
当前内核状态不再是纯净状态
后续排查 kernel panic 时可信度下降
速成记法:
MODULE_LICENSE("GPL")可以避免许可证不明导致的 taint warning,并允许模块使用部分 GPL-only 符号。
6. 这几个宏来自哪里
这几个东西主要来自:
#include <linux/module.h>
所以写模块时一般至少有:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
更完整的 hello 模块可以写成:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
printk(KERN_INFO "hello module init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "hello module exit\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yu");
MODULE_DESCRIPTION("A simple hello kernel module");
module_init(hello_init);
module_exit(hello_exit);
其中:
MODULE_AUTHOR
MODULE_DESCRIPTION
不是必须,但可以让 modinfo 看到更多模块元信息。
7. 放到项目里怎么理解
你的性能监控项目里,hello 模块对应的是生命周期模板。
真正项目模块大概是:
static int __init cpu_stat_init(void)
{
// 1. 申请设备号
// 2. 注册字符设备
// 3. 创建 /dev/xxx
// 4. 分配 mmap 缓冲区
// 5. 启动采集逻辑
return 0;
}
static void __exit cpu_stat_exit(void)
{
// 1. 停止采集逻辑
// 2. 销毁 /dev/xxx
// 3. 注销字符设备
// 4. 释放 mmap 缓冲区
}
module_init(cpu_stat_init);
module_exit(cpu_stat_exit);
MODULE_LICENSE("GPL");
所以你面试时不要停留在 hello。
要接到项目链路:
insmod cpu_stat_collector.ko
↓
module_init 执行
↓
注册字符设备 / 初始化 mmap 缓冲区
↓
Worker open /dev/xxx
↓
Worker mmap 读取 CPU / softirq 数据
↓
MetricCollector 聚合
↓
MonitorInfo gRPC 上报 Manager
项目资料里明确 Worker 端不是只读普通文件,而是混合使用 /proc、字符设备、mmap 和 eBPF map 做指标采集。
8. 这一节最小记忆版
module_init(fn)
注册模块加载入口,insmod 时执行 fn。
module_exit(fn)
注册模块卸载出口,rmmod 时执行 fn。
printk
内核态打印函数,日志通过 dmesg 查看。
MODULE_LICENSE("GPL")
声明模块许可证,避免 license warning,也影响 GPL-only 符号访问。
内核模块没有 main 函数。
加载入口靠 module_init。
卸载出口靠 module_exit。
init 里申请资源,exit 里释放资源。
9. 面试版回答
面试官问:module_init、module_exit、printk、MODULE_LICENSE 分别有什么作用?
你可以答:
module_init用来注册模块加载入口,执行insmod时内核会调用它注册的初始化函数;module_exit用来注册模块卸载出口,执行rmmod时内核会调用它注册的退出函数。内核模块没有main函数,所以入口和出口都靠这两个宏注册。printk是内核态日志打印函数,输出会进入内核日志缓冲区,通常通过dmesg查看。MODULE_LICENSE("GPL")用来声明模块许可证,避免许可证不明导致内核 taint,同时影响是否能使用某些 GPL-only 内核符号。在性能监控项目里,初始化函数会注册字符设备和 mmap 缓冲区,退出函数负责注销设备并释放资源。

402

被折叠的 条评论
为什么被折叠?



