sysfs文件系统

简介: sysfs

sysfs

Linux的设备模型采用sysfs表示。将设备的树状的结构反映到用户空间。

用户空间可以修改文件属性来修改设备的属性值。

sysfs的设计基于kobject和kset。同时,sysfs是基于VFS的虚拟文件系统,重载了file_operations和dir_operations。

所以,先从kobject和kset入手,然后再依次阅读file_operations和dir_operations。

kobject

kobject的作用是一个"连接件"。

下面是kobject的一个使用场景:

struct cdev {
       struct kobject kobj;
       struct module *owner;
       const struct file_operations *ops;
       struct list_head list;
       dev_t dev;
       unsigned int count;
   };

kobject作为“连接件”被使用者包含,作用相当于stl::list的基类。

kobject结构体

struct kobject {
        const char*name;
        struct list_headentry;
        struct kobject*parent;
        struct kset*kset;
        struct kobj_type*ktype;
        struct sysfs_dirent*sd;
        struct krefkref;
        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;
    };

初始化

1) set all fields to 0, 这一步会被使用kobject的结构体调用,如cdev。
2) kobject_init()初始化列表头部和引用计数为1, 具体是在kobject_init_internal中做的:
kobject_init() -> kobject_init_internal()
          {
              if (!kobj)
                  return;
              kref_init(&kobj->kref);
              INIT_LIST_HEAD(&kobj->entry);
              kobj->state_in_sysfs = 0;
              kobj->state_add_uevent_sent = 0;
              kobj->state_remove_uevent_sent = 0;
              kobj->state_initialized = 1;
          }
3) 设置name
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)

kobject中的引用计数

get

kobject_get() -> kref_get():
    void kref_get(struct kref *kref)
    {
        WARN_ON(!atomic_read(&kref->refcount));
        atomic_inc(&kref->refcount);
        smp_mb__after_atomic_inc();
    }

put

void kobject_put(struct kobject *kobj)
     {
         if (kobj) {
             if (!kobj->state_initialized)
                 WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
                     "initialized, yet kobject_put() is being "
                     "called.\n", kobject_name(kobj), kobj);
             kref_put(&kobj->kref, kobject_release);
         }
     }
在kref_put的第二个参数是kobject的引用计数为0时的析构函数,kref_put里会原子的减1,如果为0,会调用析构函数。
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
     {
         WARN_ON(release == NULL);
         WARN_ON(release == (void (*)(struct kref *))kfree);
         if (atomic_dec_and_test(&kref->refcount)) {
             release(kref);
             return 1;
         }
         return 0;
     }
kobject_release() 根据ktype找到这个kobj对应的真正的类型,所属的析构函数。
struct kobj_type {
        void (*release)(struct kobject *kobj);
        const struct sysfs_ops *sysfs_ops;
        struct attribute **default_attrs;
        const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
        const void *(*namespace)(struct kobject *kobj);
    };

kobj的继承层次,Ksets, subsystem(已经被移除)

kobj之间的层次关系可以通过两种方式表达:parent指针 和 kset.

parent 指针

通过parent指针可以表达树状的关系。
 但,parent更多的用来表示sysfs文件系统的树状关系。

Ksets

struct kset {
         struct list_head list;
         spinlock_t list_lock;
         struct kobject kobj;
         const struct kset_uevent_ops *uevent_ops;
     };
一个ksets是相同类型的kobj的集合。
 ksets只有在sysfs中才能发挥作用。
 一个ksets被加入到系统,在sysfs下面就会有一个目录项与之对应。
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)
     {
         1) 根据fmt生成name。
            retval = kobject_set_name_vargs(kobj, fmt, vargs);
         2) 如果kobj->kset非空,把kobj加入到kobj->kset尾部。
             if (kobj->kset) {
                if (!parent)
                    parent = kobject_get(&kobj->kset->kobj);
                kobj_kset_join(kobj);
                kobj->parent = parent;
             }
         3) 在sysfs文件系统中创建节点,同时根据kobj->ktype->default_attrs[]生成对应的文件。
            error = create_dir(kobj);
     }

subsystem

struct subsystem {
         struct kset kset;
         struct rw_semaphore rwsem;
     };
subsystem是对kset的简单的封装,rw_semaphore保护kset的链表操作。
 看一下系统中的subsys有哪些:
 max@max-gentoo ~/Code/Kernel/linux-3.19 $ ls /sys/
 block  bus  class  dev  devices  firmware  fs  kernel  module  power
 在2.6.28以后subsystem经过讨论,被认为是冗余的,kset就可以完全表示sysfs目录,kobject可以表示属性文件。
 所以不做深究了。

sysfs_dirent

kset,kobject是如何和VFS对接起来的呢?cgroup对接VFS的方式一样,通过dentry->d_fsdata,d_fsdata指向具体文件系统的实体。
插入一张图说明sysfs_dirent在sysfs目录树中的作用(我不是原创)‘

image.png

struct sysfs_dirent {
         atomic_ts_count;
         atomic_ts_active;
         struct sysfs_dirent*s_parent;
         const char*s_name;
         union {
             struct sysfs_elem_dirs_dir;
             struct sysfs_elem_symlinks_symlink;
             struct sysfs_elem_attrs_attr;
             struct sysfs_elem_bin_attrs_bin_attr;
         };
         unsigned ints_flags;
         ino_ts_ino;
         umode_ts_mode;
         struct sysfs_inode_attrs *s_iattr;
     };
sysfs文件系统中每个节点都是一个sysfs_dirent表示。

sysfs的初始化

int __init sysfs_init(void)
   {
        int err = -ENOMEM;
        sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache",
                                              sizeof(struct sysfs_dirent),
                                              0, 0, NULL);
        err = sysfs_inode_init();
        err = register_filesystem(&sysfs_fs_type);
        if (!err) {
                sysfs_mount = kern_mount(&sysfs_fs_type);
                if (IS_ERR(sysfs_mount)) {
                        printk(KERN_ERR "sysfs: could not mount!\n");
                        err = PTR_ERR(sysfs_mount);
                        sysfs_mount = NULL;
                        unregister_filesystem(&sysfs_fs_type);
                        goto out_err;
                }
        }
    }
1) kmem_cache_create
   创建一个分配sysfs_dirent的slab。
2) sysfs_inode_init();
3) register_filesystem(&sysfs_fs_type);
   向系统注册sysfs的文件系统类型。
static struct file_system_type sysfs_fs_type = {
           .name                = "sysfs",
           .get_sb                = sysfs_get_sb,
           .kill_sb        = kill_anon_super,
       };
4) sysfs_mount = kern_mount(&sysfs_fs_type);
   挂载sysfs_fs_type文件系统。

kern_mount 最终会调用sysfs_fs_type中的get_sb()完成注册过程。

static int sysfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt)
   {
       return get_sb_single(fs_type, flags, data, sysfs_fill_super, mnt);
   }

get_sb_single()会调用sysfs_fill_super():

static int sysfs_fill_super(struct super_block *sb, void *data, int silent)
   {
       struct inode *inode;
       struct dentry *root;
       sb->s_blocksize = PAGE_CACHE_SIZE;
       sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
       sb->s_magic = SYSFS_MAGIC;
       sb->s_op = &sysfs_ops;
       sb->s_time_gran = 1;
       sysfs_sb = sb;
       mutex_lock(&sysfs_mutex);
       inode = sysfs_get_inode(&sysfs_root);
       mutex_unlock(&sysfs_mutex);
       if (!inode) {
           pr_debug("sysfs: could not get root inode\n");
           return -ENOMEM;
       }
       root = d_alloc_root(inode);
       if (!root) {
           pr_debug("%s: could not get root dentry!\n",__func__);
           iput(inode);
           return -ENOMEM;
       }
       root->d_fsdata = &sysfs_root;
       sb->s_root = root;
       return 0;
   }

1) 初始化sb,并把sb赋值给全局的sysfs_sb。

2) sysfs_get_inode(&sysfs_root);

获取一个inode节点,并且初始化inode中的i_op回调函数。

3) root = d_alloc_root(inode);

在以"/"为父节点,分配dentry。

sysfs的查找

文件系统的查找最终是调用dentry->i_op->lookup().这个i_op是在sysfs_init_inode中初始化的:

inode->i_op = &sysfs_dir_inode_operations;

所以,在sysfs目录树中的查找是通过sysfs_dir_inode_operations中定义的sysfs_lookup函数来完成的。

sysfs_lookup中的查找最终落实到sysfs_find_dirent上:

struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, const unsigned char *name)
   {
       struct rb_node *p = parent_sd->s_dir.name_tree.rb_node;
       while (p) {
           int c;
           #define noderb_entry(p, struct sysfs_dirent, name_node)
           c = strcmp(name, node->s_name);
           if (c < 0) {
               p = node->name_node.rb_left;
           } else if (c > 0) {
               p = node->name_node.rb_right;
           } else {
               return node;
           }
           #undef node
       }
       return NULL;
   }

可以看到在sysfs目录树中的路径比较是通过比较字符串 sysfs_dirent->s_name来完成的。

所以,sysfs目录树中的目录名dentry->d_fsdata->s_name。

sysfs创建目录

在linux设备模型中,每注册一个kobject,就会为之创建一个目录。

int sysfs_create_dir(struct kobject * kobj)
   {
       struct sysfs_dirent *parent_sd, *sd;
       int error = 0;
       BUG_ON(!kobj);
       if (kobj->parent)
           parent_sd = kobj->parent->sd;
       else
           parent_sd = &sysfs_root;
       error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
       if (!error)
           kobj->sd = sd;
       return error;
   }

可以看到如果kobj->parent没有指定,则用全局的sysfs_root。然后调用create_dir完成sysfs目录树下的目录创建。

static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, const char *name, struct sysfs_dirent **p_sd)
   {
         umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
         struct sysfs_addrm_cxt acxt;
         struct sysfs_dirent *sd;
         int rc;
         sd = sysfs_new_dirent(name, mode, SYSFS_DIR);
         if (!sd)
             return -ENOMEM;
         sd->s_dir.kobj = kobj;
         sysfs_addrm_start(&acxt, parent_sd);
         rc = sysfs_add_one(&acxt, sd);
         sysfs_addrm_finish(&acxt);
         if (rc == 0)
             *p_sd = sd;
         else
             sysfs_put(sd);
         return rc;
   }

1) 首先申请一个sysfs_dirent结构体。

2) 然后调用sysfs_add_one 把sd加入到父节点下面。sysfs_link_sibling(sd)。

sysfs创建属性文件

sysfs目录树下的每一个文件都是一个kobj的属性。

int sysfs_add_file_mode(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type, mode_t amode)
   {
       umode_t mode = (amode & S_IALLUGO) | S_IFREG;
       struct sysfs_addrm_cxt acxt;
       struct sysfs_dirent *sd;
       int rc;
       sd = sysfs_new_dirent(attr->name, mode, type);
       if (!sd)
           return -ENOMEM;
       sd->s_attr.attr = (void *)attr;
       sysfs_addrm_start(&acxt, dir_sd);
       rc = sysfs_add_one(&acxt, sd);
       sysfs_addrm_finish(&acxt);
       if (rc)
           sysfs_put(sd);
       return rc;
   }

过程和创建目录大致相同,不同点是创建目录时的父节点是上一层节点,创建属性文件时的节点就是kobj对应的sysfs_dirent。

sysfs打开和读写操作

sysfs文件的读写函数是在sysfs_init_inode时设置的。

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文件打开

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;
       if (!sysfs_get_active_two(attr_sd))
           return -ENODEV;
       ops = kobj->ktype->sysfs_ops;
       buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
       mutex_init(&buffer->mutex);
       buffer->needs_read_fill = 1;
       buffer->ops = ops;
       file->private_data = buffer;
       error = sysfs_get_open_dirent(attr_sd, buffer);
       sysfs_put_active_two(attr_sd);
       return 0;
   }

1) 根据dentry->d_fsdata找到sysfs_dirent。

2) 根据sysfs_dirent找到父节点的kobj,这个结构里存储了操作这个文件的函数指针。

3) 申请sysfs_buffer,并把操作文件的函数指针ops赋值给buffer->ops。

4) 最后把buffer赋值给file->private_data。

文件的写入

static ssize_t
    sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
    {
        struct sysfs_buffer * buffer = file->private_data;
        ssize_t len;
        mutex_lock(&buffer->mutex);
        len = fill_write_buffer(buffer, buf, count);
        if (len > 0)
            len = flush_write_buffer(file->f_path.dentry, buffer, len);
        if (len > 0)
            *ppos += len;
        mutex_unlock(&buffer->mutex);
        return len;
    }
1) file->private_data
   首先从file中找到在open过程中申请的sysfs_buffer。
   这个buffer里有操作文件的ops。
2) fill_write_buffer
   申请一个page,并且把用户空间的buf拷贝过来。
fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t count)
       {
           int error;
           if (!buffer->page)
               buffer->page = (char *)get_zeroed_page(GFP_KERNEL);
           error = copy_from_user(buffer->page,buf,count);
           buffer->needs_read_fill = 1;
           buffer->page[count] = 0;
           return error ? -EFAULT : count;
       }
3) flush_write_buffer
   与设备模型交互,进行实质性的写入操作。
flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count)
       {
           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 rc;
           if (!sysfs_get_active_two(attr_sd))
               return -ENODEV;
           rc = ops->store(kobj, attr_sd->s_attr.attr, buffer->page, count);
           sysfs_put_active_two(attr_sd);
           return rc;
       }
最终调用kobject->ktype->ops->store(),这样就和VFS完全融合在一起了。
相关文章
|
存储 负载均衡 算法
p2p的文件系统
p2p的文件系统
139 4
|
存储 缓存 Linux
sysfs文件系统(1)
sysfs是一种虚拟文件系统,旨在提供一种访问内核数据结构的方法,从而允许用户空间程序查看和控制系统的设备和资源。sysfs文件系统通常被挂载在/sys目录下。sysfs提供了一种以树状结构组织的系统信息的方式,其中每个设备都有一个唯一的目录来表示它自己,其中包含有关设备的各种属性和状态信息的文件。这些文件通常是只读的,但有些也可以用于修改设备的某些设置。sysfs还提供了一个机制来通知用户空间程序有关设备状态更改的信息,从而使其能够对这些更改做出反应。sysfs文件系统被广泛用于Linux内核中,它为开发者提供了一种简单的方式来管理和控制系统中的各种设备和资源。
200 0
|
存储 数据可视化 Linux
文件系统-
文件系统-
72 0
|
存储 缓存 算法
文件系统(上)
文件系统(上)
183 0
|
存储 缓存 算法
文件系统(下)
文件系统(下)
170 0
|
存储 Linux Windows
【文件系统】
【文件系统】
99 0
|
Linux 程序员 Shell
sysfs文件系统与kobject
sysfs文件系统与kobject
163 0
|
Unix Linux 容器
sysfs文件系统(2)
每个内核设备直接或间接嵌入kobject属性。在添加到系统之前,必须使用kobject_ create()函数分配kobject,并将已经分配但尚未初始化的kob ject指针及其kobject_type 指针作为参数。kobject_add()函数用于添加kobject并将其链接到系统,同时根据其层次结构创建目录及其默认属性。功能与之相反的函数是kobject_ del(),将kobject删除链接
119 0
|
存储 Linux 内存技术
文件系统
文件系统
133 0
|
固态存储 Linux 内存技术
文件系统与inode
文件系统与inode
文件系统与inode