4. platform总线机制:platform_driver与platform_device的匹配流程,probe/remove函数
platform总线是Linux内核中用于管理非可枚举设备(如SoC内部集成外设、传统ISA设备等)的虚拟总线。它绕过了PCI/USB等物理总线的硬件枚举能力,通过软件方式将设备与驱动绑定。面试中,platform总线的匹配流程、probe/remove生命周期是高频考点。
核心认知: platform总线不是物理总线,而是内核为“无总线设备”提供的一套软件抽象层。它通过name字段进行匹配,匹配成功后调用驱动的probe函数完成设备初始化。
4.1 platform总线模型架构
platform总线模型由三部分组成:platform_device(设备端)、platform_driver(驱动端)和platform_bus_type(总线类型)。内核通过platform_bus_type中的match函数完成设备与驱动的配对。
4.2 platform_driver与platform_device数据结构
理解两个核心结构体的字段是掌握匹配流程的基础。
| 结构体 | 关键字段 | 作用说明 |
|---|---|---|
| platform_device | name |
设备名称,匹配时的首要依据 |
id |
设备实例编号,-1表示单实例 | |
dev |
内嵌的通用device结构体 | |
num_resources |
资源数量(IRQ、I/O内存等) | |
resource |
资源数组指针 | |
| platform_driver | driver.name |
驱动名称,与device.name匹配 |
id_table |
设备树兼容性匹配表(可选) | |
probe |
匹配成功后调用的初始化函数 | |
remove |
设备移除时调用的清理函数 | |
shutdown |
系统关机时的回调 |
4.3 匹配流程详解
platform总线的匹配由platform_match函数实现,该函数是platform_bus_type的.match回调。匹配顺序遵循以下优先级:
- 设备树OF匹配:如果驱动提供了
of_match_table,则优先与设备树节点的compatible属性进行匹配。 - ACPI匹配:在支持ACPI的平台上,尝试ACPI表匹配。
- id_table匹配:检查驱动
id_table中的name字段是否与设备name一致。 - name直接匹配:比较
driver.name与device.name是否相等。
面试陷阱: 很多开发者误以为platform匹配只比较name字段。实际上,设备树(DT)匹配的优先级最高。在设备树普及的今天,驱动通常通过
of_match_table实现匹配,name匹配仅作为传统方式的兜底。
4.4 probe函数:驱动的初始化入口
当匹配成功后,内核调用驱动的probe函数。这是驱动开发者最常接触的接口,负责完成设备初始化、资源申请、硬件配置等核心工作。
static int my_serial_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq, ret;
// 1. 获取I/O内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
// 2. 请求并映射I/O内存
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 3. 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 4. 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, my_serial_isr,
0, pdev->name, pdev);
if (ret)
return ret;
// 5. 初始化硬件寄存器
writel(CTRL_ENABLE, base + UART_CTRL);
// 6. 创建字符设备或注册其他框架
// ...
dev_info(&pdev->dev, "serial device initialized\n");
return 0;
}
最佳实践: 优先使用
devm_系列API(managed device resource),如devm_ioremap_resource、devm_request_irq。这些API会在设备移除或驱动卸载时自动释放资源,避免内存泄漏和资源泄露。
4.5 remove函数:优雅的清理出口
remove函数在设备从系统中移除或驱动卸载时被调用。它的职责是probe的逆操作:释放资源、注销设备、恢复硬件到安全状态。
static int my_serial_remove(struct platform_device *pdev)
{
struct my_serial_data *data = platform_get_drvdata(pdev);
// 1. 禁用硬件(停止中断、关闭外设时钟)
writel(0, data->base + UART_CTRL);
// 2. 注销设备节点(如字符设备)
device_destroy(my_serial_class, data->devno);
cdev_del(&data->cdev);
// 3. 释放私有数据(devm_系列自动释放,无需手动)
// devm_* 资源会自动释放
// 4. 清除drvdata
platform_set_drvdata(pdev, NULL);
dev_info(&pdev->dev, "serial device removed\n");
return 0;
}
关键注意: 如果使用了
devm_系列API,remove函数中不需要手动调用iounmap、free_irq、kfree等操作。内核会在remove返回后自动释放这些托管资源。但非托管资源(如cdev_add、device_create)必须手动清理。
4.6 驱动注册与注销流程
驱动模块的加载和卸载通过以下宏实现:
| 操作 | 宏/函数 | 说明 |
|---|---|---|
| 驱动注册 | module_platform_driver(my_driver) |
封装了module_init/exit,自动注册platform_driver |
| 手动注册 | platform_driver_register(&my_driver) |
在module_init中调用,注册到platform总线 |
| 驱动注销 | platform_driver_unregister(&my_driver) |
在module_exit中调用,从platform总线移除 |
| 设备注册 | platform_device_register(&my_device) |
注册platform设备到总线 |
| 设备注销 | platform_device_unregister(&my_device) |
从总线移除platform设备 |
// 推荐方式:使用module_platform_driver宏
static struct platform_driver my_serial_driver = {
.probe = my_serial_probe,
.remove = my_serial_remove,
.driver = {
.name = "my_serial",
.of_match_table = my_serial_of_match,
},
};
module_platform_driver(my_serial_driver);
// 等价于:
// module_init(my_serial_init); → platform_driver_register()
// module_exit(my_serial_exit); → platform_driver_unregister()
4.7 面试高频问题与避坑指南
面试官常问:
- “platform总线的匹配顺序是什么?设备树匹配和name匹配哪个优先级高?”
- “probe函数返回负数代表什么?内核会如何处理?”
- “如果probe失败,之前申请的devm资源会泄露吗?”
- “remove函数中,devm_资源还需要手动释放吗?”
- “platform_driver和platform_device的注册顺序有要求吗?”
| 问题 | 答案要点 |
|---|---|
| 匹配顺序 | OF匹配 → ACPI匹配 → id_table匹配 → name直接匹配 |
| probe返回负数 | 表示初始化失败,内核将设备标记为未绑定,释放已申请的devm资源 |
| devm资源泄露 | 不会。devm资源绑定在device生命周期上,probe失败时自动释放 |
| remove中devm处理 | 不需要手动释放,内核在remove返回后自动清理devm资源 |
| 注册顺序 | 无严格要求。先注册设备或先注册驱动均可,内核会遍历已有链表进行匹配 |
深入理解: platform总线的设计体现了Linux设备模型的核心思想——延迟绑定。设备和驱动可以独立注册,总线负责在两者之间建立连接。这种解耦设计使得驱动模块可以动态加载,设备可以热插拔,系统具有极高的灵活性。