7回目: procfs用インタフェース
本連載について
組み込みLinuxのデバイスドライバをカーネルモジュールとして開発するためのHowTo記事です。本記事の内容は全てラズパイ(Raspberry Pi)上で動かせます。
- 1回目: ビルド環境準備と、簡単なカーネルモジュールの作成
- 2回目: システムコールハンドラとドライバの登録(静的な方法)
- 3回目: システムコールハンドラとドライバの登録(動的な方法)
- 4回目: read/writeの実装とメモリのお話
- 5回目: ラズパイ用のGPIOデバドラの実装
- 6回目: ioctlの実装
- 7回目: procfs用インタフェース <--------------------- 今回の内容
- 8回目: debugfs用インタフェース
- 9回目: 他のカーネルモジュールの関数を呼ぶ / GPIO制御関数を使う
- 10回目: I2Cを使ったデバイスドライバを作る
- 11回目: デバイスツリーにI2Cデバイスを追加する
- 12回目: 作成したデバイスドライバを起動時にロードする
本記事に登場するソースコード全体
今回の内容
前回まで、基本的なシステムコール(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
関数を使用します。
#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