2. 第一个内核模块:Hello World 模块的编写、Makefile 构建、insmod/rmmod 加载与卸载

内核模块是 Linux 驱动开发的最小可加载单元。本章通过一个最简的 Hello World 模块,完整演示从源码编写、Makefile 构建到动态加载/卸载的全流程。掌握这一基础链路后,后续所有驱动实验都将复用相同的框架。

2.1 模块源码结构

一个标准内核模块至少包含两个宏:模块许可证声明和入口/出口函数。以下是最简模块的完整代码:

/* hello.c — 第一个内核模块 */
#include <linux/module.h>      /* 模块相关宏 */
#include <linux/kernel.h>      /* printk 等级 */
#include <linux/init.h>        /* __init / __exit */

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello World from kernel module!\n");
    return 0;   /* 0 表示加载成功 */
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, kernel module unloaded.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux Expert");
MODULE_DESCRIPTION("A simple Hello World module");
关键点:
  • __init / __exit 标记将函数放入特殊段,模块加载/卸载后自动释放内存。
  • printk 是内核空间的打印函数,通过 dmesg 查看输出,不要使用 printf。
  • MODULE_LICENSE("GPL") 是必须的,缺少会导致加载时提示 tainted kernel。

2.2 Makefile 构建系统

内核模块的编译依赖内核源码树(或内核头文件包)。标准 Makefile 模板如下:

# Makefile — 构建 hello.ko
obj-m := hello.o

# KDIR 指向已编译的内核源码目录
KDIR := /lib/modules/$(shell uname -r)/build

# PWD 为当前工作目录
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean
构建步骤:
  1. 将 hello.c 和 Makefile 放在同一目录。
  2. 执行 make,生成 hello.ko(内核模块文件)。
  3. 使用 make clean 清理临时文件。

2.3 模块加载与卸载命令

操作 命令 说明
加载模块 sudo insmod hello.ko 将模块插入内核,执行 hello_init
查看已加载模块 lsmod | grep hello 列出所有模块,过滤 hello
查看内核日志 dmesg | tail -5 查看 printk 输出
卸载模块 sudo rmmod hello 从内核移除模块,执行 hello_exit
模块信息 modinfo hello.ko 显示作者、描述、许可证等
常见错误:
  • “Operation not permitted” — 忘记 sudo,需要 root 权限。
  • “Invalid module format” — 内核版本与编译头不匹配,检查 uname -r 与 KDIR。
  • “Unknown symbol” — 模块依赖其他模块未加载,或符号未导出。

2.4 模块生命周期与内核交互流程

下图展示了从 insmod 到 rmmod 的完整内核交互路径:

模块生命周期与内核交互流程 insmod hello.ko rmmod hello init_module() 系统调用 delete_module() 系统调用 内核模块加载器 (module loader) 分配内存 / 解析 ELF / 重定位符号 hello_init() / hello_exit() 执行模块自定义逻辑 printk → 内核日志缓冲区 (dmesg) 用户空间 → 内核空间

2.5 模块信息与依赖管理

使用 modinfo 可以查看模块的元数据,这些信息来自源码中的 MODULE_* 宏:

$ modinfo hello.ko
filename:       /home/user/hello.ko
license:        GPL
author:         Embedded Linux Expert
description:    A simple Hello World module
depends:        (空)
vermagic:       5.15.0-generic SMP mod_unload
模块依赖链: 如果模块 A 使用了模块 B 导出的符号,则 A 依赖于 B。加载时必须先加载 B,卸载时先卸载 A。使用 lsmod 可以查看模块间的 Used by 关系。

2.6 本章小结

  • 内核模块使用 module_init / module_exit 注册入口和出口。
  • Makefile 通过 obj-m 和内核源码树完成交叉/本地编译。
  • insmod 加载模块,rmmod 卸载模块,dmesg 查看内核日志。
  • 模块信息通过 modinfo 查看,依赖关系由内核自动维护。