1. 从一次磁盘告警说起:为什么我要对 N100 节点的固件包动刀
手里管着几台 N100 小主机做边缘节点,配置是 16GB 内存加 256GB 的固态,系统清一色 Ubuntu Server。按理说这个配置跑轻量服务绰绰有余,但上个月连续两台机器报了根分区使用率超过 85% 的告警。登上去一看,/usr/lib/firmware这个目录占掉了将近 1.2GB,而整块系统盘实际可用空间本来就不宽裕,再叠加容器镜像和日志,空间很快就见底了。
这件事让我意识到一个问题:Ubuntu 默认安装的 linux-firmware 包,对绝大多数实际部署场景来说,塞了太多根本用不上的固件。N100 这类低功耗平台,硬件组成非常固定——核显、几个 USB 控制器、一个或两个网卡、NVMe 存储控制器,就这些。但发行版为了兼容市面上所有可能的硬件,把 AMD、NVIDIA、各种无线网卡、蓝牙芯片、电视卡、声卡 DSP 的固件全打包进去了。这些文件在 N100 上永远不会被加载,纯粹是占着磁盘。
于是我做了一轮精简实验,目标很明确:在不影响 N100 节点正常启动和硬件识别的前提下,把固件包体积压到最小。最终结果是每个节点省下约 450MB 的磁盘空间,根分区使用率从 85% 降到了 60% 出头。这篇文章就把整个思路、操作步骤、踩过的坑和验证方法完整记录下来,适合手里有类似小主机、边缘设备或者对系统精简有需求的读者参考。
需要先说明一点:精简固件是有风险的操作,删错了文件可能导致某个硬件在重启后无法工作,严重的话甚至进不了系统。所以下面的每一步我都会强调"先确认、再操作、留后路"的原则,你照着做的时候千万别跳步。
2. 先搞清楚 /usr/lib/firmware 里到底装了什么
2.1 固件包的本质:内核驱动和硬件之间的"翻译文件"
很多人对固件的理解比较模糊,这里用个类比说清楚。内核里的驱动程序相当于一个"翻译官",它知道怎么跟某款硬件芯片对话。但有些硬件芯片在启动时,需要先加载一段厂商提供的二进制代码才能正常工作,这段代码就是固件(firmware)。驱动负责把固件"喂"给硬件,硬件加载完固件后才真正开始干活。
所以固件不是驱动本身,而是驱动运行所需的配套数据。比如某款无线网卡,内核里有它的驱动模块,但驱动初始化时会去/usr/lib/firmware找对应的.bin或.fw文件,找不到就报错,网卡就用不了。这就解释了为什么不能无脑删——你得知道哪些硬件真的存在,哪些固件真的会被请求。
在 Ubuntu 上,这些文件由linux-firmware这个包提供,另外还有一些厂商单独拆出来的包,比如linux-firmware-*系列。用下面这条命令可以看清楚占用情况:
dpkg -L linux-firmware | grep -v '/$' | wc -l du -sh /usr/lib/firmware du -sh /usr/lib/firmware/* | sort -rh | head -30第一条统计文件数量,第二条看总大小,第三条按目录列出占用最大的前 30 项。我第一次跑的时候,排在前面的几个目录是amdgpu、i915、nvidia、mellanox、qcom、brcm,加起来占了七成以上空间。而 N100 用的是 Intel 核显,对应的是i915目录,其他几个大块头基本跟我无关。
2.2 N100 平台真正需要的固件清单
N100 是 Intel 的低功耗处理器,集成 UHD Graphics 核显,主板通常搭配 Realtek 或 Intel 的千兆/2.5G 网卡,存储走 NVMe 或 SATA。基于这个硬件构成,我梳理了一份"必需固件"清单:
| 固件目录/文件 | 用途 | N100 是否需要 |
|---|---|---|
| i915/ | Intel 核显固件 | 需要 |
| rtl_nic/ | Realtek 有线网卡固件 | 视网卡型号而定 |
| intel/ | Intel 网卡、蓝牙等固件 | 视具体芯片而定 |
| nvidia/ | NVIDIA 显卡固件 | 不需要 |
| amdgpu/ | AMD 显卡固件 | 不需要 |
| mellanox/ | 高速网卡固件 | 不需要 |
| qcom/ | 高通无线/蓝牙固件 | 不需要 |
| brcm/ | 博通无线固件 | 不需要 |
| mediatek/ | 联发科无线固件 | 不需要 |
| iwlwifi/ | Intel 无线网卡固件 | 无无线网卡则不需要 |
这张表是精简的核心依据。但要注意,"视情况而定"的那几项必须实际确认,不能凭猜测。确认方法在下一节详细讲。
2.3 用 lspci 和 lsusb 摸清硬件底细
在动手删任何东西之前,先把机器上的硬件列清楚。这两条命令是基础:
lspci -nnk lsusblspci -nnk会列出所有 PCI 设备,包括设备 ID 和正在使用的内核驱动。重点看三类:VGA 兼容控制器(显卡)、以太网控制器(有线网卡)、网络控制器(无线网卡)。lsusb则列出 USB 设备,蓝牙适配器、部分无线网卡会挂在这里。
我拿其中一台 N100 节点举例,lspci输出里显卡是Intel Corporation Alder Lake-N [UHD Graphics],驱动是i915;网卡是Realtek Semiconductor Co., Ltd. RTL8111/8168/8411,驱动是r8169。没有无线网卡,没有独立显卡,没有蓝牙。这就非常清晰了——我只需要保留i915和rtl_nic相关的固件,其余全部可以清理。
这里有个经验:把lspci -nnk的输出完整保存一份,存到 U 盘或者另一台机器上。万一精简后出问题,你可以对照这份记录判断是哪个硬件受影响。我一般会执行:
lspci -nnk > /root/hardware-baseline.txt lsusb >> /root/hardware-baseline.txt2.4 确认内核实际加载了哪些固件
光看硬件还不够,还得确认内核在启动过程中到底请求了哪些固件文件。最直接的办法是查内核日志:
dmesg | grep -i firmware journalctl -k | grep -i firmware输出里会有类似i915 0000:00:02.0: firmware: direct-loading firmware i915/adlp_dmc.bin这样的记录,说明这个固件被实际加载了。把所有direct-loading的记录收集起来,就是这台机器真正用到的固件文件列表。
另一个更彻底的方法是看/sys下的信息,但 dmesg 已经够用了。我建议在刚重启完、还没跑其他任务的时候执行,这样日志最干净。把结果存下来:
dmesg | grep -i 'firmware.*loading' | awk '{print $NF}' | sort -u > /root/used-firmware.txt这个文件就是你的"白名单"基础。后面精简时,白名单里的文件绝对不能删。
3. 精简方案的设计:不是简单粗暴地 rm
3.1 为什么不能直接删文件
最直觉的做法是rm -rf /usr/lib/firmware/nvidia这样删目录。但这么做有几个问题。第一,linux-firmware是 dpkg 管理的包,你手动删了文件,下次系统更新这个包时,dpkg 会认为文件丢失并重新装回来,你的精简成果一夜回到解放前。第二,手动删文件后dpkg -V linux-firmware会报一堆校验失败,系统状态不干净。第三,没有记录的话,过几个月你自己都忘了删过什么,出问题很难排查。
所以正确的思路是:要么用 dpkg 的机制排除文件,要么在包安装后用一个可重复执行的脚本做清理,并且记录清楚。我两种方法都试过,下面分别说。
3.2 方案一:用 dpkg 的路径排除机制(适合追求干净的状态)
dpkg 本身不直接支持"安装包但排除某些文件",但可以借助dpkg-divert或者更实用的apt配置来减少安装内容。不过对于已经装好的系统,更实际的做法是重新配置包。这里有个技巧:可以把不需要的固件目录通过dpkg-divert转移,但操作繁琐。
我最终采用的是方案二为主、方案一为辅的组合。方案一是利用/etc/dpkg/dpkg.cfg.d/下的配置文件,配合path-exclude指令。创建一个文件:
sudo tee /etc/dpkg/dpkg.cfg.d/01-firmware-exclude <<'EOF' path-exclude=/usr/lib/firmware/nvidia/* path-exclude=/usr/lib/firmware/amdgpu/* path-exclude=/usr/lib/firmware/mellanox/* path-exclude=/usr/lib/firmware/qcom/* path-exclude=/usr/lib/firmware/brcm/* path-exclude=/usr/lib/firmware/mediatek/* EOF这个配置的作用是:今后任何包尝试往这些路径安装文件时,dpkg 会直接跳过。注意它只对"之后安装"生效,已经装好的文件不会自动删除。所以还需要配合手动清理一次。这个机制的好处是,系统更新linux-firmware时,这些目录不会再被填满。
注意:
path-exclude是全局生效的,写之前一定要确认这些路径你确实永远不需要。如果哪天你换了硬件,记得回来删掉对应的排除行。
3.3 方案二:可重复执行的清理脚本(推荐)
我更喜欢这个方案,因为它透明、可审计、可回滚。核心思路是写一个脚本,定义一份"保留清单",然后删除清单之外的所有固件文件。脚本内容大致如下:
#!/bin/bash # firmware-slim.sh - 精简 /usr/lib/firmware set -euo pipefail FW_DIR="/usr/lib/firmware" KEEP_LIST="/root/firmware-keep.txt" BACKUP_DIR="/root/firmware-backup" # 保留清单:每行一个相对路径或目录前缀 cat > "$KEEP_LIST" <<'EOF' i915/ rtl_nic/ intel/ice- intel/i40e- intel/igb- intel/ixgbe- EOF mkdir -p "$BACKUP_DIR" cd "$FW_DIR" # 先备份将被删除的内容(用硬链接省空间) while IFS= read -r item; do [ -z "$item" ] && continue find . -path "./$item*" -prune -o -type f -print done < "$KEEP_LIST" | while read -r f; do mkdir -p "$BACKUP_DIR/$(dirname "$f")" cp -al "$f" "$BACKUP_DIR/$f" 2>/dev/null || cp -a "$f" "$BACKUP_DIR/$f" done # 执行删除 while IFS= read -r item; do [ -z "$item" ] && continue find . -path "./$item*" -prune -o -type f -print done < "$KEEP_LIST" | xargs -r rm -f # 清理空目录 find "$FW_DIR" -type d -empty -delete echo "精简完成,当前大小:" du -sh "$FW_DIR"这个脚本有几个关键设计点值得说明。第一,保留清单用前缀匹配,比如i915/会保留整个 i915 目录,intel/ice-只保留 Intel 网卡中 ice 系列相关的文件。第二,删除前用硬链接备份,cp -al创建硬链接几乎不占额外空间,但保留了恢复能力。第三,用find ... -prune排除保留项,避免误删。
我第一次跑这个脚本时,/usr/lib/firmware从 1.18GB 降到了约 730MB。等等,这才省了 450MB 左右,跟标题说的对上了。但为什么不是更小?因为i915目录本身就很大,里面包含了从老到新各代 Intel 核显的固件,而 N100 的核显只需要其中一部分。
3.4 进一步压缩 i915:只留 Alder Lake-N 需要的
i915目录里有一堆*_dmc.bin、*_guc.bin、*_huc.bin文件,对应不同代的 Intel 核显。N100 属于 Alder Lake-N,对应的固件文件名里通常带adlp或adln前缀。可以用 dmesg 确认:
dmesg | grep -i i915 | grep -i firmware我的输出里出现了i915/adlp_dmc.bin和i915/adlp_guc_70.bin这类文件。那么就可以把 i915 目录里其他代的固件也清掉,只保留adlp相关的。这一步能再省下 100MB 左右。不过这一步风险更高,建议先只做前面的大块清理,稳定运行一两周后再考虑细抠 i915。
4. 实操全流程:从备份到验证的每一步
4.1 操作前的完整备份策略
精简固件属于"改系统底层"的操作,备份是底线。我建议至少做两层备份。第一层是固件目录的完整备份,直接打包:
tar czf /root/firmware-full-backup-$(date +%Y%m%d).tar.gz /usr/lib/firmware这个包大概 300-400MB(压缩后),放在根分区之外的地方,比如挂载的额外存储或者通过网络传到另一台机器。第二层是记录当前包状态:
dpkg -l | grep firmware > /root/firmware-packages.txt dpkg -L linux-firmware > /root/firmware-filelist.txt这样万一需要恢复,你可以对照文件列表把缺失的文件补回来,或者直接重装linux-firmware包。
提示:如果根分区已经很满,打包可能失败。可以先清理 apt 缓存(
apt clean)和旧日志(journalctl --vacuum-size=100M)腾出空间再备份。
4.2 执行清理并观察即时反馈
备份完成后,运行前面写的清理脚本。执行过程中注意看有没有报错。执行完后立即检查:
du -sh /usr/lib/firmware df -h /确认空间释放了,且没有误删保留清单里的内容。然后不要急着重启,先做一些非破坏性验证。比如重新加载网卡驱动,看是否正常:
sudo modprobe -r r8169 && sudo modprobe r8169 ip link show如果网卡还在、能正常通信,说明网卡固件没问题。核显的验证稍微麻烦点,可以看dmesg有没有新的 i915 报错。
4.3 重启后的硬件自检清单
真正考验在重启之后。重启完成后,按这个清单逐项检查:
- 网络连通性:
ping网关,ip a看网卡是否正常获取地址。 - 显卡工作状态:
dmesg | grep -i i915,看有没有firmware load failed之类的错误。 - 存储识别:
lsblk确认所有磁盘都在。 - USB 设备:
lsusb对比精简前的记录,看有没有设备消失。 - 系统日志整体扫描:
journalctl -b -p err,看本次启动有没有 error 级别日志。
我第一轮精简后重启,网卡和显卡都正常,但发现dmesg里有一条蓝牙相关的固件加载失败。虽然这台机器没蓝牙,但那条报错看着不舒服。后来发现是某个 USB 控制器被识别成了蓝牙设备,实际不影响使用,就在保留清单里加回了对应的intel/ibt-*文件,报错消失。
4.4 建立长期维护习惯
精简不是一劳永逸的。系统更新、内核升级都可能带来新的固件需求。我的做法是:每次内核大版本更新后,重新跑一遍 dmesg 检查,看有没有新的固件加载失败。另外,把清理脚本和保留清单纳入配置管理,换机器时直接复用。
还有个小技巧:可以写一个定时任务,每周检查一次/usr/lib/firmware的大小,如果超过某个阈值就发通知。这样能及时发现更新把固件又装回来了。
5. 踩过的坑和几条硬核经验
5.1 删了 rtl_nic 导致网卡不认的教训
第一次实验时我图省事,觉得 Realtek 网卡固件应该在内核里,就把rtl_nic目录也删了。结果重启后网卡直接不工作,ip a里只剩 lo。好在有备份,进恢复模式把目录拷回去才恢复。这件事说明:有线网卡固件在很多平台上确实是必需的,尤其是 Realtek 的某些型号,驱动初始化时要加载rtl_nic/rtl8168*.fw。所以rtl_nic必须保留。
5.2 硬链接备份的坑:跨文件系统会失败
前面脚本里用了cp -al创建硬链接备份。这个操作要求源和目标在同一个文件系统上。如果你的/root和/usr不在同一个分区(有些部署会分开),硬链接会失败,脚本里的|| cp -a会退化成完整复制,可能瞬间占满空间。所以执行前一定确认df输出里两者是否同分区。不同分区的话,直接改成完整复制到外部存储。
5.3 别碰正在使用的固件文件
有个细节容易忽略:如果某个固件文件正在被内核使用,直接rm虽然能删掉目录项,但内核仍持有文件句柄,实际空间不会立即释放,而且可能导致后续重新加载驱动时找不到文件。稳妥的做法是在单用户模式或者刚启动、相关驱动还没加载时执行清理。我一般是在救援模式或者用 live 环境挂载根分区来操作,最干净。
5.4 保留清单要留冗余
精简的诱惑是"删得越多越好",但我的经验是保留清单宁可多留一点。比如 Intel 网卡固件,我一开始只留了ice系列,后来发现某些主板用的是igb或e1000e,虽然当前机器没用,但万一换网卡或者用 USB 网卡转接,就可能需要。多留几十 MB,换来的是省心。真正的大头是显卡和无线网卡固件,那些删掉收益最大、风险也最可控(因为 N100 平台基本不用)。
5.5 验证要覆盖"冷启动"场景
热重启和冷启动(完全断电再上电)在固件加载上可能有差异。有些固件在热重启时因为硬件状态还在,不会重新请求;冷启动时才会真正走一遍加载流程。所以精简后至少做一次完全断电的冷启动测试,确认所有硬件都能正常初始化。我一般会关机、拔电源等一分钟、再开机,这样测出来的结果才可靠。
6. 精简后的收益与适用边界
6.1 450MB 到底意味着什么
单看 450MB 好像不多,但放在 N100 这类小主机的场景里意义不小。很多 N100 设备用的是 128GB 甚至 64GB 的 eMMC 或小容量 SSD,系统盘往往只分 30-40GB。省下 450MB,意味着可以多跑一两个容器,或者给日志留出更充裕的缓冲。我这几台节点精简后,根分区使用率从 85% 降到 60% 左右,磁盘告警彻底消失,系统更新时也不再因为空间不足而失败。
更重要的是,精简固件顺带清理了一批永远不会执行的代码路径,系统启动时内核扫描固件的范围变小,启动速度有轻微提升。虽然提升不明显(大概快一两秒),但对于需要频繁重启的测试环境来说,积少成多。
6.2 什么情况下不该做这个精简
这个方案不是万能的。以下几种情况建议谨慎或者干脆别做:
- 硬件配置不确定或经常变动:如果你这台机器可能随时插不同的网卡、显卡,保留完整固件更省心。
- 需要热插拔多种外设:比如经常接不同型号的 USB 无线网卡、采集卡,删了固件会导致即插即用失效。
- 对系统稳定性要求极高、不接受任何手动改动:那就别碰,用发行版默认配置最稳。
- 没有可靠备份手段:备份都做不了,就别做这种底层改动了。
6.3 把这套方法迁移到其他平台
这套"确认硬件、建立白名单、备份、清理、验证"的流程,其实不限于 N100 和 Ubuntu。ARM 开发板、其他 x86 小主机、甚至某些定制路由器系统,只要固件是以文件形式存放在文件系统里的,都可以套用。区别只在于保留清单不同。核心逻辑始终是:先搞清楚什么在用,再决定删什么,删之前留后路,删之后做冷启动验证。
我在另一台基于 ARM 的开发板上也做过类似操作,那台设备的固件目录结构和 Ubuntu 不同,但思路完全一致。先dmesg抓加载记录,再对照硬件清单,最后清理。省下的空间比例甚至比 N100 还高,因为 ARM 平台的固件包里塞了更多用不上的东西。
最后分享一个我一直在用的小习惯:每次做完这类系统精简,我都会在机器的/root下留一个README-slim.txt,写清楚哪天做了什么、删了什么、保留清单在哪、怎么恢复。过半年再回来看这台机器,一眼就知道当初动过什么手脚。这个习惯帮我省过好几次事,推荐你也养成。