3. 内核模块基础:模块概念、模块与驱动的区别、模块生命周期、insmod/rmmod/lsmod/modinfo 命令详解

3.1 内核模块概念

Linux 内核是一个单体内核(Monolithic Kernel),所有核心功能(进程调度、内存管理、文件系统、网络协议栈)都运行在内核空间。如果每次添加新功能都需要重新编译整个内核并重启系统,开发和调试效率将极低。

内核模块(Kernel Module) 是 Linux 为解决此问题而设计的动态加载机制。模块是一段可以在运行时被插入内核或从内核移除的代码,它扩展了内核的功能,而无需重新编译内核或重启系统。

核心价值: 模块机制使 Linux 内核具备了“可插拔”的灵活性。驱动开发人员可以独立编译、测试一个模块,加载后立即生效,卸载后释放资源,极大缩短了“修改-编译-测试”的迭代周期。

3.2 模块与驱动的区别

在嵌入式 Linux 开发中,“模块”和“驱动”经常被混用,但它们在概念上有明确区分:

对比维度 内核模块(Module) 设备驱动(Driver)
定义 一段可动态加载/卸载的内核代码 操作和管理特定硬件设备的软件实体
范围 更广,可以是驱动、文件系统、协议栈、安全策略等 特指与硬件交互的软件层
关系 驱动通常以模块形式存在,但模块不一定是驱动 驱动可以编译进内核(built-in),也可以编译为模块
示例 hello.ko(仅打印日志)、nf_conntrack.ko(网络连接跟踪) i2c_designware.ko(I2C 控制器驱动)、usb_storage.ko(USB 存储驱动)
一句话总结: 模块是“打包和分发”的形式,驱动是“功能和职责”的内容。在字符设备驱动开发中,我们编写的驱动代码最终会被编译成一个 .ko 模块文件。

3.3 模块生命周期

一个内核模块从加载到卸载,经历四个关键阶段。理解这个生命周期是编写正确模块的基础。

内核模块生命周期 insmod 加载阶段 module_init() 初始化阶段 正常运行 运行阶段 rmmod 卸载阶段 生命周期关键说明: 1. 加载:内核将 .ko 文件从用户空间复制到内核空间,分配内存。 2. 初始化:内核调用 module_init() 指定的函数,注册设备、分配资源。 3. 运行:模块处于活动状态,响应系统调用或硬件中断。 4. 卸载:内核调用 module_exit() 指定的函数,释放资源、注销设备。

3.4 模块操作命令详解

Linux 提供了一组用户空间工具来管理内核模块。以下是四个最核心的命令:

3.4.1 insmod — 加载模块

insmod 将指定的 .ko 文件插入内核。它不会自动解析依赖关系,如果模块依赖其他模块,需要先手动加载依赖。

# 加载一个简单的字符设备驱动模块
sudo insmod hello_driver.ko

# 加载带参数的模块(模块内部需声明参数)
sudo insmod hello_driver.ko debug=1 name="mydevice"

3.4.2 rmmod — 卸载模块

rmmod 从内核中移除一个已加载的模块。如果模块正在被使用(例如设备文件被打开),卸载会失败。

# 卸载模块(只需模块名,不带 .ko 后缀)
sudo rmmod hello_driver

# 强制卸载(危险,可能导致内核崩溃,仅调试使用)
sudo rmmod -f hello_driver
⚠️ 安全提示: 切勿在生产环境中使用 rmmod -f。强制卸载会跳过模块的清理函数,可能导致内存泄漏、内核 panic 或系统不稳定。

3.4.3 lsmod — 列出已加载模块

lsmod 读取 /proc/modules 文件,以表格形式显示当前内核中所有已加载的模块信息。

$ lsmod
Module                  Size  Used by
hello_driver           16384  0
usb_storage            73728  0
i2c_designware         24576  0

输出列含义:

  • Module: 模块名称
  • Size: 模块占用的内存大小(字节)
  • Used by: 被其他模块引用的次数及引用者列表

3.4.4 modinfo — 查看模块信息

modinfo 从 .ko 文件中提取模块的元数据,包括作者、描述、许可证、参数等。

$ modinfo hello_driver.ko
filename:       /home/user/hello_driver.ko
license:        GPL
author:         Zhang San
description:    A simple character device driver example
depends:        
name:           hello_driver
version:        1.0
parm:           debug:Enable debug output (int)
parm:           name:Device name string (charp)

3.5 模块依赖与自动加载

实际项目中,一个驱动模块可能依赖多个基础模块(如 i2c-core、gpiolib)。手动管理依赖非常繁琐,因此 Linux 提供了更高级的工具:

命令 功能 与 insmod/rmmod 的关系
modprobe 智能加载/卸载模块,自动解析并加载依赖 推荐替代 insmod 和 rmmod
depmod 生成模块依赖关系文件 modules.dep modprobe 依赖此文件工作
# 使用 modprobe 加载模块(自动处理依赖)
sudo modprobe hello_driver

# 使用 modprobe 卸载模块(仅当无其他模块依赖时)
sudo modprobe -r hello_driver

3.6 模块的入口与出口函数

每个内核模块都必须声明两个宏:module_init() 和 module_exit(),它们定义了模块生命周期的起点和终点。

#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, module loaded!\n");
    return 0;  // 0 表示成功,负数表示失败
}

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

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Engineer");
MODULE_DESCRIPTION("A minimal kernel module example");
关键点:
  • __init 标记:初始化函数使用的内存,在模块加载完成后会被内核回收,节省内存。
  • __exit 标记:如果模块被编译进内核(built-in),此函数会被忽略,因为 built-in 模块不能被卸载。
  • MODULE_LICENSE("GPL") 是必须的,缺少许可证声明会导致内核报“tainted”警告。

3.7 本章小结

  • 内核模块 是 Linux 动态扩展内核功能的机制,驱动通常以模块形式存在。
  • 模块生命周期 分为加载、初始化、运行、卸载四个阶段,由 module_init 和 module_exit 控制。
  • insmod/rmmod/lsmod/modinfo 是模块管理的基础命令,modprobe 是更智能的替代方案。
  • 编写模块时,必须包含 <linux/module.h>,并声明许可证、作者等元信息。