1 #define __KERNEL__
2 #define MODULE
3
4 #include <linux/errno.h>
5 #include <linux/fs.h>
6 #include <linux/module.h>
7 #include <linux/mm.h>
8
9 static unsigned int counter = 0, busy = 0, eof = 0;
10
11 static int counter_open (struct inode * inode, struct file * file) {
12 if (busy) return - EBUSY;
13
14 busy = 1;
15 MOD_INC_USE_COUNT;
16
17 eof = 0;
18
19 return 0;
20 }
21
22 static void counter_release (struct inode * inode, struct file * file) {
23 busy = 0;
24 MOD_DEC_USE_COUNT;
25 }
26
27 static int counter_read (struct inode * inode, struct file * file,
28 char * buf, int count) {
29 char str [16];
30 int len, i;
31
32 if (eof) return 0;
33
34 sprintf (str, "%d\n", counter ++);
35 len = strlen (str) + 1;
36
37 i = verify_area (VERIFY_WRITE, buf, len);
38 if (i) return i;
39
40 memcpy_tofs (buf, str, len);
41
42 eof = 1;
43
44 return len;
45 }
46
47 static struct file_operations counter_fops = {
48 NULL, /* counter_lseek */
49 counter_read,
50 NULL, /* counter_write */
51 NULL, /* counter_readdir */
52 NULL, /* counter_select */
53 NULL, /* counter_ioctl */
54 NULL, /* counter_mmap */
55 counter_open,
56 counter_release,
57 NULL, /* counter_fsync */
58 NULL, /* counter_fasync */
59 NULL, /* counter_check_media_change */
60 NULL, /* counter_revalidate */
61 };
62
63 int init_module (void) {
64 int i;
65
66 i = register_chrdev (63, "counter", & counter_fops);
67 if (i) return - EIO;
68
69 return 0;
70 }
71
72 void cleanup_module (void) {
73 unregister_chrdev (63, "counter");
74 }
入力できたら、以下の操作を行ってコンパイルと組み込みをします。rootでないと実行できないコマンドも含まれていますが、この文章をお読みのみなさんはもちろんrootでしょう。:-)
% gcc -Wall -O2 -c counter.c
% su
Password:
# insmod counter.o
# mknod -m 444 /dev/counter u 63 0
# exit
exit
うまくいったら「cat /dev/counter」を繰り返し実行してみてください。どうですか、おもしろいでしょう?(つまらなかったらごめんなさい。)
「どこか『デバイス』ドライバなんだ」という話もありますが、基本的な枠組みは本当のデバイスドライバでも同じですので、以下はこれを例として説明していきたいと思います。このように必ずしも実際のハードウエアを伴わないデバイスを仮想デバイスといいます(やや嘘)。
% gcc -Wall -O2 -c counter.c
counter.cをコンパイルして、counter.oを作ります。
ポイントは、オブジェクトファイルのみを作って、実行ファイルは作らないという点です。実はこのドライバは「installable kernel module」という形式をとっています。「installable kernel module」というのは、カーネルを再構築したり、システムを再起動したりせずに、カーネルに新しい機能を追加するものです。ダイナミックリンクライブラリと似ていますが、ユーザプログラムが使用するライブラリではなく、カーネルに組み込まれるモジュールであるという点が異なります。あくまでモジュールなので、実行ファイルではなくオブジェクトファイルなのです。
# insmod counter.o
たったいま作成したモジュールを組み込みます。現在どういうモジュールが組み込まれているかは「lsmod」というコマンドでわかります。モジュールを削除するには「rmmod」というコマンドを使います。
# mknod -m 444 /dev/counter u 63 0
counterのためのデバイスファイルを作成します。メジャー番号63番、マイナー番号0番のキャラクタデバイスです。
メジャー番号は割り当てが決まっているので、本来勝手に使ってはいけないものです。割り当ては、カーネルソースに付属しているDocumentation/devices.txtに書かれています。ただし、それをよく読むと
60-63 LOCAL/EXPERIMENTAL USE Allocated for local/experimental use. For devices not assigned official numbers, these ranges should be used, in order to avoid conflicting with future assignments.
とあるので、60〜63番はこのような実験に使っても良いのです。
1 #define __KERNEL__
2 #define MODULE
3
4 #include <linux/errno.h>
5 #include <linux/fs.h>
6 #include <linux/module.h>
7 #include <linux/mm.h>
必要なヘッダファイルをインクルードします。カーネルに組み込むモジュールであることを表すために、__KERNEL__およびMODULEを#defineします。この#defineはヘッダファイルの中で参照されているので、#includeより前でしなければなりません。
9 static unsigned int counter = 0, busy = 0, eof = 0;
モジュール内部で使用する変数を宣言します。
先頭の「static」は重要です。組み込み時の名前衝突を回避するために、モジュールの外部から参照されない名前は、すべてstaticにしなければいけません。
各変数の意味は後になればわかります。
11 static int counter_open (struct inode * inode, struct file * file) {
12 if (busy) return - EBUSY;
13
14 busy = 1;
15 MOD_INC_USE_COUNT;
16
17 eof = 0;
18
19 return 0;
20 }
デバイスファイル(/dev/counter)がopenされると呼ばれる関数を用意しておきます。何やら二つほど引数がありますが、今回は関係ないので気にしません。(^^;
一度に多数openされるとややこしいので、busyという変数を使って排他制御を行ないます。「これじゃあ排他制御になってないじゃん」と思うかもしれませんが、Linuxではカーネルモードのプロセスはプリエンプトされないので問題ありません(と思う)。
MOD_INC_USE_COUNTは、モジュールが使用中であることを示す値を一つ増やします。この値が0でなければモジュールを削除できないので、誤って使用中のモジュールを削除してしまうおそれがなくなります。
22 static void counter_release (struct inode * inode, struct file * file) {
23 busy = 0;
24 MOD_DEC_USE_COUNT;
25 }
デバイスファイルがcloseされると呼ばれる関数を用意しています。MOD_DEC_USE_COUNTは、MOD_INC_USE_COUNTの逆です。これを忘れると、モジュールがずっと使用中になって、削除できなくなってしまいます。
27 static int counter_read (struct inode * inode, struct file * file,
28 char * buf, int count) {
29 char str [16];
30 int len, i;
31
32 if (eof) return 0;
33
34 sprintf (str, "%d\n", counter ++);
35 len = strlen (str) + 1;
36
37 i = verify_area (VERIFY_WRITE, buf, len);
38 if (i) return i;
39
40 memcpy_tofs (buf, str, len);
41
42 eof = 1;
43
44 return len;
45 }
デバイスファイルがreadされたとき呼ばれる関数を用意します。このドライバの中核部です(笑)。現在のcounterの値を10進数の文字列に直し、それをユーザプロセスが用意したバッファにコピーします。
ここで注意しなければならないのは、ユーザプロセスが用意したバッファはユーザメモリ空間にあるので、普通のポインタを使ったメモリ操作ではなく、専用のマクロ(memcpy_tofs)を使って書き込みを行うということです。また、不当な領域に書き込みを行わないよう、あらかじめverify_areaという関数で、渡されたアドレスの正当性をチェックしなければいけません。
なお、ここで使っているsprintfとstrlenは(名前と意味は同じでも)決して普通のライブラリ関数ではなく、モジュールのためにカーネルがエクスポートしている関数、ないしはヘッダに含まれているマクロです。
47 static struct file_operations counter_fops = {
48 NULL, /* counter_lseek */
49 counter_read,
50 NULL, /* counter_write */
51 NULL, /* counter_readdir */
52 NULL, /* counter_select */
53 NULL, /* counter_ioctl */
54 NULL, /* counter_mmap */
55 counter_open,
56 counter_release,
57 NULL, /* counter_fsync */
58 NULL, /* counter_fasync */
59 NULL, /* counter_check_media_change */
60 NULL, /* counter_revalidate */
61 };
デバイスファイルを操作したとき呼び出される関数を登録するための構造体です。次で説明するデバイスの登録のとき使用します。lseekやらmmapやらいろいろなインターフェースが用意されていますが、使わないところはNULLで構いません。
63 int init_module (void) {
64 int i;
65
66 i = register_chrdev (63, "counter", & counter_fops);
67 if (i) return - EIO;
68
69 return 0;
70 }
モジュールを組み込んだときに呼び出される関数です。したがって、この関数はstaticにしてはいけません。また、名前もこの通りにしなければなりません。
register_chrdevで、メジャー番号63番のcounterという名前のデバイスを登録します。ちなみに、現在登録されているデバイスの一覧は「cat /proc/devices」で見られます。
72 void cleanup_module (void) {
73 unregister_chrdev (63, "counter");
74 }
モジュールを削除したときに呼び出される関数です。init_moduleと同様に、staticにしてはならず、名前も変えてはいけません。
init_moduleで登録したデバイスを、unregister_chrdevで忘れずに削除しておきます。
が参考になるでしょう。