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目录树中的作用(我不是原创)‘
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完全融合在一起了。