Edited at

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

More than 1 year has passed since last update.


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


本連載について

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


本記事に登場するソースコード全体

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関数を使用します。


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