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
パッケージをインストールしてあれば((EPEL の rpmdevtools
パッケージをインストールしてれば依存関係のおかげでたぶん同時にインストールされていると思う。)),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
とのバッティングを防ぐには,たしかにそのようにしたほうがよい((しかし,個人的には kmodtool
という名前そのもので利用しているカーネルモジュールパッケージしか見たことがない。もちろんプライベートコピーを作ったほうがよいのはいうまでもない。))。
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 について