🧪 实验目标
编写一个最简内核模块,在加载时打印 Hello, openEuler!,卸载时打印 Goodbye, openEuler!。

一、准备工作(在 openEuler 系统中操作)
1. 安装必要工具
bash
sudo dnf install -y kernel-devel kernel-headers gcc make elfutils-libelf-devel
✅ 验证:确保
/lib/modules/$(uname -r)/build存在(指向内核源码头文件)
2. 创建工作目录
bash
mkdir ~/hello_module && cd ~/hello_module

二、编写内核模块代码
创建文件 hello.c:
c
/* 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 for openEuler");
MODULE_VERSION("0.1");
// 模块加载时执行
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, openEuler!\n");
return 0; // 成功
}
// 模块卸载时执行
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, openEuler!\n");
}
// 注册入口和出口函数
module_init(hello_init);
module_exit(hello_exit);
🔍 说明:
printk是内核版的printf,输出到内核日志(非终端)KERN_INFO是日志级别(对应 syslog 的 info)__init/__exit是内存优化标记(初始化后释放)
三、编写 Makefile
创建 Makefile(注意:必须是 Makefile,首字母大写):
makefile
# Makefile for hello.ko
obj-m += hello.o
# 获取当前内核构建目录
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
⚠️ 注意:缩进必须用 Tab(不是空格),否则 make 报错。
四、编译模块
bash
make
成功后生成:
hello.ko(可加载内核模块)hello.mod.c,hello.mod.o(中间文件)
✅ 验证:
file hello.ko应显示 "Linux kernel module"
五、加载与测试
1. 加载模块
bash
sudo insmod hello.ko
2. 查看内核日志
bash
dmesg | tail -n 2
预期输出:
[XXXXX.XXXXXX] Hello, openEuler!
3. 卸载模块
bash
sudo rmmod hello
再次查看日志:
bash
dmesg | tail -n 2
预期输出:
[XXXXX.XXXXXX] Goodbye, openEuler!
4. (可选)查看已加载模块
bash
lsmod | grep hello
六、常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
insmod: ERROR: could not insert module hello.ko: Invalid module format |
内核版本不匹配 | 确保 kernel-devel 版本与 uname -r 一致 |
make: *** /lib/modules/.../build: No such file or directory |
未安装 kernel-devel | 执行 sudo dnf install kernel-devel-$(uname -r) |
| 无输出 | 日志级别被过滤 | 检查 cat /proc/sys/kernel/printk,默认应为 4 4 1 7 |
七、扩展思考(面向学习者)
-
为什么不用
printf?→ 用户态 C 库(glibc)不可用,内核有独立运行环境。
-
printk输出去哪了?→ 写入内核环形缓冲区(ring buffer),由
klogd或journald转发到/var/log/messages或dmesg。 -
能否传递参数?
→ 可以!使用
module_param(),例如:cstatic char *name = "openEuler"; module_param(name, charp, S_IRUGO);加载时:
sudo insmod hello.ko name="World"
八、安全与规范提醒
- 内核模块拥有 最高权限(ring 0),bug 可能导致系统崩溃(oops/panic)
- 生产环境禁止随意加载未签名模块(openEuler 默认启用 Secure Boot + Lockdown LSM)
- 开发建议在 虚拟机 中进行