3. 内核模块基础

Linux 内核是一个单体内核(Monolithic Kernel),但通过内核模块(Kernel Module)机制实现了动态扩展能力。模块是可以在运行时加载到内核或从内核卸载的代码片段,无需重新编译整个内核或重启系统。在 Android 内核驱动开发中,绝大多数设备驱动都以模块形式存在。

3.1 模块概念与生命周期

内核模块本质上是一个目标文件(.ko),包含初始化函数、清理函数以及可选的导出符号。模块的生命周期分为四个阶段:加载、初始化、运行、卸载。

核心要点:模块运行在内核空间,拥有最高特权级(Ring 0),可以访问所有硬件资源和内核数据结构。模块代码中的任何错误(如空指针解引用、死锁)都会导致内核崩溃(Kernel Panic)。
内核模块生命周期状态机 编译完成 .ko 文件 已加载 insmod 运行中 init 成功 已卸载 rmmod 关键系统调用 init_module() / delete_module()

3.2 模块加载与卸载

模块的加载和卸载通过用户空间工具触发,最终调用内核系统调用完成。最简模块必须包含两个宏:module_init() 和 module_exit()。

3.2.1 最小内核模块示例

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

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Android Kernel!\n");
    return 0;  // 0 表示初始化成功
}

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

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World module for Android");

3.2.2 加载与卸载命令

操作 命令 说明
加载模块 insmod hello.ko 直接加载,不处理依赖
加载模块(含依赖) modprobe hello 自动加载依赖模块
卸载模块 rmmod hello 卸载指定模块
查看已加载模块 lsmod 列出所有模块及使用计数
查看模块信息 modinfo hello.ko 显示作者、描述、参数等
Android 特有注意事项:在 Android 设备上,insmod 和 rmmod 需要 root 权限。生产环境通常使用 modprobe 配合 /lib/modules/ 下的模块配置。部分 Android 内核启用了 CONFIG_MODULE_SIG_FORCE,要求模块必须经过签名验证才能加载。

3.3 模块参数

模块参数允许在加载时向模块传递配置值,无需重新编译。使用 module_param() 宏声明参数,支持基本数据类型和字符串。

3.3.1 参数声明与使用

#include <linux/moduleparam.h>

static int debug_level = 0;
static char *device_name = "default_dev";

module_param(debug_level, int, 0644);
module_param(device_name, charp, 0644);

MODULE_PARM_DESC(debug_level, "Debug level (0-3)");
MODULE_PARM_DESC(device_name, "Device name string");

3.3.2 参数传递方式

  • 命令行传递:insmod mydriver.ko debug_level=2 device_name="sensor0"
  • modprobe 配置文件:在 /etc/modprobe.d/ 下创建 .conf 文件,写入 options mydriver debug_level=2
  • sysfs 运行时修改:如果权限位包含写权限(如 0644),可通过 /sys/module/mydriver/parameters/debug_level 动态修改
参数类型 宏 sysfs 权限 示例
整型 module_param(name, int, perm) 0644 debug_level=2
布尔型 module_param(name, bool, perm) 0644 enable_irq=1
字符串 module_param(name, charp, perm) 0444 device_name="sensor"
数组 module_param_array(name, type, num, perm) 0444 irq_list=10,20,30
模块参数传递路径 用户空间 insmod / modprobe 内核参数解析 parse_args() 模块参数变量 static int debug_level sysfs 接口 /sys/module/.../parameters/ 运行时动态调整 echo 3 > /sys/.../debug_level

3.4 模块依赖与符号导出

模块之间可以存在依赖关系:一个模块可能使用另一个模块导出的函数或变量。内核通过符号导出(Symbol Export)机制实现模块间的接口共享。

3.4.1 符号导出宏

宏 可见性 使用场景
EXPORT_SYMBOL(symbol) 所有模块可见 通用驱动接口
EXPORT_SYMBOL_GPL(symbol) 仅 GPL 许可模块可见 需要 GPL 兼容性保护

3.4.2 导出与使用示例

导出模块(exporter.c):

#include <linux/module.h>

int add_numbers(int a, int b)
{
    return a + b;
}
EXPORT_SYMBOL(add_numbers);

static int __init exporter_init(void)
{
    pr_info("Exporter module loaded\n");
    return 0;
}

static void __exit exporter_exit(void)
{
    pr_info("Exporter module unloaded\n");
}

module_init(exporter_init);
module_exit(exporter_exit);
MODULE_LICENSE("GPL");

使用模块(importer.c):

#include <linux/module.h>

extern int add_numbers(int a, int b);

static int __init importer_init(void)
{
    int result = add_numbers(10, 20);
    pr_info("Result from exporter: %d\n", result);
    return 0;
}

static void __exit importer_exit(void)
{
    pr_info("Importer module unloaded\n");
}

module_init(importer_init);
module_exit(importer_exit);
MODULE_LICENSE("GPL");

3.4.3 依赖管理

  • 自动依赖解析:modprobe 读取 /lib/modules/$(uname -r)/modules.dep 文件,自动加载所有依赖模块
  • 手动查看依赖:modinfo -F depends exporter.ko 显示依赖列表
  • 引用计数:内核维护每个模块的引用计数(lsmod 的 Used by 列),计数不为 0 时无法卸载
模块依赖与符号导出关系图 模块 A 基础驱动层 EXPORT_SYMBOL(func_a) 模块 B 中间层 EXPORT_SYMBOL_GPL(func_b) 模块 C 设备驱动 使用 func_a + func_b 加载顺序约束 必须先加载模块 A → 再加载模块 B → 最后加载模块 C 卸载顺序相反:先卸载 C → B → A

3.5 模块编译与 Makefile

内核模块的编译依赖内核源码树中的 Kbuild 系统。标准的模块 Makefile 模板如下:

obj-m := hello.o

# 如果模块由多个源文件组成
# hello-objs := main.o helper.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
Android 内核编译提示:在 AOSP 环境中,使用 make modules 或 make modules_install 编译和安装模块。交叉编译时需要设置 ARCH 和 CROSS_COMPILE 环境变量,并指定正确的内核源码路径。

3.6 本章小结

知识点 关键命令/宏 注意事项
模块加载 insmod / modprobe Android 需 root 权限,可能要求模块签名
模块卸载 rmmod 引用计数为 0 才能卸载
模块参数 module_param() sysfs 权限位控制运行时修改能力
符号导出 EXPORT_SYMBOL() GPL 版本限制非 GPL 模块使用
依赖管理 modprobe / modules.dep 加载顺序:先依赖后使用;卸载顺序相反