3. Linux 内核模块基础:内核模块概念、模块加载与卸载、module_init 与 module_exit 宏、模块许可证与作者信息

在嵌入式 Linux 与 Android 内核开发中,内核模块(Kernel Module)是动态扩展内核功能的核心机制。它允许开发者在不重新编译整个内核、不重启设备的情况下,向运行中的内核插入或移除代码。本章将系统讲解内核模块的基本概念、生命周期管理、入口/出口宏定义以及许可证与作者信息的规范写法,为后续 GPIO 驱动开发奠定基础。

3.1 内核模块概念

内核模块是一段可以在运行时被加载到内核地址空间的代码,通常用于实现设备驱动、文件系统、网络协议等。与直接编译进内核(built-in)相比,模块化具有以下优势:

  • 动态加载/卸载:无需重启系统即可添加或移除功能。
  • 节省内存:仅在需要时加载模块,减少内核映像体积。
  • 快速迭代:开发阶段可频繁修改、编译、加载模块,无需烧录整个内核。
  • 热插拔支持:配合 udev 或设备树,可自动加载对应驱动模块。
💡 提示:在 Android 内核中,大部分硬件驱动(包括 GPIO、I2C、SPI 等)都以模块形式存在,存放在 /vendor/lib/modules/ 或 /system/lib/modules/ 目录下。
内核模块与内核核心的关系 Linux 内核核心 (Kernel Core) GPIO 驱动模块 I2C 控制器模块 网络协议模块 模块通过内核符号表(kallsyms)调用核心函数,核心通过 module_init 注册模块入口 模块之间也可相互依赖,如 GPIO 模块可被 I2C 模块调用

3.2 模块加载与卸载

内核模块的生命周期由用户空间工具和内核协同管理。常用的命令如下:

命令 功能 示例
insmod 加载指定 .ko 模块文件,不自动处理依赖 insmod gpio_driver.ko
rmmod 卸载指定模块,需确保无其他模块依赖 rmmod gpio_driver
modprobe 智能加载/卸载,自动处理模块依赖 modprobe gpio_driver
lsmod 列出当前已加载的所有模块 lsmod | grep gpio
modinfo 查看模块的详细信息(作者、描述、参数等) modinfo gpio_driver.ko
⚠️ 注意:在 Android 设备上,由于 selinux 策略限制,普通应用无法直接执行 insmod,通常需要 root 权限或通过 vendor_init 脚本在 init 阶段加载。

3.3 module_init 与 module_exit 宏

每个内核模块必须定义两个核心宏:module_init() 和 module_exit(),分别指定模块加载和卸载时调用的函数。这两个宏是模块与内核之间的“契约”。

3.3.1 宏定义与函数签名

宏 触发时机 函数返回值 函数参数
module_init(x) 执行 insmod 或 modprobe 时 int(0 成功,负值失败) 无
module_exit(x) 执行 rmmod 或 modprobe -r 时 void 无

3.3.2 标准模块代码框架

#include <linux/module.h>      // 所有模块必须包含
#include <linux/kernel.h>      // 提供 printk 等内核打印函数
#include <linux/init.h>        // 提供 __init 和 __exit 宏

static int __init gpio_driver_init(void)
{
    printk(KERN_INFO "GPIO Driver: 模块加载成功\n");
    // 此处后续将放置 GPIO 注册、中断申请等代码
    return 0;
}

static void __exit gpio_driver_exit(void)
{
    printk(KERN_INFO "GPIO Driver: 模块卸载成功\n");
    // 此处后续将放置 GPIO 释放、中断注销等代码
}

module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
🔑 关键点:
  • __init 标记:提示内核该函数仅在初始化阶段使用,加载完成后可释放占用的内存。
  • __exit 标记:提示内核该函数仅在模块卸载时使用,若模块被编译进内核(built-in),此函数会被忽略以节省空间。
  • printk 是内核空间的打印函数,KERN_INFO 表示日志级别,可通过 dmesg 查看输出。
模块生命周期:加载 → 运行 → 卸载 insmod / modprobe rmmod / modprobe -r 内核模块加载器 module_init() → gpio_driver_init() 模块常驻内存,提供服务 卸载请求 module_exit() → gpio_driver_exit()

3.4 模块许可证与作者信息

Linux 内核采用 GPL 许可证,内核模块必须声明其许可证类型,否则内核会认为该模块是“污染”内核(tainted kernel),并限制某些 GPL-only 符号的调用。此外,添加作者和描述信息有助于维护和调试。

3.4.1 常用许可证宏

宏 含义 是否允许调用 GPL-only 符号
MODULE_LICENSE("GPL") GNU General Public License v2 ✅ 是
MODULE_LICENSE("GPL v2") GPL v2 版本 ✅ 是
MODULE_LICENSE("GPL and additional rights") GPL 附加额外权利 ✅ 是
MODULE_LICENSE("Dual BSD/GPL") 双许可证:BSD 或 GPL ✅ 是
MODULE_LICENSE("Proprietary") 专有许可证 ❌ 否(内核标记为 tainted)

3.4.2 作者与描述信息宏

MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("GPIO Driver for Android Kernel Project");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:gpio-driver");
📌 最佳实践:
  • 商业项目或开源项目,建议使用 MODULE_LICENSE("GPL v2"),以确保与内核完全兼容。
  • MODULE_AUTHOR 应包含姓名和邮箱,方便社区或团队联系。
  • MODULE_DESCRIPTION 应简明扼要描述模块功能,如 "GPIO interrupt handler for sensor input"。
  • MODULE_VERSION 遵循语义化版本号,如 "1.0.0"。

3.5 完整的最小模块示例

将上述所有元素整合,得到一个完整的、可编译的内核模块源文件:

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

static int __init gpio_driver_init(void)
{
    printk(KERN_INFO "GPIO Driver: 初始化成功 (版本 %s)\n", DRV_VERSION);
    return 0;
}

static void __exit gpio_driver_exit(void)
{
    printk(KERN_INFO "GPIO Driver: 卸载完成\n");
}

module_init(gpio_driver_init);
module_exit(gpio_driver_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Android Kernel Team <kernel@android.com>");
MODULE_DESCRIPTION("GPIO Driver Module for Android Kernel Project");
MODULE_VERSION("1.0.0");
🧪 验证方法:
  1. 使用 make -C /lib/modules/$(uname -r)/build M=$(pwd) modules 编译生成 .ko 文件。
  2. 执行 insmod gpio_driver.ko 加载模块,通过 dmesg | tail 查看打印信息。
  3. 执行 lsmod | grep gpio 确认模块已加载。
  4. 执行 rmmod gpio_driver 卸载模块,再次查看 dmesg 确认卸载信息。
内核模块元数据信息结构 gpio_driver.ko MODULE_LICENSE("GPL v2") MODULE_AUTHOR(...) MODULE_DESCRIPTION(...) MODULE_VERSION("1.0.0") 这些元数据通过 modinfo 命令查看,是模块身份标识和合规性检查的重要依据

3.6 本章小结

  • 内核模块是 Linux 动态扩展功能的基石,在 Android 内核驱动开发中广泛使用。
  • module_init() 和 module_exit() 是模块的入口和出口,必须正确实现。
  • 模块许可证声明(MODULE_LICENSE)影响内核符号的访问权限,务必使用 GPL 兼容许可证。
  • 作者、描述、版本等元信息有助于模块的管理和调试,是专业开发的基本素养。
  • 掌握 insmod、rmmod、lsmod、modinfo 等工具,是调试模块的基础技能。