news 2026/9/30 8:03:07

Linux开机自动加载驱动模块:modprobe、udev与initramfs解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux开机自动加载驱动模块:modprobe、udev与initramfs解析

前几天帮人看一台跑隧道业务的机器,重启之后接口起不来,登上去lsmod | grep tun光秃秃的,手动modprobe tun立马恢复。问题不在命令本身,而在于重启之后没有任何东西替它执行那条命令。Linux 启动的时候自动加载驱动模块,听起来就是往配置文件里写一行模块名的事,但真正落到生产机器上,踩的坑散落在好几个完全不同的层面:谁负责加载、在启动的哪个阶段加载、加载失败之后你能不能第一时间看见。我在 Debian、Ubuntu、CentOS、Rocky 这几类系统上都反复折腾过这一套,有些坑是所有发行版通用的,有些则是发行版自己的历史包袱。这篇就把整条链路从头到尾拆一遍,从手动加载和开机加载的本质差别讲起,一直讲到配置写完还是不生效时该怎么一步步定位,刚接触驱动模块的人照着做也能跑通。

1. 开机没加载模块的现场:三种"自动加载"根本不是一回事

1.1 从 lsmod 和 dmesg 开始的第一轮确认

发现模块没上来的第一个动作永远是lsmod,它读的是/proc/modules,能看到当前内核里已经加载的模块名、占用内存大小、被引用次数以及依赖它的模块列表。这里有个容易误判的点:如果某个驱动是编译进内核镜像的(built-in),你在lsmod里永远看不到它,因为 built-in 的代码在内核初始化阶段就已经在内核地址空间里了,跟模块这套机制没关系。所以只靠lsmod判断"驱动有没有工作"是不完整的,正确做法是配合/sys/module/目录一起看:

lsmod | grep -w tun ls /sys/module/ | grep -i tun dmesg | grep -i tun

/sys/module/下每一个目录都对应一个已经初始化的模块或者 built-in 组件,只要目录在,说明代码已经注册进内核。dmesg则是判断"加载之后有没有真正 probe 成功"的关键,模块被modprobe拉起来只是把它塞进内核并执行了 init 函数,驱动能不能绑定到具体硬件,还得看 probe 的结果。很多人的困惑就卡在这一步:lsmod里明明有模块,设备节点却不存在,一看dmesg才发现 probe 返回了-ENODEV或者被-EPROBE_DEFER挂起重试了。

我一般会把排查顺序固定成三步:lsmod确认模块有没有在内核里,dmesg确认 probe 有没有成功,/sys/module/<name>/parameters/确认参数是不是按预期传进去了。这三步跑完,问题基本能定到具体环节。

1.2 udev 热插拔、modules-load、initramfs 三条路径的边界

"开机自动加载"这句话其实被三种机制共享,它们触发的时间点和触发条件完全不同,混在一起想就永远理不清。

第一种是udev 的自动加载。内核在探测到硬件时,会把这个设备的总线类型、厂商 ID、设备 ID 拼接成一个MODALIAS字符串,通过 netlink 发一个 uevent 到用户空间。udev 收到之后去查modules.alias文件,找到匹配的模块名就调用modprobe把它拉起来。这条路径是"设备驱动设备"的逻辑,只有匹配上 alias 才会触发,服务器上那些靠 ACPI 上报、没有实际热插拔事件的板载设备,有时候就走不通。

第二种是显式列表加载,也就是 systemd 的modules-load.d机制(Debian 老系统里的/etc/modules属于同一类)。它不关心有没有硬件,开机就按名单一个个modprobe,适合那些没有设备别名、或者你就是要它无条件存在的模块,比如tun、nf_conntrack、br_netfilter、vfat这类纯软件功能模块。

第三种是initramfs 阶段加载。根文件系统还没挂载之前,内核只能用内存里那个极小的根文件系统,也就是 initramfs。如果你的根分区在 NVMe 上、在 LVM 里、在加密卷里,那对应的驱动必须先被塞进 initramfs,否则内核连根都找不到,直接 kernel panic。这个阶段和前面两个阶段的时间差可能是几百毫秒,也可能是几秒,取决于硬件初始化速度。

提示:判断一个模块到底该走哪条路,最直接的问题是"它需不需要在根文件系统挂载前就工作"。需要,走 initramfs;不需要但当设备出现时应该自动上,走 udev;不需要但必须常驻,走 modules-load.d。

2. modprobe 和 insmod 的差别,决定了配置该往哪里写

2.1 insmod 只认路径,modprobe 会替你算依赖

insmod是最原始的工具,参数就是.ko文件的完整路径,比如insmod /lib/modules/$(uname -r)/kernel/drivers/net/tun.ko。它做的事情很少:读文件、检查格式、调用 init_module 系统调用。它完全不处理依赖,如果这个模块依赖别的模块,你得自己按顺序先 insmod 那些依赖。

modprobe是上层封装,它只接收模块名,不需要路径也不需要.ko后缀,然后去/lib/modules/$(uname -r)/下查modules.dep,把依赖树算出来,按正确顺序依次加载。它还负责读取/etc/modprobe.d/下的配置,处理 alias、options、blacklist、install 这些指令。

两者最本质的差别在于:modprobe是"按名字和策略加载",insmod是"按文件加载"。开机自动加载这件事,绝不可能是insmod干的,因为路径会因为内核版本变化而变,所有系统级机制用的都是modprobe。所以凡是和开机加载相关的配置,写的都是模块名,不带路径也不带后缀,这一点写错了系统连报错都懒得给你。

# 正确:写模块名 echo "tun" > /etc/modules-load.d/tun.conf # 错误:写路径,systemd 会找不到这个模块名然后失败 echo "/lib/modules/5.15.0-91-generic/kernel/drivers/net/tun.ko" > /etc/modules-load.d/tun.conf

顺带说一个容易被忽略的点:模块名里如果有连字符和短横线的区别,比如nf_conntrack和nf-conntrack,modprobe内部会做一定的转换容错,但配置文件里最好还是按modinfo输出的name字段原样写,不要自己想当然。

2.2 depmod 生成的 modules.dep 和 modules.alias 是整套机制的地基

在/lib/modules/$(uname -r)/下面有这么几个文件:modules.dep、modules.dep.bin、modules.alias、modules.alias.bin、modules.symbols,它们是depmod命令扫描整个目录下所有.ko之后生成的索引。

modules.dep记录的是依赖关系,每一行的格式是"模块文件路径: 它依赖的模块路径列表"。modprobe加载一个模块时,先在这里查它的依赖,把依赖树做一次拓扑排序,然后从叶子节点开始往上加载。modules.alias记录的是设备匹配规则,来自驱动源码里MODULE_DEVICE_TABLE宏展开出的字符串,格式是alias <匹配模式> <模块名>。udev 能自动加载模块,靠的就是这张表。

这两个文件的意义在于:它们是从 .ko 文件本身的内容推导出来的,不是你手写的。所以当你自己编译了一个驱动模块,用insmod挂上去能工作,用modprobe却报 "module not found",八成就是没有跑depmod,modules.dep里没有这个模块的条目。正确流程是:

# 把编译好的 .ko 拷贝到内核模块目录(或者 make modules_install) cp mydrv.ko /lib/modules/$(uname -r)/extra/ # 重建索引 depmod -a # 此时 modprobe 才能按名字找到它 modprobe mydrv

内核升级之后,/lib/modules/新版本/是新的空目录,如果第三方模块没有跟着重新编译并depmod,开机加载就会失败。这是自研驱动在自动加载上最常见的翻车原因,尤其是那些用 DKMS 管理的模块,DKMS 在重建时会自己跑 depmod,但如果重建失败,索引就停在旧状态。

2.3 手动能加载不等于开机也能加载

这一点值得单独说,因为它造成的困惑最多。你在 shell 里modprobe foo成功,不代表开机那一刻也成功,原因主要有三个。

第一个是加载顺序和依赖链的差异。你手动加载时,前面的依赖可能已经因为别的原因被加载过了,modprobe只需要处理剩下的部分。开机时环境是干净的,如果依赖链里某一环因为硬件没就绪而 probe 失败,modprobe可能仍然返回成功(模块被加载了),但设备没起来,于是你以为模块加载了,实际功能不可用。

第二个是配置文件被modprobe.d改写。/etc/modprobe.d/里可能有blacklist foo或者install foo /bin/false这类规则,手动modprobe foo时你可能用了-i(忽略配置)或者干脆是加载的别名,而 systemd 走的路径会严格执行这些规则,结果就是手动成功开机失败。排查时用modprobe -n -v foo干跑一遍,它会把你即将执行的每一步打印出来,包括要不要读哪个配置文件、解析成哪个模块,这是定位配置冲突最快的手段。

第三个是加载时机的差异。开机早期,很多子系统还没初始化完成,驱动初始化函数里的注册回调可能返回-EPROBE_DEFER,内核会把它放进一个延迟重试队列,等它依赖的资源就绪后再试。如果资源一直没就绪,模块就永远停在那里。手动加载时系统已经跑完了全套初始化流程,自然不会遇到这个问题。

3. systemd 体系下的标准答案:modules-load.d

3.1 /etc/modules-load.d/*.conf 的文件格式与命名习惯

这是目前所有使用 systemd 的发行版上最正规的做法。目录是/etc/modules-load.d/,里面放.conf结尾的文本文件,格式极简:每行一个模块名,空行和以#开头的行被忽略。

# /etc/modules-load.d/storage-extra.conf # 用于承载存储相关的附加模块 vfat exfat dm_crypt

目录的读取顺序是/usr/lib/modules-load.d/、/run/modules-load.d/、/etc/modules-load.d/,同名文件后面的会覆盖前面的,/etc优先级最高。这跟 systemd 其他配置目录的约定一致,包管理器装进来的默认配置放/usr/lib,管理员自己改的放/etc,两边不打架。

文件名我建议按用途分组而不是按模块名一个一个建文件。见过有人给每个模块单独建一个.conf,结果/etc/modules-load.d/里躺着四五十个小文件,后来要排查哪个模块是哪个业务加的,翻半天。按功能分文件,比如k8s-node.conf、storage.conf、security.conf,配合文件里的注释,半年后还能看懂。

注意:这个目录里的加载是"按文件名字典序、文件内按行序"串行执行的,但整个 service 是并行的,模块本身如果有顺序要求,不要靠文件命名去赌,用softdep或者干脆写进 initramfs 更稳妥。

3.2 Debian 系的 /etc/modules 是怎么被兼容进来的

Debian 和 Ubuntu 历史上用的是/etc/modules这个文件,格式和现在的.conf一样,一行一个模块名。systemd 时代,这个文件仍然被保留为兼容入口,由kmod包提供的那套机制接管。在同时存在/etc/modules和/etc/modules-load.d/的系统上,两边的内容都会被处理,所以在 Debian 上你可能会发现"我只改了/etc/modules,模块也上来了",或者反过来"我在/etc/modules-load.d/里加了,怎么好像有两个地方都有配置"。

我的建议很明确:新系统一律用/etc/modules-load.d/,不要往/etc/modules里加东西。原因是前者是 systemd 的跨发行版标准,迁移到 RHEL 系或者容器基础镜像时不用重新适应;后者是 Debian 的历史遗留,未来哪天被彻底废弃,你的配置就成了孤儿。如果接手一台老机器发现/etc/modules里有内容,迁移的时候注意把里面的模块名完整抄过去,别漏了带注释的边界行。

3.3 systemd-modules-load.service 报错时的定位手法

配置写完之后,别急着重启机器,先用 systemd 的单元状态和日志确认:

# 查看服务状态 systemctl status systemd-modules-load.service # 只看本次启动的日志,带时间戳 journalctl -b -u systemd-modules-load.service --no-pager # 手动触发一次,看即时输出 systemctl restart systemd-modules-load.service journalctl -b -u systemd-modules-load.service -n 50

服务失败时日志里会直接点名是哪个文件、哪一行、哪个模块加载失败了,比如 "Failed to find module 'exfat'" 或者 "Module 'foo' is blacklisted"。这里有个细节:systemd-modules-load遇到加载失败的模块会继续处理后面的模块,最后返回一个非零状态码,所以在 status 里看到failed并不代表所有模块都没上,得逐条看日志确认。

还有一种情况是服务显示 active,模块却不在lsmod里。这通常是因为模块被编译进了内核,modprobe对 built-in 模块的处理是静默成功——它发现/sys/module/foo已经存在,就直接返回 0,什么都不做。这种情况你用journalctl是看不到任何线索的,只能靠modinfo foo看它有没有filename字段来判断。

4. udev 自动加载那条链:设备一插,模块自己就上来了

4.1 MODALIAS 是怎么和 modules.alias 对上的

内核探测到设备后,会调用总线的uevent回调生成一个环境变量MODALIAS,内容形如pci:v00008086d000015B8sv00001028sd00000700bc02sc00i00。这串东西是总线名加一系列key:value对的拼接,比如v是 vendor id,d是 device id,sv/sd是 subsystem vendor/device,bc/sc/i是 class、subclass、interface。

驱动的源码里如果有MODULE_DEVICE_TABLE(pci, my_pci_id_table)这样的声明,编译时就会在.ko里生成一段 alias 信息,depmod扫描时把它抽出来写进modules.alias,格式大致是:

alias pci:v00008086d000015B8sv*sd*bc*sc*i* my_driver

udev 收到 uevent 之后,拿MODALIAS的值去modules.alias里做匹配,匹配上就modprobe那个模块。整个链路里,*是通配符,v和d之外的字段如果驱动没声明,就用*兜底,所以匹配逻辑是比较宽松的。

手工验证这条链最直接的方法是:

# 查看某设备的 modalias cat /sys/bus/pci/devices/0000:00:1f.6/modalias # 用 modprobe 按 alias 匹配(不会真的加载,只显示匹配结果) modprobe -R pci:v00008086d000015B8sv*sd*bc*sc*i* # 或者更精确地,直接用设备路径触发 udevadm test /sys/class/net/eth0

udevadm test会把 udev 处理这个设备的完整过程打印出来,包括它查了哪些规则、算了什么 alias、最终要不要加载模块。这是排查"设备插上了模块为什么不自动加载"的第一工具,比猜快得多。

4.2 自研驱动补 alias 的实操写法

自己写的驱动如果要享受 udev 自动加载,必须在源码里声明设备表,否则depmod抽不出 alias,modules.alias里没有条目,udev 自然匹配不上。以平台设备或者字符设备为例,很多时候驱动并不挂靠在标准总线上,那就没有现成的MODULE_DEVICE_TABLE可用,这时候有两个办法。

第一个是在modprobe.d里手写 alias 规则:

# /etc/modprobe.d/mydrv.conf alias char-major-240 mydrv alias my-special-device mydrv

modprobe在解析模块名时会先查 alias 表,把my-special-device解析成mydrv,然后加载它。配合 udev 规则可以在设备出现时主动触发:

# /etc/udev/rules.d/99-mydrv.rules ACTION=="add", SUBSYSTEM=="misc", KERNEL=="mydrv", RUN+="/sbin/modprobe mydrv"

第二个办法是驱动注册 misc 设备或 platform driver 时,用MODULE_ALIAS("misc:mydrv")显式声明 alias,这样depmod之后modules.alias里就会有对应条目,udev 的通用规则就能处理,不需要额外写 udev 规则。

需要提醒的是,udev 规则里执行RUN+=是在设备事件处理过程中同步跑的,如果modprobe耗时太长会阻塞 udev 事件队列,进而影响其他设备的事件处理。对于大型驱动,更推荐用ENV{MODALIAS}的方式让 udev 走标准加载路径,而不是自己RUN。

4.3 冷插拔设备开机加载失败的典型原因

冷插拔设备指的是开机时就已经在的总线设备,比如板载网卡、主板上的 SATA 控制器。这些设备在系统启动时会被总线枚举,理论上也应该产生 uevent,但在实际系统里,很多机器上的 ACPI 枚举出来的设备并不会发完整的 hotplug uevent,或者 udev 在早期 initramfs 阶段处理了一次、切到真实根之后就不再重复处理。

这就导致一个很典型的场景:某个板载设备对应的模块,用 udev 规则依赖ACTION=="add"永远触发不了,因为那个 add 事件在 initramfs 里就已经发生过了。解决办法通常有两种,一是把模块写进modules-load.d让它无条件加载,二是把RUN+="/sbin/modprobe mydrv"改成不带条件或者匹配SUBSYSTEM的通配规则,让它在冷插拔场景下也能被触发。

另外,内核参数udev.log_level=debug可以在排查 udev 早期行为时打开详细日志,但这个参数对启动速度有影响,排完记得去掉。

5. initramfs 阶段:根文件系统还没挂,谁来加载模块

5.1 哪些模块必须在 initramfs 里就位

initramfs 是内核启动时加载到内存里的一个微型根文件系统,里面的内容通常是一个 cpio 归档,包含必要的/bin、/sbin、内核模块和一套启动脚本。它的使命很短:找到真正的根文件系统、挂载它、然后把控制权交出去。

如果根文件系统所在的存储设备依赖某个驱动才能访问,那这个驱动就必须在 initramfs 里。典型的需要进 initramfs 的模块包括:NVMe 控制器驱动、RAID 控制器驱动、LVM 的dm-mod、设备映射相关模块、文件系统模块(ext4、xfs、btrfs)、加密卷相关的dm-crypt和加密算法模块、以及某些网络启动场景下的网卡驱动。

判断方法很实在:看当前系统启动时lsinitramfs列出来的模块列表,对照lsmod找出你在用但没进 initramfs 的模块。如果某个模块是根分区挂载的必经之路却没进去,那这台机器一旦内核更新、initramfs 重建之后就可能起不来。

# Debian/Ubuntu 查看 initramfs 内容 lsinitramfs /boot/initrd.img-$(uname -r) | grep '\.ko' # RHEL 系 lsinitrd /boot/initramfs-$(uname -r).img | grep '\.ko'

5.2 Debian 与 RHEL 两条更新路径的差异

Debian 和 Ubuntu 用的是initramfs-tools,配置入口是/etc/initramfs-tools/modules文件,一行一个模块名。改完之后必须跑update-initramfs -u重新生成当前内核的 initramfs,否则改了等于没改。还有/etc/initramfs-tools/conf.d/可以放一些额外配置,比如MODULES=most还是dep,前者会把大部分模块都塞进去,后者只塞探测到的必需模块。生产服务器为了稳,一般选most,代价是 initramfs 体积大一些。

RHEL、CentOS、Rocky 这些用的是dracut,配置入口是/etc/dracut.conf.d/下的.conf文件,用force_drivers+=" mydrv "强制加入模块,或者用add_drivers+=" mydrv "。改完跑dracut -f重建当前内核的 initramfs,也可以指定输出文件。dracut 的模块化程度比 initramfs-tools 高,它自己有一套 dracut module 机制,但对我们来说用 force_drivers 就够了。

发行版家族工具配置文件重建命令查看内容
Debian/Ubuntuinitramfs-tools/etc/initramfs-tools/modulesupdate-initramfs -ulsinitramfs
RHEL/CentOS/Rockydracut/etc/dracut.conf.d/*.confdracut -flsinitrd
通用临时内核参数无重新引导无

5.3 改了配置忘记更新 initramfs 的经典翻车

这是我最想强调的坑,因为它造成的后果是机器直接起不来,而不是某个功能不可用。流程是这样的:你在/etc/initramfs-tools/modules里加了一个模块,觉得配置改完了,然后过几天内核升级,包管理器自动重建 initramfs,这次重建会把你的配置带进去;但如果你的配置本身写错了模块名,或者模块依赖没满足,重建时可能只打印一个 warning 就过去了,生成的 initramfs 在启动时找不到那个模块,于是卡在挂载根文件系统那一步。

更隐蔽的一种情况是,你手动改了配置但没重建 initramfs,然后因为别的原因手动执行了dracut -f或update-initramfs -u,把错误配置一起烧进去了,重启之后才发现起不来。所以在生产机器上改 initramfs 相关配置,我固定会做三件事:改之前备份当前能启动的 initramfs 文件,改之后在测试机或者同一批的某台机器上先验证一遍,然后用lsinitramfs确认模块真的在归档里。

恢复手段也要提前准备好:手边常备一个 U 盘启动盘,能进救援模式,挂载根分区,把 initramfs 换回备份文件,或者临时在引导菜单里加rd.break/break=premount打断启动流程进去检查。这些操作平时用不上,真出事的时候能救命。

6. modprobe.d 里的参数、黑名单与顺序控制

6.1 options 传参与 softdep 的用法

/etc/modprobe.d/目录下可以放任意.conf文件,modprobe每次执行都会读取它们。常用指令有四种:alias定义别名映射,options给模块传参数,blacklist屏蔽模块,install/remove用自定义命令替换默认的加载和卸载动作。

options最常用,格式是options <模块名> <参数名>=<值> [...]。比如给网卡驱动指定多队列参数:

# /etc/modprobe.d/net-tuning.conf options ixgbe RSS=8,8 options nf_conntrack hashsize=65536

模块参数同样可以通过内核命令行传给 built-in 或者早期加载的模块,格式是<模块名>.<参数名>=<值>,这一点在调试时很有用,因为内核命令行优先于modprobe.d。

softdep用来声明模块之间的软依赖,让modprobe在加载某个模块前先加载指定的前置模块,可以带pre:和post:两种前缀:

softdep mydrv pre: crc32c_generic post: mydrv_helper

这跟硬依赖的区别在于,软依赖不会写进modules.dep,只在按名字加载时生效,适合那些依赖关系不是编译期决定的场景。用它的代价是隐式,别人看代码找不到这条关系,所以我一般只在硬依赖解决不了的时候才用。

6.2 blacklist 到底能不能"彻底禁用"

blacklist指令的效果被很多人高估了。它的真实作用是:在 alias 解析阶段屏蔽这个模块,阻止硬件自动匹配把它拉起来。也就是说,它挡的是 udev 那条自动加载路径,不挡显式的modprobe <模块名>。

# 只是阻止自动加载 blacklist pcspkr # 这才是真正的拒绝加载,任何路径都挡 install pcspkr /bin/false

所以如果你发现有些系统上明明写了 blacklist,模块还是出现在lsmod里,不用惊讶——很可能是另一个模块通过硬依赖把它拉起来了,或者某个脚本显式加载了它。真正要禁掉,用install <模块> /bin/true(返回成功但不做事)或者/bin/false(返回失败),再配合blacklist双管齐下。反过来,如果某个模块被发行版默认 blacklist 了,你想恢复,可以在/etc/modprobe.d/里加一个优先级更高的文件用alias重新映射回来,或者直接显式加载。

还有一个细节:/etc/modprobe.d/的读取顺序是按文件名字典序,后读的覆盖先读的,文件名用两位数数字前缀是常见做法,比如10-blacklist.conf、50-custom.conf、99-override.conf,这样意图一目了然。

6.3 模块签名与安全启动导致的静默失败

开了 Secure Boot 的机器上,内核只允许加载带有效签名的模块。发行版自带的模块有签名,第三方编译的模块(包括 DKMS 编译出来的)默认没有,加载时会失败,日志里通常能看到类似 "Loading of unsigned module is rejected" 或者 "Required key not available" 的字样。

# 查看模块签名信息 modinfo mydrv | grep -E 'sig_id|signer|sig_key' # 查看内核日志里的拒绝信息 dmesg | grep -i -E 'reject|signature|key'

这类失败最麻烦的地方是它在modprobe的返回码层面可能被处理得不够直白,尤其是走 udev 自动加载的时候,失败信息只在dmesg里一闪而过,你在journalctl -u systemd-modules-load里可能什么都看不到。排查时的第一动作永远是dmesg | tail -50,另外journalctl -k -b也能看到内核环形缓冲区的完整内容。

处理办法有几个方向:给模块做签名并把公钥导入内核的信任链(需要提前在 Secure Boot 的 key 管理里登记,机器重启后进 firmware 界面操作);关闭 Secure Boot;或者把驱动改成 built-in 编进内核。生产环境里我一般选择第一条,因为关 Secure Boot 涉及安全策略,改内核配置又会影响升级流程。

7. 自研驱动做成模块还是编进内核:一次项目里的取舍与验证清单

7.1 built-in 与 module 的初始化时机差异

编进内核的驱动在do_initcalls阶段执行,按core_initcall、postcore_initcall、arch_initcall、subsys_initcall、device_initcall这几个等级从高到低跑。模块的 init 函数则统一在device_initcall之后被调用,也就是说模块的初始化整体晚于 built-in。

这个差异带来的实际影响是:如果 A 驱动是 built-in,B 驱动是模块,且 B 依赖 A 提供的符号或服务,那 B 在加载时 A 一定已经就绪,顺序天然正确。反过来如果 A 是模块、B 是 built-in,B 在初始化时可能找不到 A,需要靠-EPROBE_DEFER来延迟重试。所以对于启动早期就必须可用、且被其他组件依赖的驱动,built-in 是更省心的选择。

代价是 built-in 不可卸载、增大内核镜像、参数只能通过内核命令行传、调试时改一行要重编整个内核(或者说至少重编并重启)。在快速迭代阶段,模块化的开发体验好太多,所以常见做法是开发期用模块,产品化之后再决定要不要改 built-in。

7.2 上线前我固定要跑的验证清单

自研驱动从"手动能加载"走到"开机稳定加载",中间还有一段路。我把每次交付前固定要跑的检查列成清单,按顺序执行基本不会漏:

  1. modinfo mydrv确认filename存在、depends符合预期、alias里包含你声明的设备匹配串。
  2. 把.ko放到/lib/modules/$(uname -r)/extra/或者更规范的子目录,执行depmod -a,然后grep mydrv /lib/modules/$(uname -r)/modules.dep确认索引里有它。
  3. modprobe -n -v mydrv干跑,确认加载路径上没有黑名单或 install 规则拦截。
  4. modprobe mydrv真实加载,lsmod确认在内核里,ls /sys/module/mydrv/parameters/确认参数目录存在。
  5. dmesg | tail -30看 probe 结果,重点确认没有-ENODEV、-EIO、-EPROBE_DEFER这类返回。
  6. 把模块名写进/etc/modules-load.d/,systemctl restart systemd-modules-load.service,journalctl -b -u systemd-modules-load.service确认没有报错。
  7. 如果需要早期加载,写进 initramfs 配置,重建后lsinitramfs确认归档里有这个.ko。
  8. 真正的重启验证,至少两次:一次普通重启确认冷启动行为,一次systemctl soft-reboot或者带kexec的重启确认热路径行为一致。

第 8 步我见过太多人跳过,结果在测试机上一切都好,上生产之后因为 BIOS 设置不同、设备枚举顺序不同,模块加载失败。冷启动和热重启在硬件枚举上是有差别的,尤其是那些依赖 PCI 枚举顺序、依赖固件加载的驱动,两次重启都跑一遍才算稳。

最后分享一个我自己用了很久的小习惯:在/etc/modules-load.d/的每个文件头写清楚"谁加的、什么时候加的、解决什么问题",并且在驱动的加载验证脚本里把dmesg的关键行抓出来存到/var/log/下的独立文件。模块加载失败最讨厌的地方是它经常静默通过,有日志可查,后面接手的人能省几个小时。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 8:03:01

解决备份窗口与业务干扰:KingbaseES并行、限速、永久增量组合方案

1. 备份方案的整体设计&#xff1a;并行、限速、永久增量到底怎么组合 做DBA这行&#xff0c;最怕的不是数据损坏&#xff0c;而是备份策略本身就有问题。KingbaseES作为国产数据库里用得比较多的一款&#xff0c;很多运维团队一开始用的都是最简单的那种全量备份——每天晚上跑…

作者头像 李华
网站建设 2026/9/30 8:02:58

基于MATLAB/Simulink的V2G车联网微电网24小时仿真模型详解

电动汽车接入电网到底能给微电网带来什么&#xff1f;这个问题光靠算理论公式&#xff0c;很容易把边界条件理想化&#xff0c;算完心里还是没底。所以我习惯直接搭一套基于MATLAB/Simulink的V2G车联网仿真模型&#xff0c;把一天24小时的微电网运行情况完整跑一遍。负载波动、…

作者头像 李华
网站建设 2026/9/30 8:02:09

开发者临时文件自动化清理指南:安全释放磁盘空间

干我们这行的&#xff0c;电脑上最不缺的就是临时文件。项目跑完&#xff0c;日志堆了一地&#xff1b;编译一次&#xff0c;中间产物比源码还大&#xff1b;IDE开两天&#xff0c;索引缓存直接把磁盘塞满。平时懒得管&#xff0c;等C盘飘红、磁盘空间告急&#xff0c;才知道“…

作者头像 李华
网站建设 2026/9/30 8:01:18

外骨骼运动控制:从步态意图识别到人机协同伺服落地

1. 外骨骼运动控制到底在控制什么 聊外骨骼运动控制之前&#xff0c;先把一个常见误解掰开&#xff1a;很多人默认外骨骼就是"一个会自己动的架子&#xff0c;穿上它腿就被带着走"。真做过这套东西的人都知道&#xff0c;最难的部分从来不是让它动&#xff0c;而是让…

作者头像 李华
网站建设 2026/9/30 8:01:11

RabbitMQ与Presto协同:构建高可靠异步查询任务队列

1. 协同思路&#xff1a;查询链路里的“快递员”和“计算引擎” 1.1 RabbitMQ 在查询链路中的真实角色 很多人一听到 RabbitMQ&#xff0c;第一反应就是“消息队列嘛&#xff0c;用来解耦和削峰”。这话没错&#xff0c;但落在大数据查询场景里&#xff0c;它的作用比“解耦”…

作者头像 李华
网站建设 2026/9/30 8:00:58

嵌入式软件工程师C/C++面试:从底层原理到工程实践的全栈考点解析

嵌入式软件和C/C面经这个话题&#xff0c;每年到了春招秋招、跳槽旺季都会被翻出来炒一遍。但说实话&#xff0c;市面上的面经大多停留在“背题”层面&#xff0c;背了一堆八股&#xff0c;真到面试官追问两句就露馅了。我自己带过不少新人&#xff0c;也当过面试官&#xff0c…

作者头像 李华