如何实现内核模块与内核版本的解耦
问题背景
我们当前系统使用的内核版本为A版本,我安装了一个在B版本内核上编译的一个模块catch.ko,A和B两个内核版本的KABI是兼容的。通过rpm -ql xx_mode可以看到要插入到内核的模块是放在了lib/modules/B<kernel_version>/extra/目录下。在/lib/moudles目录下也多了一个内核目录。由于KABI是兼容的,所以在B版本上编译的模块,在A版本的内核中也可以正常运行。但是当出现这种情况时,模块的自动加载就需要注意了。
linux:/lib/modules # ls 3.10.0-514.32.3.18_40.x86_64 3.10.0-514.41.4.28_63.x86_64
正常情况下,如果要内核某模块在系统启动时自动加载,需要放在/lib/modules/<uname -r>目录下,在系统安装过程中,会通过depmod命令生成模块间的依赖关系,生成modules.dep modules.dep.bin,而modprobe命令就是依赖于depmod生成的结果来实现模块自动加载的,在kmod-20-15版本前,depmod只能识别/lib/modules/<kernel version>/目录下对应的模块。
因为catch模块是放在/lib/modules/B<kernel_version>/extra目录下,而modprobe命令都是从/lib/modules/A<kernel_version>目录下去搜索寻找的,所以当使用modprobe catch时,会显示失败。那么如何解决这个问题呢?目前方法有两个:一个是使用weak-modules机制,一个是使用depmod中的externel机制。
一、weak-modules
Linux weak-modules机制的作用是判断哪些模块与当前安装的内核是kABI兼容的,如果兼容则在当前内核版本中建立该模块的软链接。weak-modules不仅应用在驱动模块安装、升级、卸载上,在安装系统、升级内核、卸载内核时也会执行该脚本。总之weak-modules机制是用来保证同一个模块能在kABI兼容的多个内核版本之间共同使用,不必每个版本都发布一个模块。
在Redhat和SUSE系统环境常使用RPM包安装管理软件,驱动模块也会使用RPM打包安装。为了使同一个驱动模块能够在多个kABI兼容的内核版本中正常运行,驱动包的post脚本中会执行weak-modules脚本来分析驱动模块是否兼容,如果兼容则建立软链接,如果满足一定条件还会刷新initrd文件。是否会刷新initrd文件及刷新条件,不同系统和版本在设计和实现上有所不同。
Redhat环境下业界标准驱动包的post脚本通过指定--add-modules和模块名调用weak-modules脚本,如果兼容则在/lib/modules/kernel-version/weak-updates目录下建立模块源文件的软链接,源文件则在其他内核版本的/lib/modules/other-kernel -version/extra目录下。Redhat驱动包通常以kmod-modname-ver-release.xx.arch.rpm命名。SUSE环境下业界标准驱动包的post脚本通过指定--add-kmp和模块名调用weak-modules2脚本,如果兼容则在/lib/modules/kernel-version/weak-updates目录下建立模块源文件的软链接,源文件则在其他内核版本的/lib/modules/other-kernel -version/updates目录下。SUSE驱动包通常以modname-kmp-flavor-ver-release.arch.rpm命名。这些都是当前现有的做法,不排除在版本演进过程中两个发行商使用方式归一或者有新的实现方式。
Redhat和SUSE系统建立软链接的实现方式上有较大的区别,但主题思路是一样的,大致如下:weak-modules脚本首先读取post脚本输入的模块参数列表,分析判断模块列表中的模块在各个内核版本中是否是兼容的,如果兼容则建立原始模块文件的软链接。
我们以Redhat举例,在catch这个模块所属包的spec文件中,我们在post后面加入如下脚本:
%post -n kmod-catch echo "/lib/modules/%{kversion}/extra/catch.ko" | /sbin/weak-modules --add-module --no-initramfs
kversion为上文所说的B kernel版本,weak-modules的用法参见 http://linux.51yip.com/search/weak-modules
linux:uname -r 3.10.0-514.41.4.28_63.x86_64 linux:/lib/modules # ls 3.10.0-514.32.3.18_40.x86_64 3.10.0-514.41.4.28_63.x86_64 linux: echo /lib/modules/3.10.0-514.32.3.18_40.x86_64/extra/catch.ko | /sbin/weak-modules --add-module --no-initramfs linux:/lib/modules/3.10.0-514.41.4.28_63.x86_64/weak-updates/ # ll total 4 lrwxrwxrwx 1 root root 73 May 26 10:54 catch.ko -> /lib/modules/3.10.0-514.32.3.18_40.x86_64/extra/catch.ko
在weak-updates目录下生成一个软链接后,在执行modprobe后,就会从这个软连接找到真正的ko模块,完成模块加载。
linux: modprobe catch linux: modinfo catch.ko filename: /lib/modules/3.10.0-514.41.4.28_63.x86_64/weak-updates/catch.ko license: GPL
二、depmod.d external
社区在17年发布一组patch,depmod支持external关键字,可以指定文件系统上的任意目录,depmod会自动识别并进入modules.dep的生成结果中。如上例子,可以将catch.ko放置在其他目录,如/opt/modules/目录下,在/etc/depmod.d/目录下去添加配置文件catch.conf,配置文件写为:
external 3.10.0-514* /opt/modules
external为depmod的关键字,用于指定模块放置的路径,3.10.0-514*代表与当前内核版本模糊匹配,当然也要保证这个大版本号的KABI是兼容的。在执行depmod时,会从depmod.d目录下搜索配置文件,然后根据配置,去/opt/modules目录下搜索模块,加入modules.dep的生成结果中。执行modprobe catch后,使用modinfo查询,可以看到modinfo显示的文件name已经发生变化:
linux:modinfo signo_catch filename: /opt/modules/catch.ko license: GPL
另外需要注意的是,在/etc/depmod.d/dist.conf文件中,配置了搜索的路径和优先级,其中built-in代码着/lib/modules/<kernel>/kernel目录下的所有驱动,即内核自带驱动模块。
# depmod.conf # # override default search ordering for kmod packaging search updates extra external built-in weak-updates
通过以上两种方法,就可以完成内核模块和内核版本之间的耦合关系,即不需要发布一个内核版本,同时发布一个对应的模块。这样操作就比较灵活,也减去很多编译模块的工作量和时间。