depmod 生成 modules.dep

tech2022-08-07  152

问题描述

昨天同事反馈说 centos 标准内核 release 的 lib/modules 目录中没有生成 modules.dep 文件,这样导致没办法使用 modprobe 来在加载模块时自动处理依赖关系。

是没有生成还是没有提交?

这个问题表面看上去应该挺好搞的,二分法用在这里就是先要判断是没有生成还是没有提交。

重新用 rpmbuild 编译 centos 内核后发现确实没有生成 modules.dep 文件,解包生成的 rpm 文件发现也没有这个问题,看来应该是没有生成。

为此我在 debian 10 的 linux-4.19.98 内核源码上进行测试。

modules.dep 是怎样生成的?

其实我压根不清楚 modules.dep 这个文件是在编译内核的哪一个步骤生成的,网上搜了下说可以调用 depmod 命令来生成,试了下没有搞出来。我一直以为这个 modules.dep 是在编译模块的时候生成的,搞了半天发现它实际是在 modules_install 这一步生成的。

重新使用 depmod 命令生成 modules.dep 文件

在执行 INSTALL_MOD_PATH=./install make modules_install 安装内核模块到 install 目录中时,拷贝完模块后会调用 depmod 生成 modules.dep 等文件。

在安装完成后执行如下命令来生成 modules.dep 等文件。

/sbin/depmod -b ./install 4.19.118

使用脚本生成 modules.dep 文件

其实 modules.dep 文件很容易生成,在每个 ko 文件的 modinfo section 中已经记录了依赖的模块,只需要将这个 section 抽出来,然后格式化输出就能够生成 modules.dep 文件。

我使用如下脚本来生成 modules.dep 文件。

#!/bin/bash if [ -n "$1" ] then cd $1 else echo "Please input an pathname like: $0 /lib/modules/4.19.98" > /dev/stderr exit 1 fi kolist=$(find . -name '*.ko' -printf %P"\n" | sort ) for one_module in $kolist do depends=$(/sbin/modinfo -F depends $one_module) output="$one_module:" IFS="," for i in $depends; do one_line=" $(find . -name "${i}.ko" -printf "%P")" output=$output$one_line done echo $output done

这个脚本的核心在于使用 modinfo 提取模块中的 depends 信息,然后查找依赖模块的路径并以 modules.dep 的格式输出。

Makefile 中的相关内容

# Target to install modules PHONY += modules_install modules_install: _modinst_ _modinst_post PHONY += _modinst_ _modinst_: @rm -rf $(MODLIB)/kernel @rm -f $(MODLIB)/source @mkdir -p $(MODLIB)/kernel @ln -s $(abspath $(srctree)) $(MODLIB)/source @if [ ! $(objtree) -ef $(MODLIB)/build ]; then \ rm -f $(MODLIB)/build ; \ ln -s $(CURDIR) $(MODLIB)/build ; \ fi @cp -f $(objtree)/modules.order $(MODLIB)/ @cp -f $(objtree)/modules.builtin $(MODLIB)/ $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst # This depmod is only for convenience to give the initial # boot a modules.dep even before / is mounted read-write. However the # boot script depmod is the master version. PHONY += _modinst_post _modinst_post: _modinst_ $(call cmd,cmd_depmod)

上述 Makefile 脚本中,modules_install 目标依赖_modinst_与 _modinst_post,_modinst_post调用 depmod生成 modules.dep等文件。

这里 $(call cmd,cmd_depmod) 中的 cmd 是一个函数,cmd_depmod 是传递给这个函数的参数。

cmd_depmod 标号的定义内容如下:

# Run depmod only if we have System.map and depmod is executable quiet_cmd_depmod = DEPMOD $(KERNELRELEASE) cmd_depmod = $(CONFIG_SHELL) $(srctree)/scripts/depmod.sh $(DEPMOD) \ $(KERNELRELEASE)

对于 cmd 函数来说,当指定了 quiet 选项后,它使用 quiet_cmd_depmod 命令执行,未指定 quiet 选项则使用 cmd_dpemod 命令执行。

cmd 函数在 scripts/Kbuild.include 中定义,内容如下:

# echo command. # Short version is used, if $(quiet) equals `quiet_', otherwise full one. echo-cmd = $(if $($(quiet)cmd_$(1)),\ echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';) # printing commands cmd = @$(echo-cmd) $(cmd_$(1))

这个语法相当复杂,我也没有完全搞懂,暂且不进行解释了。

为什么 rpmbuild 没有生成 modules.dep 等文件?

centos 标准内核使用 rpmbuild 生成内核,通过查看编译步骤,发现其中有执行 modules_install 的操作,但没有生成 modules.dep 等相关的文件。

cnetos 标准内核 3.10 中顶层 Makefile 中 _modinst_post 目标的内容如下:

# This depmod is only for convenience to give the initial # boot a modules.dep even before / is mounted read-write. However the # boot script depmod is the master version. PHONY += _modinst_post _modinst_post: _modinst_ $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modinst $(call cmd,depmod)

与 debian10 的 4.19.98 内核源码对比,发现 centos 的内核多了编译对 __fw_modinst 的编译行。

kernel.spec 中的相关内容如下:

if [ "$Flavour" != "kdump" ]; then # Override $(mod-fw) because we don't want it to install any firmware # we'll get it from the linux-firmware package and we don't want conflicts make -s %{?cross_opts} ARCH=$Arch INSTALL_MOD_PATH=$RPM_BUILD_ROOT modules_install KERNELRELEASE=$KernelVer mod-fw= %if %{with_gcov}

这里在执行 modules_install 的时候指定了 mod-fw 为空,这就导致 depmod 没有被调用,modules.dep 等文件生成失败。

了解到这点后,我在编译流程中添加调用 depmod 的操作生成 modules.dep 这些文件解决了这个问题。

modprobe 加载内核模块使用的哪些文件呢?

使用 strace 追踪 modprobe 的调用过程,发现 modprobe 并不会使用 modules.dep 文件。

strace 的相关信息记录如下:

[longyu@debian-10:07:30:49] ~ $ strace /sbin/modprobe tun 2>&1 | grep open ............ openat(AT_FDCWD, "/etc/modprobe.d", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 openat(AT_FDCWD, "/lib/modprobe.d", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 openat(AT_FDCWD, "/lib/modprobe.d/aliases.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/etc/modprobe.d/blacklist-nouveau.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modprobe.d/fbdev-blacklist.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modules/4.19.98/modules.softdep", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/etc/modprobe.d/nouveau-kms.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/etc/modprobe.d/qemu-blacklist.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modprobe.d/systemd.conf", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/proc/cmdline", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modules/4.19.98/modules.dep.bin", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modules/4.19.98/modules.alias.bin", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modules/4.19.98/modules.symbols.bin", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/lib/modules/4.19.98/modules.builtin.bin", O_RDONLY|O_CLOEXEC) = 3 openat(AT_FDCWD, "/sys/module/tun/initstate", O_RDONLY|O_CLOEXEC) = 3

可以看到它访问了 modules.dep.bin、modules.alias.bin、modules.symbols.bin、modules.builtin.bin 等文件,它并没有访问 modules.dep 文件。

man modules.dep 得到了如下证据:

DESCRIPTION modules.dep.bin is a binary file generated by depmod listing the dependencies for every module in the directories under /lib/modules/version. It is used by kmod tools such as modprobe and libkmod. Its text counterpar is located in the same directory with the name modules.dep. The text version is maintained only for easy of reading by humans and is in no way used by any kmod tool.

上述信息表明,modules.dep 只是供我们查看的,kmod 相关的命令使用的是 modules.dep.bin 这个文件。

总结

在这个问题里,我的目标是生成 modules.dep 文件,实际上这个文件只是方便用户来查看,并不会被 modprobe 所使用,这表明我在确定问题是什么这里有疏忽,偏离了正确的方向。

正确的流程应该是确认 modprobe 加载模块到底依赖哪些文件,然后再进行处理。

最新回复(0)