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),由 klogd 或 journald 转发到 /var/log/messages 或 dmesg。

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

相关推荐
Ruiery1 小时前
Linux 6.6内核内存管理深度解析(十六):mmap — 用户态“申请内存“的真相
linux·运维·服务器
欧米欧2 小时前
Linux系统篇(七),环境变量的底层溯源,核心获取机制与安全应用
linux·运维·服务器
Ruiery2 小时前
Linux 6.6内核内存管理深度解析(十八):KASLR 内核地址空间随机化
linux·运维·服务器·安全
qetfw3 小时前
UOS MariaDB 数据库:安装、最小权限与备份验证
linux·数据库·mariadb·uos
逆风的蚂蚁4 小时前
linux服务器性能调整怎么落地?我把 CPU 调度、I/O 优先级和内核参数这几个旋钮都拧了一遍
linux·运维·服务器
沫璃染墨8 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
AI_Lab0110 小时前
宝塔用了两年,有没有必要换成轻量方案?
linux·运维·服务器
by2099910 小时前
Linux GDB 调试与 CGDB 使用
linux·运维·服务器·经验分享
星恒随风10 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
feng_you_ying_li13 小时前
linux之网络层
linux·运维·网络