openEuler 内核解读(五):Linux 内核模块 “Hello World” 示例

🧪 实验目标

编写一个最简内核模块,在加载时打印 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

七、扩展思考(面向学习者)

  1. 为什么不用 printf

    → 用户态 C 库(glibc)不可用,内核有独立运行环境。

  2. printk 输出去哪了?

    → 写入内核环形缓冲区(ring buffer),由 klogdjournald 转发到 /var/log/messagesdmesg

  3. 能否传递参数?

    → 可以!使用 module_param(),例如:

    c 复制代码
    static 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
  • 开发建议在 虚拟机 中进行

✅ 至此,你已完成第一个 Linux 内核模块开发,这是通往设备驱动、eBPF、内核调试的第一块基石

相关推荐
jamon_tan5 分钟前
Linux下cmake构建方法
linux
JiaWen技术圈9 分钟前
内核子系统 nf_tables 深度解析
linux·服务器·安全·运维开发
计算机安禾14 分钟前
【Linux从入门到精通】第32篇:Nginx入门——高性能Web服务器搭建
linux·服务器·nginx
ZenosDoron23 分钟前
Linux 中,rm -r 和 -f
linux·运维·服务器
Hello.Reader28 分钟前
Ubuntu 上正确安装 Kali 虚拟机、Docker 与 kail 工具指南
linux·ubuntu·docker
原来是猿35 分钟前
Linux UDP Socket 编程入门:Echo Server/Client实现
linux·运维·udp
中微子37 分钟前
突然爆火的Warp 终端,开源1天破 4w Stars
linux·人工智能·开源
计算机安禾1 小时前
【Linux从入门到精通】第33篇:数据库MySQL/MariaDB安装与基础调优
linux·数据库·mysql
楼兰公子2 小时前
读取rpi摄像头
linux·服务器·算法
李景琰2 小时前
Debian12安装配置Mqtt之EMQX
linux·运维·服务器