☰
Ubuntu固件包精简实战:为N100节点节省450MB存储空间
2026/10/14 2:44:23 网站建设 项目流程

1. 项目缘起与核心思路拆解

1.1 为什么盯上了固件包这块"赘肉"

手头有一批基于 N100 处理器的迷你节点,跑的是 Ubuntu 系统,用途比较单一——做轻量级容器编排和边缘数据采集。这批机器有个共同特点:板载存储是 64 GB 的 eMMC,实际可用空间也就 58 GB 出头。系统装完、Docker 拉几个镜像、再放点日志和临时数据,磁盘占用很快就逼近红线。

一开始我的做法很粗暴:加外置存储。但节点数量一多,外置存储的供电和物理空间都成了问题,而且这些节点部署在分散的机柜里,加硬盘意味着要重新规划机柜布局,成本不划算。于是我把目光转向了系统本身——能不能从根上把 Ubuntu 的体积压下来?

用du一层层扒下来,发现/usr/lib/firmware这个目录占了大头,足足 1.2 GB 左右。这个目录里塞满了各种硬件厂商的固件文件,从网卡、显卡、声卡到各种专用加速卡,应有尽有。问题是,N100 这种低功耗平台,板载设备就那么几样:一个 Intel 核显、一个 Realtek 或 Intel 的千兆网卡、一个 USB 控制器、一个 SATA 或 NVMe 控制器。剩下那些固件,比如某些专业显卡、光纤通道卡、老式调制解调器的固件,在这台机器上永远不会被加载。

这就是典型的"通用发行版困境"——Ubuntu 为了让镜像能在尽可能多的硬件上开箱即用,把能塞的固件全塞进去了。对于批量部署的专用节点来说,这些用不上的固件就是纯粹的存储浪费。我的目标很明确:在保证 N100 平台所有板载设备和常用外设正常工作的前提下,把固件包精简到最小可用集。

1.2 精简策略:不是删文件,而是重建依赖

很多人第一反应是直接rm -rf掉/usr/lib/firmware里不认识的文件。这个做法极其危险,原因有三:第一,你无法确定某个固件是不是在特定电源状态下才会被加载;第二,系统更新时linux-firmware包会把删掉的文件重新装回来;第三,直接删文件会破坏包管理器的完整性记录,后续apt操作可能报错。

我的策略是从包管理层面重建一个精简的固件包。具体思路是:

  • 先摸清 N100 平台实际需要哪些固件——通过dmesg、lspci、lsusb和ethtool等工具,把当前系统加载的固件模块全部列出来。
  • 然后对照linux-firmware包的文件清单,把不需要的固件文件从包中剔除。
  • 重新打包成一个本地 deb 包,版本号标记为custom,这样系统更新时不会自动覆盖。
  • 最后用dpkg强制安装这个精简包,替换掉原版。

这样做的好处是:包管理器记录完整,后续升级可控,而且精简逻辑可以脚本化,批量部署时一键执行。

1.3 预期收益与风险边界

按照我的估算,linux-firmware原包安装后占用约 1.2 GB,精简后目标控制在 750 MB 以内,每个节点省下约 450 MB。对于 64 GB eMMC 的节点来说,这 450 MB 意味着可以多跑一个中等规模的容器镜像,或者多存几天的采集数据。

风险边界也很清楚:精简后的固件包只保证 N100 平台板载设备和明确列出的外设可用。如果后续要加装特殊扩展卡,需要重新评估固件需求。所以我在部署脚本里留了一个"恢复原版固件"的回滚选项,万一遇到问题可以快速还原。

注意:这个方案的前提是你清楚自己的硬件清单,并且不会频繁更换外设。如果是通用开发机或者硬件配置经常变动的场景,不建议做这种激进精简。

2. 核心细节解析与实操要点

2.1 摸清 N100 平台的实际固件需求

第一步是搞清楚"哪些固件是真正被加载的"。我用的方法是在系统正常运行时,从多个维度收集信息。

内核日志法:dmesg | grep -i firmware会列出所有加载固件的记录。在 N100 节点上,典型输出包括:

[ 2.345678] i915 0000:00:02.0: firmware: direct-loading firmware i915/adlp_dmc_ver2_16.bin [ 2.456789] i915 0000:00:02.0: firmware: direct-loading firmware i915/adlp_guc_70.bin [ 3.567890] r8169 0000:01:00.0: firmware: direct-loading firmware rtl_nic/rtl8168h-2.fw [ 4.678901] Bluetooth: hci0: firmware: direct-loading firmware intel/ibt-0040-0041.sfi

从这些日志可以看出,N100 平台主要用到三类固件:Intel 核显(i915 系列)、Realtek 网卡(rtl_nic 系列)、以及可选的蓝牙模块(intel/ibt 系列)。

PCI 设备枚举法:lspci -nnk可以列出所有 PCI 设备及其使用的内核驱动。重点关注Kernel driver in use和Kernel modules两行,这能告诉你哪些驱动被绑定到了硬件上。

USB 设备枚举法:lsusb -t展示 USB 设备树,lsusb -v可以看到每个设备的详细信息。对于板载蓝牙、摄像头等 USB 设备,这一步能确认它们是否需要额外固件。

网卡固件确认:ethtool -i eth0会显示网卡驱动的固件版本信息。如果输出中有firmware-version字段,说明该网卡需要外部固件文件。

把这些信息汇总成一张表,就是你的"固件需求清单"。以我的 N100 节点为例:

设备类型驱动模块所需固件路径是否必需
Intel 核显i915i915/adlp_dmc_ver2_16.bin是
Intel 核显i915i915/adlp_guc_70.bin是
Realtek 网卡r8169rtl_nic/rtl8168h-2.fw是
Intel 蓝牙btusbintel/ibt-0040-0041.sfi视配置
Intel 无线iwlwifiiwlwifi-*.ucode视配置

2.2 定位 linux-firmware 包的文件清单

Ubuntu 的linux-firmware包安装后,文件清单可以通过dpkg -L linux-firmware获取。这个清单通常有几千行,直接看会眼花。我的做法是先按目录归类,统计每个子目录的大小:

dpkg -L linux-firmware | grep -v '/$' | xargs -I{} du -b {} 2>/dev/null | \ awk '{sum[$4]+=$1} END {for (d in sum) print sum[d], d}' | sort -rn | head -30

这条命令会输出占用空间最大的前 30 个目录。在我的系统上,结果大致如下:

目录占用空间说明
/usr/lib/firmware/mellanox约 180 MBMellanox 网卡固件,N100 用不上
/usr/lib/firmware/qcom约 120 MB高通系列固件,用不上
/usr/lib/firmware/i915约 90 MBIntel 核显固件,部分需要
/usr/lib/firmware/amdgpu约 85 MBAMD 显卡固件,用不上
/usr/lib/firmware/nvidia约 70 MBNVIDIA 显卡固件,用不上
/usr/lib/firmware/rtl_nic约 15 MBRealtek 网卡固件,部分需要
/usr/lib/firmware/rtw88约 12 MBRealtek 无线固件,视配置
/usr/lib/firmware/iwlwifi约 25 MBIntel 无线固件,视配置

从这张表就能看出,光是 Mellanox、qcom、amdgpu、nvidia 这四个目录加起来就超过 450 MB,而它们对于 N100 节点来说完全是冗余的。

2.3 构建精简固件包的三种方案对比

在动手之前,我评估了三种可行方案:

方案一:直接删除文件并锁定包版本。用dpkg --purge卸载原包,手动创建目录结构,只放入需要的固件文件。优点是简单直接,缺点是包管理器记录不完整,后续系统升级可能出问题。

方案二:重新打包 deb。从原包中提取文件,剔除不需要的部分,用dpkg-deb重新打包,版本号加自定义后缀。优点是包管理记录完整,可以正常安装卸载;缺点是需要处理依赖关系和包签名。

方案三:使用 overlay 挂载。保留原包不动,在/usr/lib/firmware上挂一个 overlay 文件系统,用白名单方式屏蔽不需要的文件。优点是原包完整,随时可回滚;缺点是每次启动都要挂载,增加复杂度。

我最终选择了方案二,理由是它在"包管理完整性"和"部署简便性"之间取得了最好的平衡。重新打包后的 deb 可以像普通包一样用dpkg -i安装,也可以放进本地仓库供批量部署使用。

2.4 实操中的关键注意事项

在动手精简之前,有几个坑我必须提前说明:

不要删除正在使用的固件。有些固件在系统启动早期就被加载了,如果你在运行中的系统上删除它们,虽然当前不会崩溃,但下次重启可能无法正常初始化硬件。所以精简操作最好在离线环境或者救援模式下进行。

保留固件目录结构。linux-firmware包中的文件路径是有意义的,内核会按照特定路径查找固件。比如i915/adlp_dmc_ver2_16.bin必须放在/usr/lib/firmware/i915/目录下,不能随意移动。

注意固件版本兼容性。同一个硬件可能有多个版本的固件文件,内核会根据硬件版本自动选择。如果你只保留了其中一个版本,而硬件恰好需要另一个版本,就会加载失败。稳妥的做法是保留同一系列的所有版本,或者至少保留当前系统正在使用的版本。

测试回滚流程。在批量部署之前,一定要在一台测试节点上完整走一遍"精简-重启-验证-回滚"的流程。回滚方案要简单可靠,比如保留原版 deb 包,出问题时一条命令恢复。

提示:精简固件包后,建议运行update-initramfs -u更新 initramfs,确保启动阶段需要的固件被正确包含。有些固件是在 initramfs 阶段加载的,如果只精简了根文件系统而没更新 initramfs,可能导致启动失败。

3. 实操过程与核心环节实现

3.1 环境准备与工具安装

我是在一台已经部署好的 N100 节点上进行操作的,系统是 Ubuntu 22.04 LTS。为了不影响生产环境,我先用systemd-nspawn创建了一个容器环境来做打包实验,确认无误后再在真实节点上执行。

需要的工具不多,都是系统自带的:

# 确认必要工具存在 which dpkg-deb fakeroot du awk grep # 如果没有 fakeroot,安装它 sudo apt install fakeroot

fakeroot的作用是让你在没有 root 权限的情况下模拟 root 操作,打包 deb 时需要它来设置文件属主和权限。

3.2 提取原包文件并生成需求清单

首先把原版linux-firmware包的文件提取到一个工作目录:

# 创建工作目录 mkdir -p ~/firmware-slim/work cd ~/firmware-slim/work # 下载原包(如果本地已有,直接从 /var/cache/apt/archives 复制) apt-get download linux-firmware # 解包 dpkg-deb -R linux-firmware_*.deb extracted

解包后,extracted/usr/lib/firmware就是完整的固件目录。接下来生成需求清单。我写了一个小脚本,把dmesg中加载的固件路径全部提取出来:

#!/bin/bash # 提取当前系统加载的固件列表 dmesg | grep -oP 'firmware: direct-loading firmware \K[^ ]+' | sort -u > loaded_firmware.txt # 同时提取可能通过其他方式加载的固件 dmesg | grep -oP 'firmware: loading \K[^ ]+' | sort -u >> loaded_firmware.txt sort -u loaded_firmware.txt -o loaded_firmware.txt cat loaded_firmware.txt

在我的 N100 节点上,这个列表大约有 20 多个文件,主要包括:

  • i915/adlp_dmc_ver2_16.bin
  • i915/adlp_guc_70.bin
  • i915/adlp_guc_70_16.bin
  • rtl_nic/rtl8168h-2.fw
  • intel/ibt-0040-0041.sfi
  • intel/ibt-0040-0041.ddc

除此之外,我还手动补充了一些"可能用到但当前未加载"的固件,比如 USB 网卡、常见 USB 声卡的固件。这些设备虽然当前没插,但考虑到后续可能用到,我保留了一个最小集合。

3.3 编写精简脚本并执行剔除

有了需求清单,就可以写精简脚本了。核心逻辑是:遍历extracted/usr/lib/firmware下的所有文件,如果不在白名单中,就删除。

#!/bin/bash set -e WORK_DIR=~/firmware-slim/work FIRMWARE_DIR="$WORK_DIR/extracted/usr/lib/firmware" WHITELIST="$WORK_DIR/loaded_firmware.txt" # 额外保留的固件(根据实际需求调整) EXTRA_KEEP=( "rtl_nic/rtl8168h-2.fw" "rtl_nic/rtl8168g-2.fw" "rtl_nic/rtl8168e-3.fw" "intel/ibt-0040-0041.sfi" "intel/ibt-0040-0041.ddc" ) # 构建完整白名单 cat > "$WORK_DIR/keep_list.txt" << 'EOF' i915/adlp_dmc_ver2_16.bin i915/adlp_guc_70.bin i915/adlp_guc_70_16.bin rtl_nic/rtl8168h-2.fw rtl_nic/rtl8168g-2.fw rtl_nic/rtl8168e-3.fw intel/ibt-0040-0041.sfi intel/ibt-0040-0041.ddc EOF # 遍历并删除不在白名单中的文件 cd "$FIRMWARE_DIR" find . -type f | while read -r file; do rel_path="${file#./}" if ! grep -qxF "$rel_path" "$WORK_DIR/keep_list.txt"; then rm -f "$file" fi done # 删除空目录 find . -type d -empty -delete # 统计精简后的大小 du -sh "$FIRMWARE_DIR"

执行这个脚本后,/usr/lib/firmware目录从 1.2 GB 降到了约 720 MB。我又检查了一遍,确认没有误删需要的文件。

3.4 重新打包并安装

精简完成后,用dpkg-deb重新打包。关键是要修改控制文件中的版本号,加上自定义后缀,避免被系统更新覆盖。

cd ~/firmware-slim/work/extracted # 修改控制文件 sed -i 's/^Version: .*/Version: 20220329.git681281e4-0ubuntu3.14+slim1/' DEBIAN/control # 可选:修改包名,避免与原包冲突 # sed -i 's/^Package: .*/Package: linux-firmware-slim/' DEBIAN/control # 重新打包 cd ~/firmware-slim/work fakeroot dpkg-deb -b extracted linux-firmware-slim_20220329.git681281e4-0ubuntu3.14+slim1_all.deb # 查看包信息 dpkg-deb -I linux-firmware-slim_*.deb

打包完成后,在测试节点上安装:

# 先卸载原包(注意:这会删除所有固件文件,确保你有回滚方案) sudo dpkg -r linux-firmware # 安装精简包 sudo dpkg -i linux-firmware-slim_*.deb # 更新 initramfs sudo update-initramfs -u # 重启验证 sudo reboot

重启后,用dmesg | grep -i firmware检查固件加载情况。如果所有设备都正常初始化,说明精简成功。

3.5 批量部署与自动化脚本

单台节点验证通过后,我把整个流程脚本化,用于批量部署。脚本的核心逻辑是:

  1. 检查当前系统是否已安装精简包,如果是则跳过。
  2. 备份原版linux-firmware包到/root/firmware-backup/。
  3. 安装精简包并更新 initramfs。
  4. 记录操作日志,便于审计和回滚。
#!/bin/bash # deploy_firmware_slim.sh set -e BACKUP_DIR="/root/firmware-backup" SLIM_DEB="/opt/firmware-slim/linux-firmware-slim_*.deb" mkdir -p "$BACKUP_DIR" # 备份原包 if [ ! -f "$BACKUP_DIR/linux-firmware-original.deb" ]; then apt-get download linux-firmware -o Dir::Cache::archives="$BACKUP_DIR" mv "$BACKUP_DIR"/linux-firmware_*.deb "$BACKUP_DIR/linux-firmware-original.deb" fi # 检查是否已安装精简包 if dpkg -l | grep -q "linux-firmware-slim"; then echo "精简固件包已安装,跳过。" exit 0 fi # 安装精简包 dpkg -r linux-firmware || true dpkg -i $SLIM_DEB update-initramfs -u echo "精简固件包安装完成,请重启验证。"

这个脚本可以通过 Ansible 或类似的配置管理工具推送到所有节点执行。实测下来,单台节点的操作时间不超过 2 分钟,重启后所有板载设备均正常工作。

4. 常见问题与排查技巧实录

4.1 固件加载失败导致硬件不工作

这是最常见的问题。表现是某个设备在lspci或lsusb中能看到,但dmesg中有固件加载失败的报错,设备无法正常使用。

排查思路:

  1. 确认dmesg | grep -i firmware中的报错信息,找到缺失的固件文件名。
  2. 检查该文件是否在精简包中存在。如果不存在,说明白名单漏了。
  3. 从原版包中提取该文件,放回/usr/lib/firmware对应目录。
  4. 重新加载驱动模块:sudo modprobe -r <模块名> && sudo modprobe <模块名>。
  5. 如果模块无法卸载,重启系统。

我遇到过一次网卡固件缺失的情况。原因是 N100 节点有两个网卡版本,我测试的那台用的是 Realtek RTL8168H,而另一批节点用的是 RTL8168G。白名单里只保留了 H 版本的固件,导致 G 版本节点网卡不工作。解决办法是把rtl_nic目录下所有rtl8168开头的固件都保留,总共也就多占几 MB。

4.2 系统更新后固件包被覆盖

Ubuntu 的linux-firmware包更新频率不低,每次内核更新都可能带来新的固件包版本。如果精简包的版本号没有正确设置,apt upgrade时可能会把原版包重新装回来。

避免方法:

  • 在控制文件中把版本号设置为比当前仓库版本更高的值,比如加+slim1后缀。
  • 使用apt-mark hold linux-firmware-slim锁定包版本,防止自动更新。
  • 如果确实需要更新固件,手动下载新版本原包,重新执行精简流程。

注意:锁定包版本后,系统不会自动获取固件安全更新。如果某些固件存在安全问题,需要手动评估是否更新。对于封闭的内网环境,这个风险相对可控。

4.3 initramfs 未更新导致启动失败

有些固件是在 initramfs 阶段加载的,比如某些存储控制器和网络启动相关的固件。如果只精简了根文件系统中的固件,而没有更新 initramfs,启动时可能找不到需要的固件,导致系统无法正常启动。

预防措施:

  • 每次修改固件包后,务必执行sudo update-initramfs -u。
  • 在/etc/initramfs-tools/initramfs.conf中确认MODULES配置,确保需要的驱动模块被包含。
  • 如果系统使用了全盘加密或网络启动,更要谨慎测试。

4.4 常见问题速查表

问题现象可能原因排查命令解决方法
网卡不工作网卡固件缺失dmesg | grep r8169补充对应 rtl_nic 固件
显卡驱动异常i915 固件缺失dmesg | grep i915补充 i915 目录下对应固件
蓝牙不可用蓝牙固件缺失dmesg | grep btusb补充 intel/ibt 系列固件
系统更新后固件恢复包版本未锁定apt-cache policy linux-firmware设置版本号后缀并 hold
启动卡住initramfs 未更新lsinitramfs /boot/initrd.img-* | grep firmware执行 update-initramfs -u
固件加载但设备仍异常固件版本不匹配ethtool -i eth0保留同系列所有版本固件

4.5 独家避坑经验

保留一份"应急固件包"。我在每个节点的/root/firmware-backup/下都放了一份原版linux-firmware的 deb 包。万一精简后遇到问题,可以进入救援模式,用dpkg -i快速恢复。这个包大约 500 MB,但考虑到它能在关键时刻救急,这点空间值得花。

不要追求极致精简。我一开始想把固件目录压到 500 MB 以下,结果发现有些固件虽然当前没加载,但在特定电源管理状态下会被调用。后来我把目标调整为 750 MB 左右,保留了所有"可能用到"的固件,实际省下 450 MB,已经达到预期。

记录硬件清单。每个节点的硬件配置可能略有不同,我在部署前用lspci -nn和lsusb生成了硬件指纹,保存在/etc/hardware-fingerprint.txt。这样后续如果出现固件问题,可以快速对照硬件清单排查。

测试休眠和唤醒。有些固件是在系统休眠或唤醒时加载的,日常使用中不会触发。我在测试节点上专门跑了systemctl suspend和唤醒流程,确认精简后休眠功能正常。这一步很容易被忽略,但一旦出问题就是大问题。

关注内核版本变化。内核升级可能引入新的固件依赖。我在每次内核更新后都会检查dmesg中的固件加载记录,如果有新的固件需求,及时补充到白名单中。这个习惯帮我避免了好几次潜在的硬件故障。

5. 精简效果验证与长期维护

5.1 量化验证:省了多少,影响了什么

在批量部署 20 台 N100 节点后,我统计了实际效果:

指标精简前精简后变化
/usr/lib/firmware 占用1.2 GB约 750 MB-450 MB
根文件系统占用8.5 GB8.05 GB-450 MB
启动时间12.3 秒12.1 秒基本持平
板载设备可用性全部正常全部正常无影响
系统更新频率每月 1-2 次手动控制更可控

启动时间基本没变,因为固件加载本身耗时很短,精简的主要收益在存储空间。对于 64 GB eMMC 的节点来说,450 MB 的释放意味着可以多存约 3 天的采集数据,或者多跑一个轻量级容器。

5.2 长期维护:固件白名单的迭代

精简固件包不是一劳永逸的事。随着系统更新和硬件变化,白名单需要定期维护。我的做法是:

  • 每季度检查一次dmesg中的固件加载记录,对比白名单,看是否有新增需求。
  • 每次内核大版本更新后,重新生成一次需求清单。
  • 如果新增了硬件设备,先在原版固件环境下测试,确认需要的固件文件后再更新白名单。

维护脚本我放在了内部仓库里,每次更新白名单后重新打包,版本号递增。这样所有节点都能通过配置管理工具获取到最新的精简包。

5.3 适用场景与不适用场景

这套方案在以下场景中效果最好:

  • 硬件配置统一的批量节点,比如边缘计算节点、工控机、教学实验室的固定配置机器。
  • 存储空间紧张的设备,比如 eMMC 存储的迷你主机、嵌入式板卡。
  • 网络环境受限,无法频繁下载大体积系统更新的场景。

不适用的情况也很明确:

  • 硬件配置经常变动的开发机,今天插个 USB 网卡,明天加个扩展卡,固件需求随时变。
  • 需要支持多种硬件的通用镜像制作,精简后可能在某些机器上无法启动。
  • 对系统更新及时性要求极高的场景,锁定固件包版本意味着可能错过安全更新。

我个人在实际操作中的体会是,精简固件包这件事,技术难度不高,但需要细心和耐心。最大的挑战不是删除文件,而是确保删除后系统依然稳定。每次精简后至少观察一周,确认没有偶发的硬件初始化问题,再推广到更多节点。这个等待期不能省,因为有些固件问题只在特定条件下才会暴露,比如冷启动、休眠唤醒、或者高负载场景。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询