Linux驱动自动加载机制全解析:从module_init到udev
发布时间:2026/9/21 1:27:57来源:尧图网络
做Linux驱动开发的人迟早会遇到同一个困惑我在电脑上写了一个字符设备驱动insmod能正常加载/dev节点也能出来但为什么换到实际项目里设备插上后驱动就是不会自己加载更有意思的是网上随便找个CH340、CP2102的驱动扔到系统里就自动生效了轮到自己的驱动就得手动modprobe。这里面的差距并不是代码写得不好而是没有搞懂Linux驱动自动加载的整套设计链条。这篇是Linux驱动基础系列的第二篇专门把自动加载这件事从上到下拆一遍。我会从内核模块的加载机制讲起讲清楚module_init、depmod、udev、MODALIAS、总线匹配这几块是怎么串起来的然后给一个可以直接照着做的完整实验最后讲讲我在实际项目里排查加载问题时的经验。搞懂这套机制以后驱动装不上的问题基本都能自己定位。1. 为什么驱动能被自动加载先看懂内核模块的加载起点在谈自动加载之前得先把一个基础的认知对齐驱动本质上就是一个内核模块加载模块的过程就是把这个.ko文件里的代码搬进内核地址空间然后执行它指定的初始化函数。自动加载只是把这个过程从人手动敲命令变成系统根据设备事件自动触发所以一切的前提是先搞懂模块加载时内核到底做了什么。1.1 从module_init说起写驱动的人对module_init这个宏都很熟但未必知道它背后发生了什么。这个宏实际上把初始化函数指针放到了一个特殊的ELF段里内核在加载模块时会根据这个段里的地址找到初始化函数并调用它。它的原型展开大致是#define module_init(x) __initcall(x)最终变成了__initcall_xxx这样的符号被链接器放进.initcall段。这段代码在模块加载完成后会被标记为可释放因为初始化只需要执行一次没必要长期占用内存。这也是为什么你在/proc/kallsyms里经常能看到__init结尾的函数地址是空的——它们早就被释放了。module_init对应的就是驱动的入口一般我们会在这里做这些事注册字符设备register_chrdev或cdev_add注册platform_driver、i2c_driver、usb_driver等总线驱动结构体创建sysfs属性文件初始化硬件申请GPIO、中断、时钟等module_exit则做完全相反的事释放资源、注销设备、删除节点。这一进一出构成了驱动的生命周期。不夸张地说自动加载只负责把module_init跑起来至于module_init里注册的是什么东西那是后面设备匹配环节的事。1.2 insmod与modprobe的分工很多初学者只知道insmod能加载模块后来发现modprobe好像更高级但不清楚区别在哪。insmod就是一个纯粹的系统调用封装把模块文件读入内核触发init_module系统调用。它不关心依赖不关心路径不检查模块的其他元信息你给它一个路径它就把那个文件加载进去。modprobe就不一样了它做的事更像一个包管理器在标准模块目录下查找模块默认是/lib/modules/$(uname -r)/通过modules.dep文件检查并加载依赖模块通过modules.alias文件根据设备别名反查模块名支持/etc/modprobe.d/下的配置包括黑名单、参数、别名覆盖等所以modprobe加载模块天然就具备按需解析、自动装载依赖的能力这是自动加载机制能成立的前提。大家debian系发行版里几百个驱动模块开机时不可能全部加载真正的做法是等设备出现后动态加载对应的那个模块。另一个容易忽略的点是insmod和modprobe对路径的处理完全不同。insmod必须给文件路径modprobe只需要给模块名然后自己在标准目录里找。如果你用modprobe加载一个不在标准目录的模块它会直接报错modprobe: FATAL: Module not found。这种错误十有八九是模块没装到/lib/modules目录或者depmod生成的索引没更新。1.3 加载驱动只是上半场driver注册与设备绑定这里必须把概念理清因为很多人踩坑就踩在这。加载一个.ko文件只是把驱动的代码放进了内核并且执行了module_init注册了一个driver结构体到对应总线上。这一步完成之后驱动并不会立刻开始工作。真正让驱动干活的是后续的probe回调——也就是设备和driver完成匹配之后内核调用驱动的probe函数这时候驱动才去初始化硬件、创建设备节点、注册中断等。换句话说驱动加载成功和驱动生效是两件事。用一个比喻模块加载相当于你入职了人事系统里有了你的档案probe调用才相当于你被分配到了具体岗位开始干活。如果只有driver注册而没有device出现那驱动就永远处于待岗状态。这个分两步走的设计恰恰是自动加载的基石。内核把一个设备的出现抽象成uevent事件用户态的udev收到事件后去找匹配的driver模块加载进内核然后总线子系统重新扫描设备列表发现之前没能匹配的device现在有driver了立即调用probe。整个流程环环相扣任何一环断了驱动就懒得加载或者加载了不干活。2. 自动加载的两条链路用户态uevent 与 内核态匹配自动加载不是内核单方面完成的它需要用户态和内核态配合。把这两条链路理清楚你才能明白一个热插拔设备插入瞬间到底发生了什么也才知道出了问题该去哪一层查。2.1 用户态链路uevent、udev与modprobe的接力当一个设备出现在系统里时比如USB设备插入内核的设备模型会向用户态发送一个uevent事件。这个事件本质上是一条环境变量加动作的消息里面包含设备的类型、厂商ID、产品ID、设备名、模态别名等信息。早期SysVinit时代负责接收这个消息的是hotplug脚本现在主流发行版都是udev更准确说是systemd-udevd在监听netlink套接字。udev收到uevent后会做三件事根据事件里的子系统、设备名去/etc/udev/rules.d/和/lib/udev/rules.d/下匹配规则如果规则里有RUN或PROGRAM指令执行对应命令如果设备带有MODALIAS属性udev会把它作为参数调用modprobe重点在第3条。udev本身不知道哪个模块能驱动这个设备它只负责传递消息。MODALIAS这个环境变量才是关键格式一般是usb:v1234p5678d0100dc00dsc00dp00这样的字符串udev会调用类似这样的命令/sbin/modprobe usb:v1234p5678d0100dc00dsc00dp00modprobe拿到这个字符串后并不会直接去加载一个叫这个名字的模块而是去modules.alias索引里查找谁声明了对这个别名负责找到后再加载对应的.ko模块。这就是前面说的modprobe的别名解析能力。你可以自己验证一下cat /lib/modules/$(uname -r)/modules.alias | grep usb:v能看到一堆格式很长的记录每一行都是一个模块声明的别名。比如常见的CH340串口芯片对应模块ch341.ko通常会在代码里声明MODULE_ALIAS(usb:v1A86p7523d*dc*dsc*dp*)这样当设备插入时MODALIAS经过模糊匹配就能命中这个别名。2.2 内核态链路总线匹配与probe时机用户态链路负责把驱动模块请进内核但模块加载完成后内核态还有自己的匹配逻辑要跑。这个逻辑由总线驱动模型来管。Linux的设备模型里有一个叫bus_type的结构体每个驱动都属于某条总线比如platform_bus_type、usb_bus_type、i2c_bus_type。总线最重要的成员函数是match它决定了某个driver和某个device是否配对。以platform总线为例它依次比较以下几种情况设备树节点里的compatible属性与driver的of_match_table中的compatible字符串是否匹配设备的name字段与driver的id_table中的name是否匹配设备的name字段与driver的driver.name是否直接相等USB总线的匹配逻辑则是比较vendor ID和product IDPCI总线比较vendor/device/class代码I2C总线比较compatible和name。每条总线的门卫长得不一样但思路是共通的。匹配发生的时间点有两个很关键设备先出现driver后注册比如设备树在系统启动早期就把platform_device注册好了但驱动模块还没加载。此时系统记录下有设备未匹配当后面driver注册到总线时总线会立刻重新扫描一遍已有设备列表补做匹配和probe。driver先注册设备后出现比如USB驱动先加载了U盘后插入。设备插入时USB核心创建一个usb_device然后遍历该总线上的所有driver找到匹配的就调probe。明白了这两个时机很多奇怪现象就能解释了。比如你insmod了一个I2C驱动模块加载成功了但probe没被调用最常见的原因是设备还没注册到I2C总线上反过来如果你的I2C设备是在驱动加载之前就存在的驱动一加载就会立刻触发probe。2.3 MODALIAS与modules.alias让modprobe反向找到模块前面提到了MODALIAS和modules.alias这俩名字很像容易被搞混但它们是自动加载机制里最关键的一对概念。MODALIAS是设备侧提供的字符串描述我是什么设备modules.alias是模块侧声明的字符串描述我能支持什么设备。设备插入时内核把MODALIAS通过uevent告诉udevmodprobe拿着MODALIAS去modules.alias索引文件里反查对应的模块名。这就是自动两个字的真正含义——不是系统智能到能猜出该加载哪个驱动而是设备主动报上名来modprobe再拿着名字去查数据库。这个索引文件是depmod命令生成的。每当你往/lib/modules目录里新增了一个.ko模块都要执行depmod -a来重新扫描depmod会解析模块里的MODULE_ALIAS、MODULE_DEVICE_TABLE、MODULE_SOFTDEP等元信息然后更新modules.alias、modules.dep、modules.symbols这几个文件。忘记执行depmod是驱动无法自动加载的最常见原因之一而且报错往往是让人摸不着头脑的modprobe: FATAL: Module not found——模块明明在目录里躺着却不被识别。如果你在驱动的源码里看到类似这样的代码那就是在给自动加载做铺垫static const struct platform_device_id demo_id_table[] { { demo_auto_load, 0 }, { } }; MODULE_DEVICE_TABLE(platform, demo_id_table); MODULE_ALIAS(platform:demo_auto_load);MODULE_DEVICE_TABLE宏会把id_table里的内容写进模块的元信息区depmod识别后生成对应的别名。MODULE_ALIAS则是手动声明一个自定义别名两者是同一套机制的两种写法目的都是让modprobe能通过设备别名反查到这个模块。3. 实操篇从零编译一个支持自动加载的驱动理论讲再多不动手还是隔了一层。这一节我带你做一个完整的实验环境要求很低普通的x86_64或ARM64 Linux开发板都行只需要内核头文件齐全、用户态有udev和modprobe就行。我会把整个实验分成三步先写一个能用的misc驱动再编译安装并手动加载然后模拟设备热插拔事件观察驱动自动加载。3.1 准备一个可用的misc驱动框架为什么用misc设备而不是标准的字符设备因为misc子系统drivers/char/misc.c管理着所有主设备号为10的杂项设备每个miscdevice只需要提供一个name和file_operations注册后内核会在/dev下自动生成节点省去了手动mknod的麻烦非常适合演示用。节点名就是fast_auto_load主设备号内核自动分配10次设备号可以通过miscdevice结构体里的minor字段指定填MISC_DYNAMIC_MINOR就代表让内核动态分配。#include linux/module.h #include linux/miscdevice.h #include linux/fs.h #include linux/platform_device.h #define DEMO_NAME fast_auto_load static int demo_open(struct inode *inode, struct file *filp) { return 0; } static ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { return 0; } static const struct file_operations demo_fops { .owner THIS_MODULE, .open demo_open, .read demo_read, }; static struct miscdevice demo_miscdev { .minor MISC_DYNAMIC_MINOR, .name DEMO_NAME, .fops demo_fops, }; static int demo_probe(struct platform_device *pdev) { int ret; ret misc_register(demo_miscdev); if (ret) { pr_err(demo misc register failed\n); return ret; } pr_info(demo misc registered, node /dev/%s\n, DEMO_NAME); return 0; } static int demo_remove(struct platform_device *pdev) { misc_deregister(demo_miscdev); pr_info(demo misc deregistered\n); return 0; } static struct platform_driver demo_driver { .probe demo_probe, .remove demo_remove, .driver { .name DEMO_NAME, .owner THIS_MODULE, }, }; static struct platform_device *demo_pdev; static int __init demo_init(void) { int ret; ret platform_driver_register(demo_driver); if (ret) return ret; demo_pdev platform_device_register_simple(DEMO_NAME, -1, NULL, 0); if (IS_ERR(demo_pdev)) { platform_driver_unregister(demo_driver); return PTR_ERR(demo_pdev); } pr_info(demo module init done\n); return 0; } static void __exit demo_exit(void) { platform_device_unregister(demo_pdev); platform_driver_unregister(demo_driver); pr_info(demo module exit done\n); } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE(GPL); MODULE_ALIAS(platform: DEMO_NAME);这个代码里我做了件初学者可能觉得奇怪的事在init函数里同时注册了platform_driver和platform_device。这么做的目的就是为了演示驱动绑定过程——模块加载时driver和device一起出现内核会立即进行匹配并调用probeprobe里才去注册misc设备。实际项目中device通常来自设备树或由其他驱动注册但实验心态下自己注册device能让整个流程自洽逻辑更清晰。3.2 编译安装与手动加载验证准备好源码文件demo.c后在同目录下写标准的内核模块Makefileobj-m : demo.o KERNELDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean编译前先确认内核头文件安装好了没有的话先装否则第一个命令就会报找不到Makefile。然后make看到生成了demo.ko先别急着加载。手动验证一下模块的元信息modinfo demo.ko输出里应该能看到alias字段内容就是platform:fast_auto_load。这行就是depmod将来生成modules.alias的依据。接下来把模块安装到标准目录并重新生成依赖索引sudo mkdir -p /lib/modules/$(uname -r)/extra sudo cp demo.ko /lib/modules/$(uname -r)/extra/ sudo depmod -adepmod执行完直接加载sudo modprobe fast_auto_load如果一切正常dmesg里会看到demo module init done demo misc registered, node /dev/fast_auto_load然后去看设备节点ls -l /dev/fast_auto_load到这里手动加载已经成功了。检查一下modules.alias文件确认索引里确实记录了模块的别名grep fast_auto_load /lib/modules/$(uname -r)/modules.alias如果depmod工作正常能查到一行类似alias platform:fast_auto_load fast_auto_load的记录。这一行就是自动加载机制的地基没有它后面一切自动加载都是空谈。3.3 通过sysfs触发热插拔观察自动加载全过程现在卸载模块模拟一次完整的自动加载过程。这种验证方法不依赖任何物理硬件非常适合在没有开发板的情况下验证机制。sudo modprobe -r fast_auto_load确认系统里已经清干净后去/sys/bus/platform/devices目录看一眼能找到名为fast_auto_load的platform_device目录。这个设备是上次模块加载时注册的虽然驱动卸载了device还在真实系统中设备生命周期的设计也是这样device往往比driver更长寿。接着手动触发一次add事件echo add /sys/bus/platform/devices/fast_auto_load/uevent当这个写入发生时内核设备模型会发送uevent到用户态udev收到后解析出该设备的MODALIAS属性属性内容正是platform:fast_auto_load然后udev自动调用modprobe。你观察一下lsmod | grep fast_auto_load模块已经在运行了再看/dev/fast_auto_load节点也出来了。这就是一次完整的自动加载闭环写uevent属性文件 - 内核发事件 - udev解析MODALIAS - modprobe反查modules.alias - 加载驱动 - platform总线自动匹配 - 调用probe - 注册misc设备。整个链路里没有一个人为执行过modprobe真实命令一切都是事件驱动。这也是你在真实系统中插上CH340串口线时发生的事只不过那里的uevent来自USB核心而非sysfs的手动写入。3.4 用udev规则管理设备节点与权限驱动加载只是第一步节点出来后通常还涉及权限问题。很多新手在/dev下看到自己的节点但普通用户打开时提示permission denied原因就是节点默认权限是root:root 0600。解决这个问题的正规做法是写udev规则而不是图省事chmod 777。新建/etc/udev/rules.d/99-demo.rules内容如下KERNELfast_auto_load, MODE0666, GROUPdialout, SYMLINKdemo_dev这条规则的意思很直白当内核创建设备名为fast_auto_load的节点时把权限设为0666属组设为dialout并额外创建一个/dev/demo_dev符号链接。SYMLINK这个动作很实用很多设备名带了内核编号应用层依赖固定节点名时就用它做一层稳定的映射。改完规则后需要重新加载规则并触发一次sudo udevadm control --reload-rules sudo udevadm trigger /sys/bus/platform/devices/fast_auto_load这时再去看/dev节点权限已经变了。这里要提醒一句0666属于实验方便生产环境建议用更严格的权限方案配合dialout组之类的做细粒度管控千万别在规则里无脑给所有设备开0666。4. 自动加载失败排查从dmesg到modprobe的完整链路自动加载机制本身不复杂但排查起来经常让人头疼因为涉及的点很多而且错误信息往往不会直接告诉你根因。我根据实际工程经验整理了最常见的几类问题按排查顺序讲。4.1 先分清四种没加载的状态很多人在论坛上提问驱动没有自动加载但没加载其实包含四种完全不同的情况排查方向也完全不同。建议先准确判断你属于哪一种现象可能原因排查方向lsmod看不到模块模块名错误、alias不匹配、被blacklist屏蔽modprobe手动加载试试看报错模块加载了但probe没调用device不存在、总线匹配失败、设备树节点不对查看/sys/bus下设备列表probe调用了但报错硬件访问失败、资源申请冲突、注册失败看dmesg里probe函数的错误信息一切正常但/dev节点没有没注册misc/字符设备、regionsid冲突、udev规则改变了节点名看dmesg检查register返回值有了这个分类排查时就不会乱翻了。我自己的经验是先确认lsmod和/sys/bus再去看dmesg顺序不要乱。4.2 常见加载失败原因与对应日志特征第一类问题modprobe报FATAL。先手动执行一遍modprobe命令报错信息里藏着的线索非常直接。如果是FATAL: Module not found要么模块没装在/lib/modules/$(uname -r)/下要么depmod没有重新运行。这种错误第一反应是检查depmod -a有没有执行而不是怀疑模块代码。如果是FATAL: Module xxx not found in directory /lib/modules/xxx通常是内核版本目录不存在或内核更新后旧模块被清掉了。如果是Unknown symbol in module说明模块依赖的其他符号不存在通常是因为加载顺序问题或依赖的模块没加载。用modinfo查看depends字段先把依赖模块装上。第二类问题模块加载成功但probe没被调用。这类问题最隐蔽因为它不报错。按照上一节说的可能原因无非三种device不存在。检查/sys/bus/platform/devices下有没有对应的节点。如果你写的是platform驱动但设备树或ACPI表里根本没有对应的compatible或name那无论如何都匹配不上。compatible字符串对不上。设备树里compatible写的是vendor,device而驱动的of_match_table写了device两边不匹配总线match直接返回0。驱动id_table为空且name也不匹配。platform总线匹配的最后一步是比较driver.name和设备name这两个字符串也必须完全一致。第三类问题uevent事件发了但udev没有调modprobe。这可能是udev规则被截断或MODALIAS属性为空。检查方法udevadm info --queryall --path/sys/bus/platform/devices/fast_auto_load看输出里有没有MODALIAS字段。如果没有可能是设备模型里没生成modalias属性通常见于非常老的内核或设备模型注册不完整的情况。如果MODALIAS存在但modprobe没被调用检查/lib/udev/rules.d/里是否被某些规则提前处理了RUN指令导致跳过默认路径。4.3 一个真实的排查案例思路前阵子一个朋友遇到的问题是他做了一块I2C触摸屏驱动板驱动模块编译好装进系统lsmod能看到但插上触摸屏后dmesg里完全没有probe的日志。他怀疑是驱动代码问题代码翻了好几遍没找出毛病。我没急着看代码先让他查I2C总线上到底有没有这个设备i2cdetect -l列出所有I2C总线后再对对应总线执行i2cdetect -y 2结果发现总线上根本扫不到触摸屏的I2C地址。再往下查发现触摸屏的供电来自一个GPIO控制的LDO这个GPIO在设备树里配置成了output-high但设备树里的pinctrl设置和实际板卡不匹配导致GPIO默认输出低电平芯片没上电I2C从机自然不回ACK。模块虽然加载了但probe的调用前提是设备存在设备存在的前提是芯片得先上电。这个案例说明一个问题自动加载链条的尽头不只是模块和驱动的匹配硬件本身也要处于可被发现的状态。排查驱动问题时要习惯性地往前追溯设备为什么没被发现往往是更根本的根因。5. 嵌入式环境下更隐蔽的加载问题设备树、依赖与顺序在PC上用usb自动加载只是入门嵌入式产品里自动加载的坑比这多得多。ARM、RISC-V平台的设备基本都来自设备树模块加载和probe的时机、顺序、依赖关系每一个都可能让驱动在启动阶段加载失败。这一节讲三块容易被忽略的内容。5.1 设备树compatible与of_match_table的匹配设备树里一个设备节点的compatible属性怎么写直接决定了platform驱动在of_match_table里的匹配策略。假设你的设备树节点长这样demo_device: demo1c00000 { compatible vendor,demo-auto; reg 0x1c00000 0x1000; };驱动的of_match_table必须与之对应static const struct of_device_id demo_of_match[] { { .compatible vendor,demo-auto }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, demo_of_match); static struct platform_driver demo_driver { .probe demo_probe, .driver { .name demo_auto_load, .of_match_table demo_of_match, }, };注意一个常见误区很多人以为platform总线匹配用的是driver.name和设备树节点的compatible但其实总线在匹配时优先查找of_match_table。如果of_match_table为NULL才会退回比较name和id_table。也就是说在设备树环境下compatible是主匹配凭据driver.name反而只是辅助。依赖设备树匹配的驱动还有一个好处当设备树里的节点存在时depmod会根据of_match_table生成形如of:N*T*Cvendor,demo-auto的alias。这样即使驱动模块不在initramfs里等rootfs挂载完成后udev也能根据uevent里的MODALIAS把模块自动拉起来。加上MODULE_DEVICE_TABLE(of, ...)对自动加载的意义就在这。5.2 控制模块加载顺序softdep与initramfs嵌入式系统里经常遇到驱动A需要在驱动B之后加载的情况比如触摸屏驱动需要I2C控制器驱动先就位。最简单的办法是按顺序写在启动脚本里但这不优雅而且容易因为启动脚本执行顺序调整而出问题。modprobe提供了softdep机制可以在模块元信息里声明软依赖。在驱动源码里加MODULE_SOFTDEP(pre: i2c_dev);这行声明告诉modprobe加载本模块之前尽量先加载i2c_dev模块。注意尽量这个词softdep不是硬性依赖hard依赖会直接影响模块是否能加载softdep则只是调整加载顺序即使预依赖模块加载失败本模块依然会尝试加载。如果两个模块之间存在真实的符号依赖关系那必须用Makefile里的obj-m依赖或depmod自动生成的depends来保证softdep解决不了这个问题。更彻底的控制方式是把驱动编进initramfs或内核镜像本身。initramfs里需要加载的模块列表通常在/etc/initramfs-tools/modules里配置debian系或者通过构建脚本传入。产品发布时如果根文件系统还没挂载就需要用到的驱动比如存储控制器、网络驱动都必须提前放在initramfs里否则启动阶段根本轮不到udev来处理。5.3 built-in与module自动加载策略的取舍从机制层面想一个问题既然自动加载这么方便为什么不把驱动全部编进内核内核镜像体积会膨胀不说更重要的是很多驱动的probe时机其实不宜过早。比如一个I2C触摸屏驱动如果编进内核它会在内核启动阶段就执行probe此时I2C控制器可能还没完成时钟使能备妥设备树解析也有顺序问题反而容易出现时序竞态。所以实际产品里的策略通常是核心且必须的驱动编成built-iny功能性的、可热插拔的、不需要它就无法挂载rootfs的驱动编成模块m再配合自动加载机制在运行时拉起。这个选择没有绝对标准但有一个原则值得参考启动路径上的驱动尽量built-in非启动路径上的驱动尽量模块化。还有一个细节built-in驱动不需要MODULE_ALIAS也能正常工作因为设备匹配在驱动注册时同步完成不存在模块还没加载的问题。而模块化驱动一旦涉及到initramfs阶段就要保证modules.alias在initramfs里也存在否则udev在switch_root之后才能访问真正的/lib/modules这中间的时间窗口里设备事件会被跳过或者无法加载对应驱动。解决思路是把depmod生成的索引文件也打进initramfs具体方法依赖你使用的构建工具但原理是一致的——initramfs里必须有模块、必须有索引否则自动加载链路在启动早期就是断的。写到这里整个自动加载的链条基本串起来了。说点个人体会我见过太多人在驱动代码上花了很多时间却在一个depmod没更新或者设备树compatible多打了一个字符这种小问题上卡好几天。Linux驱动的自动加载说到底是设备模型、模块机制和用户态工具三方的协作任何一环掉链子都会表现为驱动加载失败。所以排查时不要死磕代码先把链路梳理清楚再一层层定位效率高很多。最后再分享一个小技巧如果你在嵌入式板卡上调试自动加载可以在udev规则里临时加一条RUN/bin/sh -c echo $MODALIAS /tmp/modalias.log把每次事件的MODALIAS都打出来配合modprobe的日志绝大多数加载问题都能定位到具体环节。
网站建设高端定制企业官网