読者です 読者をやめる 読者になる 読者になる

shimada-kの日記

ソフトウェア・エンジニアのブログです

仮想FSはsysfsを使おう

Linuxカーネル プログラミング

カーネルhackで使っていたシステムをprocfsからsysfsに切り替えました。

仮想ファイルはget_seconds()関数を使ってepoch時間を表示させるものを用意しました。それを行うのが以下のカーネルモジュールです。

epoch_sysfs.c

#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>

static struct kobject *epoch_kobj;

static ssize_t epoch_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
       return sprintf(buf, "%lu\n", get_seconds());
}

static struct kobj_attribute epoch_attr = __ATTR(epoch_sysfs, 0666, epoch_show, NULL);	/* 書き込みは入出力エラー */

static struct attribute *epoch_attrs[] = {
       &epoch_attr.attr,
       NULL,   /* NULLで終わってないといけない */
};

static struct attribute_group epoch_attr_group = {
	.name = "wall-time",	/* ここを有効にするとサブディレクトリが作成される */
       .attrs = epoch_attrs,
};

static int epoch_init(void)
{
       int retval;

       epoch_kobj = kobject_create_and_add("epoch", kernel_kobj);	/* /sys/kernel/epochを作成 */

	/* ファイル名は__ATTR()で指定した名前になる。最終的に/sys/kernel/epoch/wall-time/epoch_sysfsが作成される */

       if(!epoch_kobj){
               return -ENOMEM;
	}

       retval = sysfs_create_group(epoch_kobj, &epoch_attr_group);

       if(retval){
               kobject_put(epoch_kobj);
	}

       return retval;
}

static void epoch_exit(void)
{
       kobject_put(epoch_kobj);
}

module_init(epoch_init);
module_exit(epoch_exit);

MODULE_LICENSE("GPL");	/* GPL2だとsysfs_create_group()ができない */
MODULE_AUTHOR("K.Shimada");

/sys/kernel/epoch/wall-time/epoch_sysfsというファイルが作成されます。struct attribute_groupのnameメンバを指定することでディレクトリ構造を1段だけ深くすることができます。上の例だと/sys/kernel/epoch以下にwall-timeディレクトリが作成されます。

2段以上深くしたい時はkobject_create_and_add()で別個にkobjectを作ってやる必要があります。

ちなみにprocfsで同じことをするコードです。

epoch_procfs.c

#include <asm/uaccess.h>    /* copy_from_user */
#include <linux/sched.h>	/* sema_init() */
#include <linux/errno.h>
#include <linux/module.h>   /* module_init, module_exit */
#include <linux/kernel.h>   /* printk */
#include <linux/proc_fs.h>
#include <linux/stat.h>     /* S_IFREG, ... */

static struct semaphore proc_sem;

static int proc_read(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
	int len = 0;

	if(down_interruptible(&proc_sem)){
		return -ERESTARTSYS;
	}

	len += sprintf(page + len, "%lu\n", get_seconds());

	up(&proc_sem);
	*eof = 1;

	return len;
}

struct proc_dir_entry *parent;

int epoch_init(void)
{
	struct proc_dir_entry *entry;

	parent = proc_mkdir("wall-time", NULL);	/* ディレクトリ作成 */

	entry = create_proc_entry("epoch_proc", S_IFREG | S_IRUGO | S_IWUGO, parent);	/* S_***はパーミッション関連のパラメータ */

	if(entry != NULL){
		entry->read_proc  = proc_read;
	}
	else{
		return -EBUSY;
	}

	sema_init(&proc_sem, 1);

	return 0;
}

void epoch_exit(void)
{
	remove_proc_entry("epoch_proc", parent);
	remove_proc_entry("wall-time", NULL);
}

module_init(epoch_init);
module_exit(epoch_exit);

MODULE_LICENSE("GPL2");
MODULE_AUTHOR("K.Shimada");

元々sysfsの仕組みが実装された経緯はprocfsが無秩序に広がっていったことにあります。

procfsでは意識的にディレクトリを作る必要があります。言い換えるとproc_mkdir()を知らなくても、/proc直下にファイルを作ることができる、ということです。それに対し、sysfsではディレクトリ階層を意識しないといけないインターフェースになっています。

そもそも仮想ファイルはユーザ空間とカーネル空間でデータをやりとりするためのインターフェースです。なので、1つのファイルにいくつもデータを並べてそれを解析するよりも、ファイルとデータは1対1にして、データ構造を考慮して階層的にファイルを増やす、という方針の設計の方が美しいはずです。

しかも、procfsとsysfsは4096バイトまでしかデータを表示させられません。仮想ファイルは多くのデータを表示させるような用途には最初から向いていないわけです。

とは言ったものの、ファイルを階層的に増やすと、ファイルを探索するプログラムが必要になります。ということで、ディレクトリ名を引数にとってFSを探索する再帰関数のサンプルを作成しました。

f_recursive.c

#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

#define STR_PATH_MAX	64
#define BASE_PATH	"/sys/kernel/epoch"

/* BASE_PATH以下を探索してファイルの数を返す関数 */
int search_sysfs_f(char *base_path)
{
	DIR *dirp;
	struct dirent *p;
	int f = 0;

	dirp = opendir(base_path);

	while((p= readdir(dirp)) != NULL){

		/* 隠しファイルだったらスルー */
		if(strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0 || p->d_name[0] == '.'){
			continue;
		}

		struct stat buf;

		char full_path[STR_PATH_MAX];

		sprintf(full_path, "%s/%s", base_path, p->d_name);

		stat(full_path, &buf);
		printf("p->d_name:%s mode:%d\n", p->d_name, buf.st_mode);

		if((buf.st_mode & S_IFMT) == S_IFDIR){	/* ディレクトリだったら再帰 */
			printf("\tp->d_name:%s mode:%d\n", p->d_name, buf.st_mode);
			f += search_sysfs_f(full_path);
		}
		else{
			f++;
		}
	}

	closedir(dirp);

	return f;

}

int main(int argc, char *argv[])
{
	int f = 0;

	f = search_sysfs_f(BASE_PATH);

	printf("ファイルの個数 = %d\n", f);
	return 0;
}

f_recursiveで/sys/kernel/epoch以下を探索した結果です。

f:id:shimada-k:20110725092314p:image

簡素化のためエラー処理などは省いてあります。