container_ofでsysfsのコールバック関数を一元化
私が普段使っているシステムではカーネルのパラメータを出力するのにsysfsを使っています(このことは以前このブログに書きました)。
しかし、出力するファイルが多くなると同じような処理をするルーチンが多数存在することになり、無駄にソースの行数が多くなります。
例えば、これはdebianの安定版がリリースされた年を表示させるカーネルモジュールです。
debian_birthday_global.c(154行)
#include <linux/kobject.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/module.h> #include <linux/init.h> /* メンバ1つに仮想ファイル1つ */ /* [参考] http://ja.wikipedia.org/wiki/Debian */ struct code_name{ unsigned int buzz; unsigned int rex; unsigned int bo; unsigned int hamm; unsigned int slink; unsigned int potato; unsigned int woody; unsigned int sarge; unsigned int etch; unsigned int lenny; unsigned int squeeze; }; static struct code_name c_name = {1996, 1996, 1997, 1998, 1999, 2000, 2002, 2005, 2007, 2009, 2011}; static struct kobject *debian_birthday_kobj; /************************ * * ここからコールバック関数 * *************************/ static ssize_t buzz_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.buzz); } static ssize_t rex_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.rex); } static ssize_t bo_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.bo); } static ssize_t hamm_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.hamm); } static ssize_t slink_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.slink); } static ssize_t potato_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.potato); } static ssize_t woody_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.woody); } static ssize_t sarge_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.sarge); } static ssize_t etch_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.etch); } static ssize_t lenny_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.lenny); } static ssize_t squeeze_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", c_name.squeeze); } /************************* * * コールバック関数ここまで * *************************/ static struct kobj_attribute buz_attr = __ATTR(buzz, 0666, buzz_show, NULL); static struct kobj_attribute rex_attr = __ATTR(rex, 0666, rex_show, NULL); static struct kobj_attribute bo_attr = __ATTR(bo, 0666, bo_show, NULL); static struct kobj_attribute hamm_attr = __ATTR(hamm, 0666, hamm_show, NULL); static struct kobj_attribute slink_attr = __ATTR(slink, 0666, slink_show, NULL); static struct kobj_attribute potato_attr= __ATTR(potato, 0666, potato_show, NULL); static struct kobj_attribute woody_attr = __ATTR(woody, 0666, woody_show, NULL); static struct kobj_attribute sarge_attr = __ATTR(sarge, 0666, sarge_show, NULL); static struct kobj_attribute etch_attr = __ATTR(etch, 0666, etch_show, NULL); static struct kobj_attribute lenny_attr = __ATTR(lenny, 0666, lenny_show, NULL); static struct kobj_attribute squeeze_attr= __ATTR(squeeze, 0666, squeeze_show, NULL); static struct attribute *g_attrs[] = { &buz_attr.attr, &rex_attr.attr, &bo_attr.attr, &hamm_attr.attr, &slink_attr.attr, &potato_attr.attr, &woody_attr.attr, &sarge_attr.attr, &etch_attr.attr, &lenny_attr.attr, &squeeze_attr.attr, NULL, /* NULLで終わってないといけない */ }; static struct attribute_group debian_birthday_attr_group = { .attrs = g_attrs, }; static int debian_birthday_init(void) { int retval; debian_birthday_kobj = kobject_create_and_add("debian_birthday", kernel_kobj); /* /sys/kernel/debian_birthdayを作成 */ if(!debian_birthday_kobj){ return -ENOMEM; } retval = sysfs_create_group(debian_birthday_kobj, &debian_birthday_attr_group); if(retval){ kobject_put(debian_birthday_kobj); } return retval; } static void debian_birthday_exit(void) { kobject_put(debian_birthday_kobj); } module_init(debian_birthday_init); module_exit(debian_birthday_exit); MODULE_LICENSE("GPL"); /* GPL2だとsysfs_create_group()ができない */ MODULE_AUTHOR("K.Shimada");
これをinsmodすると/sys/kernel/debian_birthday/以下にコードネームがファイル名の仮想ファイルができます。そのファイルを開くとリリースされた年が表示される、というものです。
今まで11個の安定版(現在のコードネームはsqueeze)がリリースされているので、11個のファイルを作成しています。
sysfsで仮想FSを実装するサンプルは大体が簡素化のため(だと思うのですが)、staticに変数を定義しています。そのため大量のグローバル変数を宣言する必要があり、名前の競合を考慮しないといけなかったり、記述ミスが起きやすいコードと言えます。さらに、似たようなコードが多数存在するので、メンテナンス性も悪いです。
そこでコールバック関数に着目してみました。sysfsのコールバック関数は大体以下の型になっていることが多いと思います。
static ssize_t hoge_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int length = 0; /* 何かの処理 */ length += sprintf(buf + length, "~~~", ~~~); return length; }
もしコールバック関数がすべてこのような型であれば、表示させる変数だけが違うだけなので、コールバック関数をなんらかの方法で一つにまとめることができれば、ソースコートが美しくなるはずです。
美しいソースコードにはバグも入りにくいと思うので、コールバック関数を一元化してみました。
環境:Linux debian 3.0.0-1-amd64 [debian wheezy]
debian_birthday_container.c(128行)
#include <linux/kobject.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> /* kzalloc */ /* メンバ1つに仮想ファイル1つ */ /* [参考] http://ja.wikipedia.org/wiki/Debian */ struct code_name{ unsigned int buzz; unsigned int rex; unsigned int bo; unsigned int hamm; unsigned int slink; unsigned int potato; unsigned int woody; unsigned int sarge; unsigned int etch; unsigned int lenny; unsigned int squeeze; }; static struct code_name c_name = {1996, 1996, 1997, 1998, 1999, 2000, 2002, 2005, 2007, 2009, 2011}; struct kobject *debian_birthday_kobj; struct container_sysfs{ void *val_addr; struct kobj_attribute kattr; }; /* コールバック関数 */ static ssize_t release_year_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct container_sysfs *container; container = container_of(attr, struct container_sysfs, kattr); return sprintf(buf, "%d\n", *(unsigned int *)(container->val_addr)); } #define NR_SYSFS_ENTRY 11 static struct container_sysfs *c_sysfs; static struct attribute *c_attrs[NR_SYSFS_ENTRY + 1]; static struct attribute_group debian_birthday_attr_group = { .attrs = c_attrs, }; static int debian_birthday_init(void) { int retval, i; c_sysfs = (struct container_sysfs *)kzalloc(sizeof(struct container_sysfs) * NR_SYSFS_ENTRY, GFP_KERNEL); /* 個別に初期化が必要な箇所 */ c_sysfs[0].kattr.attr.name = __stringify(buzz); c_sysfs[0].val_addr = (void *)&c_name.buzz; c_sysfs[1].kattr.attr.name = __stringify(rex); c_sysfs[1].val_addr = (void *)&c_name.rex; c_sysfs[2].kattr.attr.name = __stringify(bo); c_sysfs[2].val_addr = (void *)&c_name.bo; c_sysfs[3].kattr.attr.name = __stringify(hamm); c_sysfs[3].val_addr = (void *)&c_name.hamm; c_sysfs[4].kattr.attr.name = __stringify(slink); c_sysfs[4].val_addr = (void *)&c_name.slink; c_sysfs[5].kattr.attr.name = __stringify(potato); c_sysfs[5].val_addr = (void *)&c_name.potato; c_sysfs[6].kattr.attr.name = __stringify(woody); c_sysfs[6].val_addr = (void *)&c_name.woody; c_sysfs[7].kattr.attr.name = __stringify(sarge); c_sysfs[7].val_addr = (void *)&c_name.sarge; c_sysfs[8].kattr.attr.name = __stringify(etch); c_sysfs[8].val_addr = (void *)&c_name.etch; c_sysfs[9].kattr.attr.name = __stringify(lenny); c_sysfs[9].val_addr = (void *)&c_name.lenny; c_sysfs[10].kattr.attr.name = __stringify(squeeze); c_sysfs[10].val_addr = (void *)&c_name.squeeze; /* 自動で初期化できる箇所 */ for(i = 0; i < NR_SYSFS_ENTRY; i++){ c_sysfs[i].kattr.attr.mode = 0666; c_sysfs[i].kattr.show = release_year_show; c_sysfs[i].kattr.store = NULL; c_attrs[i] = &c_sysfs[i].kattr.attr; } c_attrs[i] = NULL; /* 最後はNULLで終わらないといけない */ debian_birthday_kobj = kobject_create_and_add("debian_birthday", kernel_kobj); /* /sys/kernel/debian_birthdayを作成 */ if(!debian_birthday_kobj){ return -ENOMEM; } retval = sysfs_create_group(debian_birthday_kobj, &debian_birthday_attr_group); if(retval){ kobject_put(debian_birthday_kobj); } return retval; } static void debian_birthday_exit(void) { kfree(c_sysfs); kobject_put(debian_birthday_kobj); } module_init(debian_birthday_init); module_exit(debian_birthday_exit); MODULE_LICENSE("GPL"); /* GPL2だとsysfs_create_group()ができない */ MODULE_AUTHOR("K.Shimada");
Linuxカーネルにはcontainer_ofという最強のマクロがありますので、そちらを使っています。
#container_ofは「ポインタとそれに対応するメンバ名を渡すと、ラップしている構造体のポインタを返してくれる」というマクロです。
ポイントはstruct container_sysfs内でstruct kobj_attributeを宣言している所です。ここで宣言しているのでコールバック関数の第2引数のポインタを手がかりにcontainer_ofを使うことができます。
static変数では初期化時に__ATTRマクロが使えるのですが、今回はポインタだけstaticに定義して、後からkzallocでメモリを割り当てているので、一つ一つ初期化する必要があります。
しかしながら、行数は少なくなりましたし、コード全体としても読みやすいものになっていると思います。
今回はstruct sample_dataのメンバのアドレスを格納していますが、struct kobj_attributeをラップしている構造体の中に入っている変数はコールバック関数内から参照できる仕組みですので、要件に応じたパラメータをstruct container_sysfsに格納しておけばいい、ということになります。
今回は11個のファイルを作成して、20行ほどコードを短くすることができました。コールバック関数が今回よりも複雑な場合や、さらにファイル数が増えた場合などはコードの美しさは向上すると思います。