Tag List for デバイスドライバ

デバイスドライバ

デバイスとは、比較的大きな単位を持った機器、装置のことです。 例えば、CPUやメモリ、グラフィックボードやディスプレイ、マウスやキーボード、プリンタなどのことです。 ハードウェアと同意と考えても問題ないでしょう。 カーネルはそれらデバイス全てを管理しています。 しかし、例えば「プリンタ」と言っても、市場には様々なメーカー、機種が出回っています。 メーカーや機種が異なれば、機能や操作方法も異なります。 カラー印刷できるプリンタもあれば、両面印刷できるプリンタもあります。 それはつまり、プリンタの機種が異なれば、その数だけ、カーネル側でもコードが必要となるわけです。 しかし、プリンタの機種が異なっても、同じプリンタですから、共通する機能があります。 そのような共通部分をそれぞれのプリンタごとに用意してたら無駄です。 そのため、共通化できる部分はカーネルが受け持ち、それ以外の部分は独立させ、交換可能にしておくのが都合が良いです。 その独立した部分がデバイスドライバです。 デバイスドライバの定義はカーネルに包括されます。 つまり、デバイスドライバもカーネルの一部となります。

開発環境構築

デバイスドライバをビルドするためには、カーネルバージョンと同一バージョンの、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 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関数です。 […]