仮想FSはsysfsを使おう
カーネル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以下を探索した結果です。
簡素化のためエラー処理などは省いてあります。