2. 模块参数与导出符号
在Linux内核模块开发中,模块参数、导出符号和模块依赖是构建可配置、可复用驱动的基础设施。本章将深入剖析这三个核心机制,帮助你在Platform驱动开发中实现灵活的运行时配置和模块间协作。
2.1 模块参数:module_param宏
模块参数允许用户在加载模块时传递配置值,无需重新编译内核。通过module_param宏族,驱动开发者可以将模块内部的全局变量暴露为可调整的参数。
2.1.1 基本用法
#include <linux/module.h>
#include <linux/moduleparam.h>
static int irq_num = 10;
static char *device_name = "my_device";
static int debug_level = 0;
module_param(irq_num, int, 0644);
module_param(device_name, charp, 0644);
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(irq_num, "IRQ number for the device");
MODULE_PARM_DESC(device_name, "Device name string");
MODULE_PARM_DESC(debug_level, "Debug level (0-3)");
加载模块时,用户可以通过命令行指定参数值:
sudo insmod my_driver.ko irq_num=17 device_name="sensor0" debug_level=2
2.1.2 参数类型与权限
| 类型标识 | C语言类型 | 说明 | 典型场景 |
|---|---|---|---|
byte |
unsigned char | 单字节值 | 硬件寄存器掩码 |
short |
short | 短整数 | 传感器阈值 |
int |
int | 标准整数 | IRQ号、GPIO引脚 |
long |
long | 长整数 | 内存地址偏移 |
charp |
char * | 字符串指针 | 设备名称、路径 |
bool |
bool | 布尔值 | 启用/禁用特性 |
invbool |
bool | 反转布尔值 | 反向逻辑控制 |
权限值使用标准的Linux文件权限八进制表示:
- 0:参数完全隐藏,仅模块内部可见
- 0444:所有人可读(通过sysfs)
- 0644:所有者可读写,其他人只读
- 0600:仅所有者可读写
实战建议:在Platform驱动中,将硬件资源(如IRQ号、内存基址)设计为模块参数,可以在不修改代码的情况下适配不同硬件平台。调试阶段建议将权限设为0644,便于运行时通过sysfs动态调整。
2.1.3 数组参数
当需要传递多个同类型值时,使用module_param_array:
static int gpio_pins[4] = {17, 18, 22, 23};
static int gpio_count;
module_param_array(gpio_pins, int, &gpio_count, 0644);
MODULE_PARM_DESC(gpio_pins, "GPIO pin numbers (up to 4)");
sudo insmod gpio_driver.ko gpio_pins=10,11,12,13
2.2 导出符号:EXPORT_SYMBOL
内核模块之间通过导出符号实现函数和变量的共享。EXPORT_SYMBOL将符号暴露给所有其他模块,EXPORT_SYMBOL_GPL则限制仅GPL许可的模块可以使用。
2.2.1 导出函数
// 在模块A中定义并导出
#include <linux/module.h>
int platform_reg_read(void __iomem *base, unsigned int offset)
{
return ioread32(base + offset);
}
EXPORT_SYMBOL(platform_reg_read);
int platform_reg_write(void __iomem *base, unsigned int offset, int val)
{
iowrite32(val, base + offset);
return 0;
}
EXPORT_SYMBOL_GPL(platform_reg_write);
2.2.2 导出变量
// 导出全局变量
static struct my_device_data *global_dev_data;
EXPORT_SYMBOL(global_dev_data);
// 注意:导出变量需要去掉static,或通过指针间接导出
2.2.3 使用导出符号
// 在模块B中声明并使用
extern int platform_reg_read(void __iomem *base, unsigned int offset);
extern int platform_reg_write(void __iomem *base, unsigned int offset, int val);
void my_function(void __iomem *regs)
{
int val = platform_reg_read(regs, 0x10);
platform_reg_write(regs, 0x10, val | 0x01);
}
注意事项:
- 导出符号会增加内核符号表大小,仅导出必要的接口
- 使用
EXPORT_SYMBOL_GPL保护核心API,防止非GPL模块滥用 - 导出符号的模块必须先加载,依赖模块后加载
- 符号命名建议使用模块前缀避免冲突,如
mydrv_
2.3 模块依赖关系
模块依赖是内核模块加载顺序和符号解析的核心机制。当模块B使用了模块A导出的符号时,模块B就依赖于模块A。
2.3.1 依赖声明
使用MODULE_DEPEND宏声明依赖关系:
// 在模块B中声明
MODULE_DEPEND("module_a");
MODULE_DEPEND("i2c_core");
MODULE_DEPEND("platform_driver_base");
2.3.2 依赖关系图
2.3.3 依赖解析与加载顺序
内核模块加载器自动处理依赖关系:
- 符号解析:加载模块时,内核检查其未定义符号是否在已加载模块的导出符号表中
- 递归依赖:如果依赖模块尚未加载,
modprobe自动查找并加载依赖模块 - 循环依赖:内核不允许循环依赖,会导致加载失败
- 版本检查:依赖模块的版本必须兼容,否则符号解析失败
2.3.4 查看依赖关系
# 查看模块依赖
modinfo my_driver.ko
# 输出示例:
# depends: platform_core,i2c_core
# 查看已加载模块的依赖树
lsmod
# Module Size Used by
# sensor_driver 16384 0
# i2c_adapter 24576 1 sensor_driver
# platform_core 20480 2 i2c_adapter,gpio_expander
# 查看导出符号
cat /proc/kallsyms | grep platform_reg_read
# ffffffc0800a1000 T platform_reg_read [platform_core]
2.4 综合实战:Platform驱动中的参数与符号设计
以下是一个完整的Platform驱动示例,展示模块参数、导出符号和依赖关系的协同使用:
// platform_base.c - 基础Platform驱动库
#include <linux/module.h>
#include <linux/platform_device.h>
static int default_irq = 10;
module_param(default_irq, int, 0644);
MODULE_PARM_DESC(default_irq, "Default IRQ number");
// 导出核心API
int platform_base_probe(struct platform_device *pdev)
{
pr_info("Platform base probed, default IRQ: %d\n", default_irq);
return 0;
}
EXPORT_SYMBOL_GPL(platform_base_probe);
int platform_base_remove(struct platform_device *pdev)
{
pr_info("Platform base removed\n");
return 0;
}
EXPORT_SYMBOL_GPL(platform_base_remove);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Platform Base Driver Library");
MODULE_DEPEND("platform");
// my_device_driver.c - 依赖platform_base的驱动
#include <linux/module.h>
#include <linux/platform_device.h>
extern int platform_base_probe(struct platform_device *pdev);
extern int platform_base_remove(struct platform_device *pdev);
static int my_probe(struct platform_device *pdev)
{
// 调用基础库的probe
return platform_base_probe(pdev);
}
static int my_remove(struct platform_device *pdev)
{
return platform_base_remove(pdev);
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device",
.owner = THIS_MODULE,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My Device Driver");
MODULE_DEPEND("platform_base");
关键要点总结:
- module_param:实现驱动参数的运行时配置,支持基本类型和数组
- EXPORT_SYMBOL:构建模块间API共享机制,
_GPL变体保护核心接口 - 模块依赖:通过
MODULE_DEPEND声明依赖,内核自动管理加载顺序 - 设计原则:将通用功能封装为基础模块,导出符号供上层驱动调用,形成清晰的层次化架构