05. Linux内核喂饭版教程

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_initmodule_exitprintkMODULE_LICENSE 分别有什么作用?

你可以答:

module_init 用来注册模块加载入口,执行 insmod 时内核会调用它注册的初始化函数;module_exit 用来注册模块卸载出口,执行 rmmod 时内核会调用它注册的退出函数。内核模块没有 main 函数,所以入口和出口都靠这两个宏注册。printk 是内核态日志打印函数,输出会进入内核日志缓冲区,通常通过 dmesg 查看。MODULE_LICENSE("GPL") 用来声明模块许可证,避免许可证不明导致内核 taint,同时影响是否能使用某些 GPL-only 内核符号。在性能监控项目里,初始化函数会注册字符设备和 mmap 缓冲区,退出函数负责注销设备并释放资源。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值