2.4 sysfs文件系统
上面我们反复提及将内核对象添加到sysfs文件系统。其实,这种说法并不十分精确。在那里,已经足够了。但现在,我们需要更为严格的解释。
sysfs应该从两个角度来理解:内部表示和外部呈现。从内部表示来看,sysfs是一种表示内核对象、对象属性,以及对象关系的一种机制。sysfs核心将内核输出的对象、对象属性以及对象关系组织成树状形式,本书称为sysfs内部树。从外部呈现来看,sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构(可以称为sysfs外部树),向用户空间导出内核的设备和驱动信息,并且为内核的设备和驱动提供配置接口。
sysfs核心负责为内核中的内部表示和用户空间的外部呈现之间建立对应关系,也被称为sysfs映射:
• 内核对象被映射为用户空间的目录;
• 对象属性被映射为用户空间的常规文件;
• 对象关系被映射为用户空间的符号链接。
sysfs的代码放在fs/sysfs/中,它的公共函数原型在include/linux/sysfs.h。sysfs代码提供了两种构件,即两个方面的API:一个是内核编程接口,用于向内核其他模块提供构建内部树的API;另一个是文件系统接口,使得用户空间可以查看并操作对应的内核对象。
2.4.1 构建内核对象、对象属性和对象关系的内部树
尽管和用户空间看到的sysfs文件系统组织不完全相同,sysfs核心确实将内核对象、对象属性以及对象关系也组织成树的结构。sysfs内部树中有四种类型的节点:目录节点、链接节点、属性节点和二进制属性节点,分别和内核对象、对象关系、对象属性相对应。sysfs核心将通过sysfs文件系统呈现到用户空间。关于sysfs文件系统实现的部分,我们将在下一节阐述。
内部树的所有节点(如图2-7所示)都用sysfs_dirent描述符表示,根保存在全局变量sysfs_root中。如果是中间节点,sysfs_dirent的s_parent和s_sibling分别指向其父亲节点和下一个兄弟节点。最顶层节点sysfs_root,这两个域均为NULL。sysfs_dirent结构中的域的情况如表2-7所示。
图2-7 内部树的节点
表2-7 sysfs_dirent结构中的域(来自文件fs/sysfs/sysfs.h)
内核代码其他部分通过sysfs核心提供的API修改sysfs内部树。对内核代码可见的sysfs函数被分成三类,基于它们被导出到用户空间的对象类型(以及它们在文件系统中创建的对象类型)。
• 内核对象(目录)
• 对象属性(常规文件)
• 对象关系(符号链接)
此外,另外还有两个种类,被用来导出属性,除了导出单个ASCII文件之外。对于这两类,在文件系统中都创建常规文件:属性组和二进制文件。
1.内核对象
内核对象在内部用一个“目录节点”表示,由sysfs文件系统作为目录输出到用户空间。由于是目录节点,它下面将创建其他的节点(链接节点、属性节点和二进制节点,以及作为其后代内核对象的目录节点和代表属性组名的目录节点)。表示目录节点的数据结构为sysfs_elem_dir,sysfs_elem_dir结构中的域如表2-8所示,其children域指向了它的第一个孩子节点,沿着孩子节点的s_sibling组成一个链表。
表2-8 sysfs_elem_dir结构中的域(来自文件fs/sysfs/sysfs.h)
sysfs核心向Linux内核其他模块提供了两个对应的API函数。
• int sysfs_create_dir(struct kobject * kobj)
sysfs_create_dir为一个内核对象创建目录节点,它的步骤可归纳如下:在内存中为目录节点分配一个sysfs_dirent描述符,节点的名字为内核对象名,标志为目录;建立内核对象和目录节点之间的关联;将目录节点链入sysfs内部树,如果内核对象的parent域不为NULL,则目录节点会作为其父内核对象的子节点;否则目录节点会被作为sysfs_root的子节点。最终,如果成功,函数返回0;否则返回负的错误码。
• void sysfs_remove_dir(struct kobject *kobj)
sysfs_remove_dir将删除一个内核对象的目录节点。尽管这个函数的当前实现会负责将目录节点下面的属性节点一并删除,但是这种做法经常是引发竞争的根源,说不定哪一天就被从内核中去除。因此,建议Linux内核开发者在删除目录节点之前,自行删除它创建在该目录节点下的属性节点。
2.对象属性
对象属性在内部用一个“属性节点”表示,由sysfs作为常规文件输出到用户空间。属性节点的结构参见文件fs/sysfs/sysfs.h中的sysfs_elem_attr,而它又有一个指向属性描述符(即attribute结构,见文件include/linux/sysfs.h)的指针域。
• int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
sysfs_create_file为内核对象的属性创建一个属性节点。在内存中为属性节点分配一个sysfs_dirent描述符,节点的名字为属性名,标志为属性;将属性节点链入sysfs内部树,作为内核对象对应目录节点的子节点。反映到sysfs文件系统这将在内核对象的目录下创建一个对应名字的文件,文件的访问模式取决于属性的模式。
• void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr)
sysfs_remove_file删除内核对象和属性对应的节点,它将导致sysfs文件系统中内核对象的目录下和属性对应的文件被删除。
3.对象链接
对象链接反映了内核对象之间的关系,在内部用“链接节点”来表示。在sysfs文件系统中,它对应一个符号链接文件,而在内核中,只是两个不同内核对象之间的关联节点,具体来讲,是在一个内核对象目录节点下创建的链接节点,其target_sd域指向另一个内核对象目录节点,如表2-9所示。
表2-9 sysfs_elem_symlink结构中的域(来自文件fs/sysfs/sysfs.h)
int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name)
sysfs_create_link在两个内核对象之间创建用于关联的链接节点。第一个参数kobj是作为链接源的内核对象,第二个参数target是作为链接目标的内核对象,第三个参数name是链接名。在内存中,为链接节点分配一个sysfs_dirent描述符,节点的名字为name,标志为链接节点,节点的父亲为kobj所对应的目录节点,节点的target_sd域指向target所对应的目录节点,这个链接节点被链入sysfs内部树。反映到sysfs文件系统上,这将在kobj所对应的目录下创建名字为name的符号链接文件,指向target所对应的目录。
• void sysfs_remove_link(struct kobject * kobj, const char * name)
sysfs_remove_link删除内核对象的目录节点下给定名字的链接节点,它将导致sysfs文件系统中内核对象的目录下对应的符号连接文件被删除。
4.属性组
属性组是一个简化的接口,可以在一次调用中添加或删除一系列属性。
• int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
属性组中属性的创建是原子操作。如果任何一个属性添加失败(例如,因为内存不足或者属性名重复),这个组中已经添加的属性都将被删除,并返回错误码给调用者。
属性组包含一个属性结构的数组,以及可选的属性组名字。如果指定名字,则sysfs将在内核对象对应的目录节点下创建一个相应名字的目录节点,它将作为所有属性对应的属性节点的父亲。在组织大量的属性时,属性组非常有用。
如果没有指定属性组名字,则所有属性对应的属性节点都将被创建在和内核对象对应的目录节点下。
• void sysfs_remove_group(struct kobject * kobj, const struct attribute_group * grp)
在删除属性组时,所有属性对应的属性节点都被删除。如果属性组指定名字,则作为这些属性节点父亲的目录节点也被删除。
5.二进制属性
尽管我们介绍,对象属性为内核对象提供了获取和设置信息的一种方法。但考虑到某些信息有已知的标准格式(例如PCI配置空间寄存器)或者严格以二进制格式被使用(例如二进制firmware映像),为此引入二进制属性。
在内部,二进制属性用“二进制属性节点”来表示,由sysfs作为常规文件输出到用户空间。二进制属性节点对应的数据结构为sysfs_elem_bin_attr,在文件fs/sysfs/syfs.h中。
• int sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
sysfs_create_file为内核对象的二进制属性创建二进制属性节点。在内存中为二进制属性节点分配一个sysfs_dirent描述符,节点的父亲为内核对象所对应的节点,节点的名字为二进制属性的名字,标志为属性节点,这个二进制属性节点被链入sysfs内部树。反映到sysfs文件系统,这将在内核对象所在的目录下创建一个对应名字的二进制属性文件,文件的访问模式取决于二进制属性的模式。
• void sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
sysfs_remove_bin_file删除和内核对象的二进制属性对应的二进制属性节点,它将导致sysfs文件系统中内核对象的目录下对应名字的二进制属性文件被删除。
2.4.2 对sysfs文件的读/写转换为对属性的show和store操作
上一节介绍的API都是用来维护sysfs内部树的,也就是说,在内部组织好了内核对象、对象属性以及对象关系的结构。sysfs核心还包括用于将上面的包含内核对象、对象属性和对象关系的内部树通过sysfs文件系统导出到用户空间的代码。本节阐述对sysfs文件系统中的文件的读/写操作,如何最终转化为对内核对象的属性的show和store方法,本节内容和文件系统密切相关,请读者结合后面的“文件系统”一章进行理解。
在Linux内核初始化过程中,将调用sysfs_init函数(见文件fs/sysfs/mount.c),它将注册文件系统sysfs_fs_type,并在内核中构建了sysfs文件系统的装载实例。
sysfs在fs/sysfs/mount.c中通过sysfs_init函数进行初始化。这个函数直接被VFS初始化代码调用。它必须调用得早,因为许多子系统都依赖于sysfs以注册对象。这个函数负责三件事情。
• 创建sysfs_dir_cache高速缓存,这个高速缓存用于分配sysfs_ dirent描述符。
• 为sysfs文件系统初始化后备设备信息,这是因为sysfs文件系统比较特殊,其中的文件不支持预读,并且对文件的修改不需要被写回到磁盘——它本来就是在内存中动态构建的。
• 向VFS注册,调用register_ filesystem时传入sysfs_fs_type对象。sysfs文件系统还有一个特殊的地方,就是在内核中只有一个装载实例,无论在用户空间被装载多少次。
在内核中构建sysfs文件系统的内部装载实例,如图2-8所示。这样确保sysfs总是可以被其他内核代码使用,即使在引导过程早期,也不依赖于用户通过交互显式装载之。
图2-8 sysfs文件系统的装载实例
一旦这些动作完成,sysfs功能就到位了,可以被其他内核代码使用了。但是,用户空间要使用sysfs文件系统,还需要专门装载。
sysfs文件系统可以在启动时通过文件/etc/fstab被自动装载。大多数支持2.6内核的发行版本在/etc/sysfs都有一项用于装载sysfs。如下所示:
sysfs /sys sysfs noauto 0 0
Linux系统启动完成后,如果发现sysfs文件系统还没有被装载,用户可以通过如下命令来装载sysfs:
# mount -t sysfs sysfs /sys
注意:上面两个例子中,sysfs被装载的目录都是/sys,这并不是说sysfs一定要装载到这个目录下。它只是sysfs装载点事实上的标准位置,被各个主要发行版所采用。本书所有给出的sysfs文件系统目录或文件实例,也采用这一约定,如图2-9所示。
图2-9 sysfs文件系统的目录项表示
Linux文件系统的编程遵循特定的模式,用于将sysfs内部树导出到用户空间的sysfs文件系统也是如此。因此,sysfs文件系统的部分内容需要在读者读完本书“文件系统”那一章后,再回过头自己参照代码理解。这里我们主要看对sysfs文件的读操作如何转换为对属性的show方法的调用。类似地,对sysfs文件的写操作被转换为对属性的store方法的调用。也就是说,kobject的属性作为文件系统中的常规文件输出,sysfs将文件I/O操作转发给为属性定义的方法,提供了一种读/写内核对象属性的机制。
在“文件系统”那一章,我们会看到,用户空间在常规文件上执行read系统调用,将最终作用在这个常规文件的文件操作表中的read方法。sysfs文件系统的实现逻辑确保了属性文件的文件操作表为sysfs_file_operations(定义在文件fs/sysfs/file.c),read回调函数为sysfs_read_file。也就是说,在读属性文件时,sysfs_read_file会被执行,代码如程序2-10所示。
程序2-10 函数sysfs_read_file()代码(摘自文件fs/sysfs.c)
sysfs_read_file() 134static ssize_t 135sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) 136{ 137 struct sysfs_buffer * buffer = file->private_data; 138 ssize_t retval = 0; 139 140 mutex_lock(&buffer->mutex); 141 if (buffer->needs_read_fill || *ppos == 0) { 142 retval = fill_read_buffer(file->f_path.dentry,buffer); 143 if (retval) 144 goto out; 145 } 146 pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n", 147 __func__, count, *ppos, buffer->page); 148 retval = simple_read_from_buffer(buf, count, ppos, buffer->page, 149 buffer->count); 150out: 151 mutex_unlock(&buffer->mutex); 152 return retval; 153}
sysfs_read_file函数有四个参数:第一个参数file为指向要读取的file描述符的指针;第二个参数buf为指向用户空间缓冲区的指针,读出的属性数据将保存在这个缓冲区;第三个参数count为要读取的字节数;第四个参数pos是一个输入/输出参数,传入在文件中的起始偏移位置,传出更新后的偏移位置。
对sysfs属性文件的读/写过程用到一个缓冲区作为中转,其结构为sysfs_buffer,该结构中域的功能如表2-10所示。这样做的一个重要目的是“一次填充,多次使用”。以读为例,这个结构中指向一个页面,用来保存属性数据。第一次读时,从属性获得数据填充整个页面(当前属性数据不会超过4096个字节),这次读操作以及后续读操作依据页面的内容来提供,除非特别声明页面内容无效。
表2-10 sysfs_buffer结构中的域(来自文件fs/sysfs/file.h)
sysfs_buffer描述符在系统调用open时被分配,并保存在属性文件的file描述符的private_data域,请参见fs/sysfs.c文件的sysfs_open_file函数代码。
在read系统调用之前,一定会先执行open系统调用。换句话说,在sysfs_read_file函数执行时,file描述符的private_data必定指向一个有效的sysfs_buffer描述符。
第137行先获得这个sysfs_buffer描述符,其中有一个页面指针指向用来保存缓冲区数据的页面。在本函数被调用的时候,页面中的数据可能还是无效的(needs_read_fill域为1),或者甚至页面可能还没有被分配(第一次读取,即*ppos为零时)。第141~145行的代码处理这两种情况,调用fill_read_buffer读取内核对象属性填充这个缓冲区,函数fiee_read_buffer()代码如程序2-11所示。
程序2-11 函数fill_read_buffer()代码(摘自文件fs/sysfs.c)
sysfs_read_file()→fill_read_buffer() 74static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer) 75{ 76 struct sysfs_dirent *attr_sd = dentry->d_fsdata; 77 struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; 78 const struct sysfs_ops * ops = buffer->ops; 79 int ret = 0; 80 ssize_t count; 81 82 if (!buffer->page) 83 buffer->page = (char *) get_zeroed_page(GFP_KERNEL); 84 if (!buffer->page) 85 return -ENOMEM; 86 87 /* need attr_sd for attr and ops, its parent for kobj */ 88 if (!sysfs_get_active(attr_sd)) 89 return -ENODEV; 90 91 buffer->event = atomic_read(&attr_sd->s_attr.open->event); 92 count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page); 93 94 sysfs_put_active(attr_sd); 95 96 /* 97 * The code works fine with PAGE_SIZE return but it's likely to 98 * indicate truncated result or overflow in normal use cases. 99 */ 100 if (count >= (ssize_t)PAGE_SIZE) { 101 print_symbol("fill_read_buffer: %s returned bad count\n", 102 (unsigned long)ops->show); 103 /* Try to struggle along */ 104 count = PAGE_SIZE - 1; 105 } 106 if (count >= 0) { 107 buffer->needs_read_fill = 0; 108 buffer->count = count; 109 } else { 110 ret = count; 111 } 112 return ret; 113}
进入fill_read_buffer函数有两种可能:
• 页面已经分配,但页面中的内容无效。这主要出现在need_read_fill域设置的情况下;
• 页面还没有被分配。这一般出现在从第一次开始位置读属性数据的情况下。
因此,第82~85行首先检查页面是否已经被分配。如果没有,先为sysfs_buffer分配一个页面,保存在page域。
在第92行,调用sysfs_ops操作表的show方法将属性数据填入页面。而操作表地址也已经由sysfs_open_file函数从内核对象所属对象类型赋值保存在sysfs_buffer结构中。show函数返回已填充的字节数,我们做如下的处理。
• 如果填充字节数超过一个页面(含)。这实际是不可能的,因为我们只给了一个页面的缓冲区,而且最后应该以“\0”字节结尾。碰到这样的返回值,我们只有重新设定。这是第100~104行的目的。
• 如果返回负的错误码,则将错误码返回到调用者,见代码第110行。
• 否则表明数据已经成功填充到页面,清零needs_read_fill域,并更新count域。参见第106~108行代码。
本函数返回到sysfs_read_file函数的第142行。返回负值表示错误。若返回0,表示页面填充成功,接下来还需要将数据复制到用户空间,为此在第148和149行调用simple_read_from_buffer函数,其原型如下:
ssize_t simple_read_from_buffer(void __user *to, size_t count, loff_t *ppos, const void *from, size_t available)
第一个参数to为要读取数据到其中的用户空间缓冲区;第二个参数count为要读取的最大字节数;第三个输入/输出参数ppos为在缓冲区中的当前/更新位置;第四个参数from为要从其中读取数据的内核缓冲区;第五个参数available为内核缓冲区的长度。
simple_read_from_buffer函数的逻辑相当直观,它从缓冲区from的当前位置*ppos读取最多count个字节的数据到用户空间缓冲区to中,实际读取的字节数还受缓冲区的长度available制约。
若成功,返回读取的字节数,并且ppos指针向前移动同样的字节数。若错误,返回负的错误码。
至此,对属性在sysfs文件系统中对应文件的read系统调用,已经从内核对象sysfs操作表的show方法获得了数据,正确地返回了。
我们只是简要说明一下对sysfs文件的写操作被转换为对属性的store方法的调用的大致步骤,不准备详细跟踪代码流程。
在通过sysfs文件系统写属性文件时,它在文件操作表sysfs_file_operations(文件fs/sysfs/file.c)定义的write回调函数sysfs_write_file(文件fs/sysfs.c)会被执行。
sysfs_write_file的实现逻辑也很直接,调用了同一文件中的辅助函数fill_write_buffer和flush_write_buffer。前者将用户空间缓冲区中的数据先写入到中转缓冲区(sysfs_buff)的页面(必要时为它分配空间),并设置needs_read_fill域;后者最终调用内核对象sysfs操作表中的store回调方法。
2.4.3 为具体内核对象定义属性的规范流程
对对象属性所对应sysfs文件的读/写操作被转发给它所属内核对象的sysfs_ops操作表中的方法。sysfs_ops结构定义在文件fs/sysfs/file.h中,它只有show和store两个函数指针域。
• ssize_t (*show)(struct kobject *, struct attribute *,char *)
为内核对象的属性获取数据,填入给定的缓冲区。第一个参数为指向内核对象描述符的指针,第二个参数为指向属性描述符的指针,第三个参数为要保存数据到其中的缓冲区的指针。
• ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t)
根据给定的缓冲区,为内核对象的属性设置数据。第一个参数为指向内核对象描述符的指针,第二个参数为指向属性描述符的指针,第三个参数为要从其中获得数据的缓冲区的指针,第四个参数为缓冲区的长度。
在上述两个函数中,我们都看到了属性描述符,即attribute结构,该结构中的域如表2-11所示。它的定义非常简单,可以看作是一个“裸”的属性。其中只包含属性名和属性模式,不包含读/写这个属性值的方法。
表2-11 attribute结构中的域(来自文件include/linux/sysfs.h)
一个内核对象下有多个属性,也就是说,show和store方法要负责多个属性的处理。很容易想到的做法是,在show和store方法中根据属性名比较传入属性和内核对象下的所有可能属性,如果匹配,则调用该属性的逻辑代码。这显然是一个笨拙的方案,它至少存在两个问题:
① show和store方法会相当冗长,尤其在内核对象有多个属性的时候;
② 可扩充性差,如果要添加一个属性,还需要修改show和store方法。
Linux内核当然不会这么做,如它一贯的风格,这里的设计也很巧妙。我们以图2-10作为例子进行阐述。先总结其中的关键点,后面再给出更详细的分析。
定义一个具体的内核对象结构(例如foo_obj),以kojbect作为它的一个内嵌域,并为它定义一个具体的对象类型(例如foo_type),实现其中的sysfs_ops方法表(foo_attr_show和foo_attr_store)。
图2-10 为具体内核对象定义属性的规范优流程
定义具体内核对象的属性结构(foo_attribute),以attribute作为它的一个内嵌域,其中还包含与具体内核对象相关的show和store函数指针。如果用这个属性结构定义新的属性,则还必须为属性提供show和store的方法实现。
foo_attr_show和foo_attr_store方法实现必须将read或write系统调用转为对具体内核对象属性结构的show和store方法的调用。此外,可以额外实现两个辅助函数:foo_create_file和foo_remove_file,用于方便为具体内核对象创建和删除sysfs属性。
kobject被嵌入到一个foo_obj结构中,它通过对象类型指向一个sysfs操作表,后者的show和store回调方法分别被实例化为foo_attr_show和foo_attr_store。
为了避免上面的问题,对“裸”的属性结构进行扩充,引入特定的show和store回调方法,封装成一个新的结构。一般地,具体内核对象结构应该定义自己的属性结构,例如对于foo_obj,其属性结构foo_attribute定义如下:
struct foo_attribute { struct attribute attr; ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf); ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count); };
从参数来看,内核对象的通用回调函数,已经变成了具体属性的特定回调函数。
show的第一个参数为指向具体内核对象(foo_obj)描述符的指针,第二个参数为指向具体属性(foo_attribute)描述符的指针,第三个参数为要保存数据到其中的缓冲区的指针。
store的第一个参数为指向具体内核对象(foo_obj)描述符的指针,第二个参数为指向具体属性(foo_attribute)描述符的指针,第三个参数为要从其中获得数据的缓冲区的指针,第四个参数为缓冲区的长度。
大多数情况下,每个属性实现自己独有的回调函数。在回调函数被调用时,已经知道自己处理的是哪个属性了,基于这个原因,上面两个回调函数的第二个参数都是可以被去掉的。之所以保留它,是为了在多个属性共用show和store实现代码的时候,在代码中区分正在处理的属性。这似乎就是我们本节要解决的问题,但放在这里,就成为代码重用的一个例证了。
我们可以用这个结构来定义foo_obj的所有属性,但实际上还可以通过宏来进一步简化定义:
#define FOO_ATTR(_name, _mode, _show, _store) \ struct foo_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode}, \ .show = _show, \ .store = _store, \ }
这样,为foo_obj定义属性变得非常简单,例如:
FOO_ATTR(value,0644, value_show, value_store);
将声明一个类型为struct foo_attribute的foo_attr_value对象,其名字为value,show方法为value_show,store方法为value_store。
这样,具体属性有了自己的回调方法,在文件被读/写时,sysfs调用具体内核对象的通用回调方法,在这些方法实现中需要派发到具体属性的对应回调方法。就foo_obj结构来讲,foo_attr_show和foo_attr_store方法需要进一步派发到对应foo_attribute的show和store方法上,过程如下。
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj) #define to_foo_attr(x) container_of(x, struct foo_attribute, attr) static ssize_t foo_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); foo = to_foo_obj(kobj); if (!attribute->show) return -EIO; return attribute->show(foo, attribute, buf); } static ssize_t foo_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); foo = to_foo_obj(kobj); if (!attribute->store) return -EIO; return attribute->store(foo, attribute, buf, len); }
两个方法都将通用的struct kobject和struct attribute指针转换成指向具体内核对象(foo_obj)和属性(foo_attribute)的指针,然后调用该属性的对应方法。
为了便于为具体内核对象foo_obj创建和删除sysfs属性,又定义了两个辅助函数:foo_create_file和foo_remove_file。
int foo_create_file(struct foo_obj *foo, const struct foo_attribute *attr) { int error = 0; if (foo) error = sysfs_create_file(&foo->kobj, &attr->attr); return error; } void foo_remove_file(struct foo_obj *foo, const struct foo_attribute *attr) { if (foo) sysfs_remove_file(&foo->kobj, &attr->attr); }