2009-07-28
RHEL 5 と KMP (Kernel Module Package)
カーネルのバージョンが上がる度にカーネルモジュールもビルドしなきゃいけない,というのはめんどくさい。
しかも。
バイナリパッケージでカーネルモジュールをインストールしているとする。
このカーネルモジュールがシステム動作の上で必須だとすると,新しいカーネル(本体)がリリースされても,それに対応するバージョンのカーネルモジュールがリリースされない限り新しいカーネルにアップデートすることができない。
これは不便。そして危険。
なので,RHEL 5 から KMP (Kernel Module Package) という新しいカーネルモジュールのパッケージング方式がでてきた。
たとえば,centosplus のバイナリパッケージ(IIJ さんのミラー)で XFS のカーネルモジュールのパッケージを見ると,
- kmod-xfs-0.4-1.2.6.18_92.1.1.el5.centos.plus.i686.rpm
- kmod-xfs-0.4-1.2.6.18_92.1.6.el5.centos.plus.i686.rpm
- kmod-xfs-0.4-1.2.6.18_92.1.10.el5.centos.plus.i686.rpm
- kmod-xfs-0.4-2.i686.rpm
のようにいくつかある。
注目すべきポイントは,0.4-1 系統については,カーネルのバージョンが上がる度に新しいモジュールがリリースされているのに対して,0.4-2 系統は一つしかない(バージョンにカーネルバージョンが含まれていない)。0.4-1 は旧来のカーネルモジュールパッケージでパッケージングしていたのにたいして,0.4-2 では KMP に対応したパッケージングを行っているから。
なので,今後カーネルのリビジョンがあがろうとも,同一のカーネルモジュールパッケージ(kmod-xfs-0.4-2)をインストールしたままできちんと動く。
じゃあ,KMP 対応のカーネルモジュールパッケージには,カーネルのバージョン番号は含まれていないのか。
$ rpm2cpio kmod-xfs-0.4-2.i686.rpm | cpio -t ./lib/modules/2.6.18-92.1.13.el5 ./lib/modules/2.6.18-92.1.13.el5/extra ./lib/modules/2.6.18-92.1.13.el5/extra/xfs ./lib/modules/2.6.18-92.1.13.el5/extra/xfs/xfs.ko 1064 blocks
このように含まれてしまってる。でも,これでもきちんと動くしくみが用意されている(後述)。
KMP と DKMS の違い
上記の問題点に対処する方法は KMP だけではない。一番有名なのは DKMS というしくみ。
以下,簡単な比較。
- DKMS
- カーネル本体がアップデートするたびに,自動的にソースからカーネルモジュールをビルドする
- よってカーネルのバイナリインタフェース(kABI)が変更になっても,(ソースからビルドできる限り)DKMS 対応カーネルモジュールパッケージを更新する必要はない
- そのかわり,カーネルビルドに必要な環境(gcc や kernel-devel パッケージ)が必要になる(と思う)
- さまざまなディストリビューション(debian, Ubuntu, RHEL)に対応したフレームワークである
- ただ,RHEL(および CentOS)の場合,DKMS 本体のパッケージを EPEL や RPMForge など外部レポジトリからとってこないといけない
- KMP
kmodtool とは
後述するように,あるアーキテクチャの CPU に対応したカーネルでも,さまざまなバリエーション(PAE とか xen とか)が存在する。カーネルモジュールパッケージでは,これらのすべてのバリエーションに対応した単一の SPEC ファイルを書くこととなる。
しかし,そうすると %package -n ディレクティブを指定した SPEC 記述子を各バリエーションごとにたくさん書かなきゃいけない。それは非現実的。そこで,カーネルモジュールパッケージでは,kmodtool というヘルパスクリプトを利用して SPEC ファイルを書くことになっている。
基本的なカーネルモジュール用 SPEC ファイルでは,下記のような記述がある。
Source10: kmodtool-%{kmod_name} # ...... snip snip snip ...... # Magic hidden here. %define kmodtool sh %{SOURCE10} %{expand:%(%{kmodtool} rpmtemplate_kmp %{kmod_name} %{kversion} %{kvariants} 2>/dev/null)}
Magic hidden here 以下の部分で,kmodtool コマンドを呼び出して展開しているのがわかる。
kmodtool は redhat-rpm-config パッケージがインストールされていれば,/usr/lib/rpm/redhat/ ディレクトリにインストールされている。(本来 SOURCES/ ディレクトリにコピーしてそれを使うべきなんだけど)直接叩いてみる。
$ /usr/lib/rpm/redhat/kmodtool --help
Error: Unknown option '--help'.
You called: kmodtool --help
Usage: kmodtool <command> <option>+
Commands:
verrel <uname>
- Get "base" version-release.
variant <uname>
- Get variant from uname.
rpmtemplate <mainpgkname> <uname> <variants>
- Return a template for use in a source RPM
rpmtemplate_kmp <mainpgkname> <uname> <variants>
- Return a template for use in a source RPM with KMP dependencies
version
- Output version number and exit.
いくつかコマンドがあるけど,重要なのは rpmtemplate* というターゲット。これでさっきいった SPEC ファイルのテンプレート断片を生成することができる。
実際に生成されるテンプレート断面をみてみよう。
$ /usr/lib/rpm/redhat/kmodtool rpmtemplate_kmp foo `uname -r` ""
foo というのはダミーのカーネルモジュール名。`uname -r` でカーネル本体のバージョン番号を指定している。最後の「""」というところで,「バリエーション」を指定している。
これを実行すると,標準出力に SPEC ファイルの断片が出力される。
%package -n kmod-foo Summary: foo kernel module(s) Group: System Environment/Kernel %global _use_internal_dependency_generator 0 Provides: kernel-modules = 2.6.18-128.2.1.el5 Provides: foo-kmod = %{?epoch:%{epoch}:}%{version}-%{release} Requires(post): /sbin/depmod Requires(postun): /sbin/depmod BuildRequires: kernel-devel-%{_target_cpu} = 2.6.18-128.2.1.el5 %description -n kmod-foo This package provides the foo kernel modules built for the Linux kernel 2.6.18-128.2.1.el5 for the %{_target_cpu} family of processors. ...... snip snip snip ......
今回はバリエーションとして「""」(空文字列)を指定したからこのように単一の RPM の SPEC が生成されたけど,バリエーションに複数指定することで %package -n の値が異なる複数のテンプレート断片が出力される。
rpmtemplate と rpmtemplate_kmp の違い
ちなみに,先ほどコマンドとして指定した rpmtemplate_kmp は KMP 版の SPEC ファイルテンプレートを生成するという意味になる。似たような rpmtemplate というのは旧来のカーネルモジュールパッケージ用の SPEC ファイルテンプレートだ。
両者の違いをみると,KMP 版の SPEC ファイルがどのようなことをしているかわかる。
$ diff -u foo.legacy foo.kmp --- foo.legacy 2009-07-28 12:06:18.000000000 +0900 +++ foo.kmp 2009-07-28 12:06:25.000000000 +0900 @@ -1,9 +1,9 @@ %package -n kmod-foo Summary: foo kernel module(s) Group: System Environment/Kernel +%global _use_internal_dependency_generator 0 Provides: kernel-modules = 2.6.18-128.2.1.el5 Provides: foo-kmod = %{?epoch:%{epoch}:}%{version}-%{release} -Requires: kernel-%{_target_cpu} = 2.6.18-128.2.1.el5 Requires(post): /sbin/depmod Requires(postun): /sbin/depmod BuildRequires: kernel-devel-%{_target_cpu} = 2.6.18-128.2.1.el5 @@ -14,8 +14,21 @@ if [ -e "/boot/System.map-2.6.18-128.2.1.el5" ]; then /sbin/depmod -aeF "/boot/System.map-2.6.18-128.2.1.el5" "2.6.18-128.2.1.el5" > /dev/null || : fi + +#modules=( $(rpm -ql kmod-foo | grep '\.ko$') ) +modules=( $(find /lib/modules/2.6.18-128.2.1.el5/extra/foo | grep '\.ko$') ) +if [ -x "/sbin/weak-modules" ]; then + printf '%s\n' "${modules[@]}" | /sbin/weak-modules --add-modules +fi +%preun -n kmod-foo +rpm -ql kmod-foo | grep '\.ko$' > /var/run/rpm-kmod-foo-modules %postun -n kmod-foo /sbin/depmod -aF /boot/System.map-2.6.18-128.2.1.el5 2.6.18-128.2.1.el5 &> /dev/null || : +modules=( $(cat /var/run/rpm-kmod-foo-modules) ) +#rm /var/run/rpm-kmod-foo-modules +if [ -x "/sbin/weak-modules" ]; then + printf '%s\n' "${modules[@]}" | /sbin/weak-modules --remove-modules +fi %files -n kmod-foo %defattr(644,root,root,755) /lib/modules/2.6.18-128.2.1.el5/
おおきな違いは,以下の2点。
- KMP 版では,kernel に対する Requires がなくなっている
- なのでカーネル本体のバージョンに束縛されない
- KMP 版では
%postや%preun,%postun時に/sbin/weak-modulesというコマンドを呼び出してなんかやっている
/sbin/weak-modules というのはシェルスクリプトなので,一読してみるといい。
これがやっていることをおおまかにいうと,
- カーネルのシンボルテーブルを調べて,互換性のあるバージョンかどうかを調べる(kABI)
- 互換性がある場合,
/lib/modules/VERSION/weak-updates/ディレクトリにカーネルモジュールのリンクを貼る
なお,上記 SPEC template だと,この KMP カーネルモジュールパッケージのインストール・アンインストール時にのみ /sbin/weak-updates を呼び出しているみたいだけど,おそらく,カーネル本体のアップデート時にも呼び出されているんだと思う。
これによって,カーネルのバージョンがあがったときでも,もともとインストールしてあった KMP カーネルモジュールをロードできるようにしている。
Tips
RHEL (CentOS) では i686 カーネルモジュールビルド時は --target=CPU を付ける
$ rpmbuild -bb SPEC/kmod-foo.spec
ってやると,kernel-devel.i386 が見つからないよって怒られる。
なので,
$ rpmbuild -bb --target=i686 SPEC/kmod-foo.spec
のように,--target オプションで i686 を明示的に指定する。x86_64 の場合は指定しなくて大丈夫なんじゃないかな。
i686 の場合,(自分さえ都合がよければ)--define 'kvariants ""' を付ける
じゃあってんで i686 で
$ rpmbuild -bb --target=i686 SPEC/kmod-foo.spec
ってやると,(PAE カーネルじゃない場合)kernel-PAE-devel や kernel-xen-devel がないよって怒られる。
これは,(お手本に沿って書かれた spec では)対応するアーキテクチャのすべてのバリエーション(i686 だと PAE とか,x86_64 でも xen とか,sparc だと smp とか)のパッケージを生成しようとするから。
kvariants という変数を指定することで,ビルドするバリエーションを限定できる。
$ rpmbuild -bb --target=i686 --define 'kvariants ""' SPEC/kmod-foo.spec
とすると,無印版のカーネルに対応したモジュールだけビルドする。
あるいは,たとえば,無印版と xen に対応したモジュールをビルドしたいなら,
$ rpmbuild -bb --target=i686 --define 'kvariants "" xen' SPEC/kmod-foo.spec
のようにする。
KMP 対応の SPEC の書き方
HowTos/BuildingKernelModules - CentOS Wiki などのチュートリアルに沿って SPEC を書けばよい。
その上で,上記 kmodtool や KMP の知識があればいうことはない。
また,既存の KMP 対応モジュール(RHEL 本体に含まれるものや centosplus や CentOS extra に含まれるもの)の SPEC ファイルも参考になる。
kmodtool をどのようにソースパッケージ(SRPM)に含めるか
前述したように,redhat-rpm-config パッケージをインストールしてあれば*1,kmodtool は /usr/lib/rpm/redhat/ 以下にインストールされている。
だから SRPM に含めずにそれを使ってもよさそうなんだけど,SOURCES/ ディレクトリにコピーしておいて,それを利用するのが慣例になっているみたい。
その理由は,第一には redhat-rpm-config がアップデートした際に kmodtool も置き換わってしまうので,非互換になってしまう可能性があるからだと思う。
第二の理由は,自分が作成したい KMP カーネルモジュールが他のモジュールに依存している場合,つまり Requires: を書きたい場合は,SPEC ファイル本体でなく kmodtool の生成スクリプト部分をいじる必要があるから。
その場合は HowTos/BuildingKernelModules - CentOS Wiki に書かれていることを参考にする。また,その文書だと,kmodtool を,たとえば kmodtool-foo のようにリネームして使ったほうがよいと書いてある。他の KMP な SRPM で提供される kmodtool とのバッティングを防ぐには,たしかにそのようにしたほうがよい*2。
KMP の場合の kversion のデフォルト値はどうするか
HowTos/BuildingKernelModules - CentOS Wiki のお手本だと,カーネルバージョンの指定部分は下記のようになっている。
# If kversion isn't defined on the rpmbuild line, build for the current kernel.
%{!?kversion: %define kversion %(uname -r)}
つまり,rpmbuild 実行時にコマンドラインで指定されていなかった場合,uname -r で指定されたカーネルバージョン――すなわち現在動いているカーネルのバージョン――になる。
自分のためにパッケージを作っているのなら,それでも構わないだろう。でも,もし汎用性のあるカーネルモジュールを作りたいのなら,RHEL 5.0 初期リリース時点のカーネルバージョン
%{!?kversion: %define kversion '2.6.18-8'}
や,(現行の最新アップデートである)RHEL 5.3 時点の初期リリース時のバージョン
%{!?kversion: %define kversion '2.6.18-128'}
を指定したほうがいい気がする。もちろん対応するバージョンの kernel-devel をインストールしておかなきゃいけないけど。
参考文献
- KMP (Kernel Module Package) について
- DKMS について
