Help us understand the problem. What is going on with this article?

組み込み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
iwatake2222
クソコード、放置するのも、同罪です (自分への戒め)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした