14
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Linuxでデバイスドライバを作る

Posted at

概要

テストで使用するため、任意の値を返すデバイスドライバを作成したい。
通常ではテストしにくいデータパターン、大規模、境界条件を簡単にテストできる環境が欲しい為である。

環境

  • 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
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

毎回実施すると面倒なので、スクリプトを作っておく。

load_and_mknod.sh
#!/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を確認

  1. loading out-of-tree module taints kernel
  2. module verification failed
  3. BUG: unable to handle kernel paging request at 000000000223f000
  4. 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_posfilp->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はエラーを返さない
14
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?