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

# 概要

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

# 環境

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

まず、デバイスファイルから２バイト取得を/dev/random/に対してddをお試し。
ちゃんとランダムな２バイトが返ってきた。

```
[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）

２の`module verification failed` はプロプライエタリドライバなので問題なし。
（https://qiita.com/gm3d2/items/8346c76961d3fdb257b7）

3~４の`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
```

２行目はバッファの中身なので不定値。
write時に書き込まれる先は、デバイスファイル内ではなくファイルディスクリプタのprivate_data構造体である。read時に読み出す元も、ファイルディスクリプタのprivate_data構造体である。
３行目と４行目は、同じデバイスファイルだが、オープンしているファイルディスクリプタが異なるため、pricate_data構造体も異なるため、write/readが独立して動作し、互いに干渉しない。



**大きなバイト数を指定しても、コピーされたバイト数が増えない**

大きなバイト数をreadするため、bsパラメータに与える値を増やす。
ところが、ｂｓ=２５６までは良いが、それ以上の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になっていたので、１GBにする・・・？

そんな訳がないので、よく考えてみる。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はエラーを返さない