内核裁剪的目标
以前我一直想搞搞内核裁剪相关的东西,今天就抓住机会来搞一下。目标是在两个小时之内完成,只保留我的 linux 中需要使用的内核模块配置,其它的都删除。
为什么我要进行内核裁剪
玩内核裁剪的初衷在于我想编译主机上的 linux 内核,但因为它需要编译非常多的内核模块,编译时间太长只能放弃。
我现在正在使用的 relase 内核中模块数目统计如下:
[longyu@debian-10:08:24:33] $ find /lib/modules/4.19.0-10-amd64/ -name '*.ko' | wc
3559 3559 243267
可以看到总共有 3559 个内核模块,而我真正需要用的其实也就几百个左右。
lsmod 统计信息如下:
[longyu@debian-10:08:25:01] $ lsmod | wc
189 660 7958
可以看到我在用的只有 189 个内核模块,这就是我裁剪内核的动机所在。
make localmodconfig 大法
目标与要求都描述清楚后,我还必须说明时间上的要求。2 个小时的时间其实非常短,这只是我在睡觉前能够抽出来的最多的时间。
如果手动去做,两个小时肯定不够,但是我之前阅读一些 linux 书籍的时候发现可以生成一个系统正在使用的内核模块的配置文件。
执行如下命令就可以生成:
make localmodconfig
我相信有了这个知识,两个小时应该能够达成我的目标。
make localmodconfig 使用的注意事项
在 make localmodconfig 执行前,需要拷贝本机正在使用的内核的 config 文件到内核源码树中的 .confg 中。
示例过程如下:
[longyu@debian-10:08:37:57] linux-source-4.19 $ cp /boot/config-4.19.0-10-amd64 ./.config
[longyu@debian-10:08:38:03] linux-source-4.19 $ make localmodconfig
using config: '.config'
注意不能使用 make defconfig 生成的 .config 文件!!
使用新的 .config 文件编译内核并安装内核模块
编译完成后,执行 make modules_install 安装编译出的内核模块。在我的系统中,新的内核模块安装完成后,统计信息如下:
[longyu@debian-10:08:41:33] linux-source-4.19 $ find /lib/modules/4.19.98/ -name '*.ko' | wc
240 240 13653
可以看到现在只有 240 个内核模块,相比三千多个,这个数目已经相当可观了。
重新制作新的 initrd
手动制作 initrd 文件
我最开始手动制作了 initrd 文件,制作方法如下:
- 拷贝系统正在使用的 initrd 文件
- 解压 initrd 文件,删除 usr/lib/modules 中的模块子目录,复制新安装的模块子目录到 usr/lib/modules 中
- 制作新的文件列表并使用 cpio 生成新的 initrd 文件
- 重新压缩 initrd 文件并拷贝到 /boot 目录中
手动制作的 initrd 文件测试结果
使用新的内核 bzImage 文件与手动制作的 initrd 文件测试,在 grub 中编辑相应的项目指定使用新的内核与 initrd 文件。
进入系统后图形界面正常工作,但是发现无线网卡不能使用。查看 /var/log/kern.log 发现了如下报错:
Aug 19 23:27:19 debian-10 kernel: [ 6.926482] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-38.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926677] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-38.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926696] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-37.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926729] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-37.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926741] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-36.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926773] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-36.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926783] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-35.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926814] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-35.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926824] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-34.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926858] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-34.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926879] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-33.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926920] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-33.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926929] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-32.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.926965] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-32.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.926974] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-31.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.927098] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-31.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.927111] iwlwifi 0000:00:14.3: firmware: failed to load iwlwifi-9000-pu-b0-jf-b0-30.ucode (-2)
Aug 19 23:27:19 debian-10 kernel: [ 6.927145] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-9000-pu-b0-jf-b0-30.ucode failed with error -2
Aug 19 23:27:19 debian-10 kernel: [ 6.927147] iwlwifi 0000:00:14.3: minimum version required: iwlwifi-9000-pu-b0-jf-b0-30
Aug 19 23:27:19 debian-10 kernel: [ 6.927257] iwlwifi 0000:00:14.3: maximum version supported: iwlwifi-9000-pu-b0-jf-b0-38
Aug 19 23:27:19 debian-10 kernel: [ 6.927286] iwlwifi 0000:00:14.3: check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
A
上述报错表明 iwlwifi 加载固件失败,-2 表示 no such file,这应该是没有找到固件文件。我简单分析了下,发现我手动制作的 initrd 大小要比系统中正在使用的大很多,我怀疑可能是大小有影响。
临时的规避方法
在上面的问题出现后,我使用 dpdk 中的网卡绑定工具查看信息,发现无线网卡接口没有绑定驱动,这可能是固件加载失败的一个副作用。
我执行如下命令进行绑定,绑定后无线网卡可以正常使用。
[longyu@debian-10:22:23:27] ~ $ su -c 'echo "0000:00:14.3" > /sys/module/iwlwifi/drivers/pci\:iwlwifi/bind'
iwlwifi 驱动加载异常的问题
经过实验排查,排除了 initrd 大小的影响。这之后,我重新使用官方的内核与 initrd 进行引导,引导后继续查看 kern.log 文件的记录,发现有如下信息:
Aug 19 22:55:09 debian-10 kernel: [ 6.409271] raid6: sse2x1 gen() 12417 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.477273] raid6: sse2x1 xor() 9925 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.545269] raid6: sse2x2 gen() 16575 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.613248] raid6: sse2x2 xor() 11259 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.681247] raid6: sse2x4 gen() 18603 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.749242] raid6: sse2x4 xor() 11906 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.817269] raid6: avx2x1 gen() 27260 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.885267] raid6: avx2x1 xor() 18969 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 6.953248] raid6: avx2x2 gen() 32701 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 7.021271] raid6: avx2x2 xor() 20823 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 7.089270] raid6: avx2x4 gen() 34730 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 7.157240] raid6: avx2x4 xor() 22804 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 7.157241] raid6: using algorithm avx2x4 gen() 34730 MB/s
Aug 19 22:55:09 debian-10 kernel: [ 7.157241] raid6: .... xor() 22804 MB/s, rmw enabled
Aug 19 22:55:09 debian-10 kernel: [ 7.157242] raid6: using avx2x2 recovery algorithm
Aug 19 22:55:09 debian-10 kernel: [ 7.163216] xor: automatically using best checksumming function avx
Aug 19 22:55:09 debian-10 kernel: [ 7.202667] Btrfs loaded, crc32c=crc32c-intel
Aug 19 22:55:09 debian-10 kernel: [ 7.285982] PM: Image not found (code -22)
Aug 19 22:55:09 debian-10 kernel: [ 7.411200] EXT4-fs (nvme0n1p5): mounted filesystem with ordered data mode. Opts: (null)
Aug 19 22:55:09 debian-10 kernel: [ 7.447680] EXT4-fs (nvme0n1p6): mounted filesystem with ordered data mode. Opts: (null)
Aug 19 22:55:09 debian-10 kernel: [ 8.264892] EXT4-fs (nvme0n1p6): re-mounted. Opts: (null)
Aug 19 22:55:09 debian-10 kernel: [ 8.280733] EXT4-fs (nvme0n1p5): re-mounted. Opts: (null)
正常的 log 信息与异常的 log 信息的主要区别内容如下:
-
正常的 log 信息中先挂载了 nvme0n1p5 与 nvme0n1p6 分区,然后才加载了 iwlwifi 驱动。
-
异常的 log 信息中先加载了 iwlwifi 驱动,然后再挂载 nvme0n1p5 与 nvme0n1p6 分区。
在我的系统中 nvme0n1p6 分区与 nvme0n1p5 分区挂载点如下所示:
/dev/nvme0n1p5 30G 480M 28G 2% /
/dev/nvme0n1p6 20G 12G 7.3G 62% /usr
在我的系统中,iwlwifi 需要加载的固件信息放在如下目录中:
[longyu@debian-10:08:59:47] tmp $ locate 'iwlwifi-9000-pu-b0-jf-b0-38.ucode'
/usr/lib/firmware/iwlwifi-9000-pu-b0-jf-b0-38.ucode
有了这两个信息,问题就清楚了。iwlwifi 需要加载的固件存储在 /usr 分区中,需要挂载 nvme0n1p6 后才能访问到。
异常的执行流程中,在 /usr 分区挂载前就加载了 iwlwifi 命令并加载固件,这时候肯定要报 no such file 的错误。
什么因素导致 iwlwifi 驱动在 /usr 分区挂载前就加载了呢?
有了上面的基础,我怀疑这个问题可能是 initrd 的内容过大导致 systemd-udevd 在分区挂载前就加载了 iwlwifi 驱动。
关于 systmed-udevd 加载驱动的原理,可以查看 debian 启动流程分析 这篇博文。
我重新阅读 initrd 中的初始化脚本,没有找到怀疑点。
自动生成 initrd 文件
有了上面的怀疑后,我尝试自动生成 initrd 文件,使用 update-initramfs 重新制作 initrd 后,大小为 82M,手动制作时大小为 172M。
使用自动生成的 initrd 文件重新启动系统,这次无线网卡正常工作。
自动生成的 initrd 文件与手动生成的 initrd 文件内容对比
既然使用自动生成的 initrd 文件 iwlwifi 能够正常工作,那么问题可能是我手动制作的 initrd 文件与自动生成的 initrd 文件存在某些差异点,大小是表面差异,更重要的应该是内容的差异。
最后对比发现,update-initramfs 制作的 initrd 中没有放 iwlwifi.ko 驱动,并且 modules.alias 中删除了与 iwlwifi 相关的设备信息。这样在挂载 /usr 与 / 目录前都不会去加载 iwlwifi 驱动,当挂载了 /usr 与 / 后 modules.alias 更新,iwlwifi 模块存在了,这时再加载这个模块就没有问题了。
我删除手动制作的 initrd 中的 iwlwifi 驱动与 modules.alias 表格中与 iwlwifi 设备相关的描述,重新制作 initrd 后重新引导,这次正常了。
为什么 update-initramfs 生成的 initrd 更小
解决了 iwlwifi 驱动加载异常的问题后,我想到了一个新的问题:为什么 update-initramfs 生成的 initrd 更小?
对比发现,update-initramfs 生成的 initrd 中只放了部分的内核模块。
研究了下脚本执行的过程,发现它并不像我手动制作 initrd 那样直接拷贝整个 lib/modules/xx 到 usr/lib/modules 中,它有很多逻辑判断,对需要拷贝到 initrd 中的内核模块进行了筛选,一定程度上依赖系统 sys 目录下的文件来完成判断过程。
总结
即便你已经想到了问题可能比你预期的多,真正操作时问题可能要比你预期的还要更多一些。
裁剪内核 10 分钟,解决 initrd 的问题花了几个小时,这就是一个再具体不过的例子。真可谓是纸上得来终觉浅,绝知此事要躬行,计算机知识的掌握还是要多多实践,纯粹了解理论可能掉入纸上谈兵的旋涡中。
同时这里我也想说明问题分析的一种方法。
- 首先确定问题出现的层次,异常与正常的差异在哪一层
- 然后将正常与异常层次的输出信息进行对比,找到差异点
- 排查差异点来解决问题
一些联想
以前我在搞嵌入式的时候,经常遇到与别人执行相同的操作,但是不能得到正常结果的情况,一度让我感到很郁闷。
其实在这里的相同只是表面上的,最后真正解决了问题后,我才意识到其实我们的操作上还是有差异点,只不过这个差异点太隐含,不能立刻发现。
现在我再来回答之前我提过的这样一个问题:为什么我总是能够遇到更多的问题?
可能我的运气更差吧!可是如果将时间拉长,这种差的运气最终却带来了好的结果。
因为运气好而避过了某些坑,这些坑总有一天还会出现,因为运气差而遇到了各种坑,前期肯定进展缓慢,但是经过了这个过程后,自我的能力无疑变得更加强大。在别人眼里很难的问题,可能不过是之前自己曾经踩过的坑罢了。