開発環境構築
デバイスドライバをビルドするためには、カーネルバージョンと同一バージョンの、kernel-develとkernel-headersパッケージが必要です。 そのため、まずは自身の環境のカーネルバージョンと上記2つのパッケージがインストールされているかどうかを調べます。 # uname -r 2.6.18-238.el5 # rpm -aq | grep kernel kernel-headers-2.6.18-238.el5 kernel-devel-2.6.18-238.el5 上記例では、カーネルのバージョンが「2.6.18-238.el5」であり、同バージョンのkernel-develとkernel-headersパッケージがインストールされています。 もし、インストールされていないのであれば、yumなどを使いインストールします。 # yum install kernel-devel # yum install kernel-headers yumでバージョンが揃わなければ、rpmなど別の方法でインストールすることになります。
ドライバのバージョン確認
ドライバをビルドした環境のカーネルバージョンと、そのドライバをロードする環境のカーネルバージョンが異なると、insmod(ドライバをロードするコマンド)でエラーが発生します。 必ず両バージョンは一致させる必要があります。 開発マシンと動作マシンが同じであるなら問題ありませんが、異なるとそのようなエラーに遭遇することが多いので、注意が必要です。 ドライバをビルドした環境のカーネルバージョンはmodinfoコマンドにより確認できます。 # modinfo hello.ko filename: hello.ko license: Dual BSD/GPL srcversion: 128F11CE82080B148485924 depends: vermagic: 2.6.18-238.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1 いくつか項目が出力されますが、今回着目するのは、「vermagic」項目になります。 「vermagic」項目の内容を分割すると下表のようになります。 内容意味 2.6.18-238.el5ビルドした環境のカーネルバージョン SMPマルチプロセッサ対応 mod_unloadドライバアンロード可能 686対応するアーキテクチャ 4KSTACKSカーネルスタックを8KBから4KBにする 「SMP」はマルチプロセッサ対応を意味します。 最近のCPUはマルチコアとなっているため、「SMP」がつくことがほとんどです。
HelloWorld作成
新しいプログラミング言語を覚えるとき、一番初めに書くプログラムは大抵「HelloWorld」だと思います。それはデバイスドライバ開発でも例外ではありません。 まずは新しいディレクトリを作成し、その中に、下記コードを記述した「hello.c」という名前のファイルを作成してください。 #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE(“Dual BSD/GPL”); static int hello_init(void) { printk(KERN_ALERT “driver loaded\n”); printk(KERN_ALERT “Hello World\n”); return 0; } static void hello_exit(void) { printk(KERN_ALERT “driver unloaded\n”); } module_init(hello_init); module_exit(hello_exit); 次にMakefileを作成します。 obj-m := hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean これで、カレントディレクトリには「hello.c」と「Makefile」が存在していることになります。 # ls hello.c Makefile あとは上記プログラムをビルドしてみましょう。 # make make -C /lib/modules/2.6.18-238.el5/build M=/root/Desktop/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.18-238.el5-i686′ CC [M] /root/Desktop/kernel/hello.o Building modules, stage 2. MODPOST CC /root/Desktop/kernel/hello.mod.o LD [M] /root/Desktop/kernel/hello.ko make[1]: Leaving directory `/usr/src/kernels/2.6.18-238.el5-i686′ ビルド後、カレントディレクトリには、下記のようなファイルが作成されているはずです。 ・Module.symvers ・hello.ko ・hello.mod.c ・hello.mod.o ・hello.o # ls hello.c hello.mod.c hello.o Module.symvers hello.ko hello.mod.o Makefile ビルド後、上記のように5つのファイルが生成されていたらビルド成功です。 ソースコードの解説は後から行います。 まずは、ひととおりのドライバ作成手順を見ていきます。
ドライバのロード・アンロード
ドライバをビルドしただけでは、まだそのドライバは使えません。 使えるようにするためには、ドライバをロードしてやる必要があります。 ドライバのロードはinsmodコマンドにより行います。 koファイルがあるディレクトリに移動し、下記コマンドを実行します。 # insmod hello.ko ドライバのロードはランレベル3以降でも行えます。 それはつまり、OSの再起動なしにドライバのロード・アンロードができるということです。 ただし、insmodコマンドはroot権限で実行する必要があります。 ドライバが正常にロードされたかどうかの確認はlsmodコマンドで行えます。 # lsmod Module Size Used by hello 5504 0 ~以下省略~ lsmodコマンドは現在ロードされているカーネルモジュールを出力します。 ドライバはカーネルモジュールに含まれるので、正常にロードできていれば出力されます。 lsmodコマンドで確認できる内容は下記表になります。 列名意味 Moduleドライバ名 Sizeメモリ上のドライバサイズ Used参照カウンタ by依存ドライバ ドライバのアンロードはrmmodコマンドにより行います。 # rmmod hello rmmodコマンドで指定するパラメータはドライバファイル名ではなく、ドライバ名となります。 ドライバ名はlsmodコマンドのModule列で確認できます。 ドライバのロードはinsmodコマンドだけでなく、modprobeコマンドでも行えます。 insmodコマンドはカレントディレクトリにあるドライバをロードするためのものです。 それに対し、modprobeコマンドは「/lib/modules/`uname -r`」からドライバを探します。 modprobeコマンドはLinux起動時に自動的にドライバをロードさせたいときなど、rcスクリプトでよく使用されます。
ソースファイルの分割
ドライバを複数人で開発する場合など、ソースファイルを分割して開発したい場合もあります。 その場合は、Makefileで複数のソースファイルを指定します。 以下、ソースファイル分割のサンプルです。 main.c #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE(“Dual BSD/GPL”); extern void sub(void); static int hello_init(void) { printk(KERN_ALERT “driver loaded\n”); sub(); return 0; } static void hello_exit(void) { printk(KERN_ALERT “driver unloaded\n”); } module_init(hello_init); module_exit(hello_exit); sub.c #include <linux/module.h> #include <linux/init.h> void sub(void) { printk(“%s: sub() called\n”, __func__); } Makefile CFILES = main.c sub.c obj-m += hello.o hello-objs := $(CFILES:.c=.o) all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 上記サンプルをビルドすると、main.cとsub.cのソースファイルから「hello.ko」が作成されます。 # ls main.c Makefile sub.c # make make -C /lib/modules/2.6.18-238.el5/build M=/root/Desktop/ksample/01 modules make[1]: Entering directory `/usr/src/kernels/2.6.18-238.el5-i686′ CC [M] /root/Desktop/ksample/01/main.o CC [M] /root/Desktop/ksample/01/sub.o LD [M] /root/Desktop/ksample/01/hello.o Building modules, stage 2. MODPOST CC /root/Desktop/ksample/01/hello.mod.o LD [M] /root/Desktop/ksample/01/hello.ko […]
HelloWorldソースコード解説
今回は、以前の記事にて作成したHelloWorldのソースコードについて見ていきます。 hello.c #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE(“Dual BSD/GPL”); static int hello_init(void) { printk(KERN_ALERT “driver loaded\n”); printk(KERN_ALERT “Hello World\n”); return 0; } static void hello_exit(void) { printk(KERN_ALERT “driver unloaded\n”); } module_init(hello_init); module_exit(hello_exit); まずは上記コードをビルドし、生成されたドライバはどのような動作をするのか確認します。 このドライバはinsmodコマンドなどによりロードされるとsyslogに「driver loaded」と「Hellow World」という文字列を出力します。 # insmod hello.ko # tail -n 2 /var/log/messages Aug 17 05:31:03 localhost kernel: driver loaded Aug 17 05:31:03 localhost kernel: Hello World tailコマンドはファイルの末尾部分を出力するコマンドです。 -nオプションをつけることで、出力する行数を指定できます。 今度はドライバ「hello」をアンロードします。 # rmmod hello # tail -n 1 /var/log/messages Aug 17 19:07:17 localhost kernel: driver unloaded アンロードすると、syslogに「driver unloaded」という文字列が出力されます。 では、ソースコードの解説に移ります。 #include <linux/module.h> #include <linux/init.h> アプリケーションを開発する場合、インクルードするヘッダファイルは「stdio.h」や「string.h」などになると思いますが、それらはユーザー空間で使用するライブラリであり、ドライバ開発ではインクルードしません。 代わりに、「linux/module.h」や「linux/init.h」などをインクルードします。 MODULE_LICENSE(“Dual BSD/GPL”); この行はMODULE_LICENSEマクロでドライバのライセンスを定義しています。 この行を記述することによりこのドライバは「GPLである」と明示したことになり、GPL規約に準拠する必要があります。 この行がなくてもコンパイルは通りますが、警告が出力されます。 static int hello_init(void) { printk(KERN_ALERT “driver loaded\n”); printk(KERN_ALERT “Hello World\n”); return 0; } 上記で定義している「hello_init」関数はドライバロード時、つまりinsmodコマンド実行時などに呼び出されるエントリポイントです。 ロード時のエントリポイントの指定は「module_init」マクロで行います。 アプリケーションのエントリポイントは必然的にmain関数となりますが、ドライバの場合は「module_init」マクロでエントリポイントを指定することになります。 戻り値に「0」を返すことで、insmodコマンドは成功します。ロード時の処理で何らかのエラーが発生した場合は0以外を返します。(上記サンプルでは、無条件で「0」を返しています。) 関数内ではprintk関数を呼び出しています。 似た関数としておなじみのprintf関数がありますが、ドライバでは標準ライブラリをインクルードしていないので当然使えません。 というよりそもそも、カーネル空間で動作するドライバはコンソール画面(標準出力)がないので、使いようがありません。 しかし、それではデバックが大変なので代わりに用意されたのがprintk関数です。 […]