shimada-kの日記

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

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/以下にコードネームがファイル名の仮想ファイルができます。そのファイルを開くとリリースされた年が表示される、というものです。

f:id:shimada-k:20110926202645p:image:w640

今まで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行ほどコードを短くすることができました。コールバック関数が今回よりも複雑な場合や、さらにファイル数が増えた場合などはコードの美しさは向上すると思います。