概要
テストで使用するため、任意の値を返すデバイスドライバを作成したい。
通常ではテストしにくいデータパターン、大規模、境界条件を簡単にテストできる環境が欲しい為である。
環境
- CentOS 7.4 (kernel 3.10.0-693.21.1.el7.x86_64)
- Windows 10 Hyper-V
コンテナ上で開発し、開発したカーネルモジュールはホスト上でテスト動作させる。
+-------------------+
| device_fileコンテナ | ← 開発環境
+-------------------+
| Dockerホスト | ← テスト環境
+-------------------+
開発環境の準備
以下を参考に、kernel-develなどのカーネルモジュール開発に必要なものを入手する。
http://inaz2.hatenablog.com/entry/2014/10/09/002750
Dockerホストにて、開発用のコンテナを作成する。
docker run -it --name device_file centos /bin/bash
docker exec -it device_file /bin/bash
で、開発用コンテナに接続したら、yumを使ってパッケージをインストールする。
yum -y update
yum -y groupinstall "Development Tools”
kernel-develはyumで指定したバージョンを入手出来なかったので、自分が使用しているカーネルバージョンに合ったrpmを、以下のURLからcurlで入手。(curlでwgetと同じことができることを今回始めて知った)
ftp://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/kernel-devel.html
[root@b22e70c86903 tmp]# uname -r
3.10.0-693.21.1.el7.x86_64
[root@b22e70c86903 tmp]# curl -OL ftp://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/Packages/kernel-devel-3.10.0-693.21.1.el7.x86_64.rpm
[root@b22e70c86903 tmp]# rpm -ivh kernel-devel-3.10.0-693.21.1.el7.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:kernel-devel-3.10.0-693.21.1.el7 ################################# [100%]
3.10.0-693.21.1.el7.x86_64
が追加されていることを確認
[root@b22e70c86903 kernels]# pwd
/usr/src/kernels
[root@b22e70c86903 kernels]# ls
3.10.0-693.21.1.el7.x86_64 3.10.0-862.9.1.el7.x86_64.debug
基本的なカーネルモジュールをビルドしてカーネルにロードする
組み込みLinuxデバイスドライバの作り方 (1) を参考に進める
参考 https://qiita.com/take-iwiw/items/1fdd2e0faaaa868a2db2
参考 https://qiita.com/ymko/items/da0eb8f3dd57bf541321
/lib/modules/ にソースが配置されてない為、参考ページと同じMakefileが使用できないので、/usr/src/kernels/
からシンボリックリンクを貼る。
ln -s /usr/src/kernels/3.10.0-693.21.1.el7.x86_64/ /lib/modules/3.10.0-693.21.1.el7.x86_64/build
Makefileを用意する。
CFILES = myDeviceDriver.c
obj-m := MyDeviceModule.o
MyDeviceModule-objs := $(CFILES:.c=.o)
ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
kernel moduleのファイルを用意する。
[butada@b22e70c86903 hello]$ cat myDeviceDriver.c
#include <linux/module.h>
static int test_init(void)
{
printk("Hello my module\n");
return 0;
}
static void test_exit(void)
{
printk("Bye bye my module\n");
}
module_init(test_init);
module_exit(test_exit);
makeする。
[butada@b22e70c86903 hello]$ make
make -C /lib/modules/3.10.0-693.21.1.el7.x86_64/build M=/home/butada/hello KBUILD_VERBOSE=0 modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
CC [M] /home/butada/hello/myDeviceDriver.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/butada/hello/MyDeviceModule.mod.o
LD [M] /home/butada/hello/MyDeviceModule.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
ビルドしたモジュールを ホストで insmodするので、
コンテナからホストへコピー
$ mkdir /tmp/my_device_file
$ cd /tmp/my_device_file
$ ./copy-device-file.sh
#!/bin/bash
sudo docker cp device_file:/home/butada/hello /tmp/hello_device_file/
バージョンが合っていれば、
insmod も rmmod も成功する。
[butada@dell-vm hello]$ sudo insmod MyDeviceModule.ko
[butada@dell-vm hello]$ sudo rmmod MyDeviceModule.ko
[butada@dell-vm hello]$ sudo dmesg | tail
[36289.529627] hello: module verification failed: signature and/or required key missing - tainting kernel
[36289.531633] Hello my module
[36474.411107] Bye bye my module
もし、誤ったkernel-develバージョンを使ってmakeしてしまうと、カーネルバージョンが合わないので、以下のようなエラーになる。
[butada@dell-vm hello]$ sudo insmod MyDeviceModule.ko
insmod: ERROR: could not insert module MyDeviceModule.ko: Invalid module format
dmesg | tail
[29065.807235] hello: disagrees about version of symbol module_layout
デバイスファイルに対してwrite/readをする
組み込みLinuxデバイスドライバの作り方 (2) を参考に進める
参考 https://qiita.com/take-iwiw/items/580ec7db2e88beeac3de
ビルドする。
[butada@b22e70c86903 hello]$ make
make -C /lib/modules/3.10.0-693.21.1.el7.x86_64/build M=/home/butada/hello modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
CC [M] /home/butada/hello/myDeviceDriver.o
LD [M] /home/butada/hello/MyDeviceModule.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/butada/hello/MyDeviceModule.mod.o
LD [M] /home/butada/hello/MyDeviceModule.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
insmodする。
[butada@dell-vm hello]$ sudo insmod MyDeviceModule.ko
[butada@dell-vm hello]$ sudo rmmod MyDeviceModule.ko
[butada@dell-vm hello]$ sudo dmesg | tail
[36474.411107] hello module rmmod
[38163.488759] myDevice_init
[38171.302157] myDevice_exit
insmodしたデバイスが、cat /proc/devices
に載っていることを確認する。
[butada@dell-vm hello]$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
63 MyDevice_NAME // <-- ここ
116 alsa
:
:
デバイスファイルを作成する。
(crw-rw-rw- の c ってキャラクターデバイスのことだったのね。。)
[butada@dell-vm hello]$ sudo mknod /dev/myDevice c 63 1
[butada@dell-vm hello]$ sudo chmod 666 /dev/myDevice
[butada@dell-vm hello]$ ls -la /dev/myDevice
crw-rw-rw-. 1 root root 63, 1 7月 28 11:26 /dev/myDevice
デバイスファイルにwriteアクセスしてみるが、dmesgのメッセージが改行されていない。
これは、参考にしているソースのprintkで '\n'
を付け忘れている箇所があるためなので、適宜修正しておく。
[butada@dell-vm hello]$ sudo echo "a" > /dev/myDevice
[butada@dell-vm hello]$ dmesg | tail
[38171.302157] myDevice_exit
[38231.014767] myDevice_init
[39008.963143] myDevice_openmyDevice_writemyDevice_writemyDevice_closemyDevice_openmyDevice_writemyDevice_writemyDevice_close
[39053.369113] myDevice_open
[39053.457665] myDevice_writemyDevice_write
[39053.460154] myDevice_close
readアクセスもしてみるが、cat
をしても返ってこない。 Ctrl-C
をしても返ってこない。。
どうもラズパイとは異なり、この実装だけではきちんと動作しないようだ。
[butada@dell-vm hello]$ cat /dev/myDevice
組み込みLinuxデバイスドライバの作り方 (3) を参考に進める
参考 https://qiita.com/take-iwiw/items/6b02494a3668f79800e6#_reference-396266a1e31288600b37
ビルドする。
[butada@b22e70c86903 hello]$ make
make -C /lib/modules/3.10.0-693.21.1.el7.x86_64/build M=/home/butada/hello modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
CC [M] /home/butada/hello/myDeviceDriver.o
LD [M] /home/butada/hello/MyDeviceModule.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/butada/hello/MyDeviceModule.mod.o
LD [M] /home/butada/hello/MyDeviceModule.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-693.21.1.el7.x86_64'
モジュールのロードと、デバイスファイルの作成
[butada@dell-vm hello]$ sudo insmod MyDeviceModule.ko
[butada@dell-vm hello]$ sudo mknod --mode=666 /dev/mydevice0 c `grep MyDevice /proc/devices | awk '{print $1;}'` 0
[butada@dell-vm hello]$ sudo mknod --mode=666 /dev/mydevice1 c `grep MyDevice /proc/devices | awk '{print $1;}'` 1
[butada@dell-vm hello]$ sudo mknod --mode=666 /dev/mydevice2 c `grep MyDevice /proc/devices | awk '{print $1;}'` 2
毎回実施すると面倒なので、スクリプトを作っておく。
#!/bin/bash
sudo rmmod hello/MyDeviceModule.ko
sudo insmod hello/MyDeviceModule.ko
sudo mknod --mode=666 /dev/mydevice0 c `grep MyDevice /proc/devices | awk '{print $1;}'` 0
sudo mknod --mode=666 /dev/mydevice1 c `grep MyDevice /proc/devices | awk '{print $1;}'` 1
まず、デバイスファイルから2バイト取得を/dev/random/に対してddをお試し。
ちゃんとランダムな2バイトが返ってきた。
[butada@dell-vm hello]$ sudo dd if=/dev/random of=tmp.txt bs=1 count=2; cat tmp.txt
2+0 レコード入力
2+0 レコード出力
2 バイト (2 B) コピーされました、 0.000655 秒、 3.1 kB/秒
Pd
次に、自作のデバイスファイルに対してddを実行したところ、 カーネルパニック が発生。
[butada@dell-vm hello]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1 count=1; cat tmp.txt
packet_write_wait:
カーネルパニック時のdmesgを確認
- loading out-of-tree module taints kernel
- module verification failed
- BUG: unable to handle kernel paging request at 000000000223f000
- Oops: 0003 [#1] SMP
1のloading out-of-tree module taints kernel
は問題なし。
正常にロード出来たときのメッセージの様子。
(https://qiita.com/satoru_takeuchi/items/83c8e2f38176d2724f48)
2のmodule verification failed
はプロプライエタリドライバなので問題なし。
(https://qiita.com/gm3d2/items/8346c76961d3fdb257b7)
3~4のOops: 0003 [#1] SMP
はRed Hat Linuxでも同様の不具合がある様子だが、このカーネルパニックとの関連は不明。(たぶん無関係) いずれにせよ、0003はSWAPに関係しているエラーらしく、仮想メモリ領域を不正にアクセスしたのが原因と思われる。
組み込みLinuxデバイスドライバの作り方 (4) を参考に進める
参考 https://qiita.com/take-iwiw/items/26d5f7f4894ccc4ce227
「組み込みLinuxデバイスドライバの作り方 (4) 」を読んで、カーネルパニックの原因がわかった。
なお、本文のソースを見てもincludeなど不明なところが有ったので、GitHubからソースを入手。
https://github.com/take-iwiw/DeviceDriverLesson/blob/master/04_02/myDeviceDriver.c
insmodしてmknodする
$ ./load_and_mknod.sh
ddしてreadが・・・ついにカーネルパニックしない。
buf
に対して直接アクセスするのではなく、 copy_to_user
を使用してアドレス変換を考慮するのが必須らしい。
「d」がtmp.txtに記録されているが、これはinitで初期化しているdummyの先頭文字。
[butada@dell-vm hello_device_file]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1 count=1; cat tmp.txt
1+0 レコード入力
1+0 レコード出力
1 バイト (1 B) コピーされました、 0.0002943 秒、 3.4 kB/秒
d
ファイルディスクリプタごと、デバイスファイルごと、に独立してwrite/readできることを確認する。
test.cの実行結果
[butada@dell-vm hello]$ sudo ./a.out
DB092A40
0_A
0_B
1_A
[butada@dell-vm hello]$ sudo ./a.out
FF9892E0
0_A
0_B
1_A
2行目はバッファの中身なので不定値。
write時に書き込まれる先は、デバイスファイル内ではなくファイルディスクリプタのprivate_data構造体である。read時に読み出す元も、ファイルディスクリプタのprivate_data構造体である。
3行目と4行目は、同じデバイスファイルだが、オープンしているファイルディスクリプタが異なるため、pricate_data構造体も異なるため、write/readが独立して動作し、互いに干渉しない。
大きなバイト数を指定しても、コピーされたバイト数が増えない
大きなバイト数をreadするため、bsパラメータに与える値を増やす。
ところが、bs=256までは良いが、それ以上のbs=1Mや、bs=1Gは同じ結果で、256バイトしかコピーされない。。
[butada@dell-vm hello]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1M count=1
0+1 レコード入力
0+1 レコード出力
256 バイト (256 B) コピーされました、 0.0008258 秒、 310 kB/秒
[butada@dell-vm hello]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1G count=1
0+1 レコード入力
0+1 レコード出力
256 バイト (256 B) コピーされました、 0.0002483 秒、 1.0 MB/秒
readできるバイト数を増やす
ddのbs=1Mやbs=1Gに対応する。ソースを見ると、NUM_BUFFER定数があり、これが256になっていたので、1GBにする・・・?
そんな訳がないので、よく考えてみる。ddで1GBをコピーするときは、bs=1Gにするのではなく、bs=8K count=131072とするか?と考えたが、そのように使うのはレアケース。ユーザからの指示は1GBだが、実際に1度にこなせる量は、デバイスドライバで抑制可能のはず。ここでは、8KBとして実装する。ということで、NUM_BUFFERを、256(0.25Kバイト)から1024*1024(1024Kバイト)へ増やす。(4096倍)
ここまで大きくすれば128Kに対応できるはず。
/* read時に呼ばれる関数 */
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read\n");
printk("mydevice_read : count = %zu\n", count);
printk("mydevice_read : f_pos = %llu\n", *f_pos);
printk("mydevice_read : filp->f_pos = %llu\n", filp->f_pos);
if(count > NUM_BUFFER) count = NUM_BUFFER;
struct _mydevice_file_data *p = filp->private_data;
if (copy_to_user(buf, p->buffer, count) != 0) {
return -EFAULT;
}
return count;
}
bs=1000000000(1GB)でcount=2でやってみる。1MBまでしかないので、2MBになるはず。結果、**2097152 バイト (2.1 MB)**となった。(1024×1024×2÷1000÷1000=2.097152)
[butada@dell-vm hello_device_file]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1000000000 count=2; sudo dmesg | tail
dd: warning: partial read (1048576 bytes); suggest iflag=fullblock
0+2 レコード入力
0+2 レコード出力
2097152 バイト (2.1 MB) コピーされました、 0.0123341 秒、 170 MB/秒
[60585.253920] mydevice_open
[60585.257195] mydevice_read
[60585.257848] mydevice_read : count = 1000000000
[60585.258482] mydevice_read : f_pos = 18446612132914085656
[60585.259095] mydevice_read : filp->f_pos = 0
[60585.265629] mydevice_read
[60585.266195] mydevice_read : count = 1000000000
[60585.266698] mydevice_read : f_pos = 18446612132914085656
[60585.267237] mydevice_read : filp->f_pos = 0
[60585.268536] mydevice_close
warning: partial read (1048576 bytes); suggest iflag=fullblock
が出ているので、何らかの対策が必要そう。。(制限)
seekを試す
ddのseekを試したが、f_pos
もfilp->f_pos
も、共に変化がなく、どちらも0
。
countしてもseekしてもskipしても変わらない。
[butada@dell-vm hello_device_file]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=2 count=3 seek=1; cat tmp.txt; sudo dmesg | tail -20
3+0 レコード入力
3+0 レコード出力
6 バイト (6 B) コピーされました、 0.0075876 秒、 0.8 kB/秒
dudududu[61448.728951] mydevice_open
[61448.729583] mydevice_read
[61448.730144] mydevice_read : count = 2
[61448.730667] mydevice_read : f_pos = 0
[61448.731219] mydevice_read : filp->f_pos = 0
[61448.731754] mydevice_read
[61448.732302] mydevice_read : count = 2
[61448.732812] mydevice_read : f_pos = 0
[61448.733356] mydevice_read : filp->f_pos = 0
[61448.733870] mydevice_read
[61448.734396] mydevice_read : count = 2
[61448.734896] mydevice_read : f_pos = 0
[61448.735484] mydevice_read : filp->f_pos = 0
[61448.736186] mydevice_close
いろいろサイトを漁った結果、読み込んだバイト数でfilp->f_pos
を更新してやれば良さそうだったので、f_posの値を更新することにした。
/* read時に呼ばれる関数 */
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read\n");
printk("mydevice_read : count = %zu\n", count);
printk("mydevice_read : f_pos = %llu\n", *f_pos);
printk("mydevice_read : filp->f_pos = %llu\n", filp->f_pos);
if(count > NUM_BUFFER) count = NUM_BUFFER;
struct _mydevice_file_data *p = filp->private_data;
if (copy_to_user(buf, p->buffer, count) != 0) {
return -EFAULT;
}
*f_pos += count; // ここを追加した
return count;
}
すると、2回目と3回目のf_posが進んでいることが確認できる。
f_pos
が、0 → 1048576 → 2097152 のように増えている。(filp->f_pos
も増えている)
[butada@dell-vm hello_device_file]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=2000000 count=3; cat tmp.txt; sudo dmesg | tail -20
dd: warning: partial read (1048576 bytes); suggest iflag=fullblock
0+3 レコード入力
0+3 レコード出力
3145728 バイト (3.1 MB) コピーされました、 0.008744 秒、 360 MB/秒
dummdummdumm[90317.281705] mydevice_read : filp->f_pos = 1048576
[90352.873624] mydevice_open
[90352.874596] mydevice_read
[90352.874883] mydevice_read : count = 2000000
[90352.875189] mydevice_read : f_pos = 0
[90352.875557] mydevice_read : filp->f_pos = 0
[90352.876943] mydevice_read
[90352.877277] mydevice_read : count = 2000000
[90352.877619] mydevice_read : f_pos = 1048576
[90352.877889] mydevice_read : filp->f_pos = 1048576
[90352.879761] mydevice_read
[90352.880257] mydevice_read : count = 2000000
[90352.880584] mydevice_read : f_pos = 2097152
[90352.880981] mydevice_read : filp->f_pos = 2097152
[90352.882343] mydevice_close
[butada@dell-vm hello_device_file]$
ファイルディスクリプタ毎にf_posが独立して管理されていることを確認。
0_Aと0_Bは同じデバイスファイルで異なるファイルディスクリプタ、0_Aと1_Aは異なるデバイスファイル。
[butada@dell-vm hello]$ sudo ./a.out
EED27160
0_A
0_B
1_A
0_A
0_B
1_A
[butada@dell-vm hello]$ sudo dmesg | tail -50
[90643.773804] mydevice_open
[90643.774311] mydevice_open
[90643.774771] mydevice_open
[90643.775265] mydevice_write
[90643.775700] mydevice_write
[90643.776161] mydevice_write
[90643.776592] mydevice_read
[90643.777043] mydevice_read : count = 4
[90643.777472] mydevice_read : f_pos = 0
[90643.777894] mydevice_read : filp->f_pos = 0
[90643.778364] mydevice_read
[90643.778780] mydevice_read : count = 4
[90643.779263] mydevice_read : f_pos = 0
[90643.779678] mydevice_read : filp->f_pos = 0
[90643.780137] mydevice_read
[90643.780544] mydevice_read : count = 4
[90643.780957] mydevice_read : f_pos = 0
[90643.781395] mydevice_read : filp->f_pos = 0
[90643.781804] mydevice_read
[90643.782236] mydevice_read : count = 4
[90643.782642] mydevice_read : f_pos = 4
[90643.783080] mydevice_read : filp->f_pos = 4
[90643.783644] mydevice_read
[90643.784104] mydevice_read : count = 4
[90643.784554] mydevice_read : f_pos = 4
[90643.784963] mydevice_read : filp->f_pos = 4
[90643.785389] mydevice_read
[90643.785778] mydevice_read : count = 4
[90643.786164] mydevice_read : f_pos = 4
[90643.786547] mydevice_read : filp->f_pos = 4
[90643.786942] mydevice_close
[90643.787346] mydevice_close
[90643.787719] mydevice_close
返す値の制御
ここでは、ブロックごとにユニークな内容を返すことを要件として想定する。
-
ユニークな値を返す方法の候補
- ハッシュ関数を使う
- アドレスを返す(正直言うと後で思いついた)
- 連番を返す
今回は「連番を返す」案でいく。
ユニークな値を返し続けるだけならstatic変数を使って毎回インクリメントすれば良い。
(あとでハマるのだが、マルチスレッドの場合は、スレッドセーフでないこのコードでは、正しくインクリメントできず、マルチスレッド未サポートとなる)
filp->private_data->buffer
を使いたかったが、ここにsprintf
で書き込むとカーネルパニックになってしまったので却下。次に、関数内でstatic変数のバッファーを設けてみたが、ビルド時にframe size警告になったので却下。
結果、staticグローバル変数のバッファーを設けることにした。
/home/butada/hello/myDeviceDriver.c:90:1: warning: the frame size of 1048584 bytes is larger than 2048 bytes [-Wframe-larger-than=]
I'm guessing there's some large buffer in that routine that is stack-allocated; this is likely causing the stack frame of that function to exceed 1024 bytes, which seems to be some compiler-enforced limit for the architecture upon which you are building.
そのルーチンで配置されたスタックがバッファーよりも大きくなっているようだ。これは、その関数のスタックフレームが1024バイトを超えているようなもので…
staticグローバル変数として定義
static unsigned char g_buffer[NUM_BUFFER]; // グローバル変数として定義
/* read時に呼ばれる関数 */
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
static unsigned int counter = 0;
printk("mydevice_read\n");
printk("mydevice_read : count = %zu\n", count);
printk("mydevice_read : f_pos = %llu\n", *f_pos);
printk("mydevice_read : counter = %u\n", counter);
if(count > NUM_BUFFER) count = NUM_BUFFER;
//unsigned char b[1024];
sprintf(g_buffer, "num: %064u\n", counter); // 文字列を生成
struct _mydevice_file_data *p = filp->private_data;
//if (copy_to_user(buf, p->buffer, count) != 0) {
if (copy_to_user(buf, g_buffer, count) != 0) {
return -EFAULT;
}
*f_pos += count;
counter += 1;
return count;
}
実行結果
[butada@dell-vm hello_device_file]$ sudo dd if=/dev/mydevice0 of=tmp.txt bs=1M count=1 seek=1; cat tmp.txt; sudo dmesg | tail -20
1+0 レコード入力
1+0 レコード出力
1048576 バイト (1.0 MB) コピーされました、 0.0025766 秒、 407 MB/秒
dununununum: 000000000000000num: 000000000000000num: 000000000000000num: 0000000000000000000000000000000000000000000000000000000000000006
num: 0000000000000000000000000000000000000000000000000000000000000007
num: 0000000000000000000000000000000000000000000000000000000000000008
num: 0000000000000000000000000000000000000000000000000000000000000015
num: 0000000000000000000000000000000000000000000000000000000000000001
[ 1319.714394] mydevice_init
[ 1326.086676] mydevice_open
[ 1326.087369] mydevice_read
[ 1326.087664] mydevice_read : count = 1048576
[ 1326.087929] mydevice_read : f_pos = 0
[ 1326.088220] mydevice_read : counter = 0
[ 1326.090779] mydevice_close
[ 1376.814544] mydevice_open
[ 1376.815023] mydevice_read
[ 1376.815266] mydevice_read : count = 1048576
[ 1376.815505] mydevice_read : f_pos = 0
[ 1376.815742] mydevice_read : counter = 1
[ 1376.816853] mydevice_close
[butada@dell-vm hello_device_file]$
デバイスの終端アドレスを返す
システムコールであるlseek
を実装する。
SEEK_END
を使って終端アドレスを取得できるようになれば、デバイスの容量を取得できる。
参考 https://sites.google.com/site/linuxkernel88/sample-code/writing-a-character-driver
#define MAX_LENGTH 1024*1024*1024*10 // 10GiB
static loff_t mydevice_lseek(struct file *file,loff_t offset, int orig)
{
loff_t new_pos=0;
switch( orig )
{
case 0: /* SEEK_SET: */
printk("mydevice_lseek: SEEK_SET")
new_pos = offset;
break;
case 1: /* SEEK_CUR: */
printk("mydevice_lseek: SEEK_CUR")
new_pos = file->f_pos + offset;
break;
case 2: /* SEEK_END: */
printk("mydevice_lseek: SEEK_END")
new_pos = MAX_LENGTH - offset;
break;
}
if( new_pos > MAX_LENGTH ) new_pos = MAX_LENGTH;
if( new_pos < 0 ) new_pos = 0;
file->f_pos = new_pos;
return new_pos;
}
これだけ。(MAX_LENGTHを設定して、関数名を書き換え、printkでデバッグコードを追加しただけ)
dd
で試したところ、SEEK_END
が発行されていないため、データの読み出しが終わらなくなった。dmesg
で確認したところ、 SEEK_CUR
しか発行されていない。つまり、dd
のデータ読み出しを終わらせるためには、readで終端を返す(未確認)の工夫が必要。だが、きちんとlseekで容量を取得しているプログラムであれば、期待通りに動作した。
まとめ
任意の値を返すデバイスドライバを作成できた。
デバイスドライバを化かせるということは、通常ではテストしにくいデータパターン、大規模、境界条件を簡単にテストする環境を作れるということなので、なにかで活用したい。
尚、今回は時間の都合で以下は未実装である。時間があるときや、必要性が生じたときに、実装しようと思う。
-
未実装
- マルチスレッドに対応していない(スレッドセーフになっていない)
- 終端アドレスに到達しても、readはエラーを返さない