1. Linux总线-设备-驱动模型概述
在Linux内核中,总线-设备-驱动模型(Bus-Device-Driver Model)是设备管理的核心框架。这个模型将硬件设备、驱动程序以及连接它们的总线抽象成三个相互关联的实体,实现了硬件资源的统一管理和动态加载。
我第一次接触这个模型是在调试一块自定义的PCIe采集卡时。当时发现无论怎么修改驱动代码,设备都无法正常识别。后来通过sysfs文件系统查看设备树,才发现设备节点根本没有注册成功。这个经历让我深刻认识到理解这个模型的重要性。
2. 模型核心组件解析
2.1 总线(Bus)
总线在内核中由struct bus_type表示,它是连接设备和驱动的桥梁。常见总线类型包括:
- Platform总线:虚拟总线,用于连接片上系统(SoC)中的设备
- PCI/PCIe总线:用于扩展卡设备
- USB总线:连接USB设备
- I2C/SPI总线:用于低速外设
总线的主要职责包括:
- 维护设备和驱动列表
- 匹配设备和驱动
- 提供设备和驱动的热插拔通知
2.2 设备(Device)
设备由struct device表示,包含以下关键信息:
- 设备名称
- 所属总线
- 设备资源(IRQ、内存区域等)
- 电源管理状态
- 设备树节点信息(对于DT支持的平台)
设备注册的典型过程:
c复制struct platform_device *pdev;
pdev = platform_device_alloc("my-device", -1);
platform_device_add(pdev);
2.3 驱动(Driver)
驱动由struct device_driver表示,主要包含:
- 驱动名称
- 所属总线
- 探测(probe)和移除(remove)回调
- 电源管理操作
- 驱动私有数据
驱动注册示例:
c复制static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-driver",
},
};
module_platform_driver(my_driver);
3. 设备与驱动的匹配机制
3.1 匹配流程
当设备或驱动注册时,总线会尝试匹配它们。匹配过程如下:
- 总线调用match()回调
- 匹配成功则调用驱动的probe()
- probe()中完成设备初始化和资源分配
对于platform设备,通常通过名称匹配:
c复制static int my_match(struct device *dev, struct device_driver *drv)
{
return strcmp(dev_name(dev), drv->name) == 0;
}
3.2 设备树匹配
现代Linux广泛使用设备树描述硬件,匹配方式变为:
dts复制my-device {
compatible = "vendor,my-device";
reg = <0x10000000 0x1000>;
interrupts = <0 10 4>;
};
驱动中需要声明兼容设备:
c复制static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device" },
{},
};
MODULE_DEVICE_TABLE(of, my_of_match);
4. 模型运作流程详解
4.1 初始化阶段
- 总线初始化:内核启动时注册各类总线
- 设备注册:硬件检测或设备树解析时注册设备
- 驱动注册:模块加载时注册驱动
4.2 运行时交互
- 设备发现:热插拔或延迟探测触发匹配
- 驱动绑定:匹配成功后调用probe()
- 资源管理:驱动通过标准接口访问设备资源
4.3 电源管理
模型支持完整的电源管理:
c复制struct dev_pm_ops {
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
// 更多电源状态回调
};
5. 实际开发案例
5.1 开发一个简单的platform设备驱动
- 定义设备资源:
c复制static struct resource my_res[] = {
{
.start = 0x10000000,
.end = 0x10000FFF,
.flags = IORESOURCE_MEM,
},
{
.start = 10,
.end = 10,
.flags = IORESOURCE_IRQ,
},
};
- 实现驱动核心逻辑:
c复制static int my_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *regs;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
// 初始化设备...
return 0;
}
5.2 调试技巧
- 查看设备树:
bash复制ls /proc/device-tree/
- 检查设备注册:
bash复制ls /sys/bus/platform/devices/
- 跟踪probe调用:
bash复制echo 1 > /sys/kernel/debug/tracing/events/platform/enable
cat /sys/kernel/debug/tracing/trace_pipe
6. 高级主题与优化
6.1 延迟探测
对于依赖其他驱动的设备,可以实现延迟探测:
c复制static int my_probe(struct platform_device *pdev)
{
if (!other_driver_ready())
return -EPROBE_DEFER;
// 正常初始化
}
6.2 驱动分离与分层
复杂驱动可以采用分离架构:
- 核心层:处理通用功能
- 适配层:处理硬件差异
- 接口层:提供用户API
6.3 热插拔支持
实现热插拔通知:
c复制static int my_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_BOUND_DRIVER:
// 处理驱动绑定
break;
}
return NOTIFY_OK;
}
7. 常见问题排查
7.1 设备未识别
检查步骤:
- 确认设备物理连接正常
- 检查内核日志是否有相关错误
- 验证设备是否出现在sysfs中
- 检查设备树是否正确描述设备
7.2 probe函数未调用
可能原因:
- 设备/驱动名称不匹配
- 设备树compatible属性不匹配
- 依赖资源未就绪(返回了-EPROBE_DEFER)
7.3 资源冲突
调试方法:
- 检查/proc/iomem和/proc/interrupts
- 使用devm_系列函数管理资源
- 确保资源释放与分配对称
8. 性能优化建议
- 延迟初始化:非关键路径代码延后加载
- 中断优化:使用线程化中断处理耗时操作
- DMA优化:合理使用流式DMA映射
- 电源管理:实现runtime PM支持
9. 最佳实践总结
- 始终使用设备树描述硬件资源
- 优先使用devm_资源管理函数
- 实现完整的电源管理支持
- 为驱动添加详尽的文档注释
- 充分利用内核提供的调试工具
在实际项目中,我发现这个模型最大的优势在于其一致性。无论是开发USB设备驱动还是I2C传感器驱动,核心的注册、匹配、探测流程都是相似的。这种一致性大大降低了学习新总线类型的门槛。