Linux设备模型三要素:bus-device-driver匹配机制深度解析
1. 这不是“加载顺序”的问题而是Linux设备模型的骨架逻辑你翻过《Linux设备驱动开发详解》第3版也看过LDD3英文原版里那张著名的bus-device-driver三角图你在调试一个i2c从设备时反复dmesg | grep -i probe却始终看不到匹配成功的日志你把设备树节点加进去了cat /sys/bus/i2c/devices/下却空空如也甚至在Xilinx Zynq平台上烧写完bitstreamls /sys/class/misc/里该出现的字符设备压根没影儿——这些都不是“顺序错了”这么简单。真正卡住你的是Linux内核设备模型里那套被封装得严严实实、但又必须亲手拆开才能理解的注册-匹配-绑定三重机制。我在Zynq MPSoC上带团队做过6个量产级驱动项目从PCIe FPGA加速卡到MIPI CSI-2摄像头模组踩过的坑几乎都绕不开bus、device、driver这三者之间那0.3秒内的微妙时序与状态流转。它不叫“加载顺序”它叫设备模型的状态机演进bus是舞台device是演员driver是剧本而kernel做的是确保演员一登台剧本就自动翻到对应页码——前提是演员的工牌compatible字符串、剧本的索引of_match_table和舞台的排班表bus-match函数三者严丝合缝。今天这篇我不讲概念定义不列函数原型只带你用printk打点/sys目录实时观察内核源码片段对照的方式把整个流程像拆解一台机械手表一样一颗螺丝一颗齿轮地还原出来。适合正在写设备树、调试probe失败、或者准备Linux驱动面试的工程师——尤其当你看到no driver found for device却查遍dts和ko文件都找不到破绽时这篇文章就是你该打开的第一页。2. 设备模型三大组件的本质与注册时机2.1 bus不是总线硬件而是设备分类与匹配规则的抽象容器很多人一看到platform_bus_type就以为这是指物理上的PCIe或AMBA总线这是根本性误解。bus在内核中本质是一个“设备分类器匹配引擎”的复合体。它不负责数据传输只负责回答一个问题“这个device该归哪类driver管”以最常用的platform_bus_type为例它的定义在drivers/base/platform.c中struct bus_type platform_bus_type { .name platform, .match platform_match, // 核心匹配逻辑在此 .uevent platform_uevent, .dma_mask platform_dma_mask, .dev_groups platform_dev_groups, };关键在.match字段——它指向platform_match()函数。这个函数才是决定“谁配谁”的裁判。而platform_match()的逻辑极其精简static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev to_platform_device(dev); struct platform_driver *pdrv to_platform_driver(drv); // ① 先比device tree的compatible属性最高优先级 if (of_driver_match_device(dev, drv)) return 1; // ② 再比platform_device_id表传统方式已逐步淘汰 if (pdrv-id_table) return platform_match_id(pdrv-id_table, pdev) ! NULL; // ③ 最后比名字name字段兜底方案 return (strcmp(pdev-name, drv-name) 0); }看到没匹配不是靠“谁先注册谁优先”而是靠属性比对结果。of_driver_match_device()会逐条比对设备树节点里的compatible xlnx,axi-dma-1.00.a, generic-dma和driver的of_match_table数组。这意味着哪怕driver模块晚加载10秒只要设备树节点已存在boot时解析probe就会在driver加载瞬间触发。我曾在一个客户项目里故意把axidma.ko延迟到系统启动后5分钟才insmod结果dmesg里立刻打出axidma f8003000.dma: Probed successfully——因为设备早已在/sys/devices/platform/下静候多时。提示platform_bus_type的注册发生在drivers/base/platform.c的platform_bus_init()中该函数在start_kernel()的rest_init()阶段被调用属于内核早期初始化early initcall。这意味着所有platform设备和driver都必须在这个bus注册之后才能注册否则device_register()会返回-ENODEV。2.2 device不是硬件本身而是内核对硬件存在的“声明书”struct device在内核里是个“存在证明”。它不操作硬件寄存器只登记硬件的身份信息。以一个典型的AXI DMA设备为例其设备树节点axidmaf8003000 { compatible xlnx,axi-dma-1.00.a, generic-dma; reg 0xf8003000 0x1000; interrupts 0 57 4, 0 58 4; #address-cells 1; #size-cells 1; ranges; dma-channel0 { compatible xlnx,axi-dma-mm2s-channel; dma-channels 1; }; };当内核解析此节点时会调用of_platform_bus_create()创建struct platform_device它是struct device的子类并填充关键字段字段值作用dev.namef8003000.dma/sys/devices/platform/下的目录名dev.of_node指向该DTS节点的struct device_node*提供compatible等属性读取入口dev.parentplatform_bus绑定到platform busdev.busplatform_bus_type明确归属bus类型device注册的时机分两类静态注册设备树或ACPI表在boot时解析完成of_platform_populate()遍历所有节点并调用of_platform_device_create()此时device_register()在start_kernel()的rest_init()阶段完成。动态注册用户空间通过echo 0000:01:00.0 /sys/bus/pci/drivers/pcieport/bind触发PCI设备热插拔或驱动代码中调用platform_device_register()手动添加。注意device_register()内部会调用bus_add_device()后者执行核心动作——将device加入bus-p-devices链表并触发bus-match()进行首轮匹配。如果此时driver尚未注册匹配失败device进入“unbound”状态静静等待driver出现。2.3 driver不是驱动程序而是设备操作接口的“契约模板”struct device_driver是内核与驱动开发者之间的契约。它不包含具体硬件操作代码只声明“我能服务哪些设备”以及“如何服务”。以axidma驱动为例其核心结构static const struct of_device_id axidma_of_match[] { { .compatible xlnx,axi-dma-1.00.a }, { .compatible generic-dma }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, axidma_of_match); static struct platform_driver axidma_driver { .probe axidma_probe, // 设备匹配成功后调用 .remove axidma_remove, .driver { .name axidma, // name字段用于fallback匹配 .of_match_table axidma_of_match, // DTS匹配主入口 .owner THIS_MODULE, }, };driver_register()的执行路径是driver_register()→bus_add_driver()→driver_attach()。而driver_attach()才是真正的“主动出击”int driver_attach(struct device_driver *drv) { return bus_for_each_dev(drv-bus, NULL, drv, __driver_attach); } static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv data; if (!driver_match_device(drv, dev)) // 关键调用bus-match() return 0; if (dev-driver) // 设备已有driver跳过 return 0; return driver_probe_device(drv, dev); // 匹配成功启动probe }这里暴露了核心真相driver注册时会主动遍历bus上所有已注册的device挨个调用match函数。所以“谁先谁后”根本不重要——device早注册driver晚注册driver会自己找上门driver早注册device晚注册如热插拔device注册时会反向触发match。我曾在调试USB摄像头时先加载uvcvideo.ko再插入UVC设备dmesg里清晰显示usb 1-1: new high-speed USB device... uvcvideo: Found UVC 1.00 device...——这就是device注册时触发的反向匹配。3. 匹配流程的四步状态机与关键断点验证3.1 状态机全景从注册到probe的完整生命周期整个流程不是线性流水线而是一个带反馈的状态机。我们以platform设备为例画出真实内核中的状态流转[Device注册] ↓ device_register() → bus_add_device() → bus-match() → match失败 → device进入unbound状态 ↓ [Driver注册] ↓ driver_register() → bus_add_driver() → driver_attach() → 遍历bus-p-devices链表 ↓ 对每个device调用driver_match_device() → 调用bus-match() → match成功 → driver_probe_device() ↓ driver_probe_device() → really_probe() → 调用driver-probe() → probe成功 → device-driver drv ↓ [绑定完成] device状态变为bound/sys/devices/.../driver链接指向driver目录四个关键状态节点Unbounddevice已注册但无driver绑定/sys/devices/platform/f8003000.dma/driver为空链接Matchingdriver注册时触发的__driver_attach()循环或device注册时触发的bus-match()Probingreally_probe()执行中driver-probe()函数正在运行Boundprobe返回0device_bind_driver()完成/sys/devices/.../driver指向driver目录实操验证法在目标设备目录下执行ls -l driver。若输出driver - ../../../bus/platform/drivers/axidma说明已bound若报错No such file or directory则处于unbound状态。3.2 断点1验证device是否真正注册成功别信dmesg里那句“of_platform_populate: created device f8003000.dma”要亲眼确认。进入/sys/devices/platform/目录# 查看所有platform设备 ls /sys/devices/platform/ # 输出示例f8000000.ps7-scu f8003000.dma f8004000.spi ... # 进入目标设备目录检查核心属性 cd /sys/devices/platform/f8003000.dma ls -l # 必须存在driver/、of_node/、name、modalias、uevent cat name # 应输出f8003000.dma cat modalias # 应输出platform:f8003000.dma ls of_node/ # 应列出compatible、reg、interrupts等属性文件常见陷阱设备树节点名含非法字符如后跟非十六进制地址导致of_platform_bus_create()解析失败/sys/devices/platform/下无对应目录。ranges属性缺失且子节点有reg地址导致of_address_to_resource()失败device注册时platform_device_add()返回错误但dmesg可能只打印Failed to add device而不显式报错。status disabled未改为okay节点被忽略/sys/devices/platform/下自然无影无踪。3.3 断点2验证driver是否完成注册与匹配触发insmod axidma.ko后不能只看dmesg有没有probe日志。要确认driver已真正挂载到bus上# 查看platform bus上所有已注册driver ls /sys/bus/platform/drivers/ # 输出应包含axidma # 进入driver目录检查绑定状态 cd /sys/bus/platform/drivers/axidma ls -l # 必须存在bind、unbind、uevent、module、name cat name # 应输出axidma ls modules/ # 应列出axidma.ko的符号链接 # 关键查看driver是否已绑定任何device ls -l # 若有类似../f8003000.dma - ../../../devices/platform/f8003000.dma的链接说明已bound # 若无任何设备链接说明匹配失败匹配失败的典型信号dmesg中出现axidma: probe of f8003000.dma failed with error -EPROBE_DEFERdefer是正常重试dmesg中出现axidma: no device found for f8003000.dma名字不匹配/sys/bus/platform/drivers/axidma/下无任何设备链接且dmesg无probe日志3.4 断点3抓取match函数的实际执行路径当怀疑匹配逻辑有问题时最直接的方法是让内核告诉你match函数到底比了什么。修改platform_match()函数需重新编译内核或使用kprobe// 在drivers/base/platform.c的platform_match()开头添加 printk(KERN_INFO PLATFORM_MATCH: dev%s, drv%s\n, dev_name(dev), drv-name); if (dev-of_node) { const char *compat of_get_property(dev-of_node, compatible, NULL); printk(KERN_INFO dev compatible%s\n, compat ? compat : NULL); } if (drv-of_match_table) { const struct of_device_id *match of_match_device(drv-of_match_table, dev); printk(KERN_INFO drv match%s\n, match ? YES : NO); }编译加载后dmesg | grep PLATFORM_MATCH将输出每次匹配的详细比对过程。我在调试一个Xilinx HDMI IP时发现dmesg显示dev compatiblexlnx,v_hdmi_tx_ss_v1_0而driver的of_match_table里写的是xlnx,v-hdmi-tx-ss-v1.0——连横杠和下划线都错了自然匹配失败。这种细节光看代码review根本发现不了必须靠运行时日志。3.5 断点4probe函数执行前的最后屏障即使match成功really_probe()还会做两道安全检查电源管理检查调用pm_runtime_get_sync(dev)若设备电源未开启probe会被阻塞。依赖资源检查调用driver_deferred_probe_check_state()若依赖的clock、reset、phy等资源未ready返回-EPROBE_DEFER。验证方法在probe函数第一行加printk(KERN_INFO AXIDMA PROBE START\n);若dmesg无此日志但driver目录下已有设备链接说明卡在really_probe()的前置检查。此时需检查# 查看设备电源状态 cat /sys/devices/platform/f8003000.dma/power/runtime_status # 应为active # 查看clock是否enable cat /sys/kernel/debug/clk/axi_dmac_clk/clk_rate # 应为非零值 # 查看reset状态 cat /sys/bus/platform/devices/f8003000.dma/reset # 应为deasserted4. 四大经典故障场景与根因定位手册4.1 场景一设备树节点存在但/sys/devices/platform/下无设备目录现象dmesg无任何关于该节点的解析日志ls /sys/devices/platform/ | grep your_device为空。根因分析树层级错误节点未放在/amba或/soc等标准parent节点下。ARM平台要求platform设备必须位于/amba或/soc节点内否则of_platform_populate()跳过。status属性status disabled未修改节点被内核忽略。compatible拼写compatible xlnx,axi-dma-1.00.a中版本号1.00.a与IP核实际版本不符如实际是1.01.a但此错误不影响device注册只影响后续match。reg地址冲突reg 0xf8003000 0x1000中地址已被其他设备占用of_address_to_resource()返回-EINVALdevice注册失败。定位命令# 查看内核启动时的设备树解析日志 dmesg | grep -i of_platform_populate\|failed to add device # 检查设备树二进制文件是否包含该节点 dtc -I dtb -O dts -o extracted.dts /proc/device-tree/soc/your_devicef8003000 # 若报错Error: extract.dts: No such file说明节点未被解析4.2 场景二/sys/devices/platform/下有设备但driver目录无绑定链接现象ls /sys/bus/platform/drivers/有driver名ls /sys/bus/platform/drivers/your_driver/下无设备链接dmesg无probe日志。根因分析树compatible不匹配设备树compatible与driver的of_match_table字符串完全不一致注意大小写、空格、版本号。driver未export symbol若driver中使用了EXPORT_SYMBOL_GPL()导出的函数而模块未声明MODULE_LICENSE(GPL)会导致request_module()失败driver无法加载。模块依赖缺失driver依赖libphy.ko或dmaengine.ko但这些模块未提前加载insmod时静默失败。定位命令# 查看driver的modalias是否与device匹配 cat /sys/devices/platform/f8003000.dma/modalias # 输出platform:f8003000.dma cat /sys/bus/platform/drivers/axidma/modalias # 输出platform:axidma # 若两者不等说明driver的name字段与device name不一致fallback匹配失败 # 强制触发匹配debug用 echo f8003000.dma /sys/bus/platform/drivers/axidma/bind # 若报错Device f8003000.dma does not exist说明device name不匹配 # 若报错No such device说明device未注册4.3 场景三driver绑定成功但probe函数永不执行现象ls /sys/bus/platform/drivers/axidma/下出现f8003000.dma链接但dmesg无probe日志cat /sys/devices/platform/f8003000.dma/driver显示链接有效。根因分析树probe函数返回非0值return -ENODEV;等错误码导致really_probe()回滚绑定。中断请求失败request_irq()返回-EBUSY中断号被占或-EINVAL中断号无效。内存映射失败devm_ioremap_resource()返回NULL通常因reg地址范围与设备树ranges不匹配。定位命令# 查看probe函数是否被调用需在probe内加printk dmesg | tail -20 | grep AXIDMA PROBE # 若无输出说明卡在really_probe()的前置检查 # 检查中断状态 cat /proc/interrupts | grep 57 # 查看中断57是否被其他设备占用 # 检查IO内存映射 cat /proc/iomem | grep f8003000 # 应显示f8003000-f8003fff : f8003000.dma4.4 场景四probe执行成功但设备功能异常如DMA不工作现象dmesg显示Probed successfully/dev/xdma0存在但应用层read()返回0或超时。根因分析树时钟未enableclk_prepare_enable()未调用IP核时钟门控关闭。复位未释放reset_control_deassert()未调用IP核处于复位态。DMA buffer未cache cleanARM平台需调用dma_sync_single_for_device()确保CPU写入buffer的数据被刷入物理内存。定位命令# 查看clock状态 cat /sys/kernel/debug/clk/axi_dmac_clk/clk_rate # 应为非零 cat /sys/kernel/debug/clk/axi_dmac_clk/clk_enabled # 应为1 # 查看reset状态 cat /sys/bus/platform/devices/f8003000.dma/reset # 应为deasserted # 检查DMA buffer地址是否正确映射 dmesg | grep -i dma # 查找DMA buffer at 0x...日志5. 实战从零构建一个可验证的platform驱动框架5.1 构建最小可运行设备树节点在arch/arm/boot/dts/zynq-zc702.dts中添加amba { testdev43c00000 { compatible mycompany,testdev-1.0; reg 0x43c00000 0x1000; interrupts 0 89 4; clocks clkc 15; clock-names apb_clk; resets rst 15; #address-cells 1; #size-cells 1; status okay; }; };关键点说明amba确保节点被of_platform_populate()处理compatible字符串简洁明确避免特殊字符reg地址选Zynq PS端未使用的AXI GP区域0x43c00000interrupts使用GIC SPI中断号89需查Zynq TRM确认可用clocks和resets引用必须存在否则probe时clk_get()或reset_control_get()失败5.2 编写最小probe函数与调试桩#include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/io.h #include linux/interrupt.h #include linux/clk.h #include linux/reset.h #define TESTDEV_BASE 0x43c00000 #define TESTDEV_REG_STATUS 0x0 static void __iomem *testdev_base; static struct clk *testdev_clk; static struct reset_control *testdev_rst; static int testdev_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct resource *res; int ret; printk(KERN_INFO TESTDEV PROBE START\n); // 1. 获取时钟 testdev_clk devm_clk_get(dev, apb_clk); if (IS_ERR(testdev_clk)) { ret PTR_ERR(testdev_clk); dev_err(dev, Failed to get clock: %d\n, ret); return ret; } // 2. 使能时钟 ret clk_prepare_enable(testdev_clk); if (ret) { dev_err(dev, Failed to enable clock: %d\n, ret); return ret; } // 3. 获取reset testdev_rst devm_reset_control_get(dev, NULL); if (IS_ERR(testdev_rst)) { ret PTR_ERR(testdev_rst); dev_err(dev, Failed to get reset: %d\n, ret); goto err_clk; } // 4. 释放reset ret reset_control_deassert(testdev_rst); if (ret) { dev_err(dev, Failed to deassert reset: %d\n, ret); goto err_clk; } // 5. IO内存映射 res platform_get_resource(pdev, IORESOURCE_MEM, 0); testdev_base devm_ioremap_resource(dev, res); if (IS_ERR(testdev_base)) { ret PTR_ERR(testdev_base); dev_err(dev, Failed to ioremap: %d\n, ret); goto err_rst; } // 6. 读取状态寄存器验证 printk(KERN_INFO TESTDEV STATUS 0x%x\n, readl(testdev_base TESTDEV_REG_STATUS)); printk(KERN_INFO TESTDEV PROBE SUCCESS\n); return 0; err_rst: reset_control_assert(testdev_rst); err_clk: clk_disable_unprepare(testdev_clk); return ret; } static int testdev_remove(struct platform_device *pdev) { reset_control_assert(testdev_rst); clk_disable_unprepare(testdev_clk); return 0; } static const struct of_device_id testdev_of_match[] { { .compatible mycompany,testdev-1.0 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, testdev_of_match); static struct platform_driver testdev_driver { .probe testdev_probe, .remove testdev_remove, .driver { .name testdev, .of_match_table testdev_of_match, }, }; module_platform_driver(testdev_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name);5.3 编译、加载与逐级验证脚本编写verify.sh自动化验证#!/bin/bash # 步骤1确认设备树已编译进zImage if ! grep -q testdev43c00000 arch/arm/boot/dts/zynq-zc702.dtb; then echo ERROR: Device tree node not found in DTB exit 1 fi # 步骤2检查/sys/devices/platform/下是否存在 if [ ! -d /sys/devices/platform/testdev43c00000 ]; then echo ERROR: Device not registered in sysfs exit 1 fi echo ✓ Device registered: /sys/devices/platform/testdev43c00000 # 步骤3加载驱动模块 insmod testdev.ko sleep 1 # 步骤4检查driver是否绑定 if [ -L /sys/bus/platform/drivers/testdev/testdev43c00000 ]; then echo ✓ Driver bound successfully else echo ERROR: Driver not bound exit 1 fi # 步骤5检查dmesg probe日志 if dmesg | grep -q TESTDEV PROBE SUCCESS; then echo ✓ Probe executed successfully else echo ERROR: Probe failed dmesg | tail -20 exit 1 fi echo ALL TESTS PASSED运行./verify.sh每一步失败都会给出明确提示和下一步排查方向。这套验证流程我在带新人时强制要求他们手敲一遍比看十遍文档都管用。6. 高阶技巧动态调试与性能优化实战6.1 使用debugfs实时监控设备模型状态内核提供/sys/kernel/debug/devices_deferred和/sys/kernel/debug/device_tree/等接口。启用debugfs# 挂载debugfs mount -t debugfs none /sys/kernel/debug # 查看所有deferred probe的设备因资源未ready而延迟probe cat /sys/kernel/debug/devices_deferred # 查看设备树解析后的完整结构 ls /sys/kernel/debug/device_tree/ # 进入节点查看属性 cat /sys/kernel/debug/device_tree/amba/testdev43c00000/compatible实战案例某次调试PCIe设备时dmesg反复出现deferring probe for device但/sys/kernel/debug/devices_deferred为空。最终发现是pci_bus_add_devices()调用时机过早设备resource未分配完成。解决方案是在pci_scan_child_bus()后显式调用pci_bus_assign_resources()。6.2 减少probe时间的三个硬核技巧probe函数执行时间直接影响系统启动速度。针对嵌入式设备我总结三条铁律异步化耗时操作将request_firmware()、i2c_smbus_read_block_data()等可能阻塞的操作移到workqueue中执行probe只做资源申请和初始化。static void testdev_work_func(struct work_struct *work) { // 耗时firmware加载放在这里 request_firmware(fw, testdev.fw, pdev-dev); } static int testdev_probe(...) { INIT_WORK(testdev_work, testdev_work_func); schedule_work(testdev_work); // probe立即返回 return 0; }延迟clock enable不在probe中clk_prepare_enable()而在第一个open()系统调用时才enable在release()时clk_disable_unprepare()。这对低功耗设备至关重要。预分配DMA buffer使用dma_alloc_coherent()在probe中一次性分配足够大的buffer池避免运行时频繁alloc/free带来的cache一致性开销。6.3 设备树配置的黄金法则compatible字段必须唯一且可扩展mycompany,ip-core-v1优于mycompany,ip-core便于未来兼容v2版本。interrupts属性必须用GIC_SPI IRQ_NUM IRQ_TYPE格式Zynq平台IRQ_TYPE必须是4level-high2edge-rising会导致中断丢失。clocks属性必须与/clocks节点精确匹配clkc 15中15是clock ID必须与clock-names中apb_clk顺序一致。避免在device节点中使用phandle交叉引用phy-handle my_phy;必须确保my_phy在同一个DTS文件中定义跨文件引用需用/include/或/plugin/。我在给某军工客户做国产化适配时发现他们提供的设备树里interrupts 0 89 2edge-rising导致中断在高负载下10%概率丢失。改成0 89 4后连续72小时压力测试零中断丢失。这种细节只有在真实硬件上跑起来才能暴露。7. 面试高频题深度拆解与应答策略7.1 “请描述Linux设备模型中bus、device、driver的关系”错误答法“bus是总线device是设备driver是驱动三者通过match函数匹配。”太浅没体现机制专业答法“三者构成一个松耦合的状态机bus是匹配规则的载体它不管理硬件只提供.match()函数定义‘谁配谁’device是硬件存在的声明它注册时会触发bus匹配若失败则进入unbound状态等待driverdriver注册时会主动遍历bus上所有unbound device调用match函数尝试绑定。匹配成功后driver的.probe()被调用完成硬件初始化。整个过程不依赖注册顺序而是由内核自动协调——device早注册driver晚来会主动找driver早注册device热插拔时会反向触发匹配。”7.2 “device和driver注册谁先谁后会影响匹配吗”错误答法“driver必须先注册否则device找不到driver。”完全错误专业答法“完全不影响。内核设计就是为了解耦device注册时若driver未就绪它只是安静待在bus的unbound链表里driver注册时driver_attach()会遍历所有unbound device并逐一匹配。我在线上系统中做过实验先启动系统device已注册再动态insmod驱动模块probe立刻触发。反过来先加载driver再通过echo 1 /sys/bus/platform/drivers/testdev/unbind解绑再echo 1 /sys/bus/platform/drivers/testdev/bind同样能触发probe。这证明匹配是双向触发的而非单向依赖。”7.3 “probe函数返回-ENODEV和-EPROBE_DEFER有什么区别”错误答法“-ENODEV是设备不存在-EPROBE_DEFER是需要重试。”没说清本质专业答法“-ENODEV表示永久性
上一篇/下一篇内容由系统自动关联
返回资讯列表 →