设备模型-sysfs

前言

在Linux内核里,kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。Kobject为Linux设备模型提供了很多有用的功能,比如引用计数,接口抽象,父子关系等等。引用计数本质上就是利用kref实现的;

另外,Linux设备模型还有一个重要的数据结构kset。Kset本身也是一个kobject,所以它在sysfs里同样表现为一个目录,但它和kobject的不同之处在于kset可以看作是一个容器,如果你把它类比为C++里的容器类如list也无不可。Kset之所以能作为容器来使用,其内部正是内嵌了一个双向链表结构struct list_head。

在接下来的篇幅里我们会逐步看到这个关系图在内核里是如何建立的。本文的示例代码可以从这里下载,下文中的两个实作都在这个示例代码里。

Kobject
Kobject在Linux内核里的定义如下:

struct kobject {
    const char      *name;
    struct list_head    entry;
    struct kobject      *parent;
    struct kset     *kset;
    struct kobj_type    *ktype;
    struct sysfs_dirent *sd;
    struct kref     kref;
    unsigned int state_initialized:1;
    unsigned int state_in_sysfs:1;
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;
};

内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。在kobject的定义里,name表示的是kobject在sysfs中的名字;指针parent用来指向kobject的父对象;Kref大家应该比较熟悉了,kobject通过它来实现引用计数;Kset指针用来指向这个kobject所属的kset,下文会再详细描述kset的用法;对于ktype,如果只是望文生义的话,应该是用来描述kobject的类型信息。Ktype的定义如下:

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
};

函数指针release是给kref使用的,当引用计数为0这个指针指向的函数会被调用来释放内存。sysfs_ops和attribute是做什么用的呢?前文里提到,一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。Ktype里的attribute是默认的属性,另外也可以使用更加灵活的手段,本文的重点还是放在default attribute。

实例分析

下面看一个实作。在这个实作里,我们定义一个内嵌kobject的结构。

struct my_kobj {
    int val;
    struct kobject kobj;
};

最终我们的目的是在内核里构建这样的架构。

对应sysfs里的目录关系是:

mykobj1/
|– mykobj2
| |– name
| -- val
|-- name
– val

这是module_init代码。

static int __init mykobj_init(void)
{
    printk(KERN_INFO "mykobj_init\n");

    obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    if (!obj1) {
        return -ENOMEM;
    }
    obj1->val = 1;

    obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    if (!obj2) {
        kfree(obj1);
        return -ENOMEM;
    }
    obj2->val = 2;

    my_type.release = obj_release;
    my_type.default_attrs = my_attrs;
    my_type.sysfs_ops = &my_sysfsops;

    kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1");
    kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2");

    return 0;
}

这段代码可以分作三个部分。第一部分是分配obj1和obj2并赋值;第二部分是初始化kobj_type变量my_type;第三部分是调用kobject_init_and_add函数来初始化kobject并把它加入到设备模型的体系架构(也就是上文中提到的内核中的那棵树)中。kobject_init_and_add是简化的写法,这个函数也可以分两步完成:kobject_init和kobject_add。

int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
         struct kobject *parent, const char *fmt, ...);

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj, struct kobject *parent,
    const char *fmt, ...);

kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。

前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。

struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,所以在module_exit时要记得用kobject_put来释放引用计数。

我们再回到实作中,看看如何使用ktype。代码里,my_attrs是这样定义的:

struct attribute name_attr = {
    .name = "name",
    .mode = 0444,
};

struct attribute val_attr = {
    .name = "val",
    .mode = 0666,
};

struct attribute *my_attrs[] = {
    &name_attr, 
    &val_attr,
    NULL,
};

结构体struct attribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。这里需要着重指出的是,数组my_attrs的最后一项一定要赋为NULL,否则会造成内核oops。

sysfs_ops的代码如下:

ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buffer)
{
    struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);
    ssize_t count = 0;

    if (strcmp(attr->name, "name") == 0) {
        count = sprintf(buffer, "%s\n", kobject_name(kobj));
    } else if (strcmp(attr->name, "val") == 0) {
        count = sprintf(buffer, "%d\n", obj->val);
    }

    return count;
}

ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size)
{
    struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);

    if (strcmp(attr->name, "val") == 0) {
        sscanf(buffer, "%d", &obj->val);
    }

    return size;
}

struct sysfs_ops my_sysfsops = {
    .show = my_show,
    .store = my_store,
};

读文件会调用my_show,写文件会调用my_store。

最后是module_exit:

static void __exit mykobj_exit(void)
{
    printk(KERN_INFO "mykobj_exit\n");

    kobject_del(&obj2->kobj);
    kobject_put(&obj2->kobj);

    kobject_del(&obj1->kobj);
    kobject_put(&obj1->kobj);

    return;
}

kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。这里需要指出的是,释放的顺序应该是先子对象,后父对象。因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中,如果先通过kobject_put来释放obj1,那kobject_del(&obj2->kobj)就会出现内存错误。

在这个实作中,我们建立了两个对象obj1和obj2,obj1是obj2的父对象,如果推广开来,obj1可以有更多的子对象。在Linux内核中,这种架构方式其实并无太大的实际价值,有限的用处之一是在sysfs里创建子目录(Linux内核里有这种用法,这种情况下,直接调用内核提供的kobject_create来实现,不需要自定义数据结构并内嵌kobject),而且,创建子目录也是有其他的办法的。我们知道,Linux设备模型最初的目的是为了方便电源管理,这就需要从上到下的遍历,在这种架构里,通过obj1并无法访问其所有的子对象。这个实作最大的意义在于可以让我们比较清晰的理解kobject如何使用。通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。

Kset
Kset的定义如下:

struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
};

Kset结构里的kobj表明它也是一个kobject,list变量用来组织它所有的子对象。

我们直接看一个实作。在这个实作里,我们将构建如下的架构。

对应sysfs里的目录关系是:

my_kset/
|– mykobj1
| |– name
| -- val
– mykobj2
|– name
`– val

这个实作和前一个差别很小,代码。

static int __init mykset_init(void)
{
    printk(KERN_INFO "mykset_init\n");

    my_kset = kset_create_and_add("my_kset", NULL, NULL);
    if (!my_kset) {
        return -ENOMEM;
    }

    obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    if (!obj1) {
        kset_unregister(my_kset);
        return -ENOMEM;
    }
    obj1->val = 1;

    obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
    if (!obj2) {
        kset_unregister(my_kset);
        kfree(obj1);
        return -ENOMEM;
    }
    obj2->val = 2;

    obj1->kobj.kset = my_kset;
    obj2->kobj.kset = my_kset;

    my_type.release = obj_release;
    my_type.default_attrs = my_attrs;
    my_type.sysfs_ops = &my_sysfsops;

    kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1");
    kobject_init_and_add(&obj2->kobj, &my_type, NULL, "mykobj2");

    return 0;
}

static void __exit mykset_exit(void)
{
    printk(KERN_INFO "mykset_exit\n");

    kobject_del(&obj1->kobj);
    kobject_put(&obj1->kobj);

    kobject_del(&obj2->kobj);
    kobject_put(&obj2->kobj);

    kset_unregister(my_kset);

    return;
}

在module_init里,我们首先调用kset_create_and_add创建my_kset,接下来把my_kset赋给obj1和obj2,最后调用kobject_init_and_add来添加obj1和obj2。这里需要注意的是,kobject_init_and_add参数里的parent都是NULL,在这种情况下,obj1和obj2的父对象由kobject结构里的kset指针决定,在这个实作里就是my_kset。在module_exit里,我们还需要额外调用kset_unregister来释放之前创建的my_kset。

注:看过LDD3的读者应该对Linux Device Model一章中的subsystem还有印象,我在这里注明一下,从2.6.23开始Linux内核就抛弃了subsystem,subsystem其实只是kset的一个马甲,所以抛弃它对Linux设备模型并没什么影响。

在上文中,我们介绍到如何使用default attribute。Default attribute使用很方便,但不够灵活。比如上篇文章在Kobject一节中提到的那个例子,name和val这两个attribute使用同一个show/store函数来访问,如果attribute非常多,show/store函数里的分支就会很凌乱。

为了解决这个问题,我们可以参考内核提供的kobj_attribute。在内核里,kobj_attibute是这样定义的:


struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count);
};

每一个attribute会对应自己的show/store函数,这样就极大的提高了灵活性。可是,在上一篇文章中我们的认知是,sysfs是通过kobject里的kobj_type->sysfs_ops来读写attribute的,那如果要利用kobj_attribute中的show/store来读写attribute的话,就必须在kobj_type->sysfs_ops里指定。Linux内核提供了一个默认的kobj_type类型dynamic_kobj_ktype来实现上述的操作。

/* default kobject attribute operations */
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
                  char *buf)
{
    struct kobj_attribute *kattr;
    ssize_t ret = -EIO;

    kattr = container_of(attr, struct kobj_attribute, attr);
    if (kattr->show)
        ret = kattr->show(kobj, kattr, buf);
    return ret;
}

static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,
                   const char *buf, size_t count)
{
    struct kobj_attribute *kattr;
    ssize_t ret = -EIO;

    kattr = container_of(attr, struct kobj_attribute, attr);
    if (kattr->store)
        ret = kattr->store(kobj, kattr, buf, count);
    return ret;
}

const struct sysfs_ops kobj_sysfs_ops = {
    .show   = kobj_attr_show,
    .store  = kobj_attr_store,
};

static void dynamic_kobj_release(struct kobject *kobj)
{
    pr_debug("kobject: (%p): %s\n", kobj, __func__);
    kfree(kobj);
}

static struct kobj_type dynamic_kobj_ktype = {
    .release    = dynamic_kobj_release,
    .sysfs_ops  = &kobj_sysfs_ops,
};

kobj_attribute是内核提供给我们的一种更加灵活的处理attribute的方式,但是它还不够。只有当我们使用kobject_create来创建kobject时,使用kobj_attribute才比较方便,但大部分情况下,我们是把kobject内嵌到自己的结构里,此时就无法直接使用内核提供的dynamic_kobj_ktype,因此,我们需要创建自己的kobj_attribute。

本文接下来将围绕一个实作来看看如何创建自己的kobj_attribute,sample code可以从这里下载。这个sample code是基于上篇文章kobject中的例子修改而来的,看过那个例子的读者应该会比较轻松。

首先,我们需要定义自己的attribute:

struct my_attribute {
        struct attribute attr;
        ssize_t (*show)(struct my_kobj *obj, struct my_attribute *attr, 
                        char *buf);
        ssize_t (*store)(struct my_kobj *obj, struct my_attribute *attr, 
                        const char *buf, size_t count);
};

在my_attribute里,我们的show/store直接操作my_kobj,这样更加方便。

参考Linux内核,kobj_type里的sysfs_ops这样定义:

static ssize_t my_attr_show(struct kobject *kobj, struct attribute *attr,
                  char *buf)
{
    struct my_attribute *my_attr;
    ssize_t ret = -EIO;

    my_attr = container_of(attr, struct my_attribute, attr);
    if (my_attr->show)
        ret = my_attr->show(container_of(kobj, struct my_kobj, kobj), 
                my_attr, buf);
    return ret;
}

static ssize_t my_attr_store(struct kobject *kobj, struct attribute *attr,
                   const char *buf, size_t count)
{
    struct my_attribute *my_attr;
    ssize_t ret = -EIO;

    my_attr = container_of(attr, struct my_attribute, attr);
    if (my_attr->store)
        ret = my_attr->store(container_of(kobj, struct my_kobj, kobj), 
                my_attr, buf, count);
    return ret;
}

下面就可以分别对name和val两个attribute定义自己的show/store。name这个attribute是只读的,只要为它定义show即可。

ssize_t name_show(struct my_kobj *obj, struct my_attribute *attr, char *buffer)
{
    return sprintf(buffer, "%s\n", kobject_name(&obj->kobj));
}

ssize_t val_show(struct my_kobj *obj, struct my_attribute *attr, char *buffer)
{
    return sprintf(buffer, "%d\n", obj->val);
}

ssize_t val_store(struct my_kobj *obj, struct my_attribute *attr, 
        const char *buffer, size_t size)
{
    sscanf(buffer, "%d", &obj->val);

    return size;
}

接下来,利用内核提供的宏__ATTR来初始化my_attribute,并建立attribute数组。

struct my_attribute name_attribute = __ATTR(name, 0444, name_show, NULL);
struct my_attribute val_attribute = __ATTR(val, 0666, val_show, val_store);

struct attribute *my_attrs[] = {
    &name_attribute.attr, 
    &val_attribute.attr,
    NULL,
};

其中,宏__ATTR的定义如下:

#define __ATTR(_name,_mode,_show,_store) { \
    .attr = {.name = __stringify(_name), .mode = _mode },   \
    .show   = _show,                    \
    .store  = _store,                   \
}

在module_init里,我们调用sysfs_create_files来把attribute增加到sysfs中。

retval = sysfs_create_files(&obj->kobj, 
            (const struct attribute **)my_attrs);
if (retval) {
    // ...
}

在kobject对应的目录里,还可以创建子目录,Linux内核里是用attribute_group来实现。在本例中,我们可以这么做:

struct attribute_group my_group = {
    .name     = "mygroup",
    .attrs    = my_attrs,
};

然后在module_init里调用sysfs_create_group来添加。

retval = sysfs_create_group(&obj->kobj, &my_group);
if (retval) {
    // ...
}

本例创建的attribute_group中包含的attribute也是my_attrs,所以在子目录mygroup下的文件和mykobj目录下的文件完全一致。

最后我们得到的目录结构是这样的。

mykobj/
|– mygroup
| |– name
| -- val
|-- name
– val

完成这个实作之后,你可以用命令echo 2 > /sys/mykobj/val来修改mykobj下的val文件,可以观察到/sys/mykobj/mygroup/val的内容也会变成2,反之亦然。

源码分析

3.2 attibute文件的创建

在linux内核中,attibute文件的创建是由fs/sysfs/file.c中sysfs_create_file接口完成的,该接口的实现没有什么特殊之处,大多是文件系统相关的操作,和设备模型没有太多的关系,这里先略过不提。
3.3 attibute文件的read和write

看到struct attribute的原型时,也许我们会犯嘀咕,该结构很简单啊,name表示文件名称,mode表示文件模式,其它的字段都是内核用于debug Kernel Lock的,那文件操作的接口在哪里呢?
不着急,我们去fs/sysfs目录下看看sysfs相关的代码逻辑。
所有的文件系统,都会定义一个struct file_operations变量,用于描述本文件系统的操作接口,sysfs也不例外:

  /* fs/sysfs/file.c, line 472 */
  const struct file_operations sysfs_file_operations = {
      .read       = sysfs_read_file,
      .write      = sysfs_write_file,
      .llseek     = generic_file_llseek,
      .open       = sysfs_open_file,
      .release    = sysfs_release,
      .poll       = sysfs_poll,
  };

attribute文件的read操作,会由VFS转到sysfs_file_operations的read(也就是sysfs_read_file)接口上,让我们大概看一下该接口的处理逻辑。



 /* fs/sysfs/file.c, line 127 */
  static ssize_t
  sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos)
  {
      struct sysfs_buffer * buffer = file->private_data;
      ssize_t retval = 0;

      mutex_lock(&buffer->mutex);
      if (buffer->needs_read_fill || *ppos == 0) {
         retval = fill_read_buffer(file->f_path.dentry,buffer);
         if (retval)
             goto out;
     }
  ...
  }
  /* fs/sysfs/file.c, line 67 */
 static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
  {           
     struct sysfs_dirent *attr_sd = dentry->d_fsdata;
     struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
     const struct sysfs_ops * ops = buffer->ops;
     ...        
     count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);
     ...
  }

read处理看着很简单,sysfs_read_file从file指针中取一个私有指针(注:大家可以稍微留一下心,私有数据的概念,在VFS中使用是非常普遍的),转换为一个struct sysfs_buffer类型的指针,以此为参数(buffer),转身就调用fill_read_buffer接口。
而fill_read_buffer接口,直接从buffer指针中取出一个struct sysfs_ops指针,调用该指针的show函数,即完成了文件的read操作。
那么后续呢?当然是由ops->show接口接着处理咯。而具体怎么处理,就是其它模块(例如某个driver)的事了,sysfs不再关心(其实,Linux大多的核心代码,都是只提供架构和机制,具体的实现,也就是苦力,留给那些码农吧!这就是设计的魅力)。
不过还没完,这个struct sysfs_ops指针哪来的?好吧,我们再看看open(sysfs_open_file)接口吧。


 /* fs/sysfs/file.c, line 326 */
  static int sysfs_open_file(struct inode *inode, struct file *file)
  {
      struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
      struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
      struct sysfs_buffer *buffer;
     const struct sysfs_ops *ops;
      int error = -EACCES;
/* need attr_sd for attr and ops, its parent for kobj */
     if (!sysfs_get_active(attr_sd))
     return -ENODEV;

 /* every kobject with an attribute needs a ktype assigned */
     if (kobj->ktype && kobj->ktype->sysfs_ops)
         ops = kobj->ktype->sysfs_ops;
     else {
         WARN(1, KERN_ERR "missing sysfs attribute operations for "
             "kobject: %s\n", kobject_name(kobj));
         goto err_out;
     }

     ...

     buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
     if (!buffer)
         goto err_out;

     mutex_init(&buffer->mutex);
     buffer->needs_read_fill = 1;
     buffer->ops = ops;
     file->private_data = buffer;
     ...
  }


哦,原来和ktype有关系。这个指针是从该attribute所从属的kobject中拿的。再去看一下"Linux设备模型_Kobject”中ktype的定义,还真有一个struct sysfs_ops的指针。
我们注意一下14行的注释以及其后代码逻辑,如果从属的kobject(就是attribute文件所在的目录)没有ktype,或者没有ktype->sysfs_ops指针,是不允许它注册任何attribute的!
经过确认后,sysfs_open_file从ktype中取出struct sysfs_ops指针,并在随后的代码逻辑中,分配一个struct sysfs_buffer类型的指针(buffer),并把struct sysfs_ops指针保存在其中,随后(注意哦),把buffer指针交给file的private_data,随后read/write等接口便可以取出使用。嗯!惯用伎俩!
顺便看一下struct sysfs_ops吧,我想你已经能够猜到了。

/* include/linux/sysfs.h, line 124 */
struct sysfs_ops {
ssize_t (show)(struct kobject , struct attribute ,char );
ssize_t (store)(struct kobject ,struct attribute ,const char , size_t);
const void (*namespace)(struct kobject , const struct attribute *);
};



attribute文件的write过程和read类似,这里就不再多说。另外,上面只分析了普通attribute的逻辑,而二进制类型的呢?也类似,去看看fs/sysfs/bin.c吧,这里也不说了。

讲到这里,应该已经结束了,事实却不是如此。上面read/write的数据流,只到kobject(也就是目录)级别哦,而真正需要操作的是attribute(文件)啊!这中间一定还有一层转换!确实,不过又交给其它模块了。 下面我们通过一个例子,来说明如何转换的。
4. sysfs在设备模型中的应用总结

让我们通过设备模型class.c中有关sysfs的实现,来总结一下sysfs的应用方式。
首先,在class.c中,定义了Class所需的ktype以及sysfs_ops类型的变量,如下:

/* drivers/base/class.c, line 86 */
static const struct sysfs_ops class_sysfs_ops = {
.show = class_attr_show,
.store = class_attr_store,
.namespace = class_attr_namespace,
};

static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops,
.release = class_release,
.child_ns_type = class_child_ns_type,
};


由前面章节的描述可知,所有class_type的Kobject下面的attribute文件的读写操作,都会交给class_attr_show和class_attr_store两个接口处理。以class_attr_show为例:

/* drivers/base/class.c, line 24 */
#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)

static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct class_attribute *class_attr = to_class_attr(attr);
struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO;

 if (class_attr->show)
 ret = class_attr->show(cp->class, class_attr, buf);
 return ret;

}


该接口使用container_of从struct attribute类型的指针中取得一个class模块的自定义指针:struct class_attribute,该指针中包含了class模块自身的show和store接口。下面是struct class_attribute的声明:
  /* include/linux/device.h, line 399 */
  struct class_attribute {
      struct attribute attr;
      ssize_t (*show)(struct class *class, struct class_attribute *attr,
                     char *buf);
     ssize_t (*store)(struct class *class, struct class_attribute *attr,
                      const char *buf, size_t count);
      const void *(*namespace)(struct class *class,
                                  const struct class_attribute *attr); 
  };

“`

因此,所有需要使用attribute的模块,都不会直接定义struct attribute变量,而是通过一个自定义的数据结构,该数据结构的一个成员是struct attribute类型的变量,并提供show和store回调函数。然后在该模块ktype所对应的struct sysfs_ops变量中,实现该本模块整体的show和store函数,并在被调用时,转接到自定义数据结构(struct class_attribute)中的show和store函数中。这样,每个atrribute文件,实际上对应到一个自定义数据结构变量中了。

猜你喜欢

转载自blog.csdn.net/qq_27087571/article/details/55822727
今日推荐