ユーザランドからデバイスドライバを呼び出す
前回のハローワールドは「module_init」と「module_exit」というカーネルの機能を使用して、デバイスドライバをロードした時とアンロードした時にsyslogに文字を表示した。
public2016.hatenablog.com
さてハローワールドデバイスドライバはロードしてアンロードされるまでの間はどんな仕事をしているかというと・・・
なにもしない!
何もしません。メモリを無駄に食うだけ。
デバイスドライバは基本的に命令されたことをやるだけなので命令がなければ動けない。
で、前述のドライバはロードとアンロード命令に対応する機能しかないからロード後は動かない。
例えば「グッドナイトワールド」とsyslogに出す関数を実装しても、その関数は実行されないわけ。どうにかしてこのドライバに命令してグッドナイトワールド関数を実行したくても、ドライバ自身に「命令を受け付けるクチ」がないのでどうしようもない。
そんなドライバ何の役にも立たないから、今回は「命令を受け付けるクチ」を用意するよ。
ユーザランド側の処理
「命令を受け付けるクチ」を作るにはmknodコマンドを使う
mknod ファイル名 c メジャー番号 マイナー番号
ファイル名=「命令を受け付けるクチ」
デバイスドライバはユーザランドではファイルとして表現されるんです。ファイル?なんでデバイスがファイル?って感じだよね。パソコンってマウスで操作したり音楽を鳴らせたりするけど、そういった機能を実現してるのは「メモリにデータを書き込む」とか「キャッシュから読みだしたデータをCPUが処理する」とかいった処理が複雑に関連してできてて、そう考えるとファイルとして読み書きするってのもそんなに違和感なくない?
メジャー番号=ファイルとデバイスドライバを紐付ける超重要な数値
システム上は/proc/devicesで管理されてる
メジャー番号 ドライバ内で設定した名前
cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 6 lp 7 vcs 10 misc 13 input 21 sg 29 fb 99 ppdev 116 alsa 128 ptm 136 pts 180 usb 189 usb_device 216 rfcomm 252 hidraw 253 bsg 254 rtc
マイナー番号
1つのデバイスドライバで複数のハードウェアを制御する場合、ハードウェア毎に割り振ったりして使う。プリンターのドライバ一個入れておけばそのメーカーの複数型番のプリンターが使えるみたいな感じ。型番が違うけどだいたい処理は同じだから同じデバイスドライバで制御して、微妙な差異分はマイナー番号で型番を特定して型番毎に処理を分岐させたり。
まぁとりあえず作ってみましょ。
sudo mknod ./testdev c 250 0
普通はカレントディレクトリに作ったりしないけど細かいことは気にしない!
llコマンドで見てみると・・・
crw-r--r-- 1 root root 250, 0 9月 2 22:51 testdev
まず先頭がcになっている。これはさっき指定したc。250, 0も指定したメジャー番号とマイナー番号。よしよしちゃんと作れた。これで呼び出し側の準備はオッケー
デバイスドライバ側の処理
前回作ったハローワールドをベースにいくつか処理を追加した。
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> //プロトタイプ static __init int test_driver_init(void); static __exit void test_driver_exit(void); int test_driver_open(struct inode *inode, struct file *filp); int test_driver_release(struct inode *inode, struct file *filp); //グローバル変数 static struct file_operations test_driver_fops ={ .owner = THIS_MODULE, .open = test_driver_open, .release = test_driver_release, }; static dev_t dev_id; struct cdev c_dev; // マクロ module_init(test_driver_init); module_exit(test_driver_exit); MODULE_LICENSE("GPL2"); //デバイスドライバオープン関数 int test_driver_open(struct inode *inode, struct file *filp) { printk(KERN_DEBUG "open call \n"); return 0; } //デバイスドライバクローズ関数 int test_driver_release(struct inode *inode, struct file *filp) { printk(KERN_DEBUG "release call\n"); return 0; } //モジュール初期化関数 static int __init test_driver_init(void) { printk(KERN_DEBUG "init call\n"); dev_id = MKDEV(250 ,0); register_chrdev_region(dev_id,1,"test_driver_base"); cdev_init(&c_dev, &test_driver_fops); cdev_add(&c_dev, dev_id,4); return 0; } //モジュール終了関数 static void __exit test_driver_exit(void) { printk(KERN_DEBUG "_init exit\n"); cdev_del(&c_dev); unregister_chrdev_region(dev_id, 1); }
ハローワールドからの超重要な変更1つ目は
cdev構造体変数を追加
struct cdev c_dev;
この構造体変数はキャラクタデバイスを表す超超重要な変数。カーネルはこの変数を介して作成したデバイスドライバに仕事を振ってくる。
実際にc_devをどう使ってるかも見ていくよ〜。
変更2つ目は
ファイル操作時の関数登録
ハローワールドの時はデバイスのロードとアンロードだけをハンドリングしたけど、それだけじゃユーザランドからの呼び出し口がないからmknodでファイルを作った。そうするとカーネルさんがこのファイルへの操作(open/close/read/write等)をハンドリングしてドライバに伝えてくれるから、それに備えた実装をする必要がある
それを実現しているのがコレ↓
cdev_init(&c_dev, &test_driver_fops);
さっき述べた超超重要な構造体変数(c_dev)と、ファイル操作関数ポインタ群(file_operations構造体)を渡している。
ファイル操作関数ポインタ群っていうのは「mknodで作ったファイルがopenされたら、このドライバに定義しているtest_driver_openを呼んでね」という意味の関数ポインタなどを含んだ変数だ。
static struct file_operations test_driver_fops ={ .owner = THIS_MODULE, .open = test_driver_open, .release = test_driver_release, };
変更3つ目は
メジャー番号の取得
ファイル操作時の関数登録が終わって意気揚々とmknodで作ったファイルをオープン!・・・
しても失敗します。
なぜならtestdevはメジャー番号250のドライバに紐付いているから。
crw-r--r-- 1 root root 250, 0 9月 2 22:51 testdev
ファイル操作時の関数登録しただけではまだどのファイル(メジャー番号)とも紐付いていないのだ。
メジャー番号を登録しているのはこの2行
dev_id = MKDEV(250 ,0); register_chrdev_region(dev_id,1,"test_driver_base");
これを実行すると/proc/devices にメジャー番号とドライバ名前(任意)の関連が書き込まれる
sudo cat /proc/devices
Character devices:
・
・
250 test_driver_base
・
・
後処理もわすれずに
c_dev変数はカーネルからの呼び出しにつながっているから、終了時は明示的な開放が必要。メジャー番号も同様。
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
ユーザランドからデバイスドライバを呼び出す
さてようやく準備ができた
デバイスドライバをビルド(ハローワールドと同じ手順)してロードして・・・
ユーザランド側のコードをチョロチョロっと書いてみる
test_user.c
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() { int fd=open("./testdev",O_RDWR); printf("fd =%d \n",fd); close(fd); return 0; }
普通にビルド
gcc test_user.c
実行し、成功するとfc=正数となる
syslogにもprintkで書いたopenやcloseのメッセージが出る。
ユーザランドからデバイスドライバの最もシンプルな呼び出し、完了!
次回はreadを追加するよー。
public2016.hatenablog.com