RHEL5用カーネルモジュールの作成とkmodtool/weak-modules

随分と久しぶりにLinuxカーネルモジュールを作成して、CentOS5用にRPM化しようとしたら、何やら色々とお作法が変わったみたい(ほんと以前どうだったか思い出せないぐらい過去なので実は単純に忘れていただけかもしれないけど)なので、メモ。(RHEL6用も作る事になりそうだし、一回まとめとかないと忘れる。)

基本的な方法と背景知識

以下のサイトを参考にした。これで基本的には問題なくRPMが作成できる。単純に作るだけなら、ありがたく参考にしてそのままやれば良い。

kmodtool

そうは言っても、何をしているのか、わからないままなのは気持ち悪いのでもう少し調べてみる。
RedHatKernelModulePackagesを読むと、/usr/lib/rpm/redhat/kmodtoolを使ってSPECファイルを記述するように書かれている。

上記の参考サイトにも書かれているが、これはつまり、引数 rpmtemplate_kmp を与えることで、カーネルモジュール用のSPECが展開されるユーティリティである。そして、ここで作成されるカーネルモジュール用のSPECの内容は、通常のものとほとんど差が無い。
ポイントは /sbin/weak-modules の呼び出しで、具体的には以下のような処理が展開される。

# verrel はカーネルのバージョン、kmod_name はモジュール名。

%post          -n kmod-${kmod_name}${dashvariant}
modules=( \$(find /lib/modules/${verrel}${variant}/extra/${kmod_name} \
        | grep '\.ko$') )
if [ -x "/sbin/weak-modules" ]; then
  printf '%s\n' "\${modules[@]}" | /sbin/weak-modules --add-modules
fi

%postun        -n kmod-${kmod_name}${dashvariant}
modules=( \$(cat /var/run/rpm-kmod-${kmod_name}${dashvariant}-modules) )
if [ -x "/sbin/weak-modules" ]; then
  printf '%s\n' "\${modules[@]}" | /sbin/weak-modules --remove-modules
fi

つまり、新規追加・削除したカーネルモジュール名をSTDINにして、/sbin/weak-modules を呼び出している。その際の引数は、--add-modules か --remove-modules を指定している。
weak-modulesはスクリプトで、パッケージは以下の通り。これならインストールされてない事はありえない。

$ rpm -qf /sbin/weak-modules
module-init-tools-3.3-0.pre3.1.60.el5_5.1

$ rpm -ql module-init-tools
/sbin/depmod
/sbin/insmod
/sbin/lsmod
/sbin/modinfo
/sbin/modprobe
/sbin/rmmod
/sbin/weak-modules
....

weak-modules

このスクリプトは大した量では無いし、ユーティリティが別にあって、とかではないので比較的簡単。肝心な部分なので少し真面目に読む。

引数と構成

まず、受け付け可能な引数だけど、usage()を見れば、主な引数は4種類。

--add-modules
追加するモジュールのリストをSTDINから読み込み、kABIコンパチのカーネルバージョン向けweak-updates/ディレクトリへ、シンボリックリンクを作成する。
--remove-modules
削除するモジュールのリストをSTDINから読み込み、kABIコンパチのカーネルバージョン向けweak-updates/ディレクトリから、該当するシンボリックリンクを削除する。
--add-kernel
引数指定(または現在)のカーネルに対して、kABIコンパチになるモジュールを探して、シンボリックリンクを追加する。
--remove-kernel
引数指定(または現在)のカーネルから、モジュールへのシンボリックリンクを削除する。

それぞれの引数に対応する関数(--add-modules ならば add_modules())があり、基本的な流れとしては、引数を見て対応する関数を呼び出し、何らかの修正(モジュール新規追加など)がおこなわれれば、そのカーネルに対する depmod や new-kernel-pkg 等が実施される。

add_modules 処理イメージ

新規にモジュール追加を行う際の配置イメージを具体的に書いておく。
最初に前提の確認。これからモジュールをインストールするマシンに、以下のカーネルがインストールされているとする。

そうすると、以下のディレクトリが存在する。

/lib/modules/2.6.18-194.32.1.el5/
              extra/
              weak-updates/
           2.6.18-238.9.1.el5/
              extra/
              weak-updates/
           2.6.10-123.1.1/
              extra/
              weak-updates/

そして、これからインストールするモジュールの名前を "hoge-kmod" とする。
普通にrpmによりファイルコピーが行われると、以下のようにファイルが存在するようになる。extraディレクトリ配下にディレクトリを作成して配置する規則となっている。

/lib/modules/2.6.18-194.32.1.el5/
              extra/
                  hoge-kmod/hoge.ko (...等の*.koファイル)

この後(%post)で、weak-modules --add-modules が呼び出される。
上記の3つのカーネル(実際には(1)はパスして(2),(3))のkABIを順番にチェックして、コンパチであれば、そのカーネルのweak-updates/ディレクトリ配下にモジュールへのシンボリックリンクを作成する。以下のようになる。

/lib/modules/2.6.18-194.32.1.el5/
              extra/
                  hoge-kmod/hoge.ko
              weak-updates/
           2.6.18-238.9.1.el5/
              extra/
              weak-updates/
                  hoge-kmod/hoge.ko@
                  (上記2.6.18-194.32.1.el5/配下のhoge.koへのシンボリックリンク)
           2.6.10-123.1.1/
              extra/
              weak-updates/

これにより、モジュールのRPMパッケージでは明示的に指定していなかった 2.6.18-238.9.1.el5 においても、hoge.ko が利用可能(後処理は省略)となったことになる。要はこれだけ。

これが解れば、他の引数での処理はこれより単純だし、まあ予想もつく。
カーネルパッケージのアップデートに対して、kABIコンパチであれば、わざわざ対応するバージョンのモジュールパッケージRPMを作成・インストールしなくても良くなる。なるほど。

kABIコンパチの判定

あるカーネルが、モジュールが本来ターゲットにしていたカーネルに対してkABIコンパチかどうかを判定するのは、module_is_compatible()でおこなっている。
要は、追加するモジュールが必要とするシンボルが、追加される対象のカーネルおよびモジュール群の中に含まれていて、かつ、シンボルのチェックサム(modversions, symvers )が等しければコンパチ。そうで無ければコンパチでは無いという事になる。
カーネルについては /boot/symvers-$(カーネルバージョン).gz から、モジュールについては /lib/modules/$(カーネルバージョン)/extra 配下の *.ko に対して nm をかけた結果からシンボルを抽出している。

# 追加対象のモジュールからシンボルとチェックサムを取得する処理
/sbin/modprobe --dump-modversions 対象モジュール

# 追加される対象のカーネル(2.6.18-238.9.1.el5)からシンボルとチェックサムを
# 取得する処理
zcat /boot/symvers-2.6.18-238.9.1.el5.gz \
  | sed -r -ne 's:^(0x[0]*[0-9a-f]{8}\t[0-9a-zA-Z_]+)\t.*:\1:p'

# 追加される対象のカーネル(2.6.18-238.9.1.el5)向けに拡張インストールされた
# モジュールのシンボルとチェックサムを取得する処理
find /lib/modules/2.6.18-238.9.1.el5/extra -name '*.ko' \
  | xargs nm \
  | sed -nre 's:^[0]*([0-9a-f]{8}) A __crc_(.*):0x\1 \2:p'
他にはどんなパッケージが使っている?

以下のようなスクリプトを動かしてみた。

#!bin/sh

for i in `rpm -qa`
do
      if found=`rpm -q $i --scripts | grep weak-modules 2>/dev/null`; then
         echo "Found package: " $i
      fi
done

実行結果は以下の通り。

$ sh ./check-weak-modules-package.sh
Found package:  kernel-2.6.18-194.32.1.el5
Found package:  kernel-debug-2.6.18-238.9.1.el5
Found package:  kernel-2.6.18-238.1.1.el5
Found package:  kernel-2.6.18-238.9.1.el5

$ ls /lib/modules/
2.6.18-194.32.1.el5  2.6.18-238.1.1.el5  2.6.18-238.9.1.el5
2.6.18-238.9.1.el5debug

まあ、予想通りで、対応もとれている模様。

一応、確認で中身を見ると以下のような内容となっており、引数が --*-kernel となっているのがわかる。(2.6.18-194.32.1.el5 の場合)

if [ -x /sbin/weak-modules ]
 /sbin/weak-modules --add-kernel 2.6.18-194.32.1.el5 || exit $?
...
if [ -x /sbin/weak-modules ]
 /sbin/weak-modules --remove-kernel 2.6.18-194.32.1.el5 || exit $?

まとめ

RHELカーネルモジュールのRPM化には、kmodtoolを活用する。
kmodtool(weak-modules)は、カーネルモジュールを提供する際に、kABIコンパチなカーネル間でのパッケージング、インストールの手間を削減する。
その実現は、ディレクトリ構成規則、シンボル情報を使用したkABIコンパチの確認、シンボリックリンクの活用によっておこなわれている。