組み込みLinuxデバイスドライバの作り方 (7)

# 7回目: procfs用インタフェース

# 本連載について
組み込みLinuxのデバイスドライバをカーネルモジュールとして開発するためのHowTo記事です。本記事の内容は全てラズパイ(Raspberry Pi)上で動かせます。

- [1回目: ビルド環境準備と、簡単なカーネルモジュールの作成](https://qiita.com/take-iwiw/items/1fdd2e0faaaa868a2db2)
- [2回目: システムコールハンドラとドライバの登録(静的な方法)](https://qiita.com/take-iwiw/items/580ec7db2e88beeac3de)
- [3回目: システムコールハンドラとドライバの登録(動的な方法)](https://qiita.com/take-iwiw/items/6b02494a3668f79800e6)
- [4回目: read/writeの実装とメモリのお話](https://qiita.com/take-iwiw/items/26d5f7f4894ccc4ce227)
- [5回目: ラズパイ用のGPIOデバドラの実装](https://qiita.com/take-iwiw/items/cd1d7734c8911830386d)
- [6回目: ioctlの実装](https://qiita.com/take-iwiw/items/ade0a73d4c05fc7961d3)
- __[7回目: procfs用インタフェース](https://qiita.com/take-iwiw/items/548444999d2dfdc06f46)  <--------------------- 今回の内容__ 
- [8回目: debugfs用インタフェース](https://qiita.com/take-iwiw/items/1ef4b629f9b9bab4d222)
- [9回目: 他のカーネルモジュールの関数を呼ぶ / GPIO制御関数を使う](https://qiita.com/take-iwiw/items/e92a950a2cf57d1a3ded)
- [10回目: I2Cを使ったデバイスドライバを作る](https://qiita.com/take-iwiw/items/dbc544864f0e9873270a)
- [11回目: デバイスツリーにI2Cデバイスを追加する](https://qiita.com/take-iwiw/items/0d13142863d9ed064e41)
- [12回目: 作成したデバイスドライバを起動時にロードする](https://qiita.com/take-iwiw/items/b9dd02724e83e36dabc3)

### 本記事に登場するソースコード全体
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/07_01

## 今回の内容
前回まで、基本的なシステムコール(open, close, read, write)に加えて、ioctlの実装を行いました。これで、デバイスドライバとして最低限必要なインターフェイスはほぼ網羅できました(select/pollはスキップします)。

実際に、デバイス(例えば、センサーやアクチュエータ)を制御する開発をしていると、デバッグ用にパラメータを変えたり、パラメータを取得したいことがあります。こういったことが、シェル上からできると非常に便利です。これは、procファイルシステム(procfs)のファイルに対してread/writeすることで実現できます。例えば、以下のようなファイルを作るとします。

- /proc/mydevice_sensor0
- /proc/mydevice_motor0

`/proc/mydevice_sensor0`の値を読むことでセンサーの値を確認出来たり(例えば、`cat`)、`/proc/mydevice_motor0`に値を書き込むことでモーターを制御できます(例えば、`echo`)。基本的には、今まで作ってきたデバイスドライバのread/writeと同じです。しかし、今まではデバイスファイル(`/dev/mydevice`)に対してのread/writeでした。今回はprocfsのファイルに対して行います。これは、いくらでも作ることが出来ます。そのため、デバイスは1つでも、確認したいパラメータがいくつもあるときに便利です。

## ノート (追記)
コメントにて@rarul さんからご指摘をいただきました。procfsはあくまでもプロセスの情報を配置するのに使用して、上述したようなデバッグ用途にはdebugfsを使用することが推奨されているようです。debugfsについては次回記載します。デバッグ用には使うべきではありませんが、procfsの作り方としては有効だと思いますので、本記事はこのまま残します。

# procfs用インターフェースの実装
procfs用インターフェースを作るには、ドライバがロード(insmod)される所で、`proc_create`という関数によって、ファイル名とread/write時に呼んでほしい関数を登録するだけです。登録するときには、`struct file_operations`テーブルにread/writeハンドラ関数を設定します。実はこれは、通常のデバイスドライバのハンドラ登録と全く同じ手順です。使用する関数が違うだけです。取り除くためには`remove_proc_entry`関数を使用します。

```c:myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

/*** このデバイスに関する情報 ***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/devices等で表示されるデバイス名 */
#define PROC_NAME "MyDevice_test"			/* /procに作るprocfsの名前 */

/* procfsテスト用変数 */
static char proc_test_string[16];
static int  flag_read = 0;

/* /proc/MyDevice_testにアクセスしたときに呼ばれる関数 */
static int mydevice_proc_open(struct inode *inode, struct file *file)
{
	printk("mydevice_proc_open\n");
	flag_read = 0;
	return 0;
}

/* /proc/MyDevice_testのread時に呼ばれる関数 */
static ssize_t mydevice_proc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_proc_read\n");

	if (flag_read == 0) {
		int len;
		len = sprintf(buf, "%s\n", proc_test_string);
		flag_read = 1;
		return len;
	} else {
		return 0;
	}
}

/* /proc/MyDevice_testのwrite時に呼ばれる関数 */
static ssize_t mydevice_proc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_proc_write\n");

	if (count > sizeof(proc_test_string)) count = sizeof(proc_test_string) - 1;
	if (copy_from_user(proc_test_string, buf, count)) {
		return -EFAULT;
	}
	proc_test_string[count] = '\0';
	return count;	
}

/* procfs用のハンドラテーブル */
static struct file_operations mydevice_proc_fops = {
	.owner = THIS_MODULE,
	.open  = mydevice_proc_open,
	.read  = mydevice_proc_read,
	.write = mydevice_proc_write,
};

/* ロード(insmod)時に呼ばれる関数 */
static int mydevice_init(void)
{
	printk("mydevice_init\n");

	struct proc_dir_entry *entry;
	/* procfsを作成する */
	entry = proc_create(PROC_NAME, S_IRUGO | S_IWUGO, NULL, &mydevice_proc_fops);
	if (entry == NULL) {
		printk(KERN_ERR "proc_create\n");
		return -ENOMEM;
	}

	return 0;
}

/* アンロード(rmmod)時に呼ばれる関数 */
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");

	/* procfsを取り除く */
	remove_proc_entry(PROC_NAME, NULL);
}

module_init(mydevice_init);
module_exit(mydevice_exit);
```

`/proc/MyDevice_test`というファイルを作ることにします。このファイルをreadした時の処理(`mydevice_proc_read`)、writeした時の処理(`mydevice_proc_write`)、openした時の処理(`mydevice_proc_open`)を実装しています。open時の処理は、シェルから読み書きするたびに必ず呼ばれます。無くても大丈夫だと思います。実際、closeは省略しています。

`mydevice_proc_write`では、ユーザが設定した文字列を内部のstatic変数(`proc_test_string`)に保持します。`mydevice_proc_read`で保持した内容を返します。1回の読出し(`cat`)につき、1回だけ呼んでほしいので、フラグ管理してopen時にクリアしています。これは、もっといい方法があると思います。

モジュールのロード時に、`proc_create`関数で、`/proc/MyDevice_test`ファイルを作ります。`S_IRUGO | S_IWUGO`は0666と同じで、全ユーザにRWアクセス権を付与します。


# procfs経由でパラメータを読み書きしてみる
下記コマンドでビルド、ロードします。

```
make 
sudo insmod  MyDeviceModule.ko
ls /proc/MyDevice_test
/proc/MyDevice_test

echo "abc" > /proc/MyDevice_test
cat /proc/MyDevice_test
abc
```

ロード後、`/proc/MyDevice_test`が作られます。これに対してechoで値を書き込みます。その後、catで読むと、先ほど書いた値が出力されています。

# ノート
本記事の内容は、「Linuxデバイスドライバプログラミング (平田 豊)」の内容に沿っています。
procファイルシステムへの登録のため、本では`create_proc_entry`を使用していました。しかし、現在では廃止されています。本記事では代わりに、`proc_create`を使用しています。

# ノート2: sysfs
コメントでもご指摘いただいているように、procfsでは、本来はプロセスに関する情報をやり取りします。次回の記事で記載していますが、debugfsではデバッグ用の情報をやり取りします。

これらとは別に、モジュールとパラメータをやり取りする仕組みがあります。モジュールの情報やパラメータを、/sysディレクトリ以下にできるsysfsとして確認することができますす。これは、procfsと同じに見えますが、procfsは古い方法で、僕がこの記事で書いてしまったように、デバドラ開発者が自由に追加できます。その結果、無秩序状態になってしまっています。sysfsは正しいお作法に則って登録する必要があります。そして、各ファイルはディレクトリ毎に整理されて管理されています。

色々な方法があるのですが、単にパラメータを見せるだけなら、`module_param()`というヘルパーマクロで実現できます。これによって、モジュールロード時にパラメータを設定したり、パラメータ(変数)の値を読むことが出来ます。ただし、カーネルロード後に、パラメータを変えることはできないようです。



実際に使ってみます。変数の宣言と、`module_param()`の読出しの2文だけでOKです。`param1`という変数をパラメータとして使う例を下記に示します。



```
static int param1 = 10;
module_param(param1, int, S_IRUGO);
```

`/sys/module/MyDeviceModule/parameters/param1`を読むことで、`param1`の値を確認できます。また、insmod時に値を指定することもできます。

```
sudo insmod MyDeviceModule.ko
cat /sys/module/MyDeviceModule/parameters/param1
  10

sudo rmmod MyDeviceModule
sudo insmod MyDeviceModule.ko param1=20
cat /sys/module/MyDeviceModule/parameters/param1
  20
```