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、内核调试的第一块基石

相关推荐
yuuki2332331 小时前
【Linux】开发工具链全解析:从 apt 到 gdb
linux·运维·服务器
wangjialelele1 小时前
C++11、C++14、C++17、C++20新特性解析(一)
linux·c语言·开发语言·c++·c++20·visual studio
²º²²এ松1 小时前
vs code连接ubuntu esp项目
linux·数据库·ubuntu
浪客灿心1 小时前
Linux进程信号
linux
一勺菠萝丶2 小时前
芋道框架 - API 前缀区分机制
java·linux·python
西木Qi3 小时前
Centos10及下载
linux
面对疾风叭!哈撒给3 小时前
Linux之Docker安装Mysql 8.0+
linux·mysql·docker
代码AC不AC3 小时前
【Linux】进程池
linux·主从模式·进程池
feng一样的男子3 小时前
Rocky Linux 9 配置 IPv6 完整指南
linux·网络
十五年专注C++开发3 小时前
Linux 下用 VS Code 高效调试
linux·运维·服务器·c++·vscode