Linuxデバイスドライバ開発

オライリー第三版(2.6系)を参考にドライバ開発を行います。動作確認はカーネル3.2で実施します。

ユーザランドからデバイスドライバを呼び出す

前回のハローワールドは「module_init」と「module_exit」というカーネルの機能を使用して、デバイスドライバをロードした時とアンロードした時にsyslogに文字を表示した。
public2016.hatenablog.com


さてハローワールドデバイスドライバはロードしてアンロードされるまでの間はどんな仕事をしているかというと・・・





なにもしない!





何もしません。メモリを無駄に食うだけ。

デバイスドライバは基本的に命令されたことをやるだけなので命令がなければ動けない。

で、前述のドライバはロードとアンロード命令に対応する機能しかないからロード後は動かない。

例えば「グッドナイトワールド」とsyslogに出す関数を実装しても、その関数は実行されないわけ。どうにかしてこのドライバに命令してグッドナイトワールド関数を実行したくても、ドライバ自身に「命令を受け付けるクチ」がないのでどうしようもない。



そんなドライバ何の役にも立たないから、今回は「命令を受け付けるクチ」を用意するよ。

ユーザランド側の処理

「命令を受け付けるクチ」を作るにはmknodコマンドを使う

mknod ファイル名 c メジャー番号 マイナー番号

ファイル名=「命令を受け付けるクチ」

デバイスドライバユーザランドではファイルとして表現されるんです。ファイル?なんでデバイスがファイル?って感じだよね。パソコンってマウスで操作したり音楽を鳴らせたりするけど、そういった機能を実現してるのは「メモリにデータを書き込む」とか「キャッシュから読みだしたデータをCPUが処理する」とかいった処理が複雑に関連してできてて、そう考えるとファイルとして読み書きするってのもそんなに違和感なくない?

c ファイルの種類を示す文字

cはキャラクタデバイス。他にもb(ブロックデバイス)もある。ほとんどのデバイスはキャラクタデバイスとして実装するのが適しているからとりあえずしばらくはc固定で。

メジャー番号=ファイルとデバイスドライバを紐付ける超重要な数値

システム上は/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  92 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  92 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