LINUX SYSFS文件系统分析之四 文件处理及相关系统调用分析

上一篇文章主要介绍了sysfs目录的创建,本篇主要介绍sysf文件的创建,以及文件操作相关的系统调用。

在开始介绍文件创建之前,我们将之前说明的kobject、sysfs_dirent、sysfs_open_dirent、sysfs_buffer、sysfs_ops、attribute、file、dentry等结构体关联的简略图提供出来,而sysfs文件系统进行目录及文件的创建,其实说白了就是完成这些结构体之间的关联操作。

如下图所示,主要涉及linux内核三大子模块,分别为VFS模块、进程处理模块(该模块通过文件描述符与sysfs、VFS关联)、sysfs模块。

这三个模块的关联说明如下:

  1. VFS模块主要通过struct dentry结构体变量,获取sysfs的sysfs_dirent结构体变量,而该变量为sysfs中目录与文件创建的关键变量,sysfs中以该类型变量表示目录与文件。
  2. 而进程处理模块则通过文件描述符变量与sysfs中的sysfs_buffer变量关联,通过该变量可实现对属性文件的读写操作。
  3. sysfs模块中主要涉及两部分,其中
    1. kobject则对应目录部分,包括了kobj sysfs diernt类型变量以及kobject对应的处理方式kobj_type(提供kobject的store/show与release接口等);
    2. kobject属性在sysfs中被抽象为普通文件,而该普通文件也包含了sysfs_dirent、sysfs_open_dirent、sysfs attribute、 具体模块属性xxx_attribute、sysfs_buffer(主要与文件描述符关联,并表示每一个通过进程打开该属性文件相关的私有信息:如pos等)

 

通过该图,可帮助我们快速建立sysfs 目录与文件相关的信息,其实目录与文件的创建流程也就是实现下图这些结构体变量之间的关联操作。在linux内核代码阅读中,只要把各模块结构体变量之间的关联理清,则基本上即熟悉了该模块相关的实现流程(因代码的实现总体上来说就是实现这些结构体之间的关联)。

 

 

 

 

sysfs文件创建相关接口说明

       在上一篇文章的介绍中,通过目录dentry的i_op接口定义,未实现create、mkdir等系统调用接口,因此我们在应用层是无法通过系统调用创建一个文件的。

const struct inode_operations sysfs_dir_inode_operations = {

.lookup                = sysfs_lookup,

.permission        = sysfs_permission,

.setattr        = sysfs_setattr,

.getattr        = sysfs_getattr,

.setxattr        = sysfs_setxattr,

};

      但sysfs在内核层提供了创建sysfs文件的接口,供kobject相关的接口调用,从而实现内核层创建sysfs文件的功能。

针对sysfs文件系统创建文件以及文件操作相关的系统调用,均定义在fs/sysfs/file.c中。针对

这个接口文件的创建,其接口为sysfs_add_file(该接口通过调用sysfs_add_file_mode实现文件的创建操作)。下面我们分析下sysfs_add_file_mode这个接口,该接口的流程图如下所示。

该接口的功能如下:

1.通过父节点的kobject,获取namespace;

2.设置创建文件的类型(bin文件或者attr文件)

3.调用sysfs_new_dirent创建一个新的sysfs_dirent变量,并插入至其父节点sd的红黑节点中(若已存在相同namespace、相同名称的文件,则返回失败)。

文件的创建流程与目录的创建类型均是调用sysfs_new_dirent与sysfs_add_one实现sd的创建与插入节点操作。

 

在创建文件时,会让上层调用接口传递struct attribute类型的变量,并赋值给sd->s_attr.attr,而该属性变量即可实现属性文件的读写操作(通过container_of,获取包含该成员struct attribute的变量,从而获取该变量的show/store)。具体在下面的文件操作系统调用说明中会详细介绍。

该接口为最里层的实现,而在外层,针对bin文件与attr文件,又分别进行了封装,这两个接口的定义如下:

 

通过传递SYSFS_KOBJ_ATTR,说明创建attr文件

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)

{

BUG_ON(!kobj || !kobj->sd || !attr);

 

return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);

 

}

通过传递SYSFS_KOBJ_BIN_ATTR,说明创建bin文件

int sysfs_create_bin_file(struct kobject *kobj,

  const struct bin_attribute *attr)

{

BUG_ON(!kobj || !kobj->sd || !attr);

 

return sysfs_add_file(kobj->sd, &attr->attr, SYSFS_KOBJ_BIN_ATTR);

}

 

 

而针对属性文件创建接口sysfs_ceate_file,内核的其他模块,则对该接口进行封装分别实现创建不同文件的功能,比如class_create_file、bus_create_file接口等,这些接口其实也就是根据自定义的属性变量不同来分别的,如bus_create_file则根据结构体变量struct bus_attribute ,实现bus属性文件私有的store/show接口,而class_create_file则根据结构体变量struct class_attribute来实现class下属性文件的store/show接口

struct bus_attribute {

struct attribute        attr;

ssize_t (*show)(struct bus_type *bus, char *buf);

ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);

};

 

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);

};

 

 

 

文件操作相关的系统调用接口说明

      针对LINUX VFS系统中open系统调用,在之前分析的文章中已经说明过(LINUX VFS分析之do_sys_open接口的文件打开接口do_last分析),系统调用通过调用do_dentry_open接口,实现将inode->i_fop赋值给文件描述符的f_op,之后通过file->f_op->open实现文件的打开操作。

       而在分析devtmpfs时,针对字符设备文件的文件操作接口,通过file->f_op->open调用chrdev_open接口,在chrdev_open接口中重新设置file->f_op,从而将字符设备文件的操作接口重新映射至具体的字符设备驱动提供的文件操作接口中。

 

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,

};

 

   在sysfs文件系统中,针对属性文件,其文件操作接口的定义如上所示,主要实现open、read、write、poll接口。其中poll接口主要用于sysfs的uevent事件(应用层可借助于select或epoll实现异步监听sysfs相关的event事件),而read/write接口则分别实现属性的读取与写入操作。而关于read/write接口是如何关联到具体属性的store/show接口的,其处理方式和devtmpfs相似又有所区别,在sysfs_open_file接口里,其通过父目录的kobject->ktype->sysfs_ops获取其父目录注册的sysfs_ops接口,并作为struct sysfs_buffer 类型的变量的成员变量,在read、write接口调用时会通过该sysfs_ops接口指针,实现具体文件的读写调用。

 

      而关于属性文件相关的结构体struct sysfs_buffer与kobject等结构的关联,之前已在结构体一节进行了说明,此处再说明一下,这些结构体的关联如下。

当一个属性文件被打开一次后,则会创建一个sysfs_open_dirent类型的结构体变量,而该变

量中的buffers链表,则会将所有调用open接口打开该文件时创建的struct sysfs_buffer变量链接在一起。同时每一个文件描述符的私有指针private_data指向一个struct sysfs_buffer变量。struct sysfs_buffer变量中存储了一个文件描述符中针对该文件的pos以及读写属性接口(通过sysfs_ops接口)。

下面我们分析下属性文件的open接口sysfs_open_file,该接口的流程图如下

主要实现的功能即建立上述结构体之间的关联:

  1. 通过父对象的ktype->sysfs_ops获取该kobject对象的store/show接口,如针对bus目录,其sysfs_ops的定义如下:
     
static const struct sysfs_ops bus_sysfs_ops = {

.show        = bus_attr_show,

.store        = bus_attr_store,

};
  1. 做合法性检查,即是否正确拥有对该文件的操作权限;
  2. 创建sysfs_buffer类型的变量,作为本文件描述符的私有变量,存储该文件的store/show接口以及文件操作信息(pos、event、是否需要更新读信息标志needs_read_fill等)
  3. 获取/创建sysfs_open_dirent类型变量,并将上述3中创建的sysfs_buffer变量插入其buffer链表中。

以上即是打开操作。

我们以bus为例,在创建bus类型的kobject时,设置kobject->ktype->sys_ops为bus_sysfs_ops ,bus_sysfs_ops 中的show、store定义如下,这两个接口通过传递struct attribute *attr类型变量,通过container_of接口获取struct bus_attribute类型的变量,然后再根据获取的struct bus_attribute类型的变量,再调用属性的文件的store、show接口,实现对属性文件中各属性的读写操作。

 

/*

 * sysfs bindings for buses

 */

static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,

     char *buf)

{

    struct bus_attribute *bus_attr = to_bus_attr(attr);

    struct subsys_private *subsys_priv = to_subsys_private(kobj);

    ssize_t ret = 0;

 

    if (bus_attr->show)

        ret = bus_attr->show(subsys_priv->bus, buf);

    return ret;

}

 

static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,

      const char *buf, size_t count)

{

    struct bus_attribute *bus_attr = to_bus_attr(attr);

    struct subsys_private *subsys_priv = to_subsys_private(kobj);

    ssize_t ret = 0;

 

    if (bus_attr->store)

        ret = bus_attr->store(subsys_priv->bus, buf, count);

    return ret;

}

而我们在创建bus属性文件时,只要创建struct bus_attribute类型的变量,并通过设置该struct bus_attribute变量的show、store接口,然后在创建属性文件时,将bus_attribute.attr传递给文件对应sysfs_dirent->s_attr.attr,然后在bus_attr_store/bus_attr_show接口中,即可根据sysfs_dirent->s_attr.attr,通过container_of获取到定义的struct bus_attribute类型的变量,即可实现该属性文件的读写接口的定义。内核中各模块通过bus_add_attrs接口,实现创建bus相关的属性文件。

 

而sysfs的read系统调用的接口如下(在fill_read_buffer中调用的sys_ops->show),即我们上面说的,调用kobject->ktype->sys_ops->show,然后再根据具体kobject的show接口,通过container_of,获取具体文件的xxx_attribute类型的变量,从而调用该变量定义的store/show接口。若上述所说的bus文件的读写。

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;

}

pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n",

 __func__, count, *ppos, buffer->page);

retval = simple_read_from_buffer(buf, count, ppos, buffer->page,

 buffer->count);

out:

mutex_unlock(&buffer->mutex);

return retval;

}

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;

int ret = 0;

ssize_t count;

 

if (!buffer->page)

buffer->page = (char *) get_zeroed_page(GFP_KERNEL);

if (!buffer->page)

return -ENOMEM;

 

/* need attr_sd for attr and ops, its parent for kobj */

if (!sysfs_get_active(attr_sd))

return -ENODEV;

 

buffer->event = atomic_read(&attr_sd->s_attr.open->event);

count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);

 

sysfs_put_active(attr_sd);

 

/*

 * The code works fine with PAGE_SIZE return but it's likely to

 * indicate truncated result or overflow in normal use cases.

 */

if (count >= (ssize_t)PAGE_SIZE) {

print_symbol("fill_read_buffer: %s returned bad count\n",

(unsigned long)ops->show);

/* Try to struggle along */

count = PAGE_SIZE - 1;

}

if (count >= 0) {

buffer->needs_read_fill = 0;

buffer->count = count;

} else {

ret = count;

}

return ret;

}

 

针对写文件的系统调用接口sysfs_write_file,和sysfs_read_file类型,此处不再分析。

 

 

 

本章主要说明了sysfs的文件创建以及文件操作相关的系统调用接口。在我们理解vfs以及sysfs的结构体关联后,文件创建以及文件操作相关的系统调用分析起来也比较简单。

发布了140 篇原创文章 · 获赞 30 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/lickylin/article/details/102653175
今日推荐