1. 问题现象与初步判断
1.1 开机卡在“Loading driver”到底发生了什么
我先复述一下典型场景:某台服务器或者台式机,换了新硬盘控制器、新网卡,或者安装系统时选错了驱动源,重启之后卡在启动阶段的“Loading driver…”提示,或者干脆直接进入 dracut 提示符(dracut:/#),系统拒绝继续引导。我第一次遇到这种情况时,第一反应是“引导坏了”,后来才发现,真正的原因是 initramfs 里没有包含对应硬件的驱动模块。
把整个启动流程拆开看,引导阶段系统先加载的是 initramfs(初始内存文件系统),这个文件系统承担的任务非常纯粹:把根文件系统挂载起来之前,先把必要的内核模块、驱动、挂载工具都准备好。如果 initramfs 里缺了磁盘控制器驱动、网卡驱动或者文件系统模块,系统就找不到根分区,自然无法继续。
很多人在这个问题上容易绕弯路,因为它的表象和真正原因经常不对应。比如我之前遇到过一台机器,启动时报错信息看起来是文件系统损坏,但实际排查后确认根分区是完好的,问题出在 initramfs 打包时漏掉了特定驱动。这里有个很重要的判断技巧:如果系统能进入 dracut 提供的 rescue shell(就是那个dracut:/#提示符),说明内核本身没问题,缺少的是驱动或挂载所需的模块。反过来,如果连内核都起不来,那就不是 dracut 能解决的范畴了。
1.2 为什么 initramfs 会缺驱动
这个问题往往不是“碰巧没装上”,而是有明确的触发路径,我总结下来大致有这几类:
- 系统从一个环境制作镜像,然后迁移到另一个硬件平台。比如在虚拟机里做好的系统镜像,直接拿去物理机启动,硬件差异导致 initramfs 里没有目标机器的磁盘控制器驱动。
- 安装系统时选择的是最小化安装或定制化安装,某些非默认驱动没有强制写入 initramfs。
- 更新内核后没有同步重新生成 initramfs。内核版本更新后,原有的 initramfs 往往需要重新生成,但很多人只更新了内核包,忽略了这一步。
- 自编译内核或自编译驱动模块,模块已安装到
/lib/modules/下,但 initramfs 打包时没有被拾取。
从 Linux 系统的设计角度看,initramfs 是根文件系统和内核之间的“过渡层”,它不需要包含所有驱动,只需要包含从引导到根分区挂载这一阶段所需的模块。这个“够用就好”的设计在大多数时候没问题,但一旦硬件环境发生变化,或者根文件系统所在设备比较特殊,就很容易出现“驱动不在 initramfs 里”的尴尬局面。
2. dracut 的工作原理与排查思路
2.1 dracut 到底是干什么的
dracut 在我看来更像一个“定制化打包工具”。它读取系统当前的配置、内核版本、模块依赖关系,然后按照一定的规则把内核模块、udev 规则、挂载工具、文件系统驱动组装进 initramfs 镜像文件。
打个比方,initramfs 就像是一个“紧急救援包”,里面装了从开机到挂载根分区这一路上可能用到的东西。dracut 就是这个救援包的打包员,它根据你指定的规则决定往包里塞什么。默认情况下,dracut 会扫描/lib/modules/$(uname -r)/下所有已安装的内核模块,但“扫描”不等于“全部打包”,它有自己的取舍逻辑。
这个取舍逻辑非常关键。dracut 默认会按照主机当前的硬件配置来生成 initramfs,也就是说,它倾向于打包“当前这台机器启动所需”的模块,而不是“所有可能用到的模块”。这就是为什么把系统盘拆下来插到另一台机器上,或者换了主板之后,经常会遇到找不到根设备的原因。
2.2 产生 dracut rescue shell 的核心触发机制
当系统进入 initramfs 后,会依次执行一系列任务:加载模块、创建设备节点、等待设备就绪、尝试挂载根分区。任何一个环节失败导致根分区挂载不了,dracut 就会进入交互式 rescue shell,并给出一个错误信息。
最常见的错误提示包括:
ERROR: Failed to mount the real root device:挂载根分区失败。ERROR: Unable to detect root device:找不到根设备。Waiting for 10 seconds...这类等待设备超时的提示。
出现这些提示时,先不要急着重新制作 initramfs。在 rescue shell 里可以先执行一些诊断命令,看看当前系统到底能不能识别到硬盘。比如在dracut:/#提示符下执行ls /dev/sd*,如果能看到类似/dev/sda、/dev/sdb这样的设备节点,说明内核已经发现了磁盘,问题可能出在设备路径不对或者根分区参数不正确。如果ls /dev/sd*什么都看不到,而你的磁盘是 NVMe 的,那就需要检查是不是缺少 NVMe 驱动。
我曾经遇到一个很有意思的情况:ls /dev/sd*能看到设备,但根分区始终挂载失败。后来在 rescue shell 里执行blkid才发现,根分区的文件系统是 XFS,而 initramfs 里没有 XFS 模块。这种“驱动在、文件系统驱动不在”的情况很容易被忽视。
3. 一步步解决“驱动无法加载”问题
3.1 常规修复流程:重新生成 initramfs
这是最直接的修复方式。在系统还能正常启动的情况下,或者通过 live 环境挂载根分区进入 chroot 后,重新生成 initramfs 就能解决问题。具体步骤如下:
# 查看当前内核版本 uname -r # 重新生成 initramfs dracut -f /boot/initramfs-$(uname -r).img $(uname -r)这里解释每个参数的含义:
-f:强制覆盖已有文件。如果不加这个参数,当目标文件存在时,dracut 会拒绝覆盖。/boot/initramfs-$(uname -r).img:指定输出文件名。$(uname -r):指定要生成 initramfs 对应的内核版本。
如果有多内核版本并存,最好直接查看/boot目录,确认每个内核版本对应的 initramfs 文件名格式。有些发行版用的是/boot/initramfs-xxx.img,有些用的是/boot/initrd.img-xxx,操作前先确认一下,避免把文件弄乱。
对于 RHEL/CentOS 系发行版,可以直接用:
dracut -f不接参数时,dracut 会默认为当前运行的内核版本重新生成 initramfs 并覆盖原文件,但为了行为明确,我习惯还是把文件名和内核版本写全。
3.2 场景一:系统还能启动,但升级内核后驱动加载异常
这种情况最常见于内核小版本升级后。系统能正常启动,但某个硬件功能异常,比如网卡不工作、磁盘性能异常,此时可以按上面命令重新生成 initramfs,然后重启验证。
如果重新生成之后问题依旧,就要考虑是不是模块本身没装全。可以执行:
# 查看内核模块目录 ls /lib/modules/$(uname -r)/ # 查看某个模块是否存在 find /lib/modules/$(uname -r) -name "*.ko*" | grep <模块名>如果模块文件不存在,说明是内核模块没安装,不是 initramfs 问题。这时候需要安装对应的内核模块包,或者确认是不是内核版本和设备驱动不兼容。
3.3 场景二:系统无法启动,卡在 dracut 提示符
这种情况下,你需要在 rescue shell 里做紧急处理,或者用 live 环境进行修复。如果只是临时想验证一下是不是驱动问题,可以试试在 rescue shell 里手动加载模块:
# 在 dracut rescue shell 中尝试加载模块 modprobe <模块名> # 加载完之后,看看磁盘设备是否出现 ls /dev/sd*如果能通过modprobe手动加载模块并看到磁盘设备,那么下一步就是退出 shell,让引导流程继续。但这里有个技巧:直接执行exit可能不会重试挂载根分区,需要手动执行:
# 退出 rescue shell 并重试 exit如果exit后仍失败,说明 dracut 生成的 initramfs 没有包含该模块,这只能通过重新生成 initramfs 来解决。
在 rescue shell 里还有一种方法可以尝试:直接编辑引导参数中的root=参数。比如你的根分区实际是/dev/sda2,但引导参数里写的是/dev/mapper/xxx这类逻辑路径,而对应的 device mapper 模块又没加载,就会失败。可以临时修改引导参数,把root=改为实际设备路径,或者用root=UUID=xxx的方式指定根分区。
# 在 rescue shell 中查看可用设备 blkid记录下根分区对应的 UUID,然后在引导参数里设置:
root=UUID=<你的UUID>这种方法只适合临时验证,重启后参数不会保留。要彻底解决,还得回到重新生成 initramfs 的路子上来。
3.4 场景三:镜像迁移后驱动不匹配
把系统迁移到不同硬件平台上使用时,最稳妥的方式是在迁移前就准备好目标平台的驱动。但实际中很多人是直接迁移,启动失败后才开始修。这时候的修复思路是:
- 用 live 环境启动,挂载原系统的根分区。
- chroot 进入原系统。
- 确保网络可用或驱动包已准备好。
- 安装目标平台所需的驱动包。
- 重新生成 initramfs。
这套流程要注意 chroot 环境的准备。很多人挂载完根分区后直接chroot /mnt,结果发现 dracut 命令找不到,或者模块安装后 initramfs 生成报错。正确做法是把必要目录都绑定挂载进去:
# 挂载根分区 mount /dev/sdX2 /mnt # 挂载 /boot 分区(如果独立分区) mount /dev/sdX1 /mnt/boot # 绑定挂载虚拟文件系统 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys # chroot 进入系统 chroot /mnt /bin/bash绑定挂载不是仪式感,而是因为 chroot 环境里的设备节点、进程信息、内核接口都需要依赖宿主机的/dev、/proc、/sys。如果只挂载根分区不绑定这些目录,后续执行dracut时很可能会因为设备节点缺失而报错,或者扫描模块时行为异常。
在 chroot 环境下重新生成 initramfs:
# 在 chroot 环境中 dracut -f /boot/initramfs-$(uname -r).img $(uname -r)生成完毕后退出 chroot,卸载分区,重启验证。
3.5 场景四:不想重新生成整个 initramfs,只追加驱动模块
有时候你只是临时需要一个驱动进 initramfs,不想因为生成整个镜像而更改其他东西,可以使用 dracut 的--add-drivers参数:
dracut -f --add-drivers "驱动模块名" /boot/initramfs-$(uname -r).img $(uname -r)要注意--add-drivers后面的模块名不需要写.ko后缀,也不需要写完整路径,dracut 会自动在/lib/modules/$(uname -r)/下查找。
如果模块依赖其他模块,dracut 会自动分析依赖关系一并加入。这一点在实战中很省心,比如你添加网卡驱动时,它可能依赖底层总线驱动,dracut 会通过模块间的depends关系自动补齐。
4. 使用 dracut 的核心配置与机制
4.1 通过配置文件统一管理驱动
dracut 的配置目录是/etc/dracut.conf.d/,这个目录在很多发行版上默认就存在。如果你的机器经常需要处理“驱动无法加载”的问题,或者有多台硬件配置相近的机器需要统一管理,建议不要每次都敲命令行参数,而是写一个配置文件。
新建一个配置文件,比如/etc/dracut.conf.d/add-drivers.conf:
# 指定需要强制加入 initramfs 的模块 add_drivers+=" xfs nvme "这里的写法有一点讲究:add_drivers+=" xfs nvme "的+=表示在现有配置基础上追加内容,字符串前后加空格是为了避免和已有内容粘连。如果直接写add_drivers="xfs nvme",会覆盖 dracut 自带的一些默认配置,可能影响生成结果。
配置完成后,执行一次完整的生成操作即可:
dracut -fdracut 读取配置的优先级是/etc/dracut.conf.d/下的.conf文件高于/etc/dracut.conf,多个.conf文件之间按文件名排序顺序读取。所以如果你发现某个配置没生效,可以先看看目录下是否有其他配置文件起了冲突。
4.2 常用 dracut 参数与用途
| 参数 | 用途说明 | 使用建议 |
|---|---|---|
-f | 强制覆盖现有 initramfs 文件 | 每次生成都建议加上,防止文件存在导致生成失败 |
--add-drivers | 强制添加指定驱动模块 | 用于补充特定硬件驱动 |
--omit-drivers | 移除指定驱动模块 | 一般不常用,特殊场景调试用到 |
--hostonly | 仅为主机当前硬件生成最小 initramfs | 默认行为之一,迁移系统时需要谨慎 |
-v | 显示生成过程中的详细信息 | 排查问题时强烈建议加,能看到每个模块的处理过程 |
--rebuild | 触发强制重建,即使配置未变化 | 在 initramfs 损坏或配置被误改时使用 |
--hostonly这个参数值得单独强调。很多发行版安装系统的默认行为就是 hostonly 模式,生成的是“刚好够当前机器启动”的最小 initramfs。这种模式比全量模式启动更快、镜像更小,但代价是可移植性差。如果你确定这台机器之后不会变更硬件,hostonly 没问题;如果机器可能换硬件,或者要作为模板复制到多台机器,建议用非 hostonly 模式重新生成一次 initramfs。
4.3 dracut 生成 initramfs 的关键过程
很多人以为 dracut 就是把模块文件打包到一起,实际上它的工作远比“打包”复杂。一个完整的生成过程大致分这几个阶段:
- 收集模块:扫描
/lib/modules/$(uname -r)/下的模块依赖关系,生成模块树。 - 安装钩子脚本:initramfs 里有多个执行阶段(
cmdline、pre-mount、mount、pre-pivot、cleanup等),dracut 会把对应的钩子脚本安装进去。 - 生成 udev 规则:initramfs 里的设备管理依赖 udev,dracut 需要把必要的 udev 规则文件复制进去。
- 安装二进制工具:比如
mount、blkid、modprobe、lvm等,这些工具在 initramfs 阶段就需要可用。 - 打包压缩:最终生成 cpio 格式的 initramfs,并通常会用 gzip 或 xz 压缩。
如果加-v参数运行 dracut,可以看到它在每个阶段输出了大量信息。正常生成时,输出的末尾会出现类似/boot/initramfs-xxx.img is created的提示。如果生成过程中某个环节报错,输出信息里会直接指明缺失了什么,排查效率比事后瞎猜高得多。
4.4 initramfs 损坏后的紧急恢复
有一种特殊情况:initramfs 文件本身损坏或误删了。这时候系统会直接提示找不到/boot/initramfs-xxx.img,根本无法进入 dracut rescue shell。
处理方法也很直白:用安装镜像或 live 环境启动,挂载根分区和 boot 分区,chroot 后重新生成 initramfs。如果 boot 分区是独立分区,注意先挂载 boot 分区再执行生成命令,否则生成的文件会写到根分区的 /boot 目录,但引导时加载的却是独立 boot 分区里的旧文件。
在这个场景下,我曾经犯过一个低级错误:根分区和 boot 分区都是独立的,我只挂载了根分区就 chroot 进去,执行dracut -f后提示成功,重启后仍然找不到 initramfs。后来才意识到 boot 分区没有挂载,生成的文件根本没写在引导读取的位置。这种“提示成功但实际无效”的情况,在排查时要格外留意。
5. 驱动加载失败的进阶排查与解决
5.1 不仅仅是存储驱动,网卡驱动也会导致类似问题
在通过 PXE 或网络引导安装系统的场景里,网卡驱动缺失导致的启动失败并不少见。系统在 initramfs 阶段需要加载网卡驱动,才能从网络上下载根文件系统镜像或远程存储资源。如果网卡型号太新,而 initramfs 里的驱动太旧,就会卡在“找不到网络设备”这一步。
同样,可以先用 dracut 的--add-drivers把网卡驱动强制加进 initramfs,也可以把网卡固件文件一并补充进去。有些网卡的驱动模块和固件是分离的,模块加载时还要找到对应的.bin固件文件。这时候如果只加模块不加固件,驱动加载依然会失败。
判断固件缺失的方法很简单,dracut 生成过程中如果出现无法找到固件文件的警告,日志里会明确提示。也可以在系统启动后用dmesg | grep firmware查看固件加载情况。
5.2 怎么快速定位缺失的驱动模块
定位缺失模块,最直接的方法是看启动时的错误日志,但 dracut rescue shell 环境下日志不一定完整保留。实际操作中我更推荐反推法:在 rescue shell 里确认根设备是什么类型,然后反推需要哪些模块。
例如根分区在 NVMe 硬盘上,就需要nvme模块。根分区在 LVM 逻辑卷上,就需要dm-multipath、lvm2等模块。根分区使用的是 XFS 文件系统,就需要xfs模块。根分区做了 RAID,就需要对应的 RAID 驱动模块。
如果无法确认,可以先用 live 环境启动原系统,在 chroot 环境下手动执行模块加载测试:
# 测试某模块能否正常加载 modprobe <模块名> # 查看模块信息 modinfo <模块名>modinfo输出的信息里,可以看到模块依赖、授权协议、描述信息,对于确认模块是否匹配当前内核很有价值。如果模块和内核版本不兼容,modinfo会直接报错,这种问题就不是 dracut 能解决的了。
5.3 特殊文件系统模块缺失的坑
这里我要特别提一下 ZFS 这类特殊文件系统。由于 ZFS 模块往往不是随内核自带的,而是通过 DKMS 或手动编译安装,重装或者升级内核后,很容易出现安装好的 ZFS 模块在 initramfs 生成时没有被拾取的情况。
处理方案有两种:
- 在配置文件中显式添加 ZFS 模块。
- 确认 ZFS 模块的 DKMS 状态正常,并且
/lib/modules/$(uname -r)/extra/或对应目录下能看到.ko文件。
# 确认模块文件位置 find /lib/modules/$(uname -r) -name "*zfs*"如果模块文件存在,但 dracut 生成时没包含进去,可以手动指定:
dracut -f --add-drivers "zfs" /boot/initramfs-$(uname -r).img $(uname -r)生成完成后,可以用lsinitrd工具检查 initramfs 内部文件列表,确认模块是否真的打进去了。
5.4 验证 initramfs 内容:lsinitrd 的使用
lsinitrd是排查这类问题的一个利器,它会直接列出 initramfs 镜像里的模块文件。用法很简单:
# 查看 initramfs 内所有内容 lsinitrd /boot/initramfs-$(uname -r).img # 只查看内核模块 lsinitrd -k /boot/initramfs-$(uname -r).img | grep "\.ko" # 检查某个具体模块是否存在 lsinitrd /boot/initramfs-$(uname -r).img | grep <模块名>我习惯在生成 initramfs 之后、重启之前,先跑一次lsinitrd确认关键模块都在。这一步虽然多花几秒钟,但能避免反复重启测试,实际效率反而更高。
另外有一个小技巧:如果你不确定 initramfs 里缺少什么,可以先看看当前系统“需要什么”。在正常的系统环境下执行:
# 查看当前内核加载了哪些模块 lsmod对比lsinitrd里的模块列表和lsmod的输出,能快速发现差异。这个对比法在迁移机器时特别有用。
5.5 grub 引导参数中的 root 指定方式
dracut 并不是唯一需要检查的环节,引导参数里root=的指定方式也会直接影响根分区能否挂载。常见的问题是在迁移磁盘后用旧的/dev/sdX路径指定根分区,但设备的枚举顺序变了,原来的 sda 变成了 sdb,引导自然失败。
推荐的做法是在安装系统后尽快记录根分区的 UUID,并且在 grub 配置里用root=UUID=xxx的方式指定。UUID 是基于文件系统本身的唯一标识,不依赖设备枚举顺序,比/dev/sdX可靠得多。
# 查看根分区 UUID blkid /dev/sda2grub 配置中就是:
root=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx如果引导参数里的 root 用了 UUID,但 initramfs 里没有对应的文件系统驱动,也会失败。所以“引导参数指定 root 的方式”和“initramfs 里包含的驱动”是两套独立因素,排查时不要混为一谈。
6. 典型问题排查速查表与个人经验
6.1 问题排查速查表
下表是我在工作中总结的“驱动无法加载”问题快速定位方式,适合在紧急情况下逐条对照:
| 故障现象 | 可能原因 | 快速验证方法 | 处理方案 |
|---|---|---|---|
rescue shell 中ls /dev/sd*为空 | 缺少磁盘控制器驱动 | 执行modprobe ahci或对应驱动名 | 重新生成 initramfs 并强制添加驱动 |
blkid能识别分区,但挂载根分区失败 | 缺少文件系统驱动 | modprobe xfs/modprobe ext4 | 重新生成 initramfs 并添加文件系统模块 |
引导提示找不到initramfs-xxx.img | initramfs 文件缺失或损坏 | 检查/boot目录 | chroot 环境重新生成 initramfs |
| 更换硬件后卡在 Waiting for device | initramfs 按旧主机硬件生成 | 用 live 环境对比lsmod和lsinitrd | 非 hostonly 模式重新生成 initramfs |
| 网卡无法识别,网络安装失败 | 缺少网卡驱动或固件 | 检查固件加载日志 | 添加驱动模块及对应固件 |
6.2 常见但容易被忽略的问题
在处理 initramfs 相关问题时,有几个细节容易被忽略,我踩过坑之后特别在意这几件事:
检查/boot分区空间是否充足。initramfs 文件虽然不大,但生成过程需要临时空间,如果/boot空间已满,生成会在最后阶段失败,且错误信息有时并不直观。建议执行生成操作前先df -h /boot看一下。
注意内核版本一致性。dracut -f默认对应当前运行的内核版本,如果你要修复的是另一个内核版本的 initramfs,需要显式指定内核版本,否则修完之后启动的还是旧文件。这一点在系统里有多个内核版本并存时特别容易出问题。
不要忽略模块依赖。手动用--add-drivers添加模块时,dracut 会自动分析依赖,但如果模块依赖链中有固件文件缺失,dracut 只会生成警告而不会强制报错。重启后驱动加载失败时,日志里才会暴露真正的固件缺失问题。
6.3 最后的实用建议
个人经验是:在生成 initramfs 之前,先明确系统根分区所在的设备类型和文件系统类型,然后有意识地把驱动列表过一遍。不用背太多参数,记住“先确认模块是否已安装,再确认初始化镜像是否包含它”这个排查顺序,就能避免大部分问题。
另外,如果你在一个需要频繁拆盘迁移的工作环境里,强烈建议把 dracut 的生成配置固定下来,把常用驱动写进/etc/dracut.conf.d/的配置文件里,同时在换硬件平台后第一时间刷新 initramfs。这不是什么高深技巧,但能省掉大量踢机柜的体力活。