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目录树中的作用(我不是原创)‘

sysfs文件系统

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完全融合在一起了。
上一篇:Linux 文件系统


下一篇:通过windows子系统配置kubernetes客户端