统一设备模型(四):sysfs文件系统的分析
1.7.1 devices (include/linux/device.h) 2
1.7.2 bus drivers (include/linux/device.h) 3
1.7.3 device drivers (include/linux/device.h) 3
1 sysfs功能特性
貌似是从3.14开始,内核的sysfs底层又划分出了一个kernfs,据称是为了加强代码复用;虽然用起来都差不多,但是底层的一些接口,在分析的时候,会跟旧内核完全不同。
根据内核文档介绍,sysfs是一个基于ram-based的文件系统,最初基于ramfs(有点拗口~~)。它提供了一种方法,将内核数据结构、它们的属性以及它们之间的联系导出到用户空间。
sysfs与kobject基础设施有关。有关kobject接口的更多信息,可以参考Documentation / kobject.txt。后续博文也会专门分析kobj。
通过内核文档的介绍,sysfs需要实现两点:
- 适配linux的vfs,提供一般文件系统的功能:目录、文件等;
- Sysfs内部的实现,要与kobj紧密结合。
1.1如何使用sysfs
sysfs通过CONFIG_SYSFS配置项来使能。使用以下命令来挂载:
mount -t sysfs sysfs /sys
1.2目录创建
每个kobj被注册,将对应一个sysfs目录的创建。每个目录被创建于父kobj对应的目录下,父目录为空,则对应sysfs的顶层目录。注:kobj.parent的设置也会与kset相关。
1.3属性
可以以文件系统中的常规文件的形式导出kobj的属性。Sysfs将文件I/O操作转发到为由attribute属性定义的方法,该方法提供了读取和写入内核属性的方法。
属性应为ASCII文本文件,最好每个文件只有一个值。 应当注意的是,每个文件仅包含一个值可能不是有效的,因此普遍的做法是:为同类目标使用一组数据值。另外,kernel禁止混合类型、花哨的格式化属性文件。
原生的attribute不包含读取或写入属性的方法。鼓励子系统定义自己的属性结构和包装函数,以添加和删除特定对象类型的属性。
Attribute使用示例推荐:
struct attribute {
char * name;
struct module *owner;
umode_t mode;
};
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
以上是属性文件的结构定义和文件操作接口,而对于device子系统来说:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
int device_create_file(struct device *, const struct device_attribute *);
void device_remove_file(struct device *, const struct device_attribute *);
1.4子系统相关的回调函数
当一个子系统定义了一个新的attribute type后,它必须实现一组sysfs的操作,来转发读写调用到attribute拥有者的show和store函数中。
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
注:子系统应该已经将struct kobj_type定义为此类型的描述符,这是存储sysfs_ops指针的位置。
当一个文件被读写时,sysfs会为该类型文件调用合适的方法。这些方法函数会将通用的kobj和attribute转换为适当的指针类型,并调用相关联的操作函数。如下所示:
#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = to_dev(kobj);
ssize_t ret = -EIO;
if (dev_attr->show)
ret = dev_attr->show(dev, dev_attr, buf);
if (ret >= (ssize_t)PAGE_SIZE) {
print_symbol("dev_attr_show: %s returned bad count\n",
(unsigned long)dev_attr->show);
}
return ret;
}
1.5读写属性数据
要读取或写入属性,在声明属性时必须指定show()或store()方法。方法类型应该与为device_attribute属性定义的类型一样简单:
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
换句话说,它们应该使用一个对象、一个属性和一个缓冲区作为参数。
Sysfs分配一个buffer(大小为PAGE_SIZE),然后传递给操作函数。Sysfs会为每个读写操作来调用一次操作函数。这要求操作函数需要实现以下行为:
- On read(2)
show()函数应该填充整个buffer缓冲区。考虑到一个属性只能是一个值或一组相似的值,要实现show()并不难。
read()操作允许用户空间随意地在整个文件上任意地进行部分读取和转发寻找。 如果用户空间返回零,或者使用偏移量为0的pread(2),则将再次调用show()方法,重新装入,以填充缓冲区。
- On write(2)
sysfs期望在第一次写入期间传递整个缓冲区。 Sysfs然后将整个缓冲区传递给store()方法。而store()函数在数据最后会添加一个终止符,这使得像sysfs_streq()这样的字符串函数能够安全使用。
当写sysfs文件时,用户空间程序应该先读取整个文件并修改希望改变的值,然后将整个buffer写回文件。
Attribute操作函数应该在相同的buffer上执行读写操作。
一个非常简单的device attribute实现如下:
static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}
static ssize_t store_name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
snprintf(dev->name, sizeof(dev->name), "%.*s",
(int)min(count, sizeof(dev->name) - 1), buf);
return count;
}
static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);
注:实际使用过程中,用户空间是不能给设备进行重命名操作。
1.6顶层目录
Sysfs的顶层目录:
block bus class dev devices firmware fs hypervisor kernel module power
/bus目录包括了所有注册过的总线目录(如platform等),每个具体的子目录下,又包含两个目录:
devices/
drivers/
devices/目录下是symlinks,链接至/sys/devices/目录下。
drivers/包含为该特定总线上的设备加载的每个设备驱动程序的目录(这假设驱动程序不跨越多个总线类型)。
fs/目录包含一些文件系统。
dev/目录包含两个子目录:char/和block/。在这两个子目录,都是名为<major>:<minor>的symlinks文件。这些symlinks文件指向/sys/devices下的相应设备。/ sys / dev提供了从stat(2)操作的结果中查找设备的sysfs接口的快速方法。
1.7当前接口
1.7.1 devices (include/linux/device.h)
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
Declaring:
DEVICE_ATTR(_name, _mode, _show, _store);
Creation/Removal:
int device_create_file(struct device *dev, const struct device_attribute * attr);
void device_remove_file(struct device *dev, const struct device_attribute * attr);
1.7.2 bus drivers (include/linux/device.h)
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf);
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};
Declaring:
BUS_ATTR(_name, _mode, _show, _store)
Creation/Removal:
int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);
1.7.3 device drivers (include/linux/device.h)
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *, char * buf);
ssize_t (*store)(struct device_driver *, const char * buf,
size_t count);
};
Declaring:
DRIVER_ATTR(_name, _mode, _show, _store)
Creation/Removal:
int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);
2 初始化
目前这块,讲多了太大,讲少了没用;还是不讲了,留着《文件系统》系列博文再详细分析。
3 对外的接口
sysfs的对外接口,就是文件的普通操作:目录/文件的创建、读写等。以下就是详细分析这些接口的操作。
用户空间无法对sysfs目录进行重命名。
某些分叉调用路径,是以kobject-example.c (samples\kobject)来进行分析。
3.1 新建目录接口
目录创建函数为sysfs_create_dir_ns(structkobject *kobj, constvoid *ns)
创建目录,只需要kobj即可。这表明,kobj直接对应sysfs里的一个目录。
3.2 新建文件接口
文件新建接口有两个:普通ascii文件和bin文件。
(1)int sysfs_create_files(structkobject *kobj, const structattribute **ptr)
创建一个文件,除了需要传递attribute外,还需要传递该attr对应的kobj。
属性文件必须依赖于kobj。
(2)int sysfs_create_bin_file(structkobject *kobj,const structbin_attribute *attr)
bin文件同上,它在普通attribute基础上,封装了其他成员。
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
以上两个函数都会调用同一个函数:sysfs_add_file_mode_ns()。
sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode, ns)
struct kobject *kobj = parent->priv
// 以kobject-example.c为例
// dynamic_kobj_ktype.sysfs_ops = &kobj_sysfs_ops
struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops
// kobj_sysfs_ops = {.show = kobj_attr_show, .store = kobj_attr_store,}
if (sysfs_ops->show && sysfs_ops->store)
// mode=0664=0x0298 SYSFS_PREALLOC=10000=0x2710
if (mode & SYSFS_PREALLOC)
ops = &sysfs_prealloc_kfops_rw
kn = __kernfs_create_file(parent, attr->name, mode & 0777, size, ops, attr, ns, key)
kn = kernfs_new_node(parent, name, (mode & S_IALLUGO) | S_IFREG, flags)
__kernfs_new_node(kernfs_root(parent), name, mode, flags)
// parent保存在kn->parent中,通过kn->parent->priv->ktype->sysfs_ops可以得到sysfs_ops
kn->parent = parent
// sysfs_prealloc_kfops_rw={.read = sysfs_kf_read, .write = sysfs_kf_write, }
// attr = struct kobj_attribute foo_attribute.attr
kn->attr.ops = ops
kn->priv = priv;
// sysfs_prealloc_kfops_rw未定义seq_show
if (ops->seq_show)
kn->flags |= KERNFS_HAS_SEQ_SHOW;
// sysfs_prealloc_kfops_rw未定义mmap
if (ops->mmap)
kn->flags |= KERNFS_HAS_MMAP;
kernfs_add_one(kn); //添加文件
创建文件的时候,把attribute保存在kn->priv中,该变量在文件读写时会使用。
总结:创建文件时,kn->attr.ops = ops = sysfs_prealloc_kfops_rw={.read = sysfs_kf_read, .write = sysfs_kf_write, }
kn->priv = priv = struct kobj_attribute foo_attribute.attr
3.3 打开文件接口
sysfs在挂接时,得到了该文件系统的fops就是kernfs_file_fops。因此会调用kernfs_file_fops.open = kernfs_fop_open。文件打开是个无比复杂的过程,简述如下:
对于任何文件的打开操作,VFS都会转化为对该文件系统的fops的open操作,对于sysfs就是:
kernfs_file_fops.open = kernfs_fop_open
注意:新内核的sysfs是基于kernfs。
kernfs_fop_open()
// kn指向file->f_path.dentry->d_fsdata
struct kernfs_node *kn = file->f_path.dentry->d_fsdata
// 在创建文件时赋值=sysfs_prealloc_kfops_rw
ops = kernfs_ops(kn);
return kn->attr.ops
// sysfs_prealloc_kfops_rw->read存在
sysfs_prealloc_kfops_rw->write存在
sysfs_prealloc_kfops_rw->mmap不存在
has_read = ops->seq_show || ops->read || ops->mmap;
has_write = ops->write || ops->mmap;
has_mmap = ops->mmap;
// allocate a kernfs_open_file for the file
of = kzalloc(sizeof(struct kernfs_open_file), GFP_KERNEL)
// 通过kn->parent->priv,可以得到kobj
of->kn = kn;
of->file = file
seq_open(file, NULL)
| struct seq_file *p = kzalloc(sizeof(*p), )
| // file和struct seq_file *p互相关联,也可以说:将seq_file *p保存在file->private_data中
| file->private_data = p
| p->file = file;
| file->f_mode &= ~FMODE_PWRITE
// 将of保存在seq_file *p->private
((struct seq_file *)file->private_data)->private = of
kernfs_get_open_node(kn, of)
list_add_tail(&of->list, &on->files)
总结:打开文件时,并没有将sysfs_ops直接保存在file中;而是首先新建了struct kernfs_open_file * of,并将kn保存在of中(of->kn = kn);然后新建了seq_file *p,将seq_file *p保存在file->private_data中;最后,将of保存在seq_file *p->private。所以,在读写文件时,寻找sysfs_ops的过程为:
首先,由file->private_data得到seq_file *p;
然后,由seq_file *p->private得到struct kernfs_open_file * of,由of->kn,得到kernfs_node;
最后,由kn->parent->priv得到kobj,然后由kobj->ktype->sysfs_ops得到目标。
3.4 读文件接口
因为sysfs的fops等于kernfs_file_fops,而kernfs_file_fops.read=kernfs_fop_read()。
kernfs_fop_read(struct file *file, char __user *user_buf,size_t count, loff_t *ppos)
// 首先通过file得到kernfs_open_file *of
struct kernfs_open_file *of = kernfs_of(file)
if (of->kn->flags & KERNFS_HAS_SEQ_SHOW)
return seq_read(file, user_buf, count, ppos);
else
return kernfs_file_direct_read(of, user_buf, count, ppos);
// sysfs_prealloc_kfops_rw={.read = sysfs_kf_read,.write = sysfs_kf_write,}
//再由of->kn得到kernfs_ops *ops
struct kernfs_ops *ops = kernfs_ops(of->kn)
return kn->attr.ops
len = ops->read(of, buf, len, *ppos) // = sysfs_kf_read
// = dynamic_kobj_ktype.sysfs_ops = &kobj_sysfs_ops
// 最后得到sysfs_ops
struct sysfs_ops *ops = sysfs_file_ops(of->kn)
struct kobject *kobj = of->kn->parent->priv
// struct sysfs_ops kobj_sysfs_ops = {.show = kobj_attr_show,.store = kobj_attr_store,}
len = ops->show(kobj, of->kn->priv, buf)
struct kobj_attribute *kattr
kattr = container_of(attr, struct kobj_attribute, attr)
// static struct kobj_attribute foo_attribute = __ATTR(foo, 0664, foo_show, foo_store)
// 完成attr定义时的最终调用
kattr->show(kobj, kattr, buf)
总结:首先,通过vfs层的fops找到kernfs_fop_read();
然后,进入kernfs层,调用kernfs_ops->read();
再然后,进入sysfs层,调用sysfs_ops->show(),此时的sysfs层对应的是kobj的ktype;
最后,调用子系统层的xxx_attribute.show()函数,子系统层对应的device、bus等等。
3.5 写文件接口
因为sysfs的fops等于kernfs_file_fops,而kernfs_file_fops.write=kernfs_fop_write()。
这个调用过程与读文件非常相似,不再敖述。
kernfs_fop_write(struct file *file, const char *user_buf, size_t count, loff_t *ppos)
// 首先通过file得到kernfs_open_file *of
struct kernfs_open_file *of = kernfs_of(file)
copy_from_user(buf, user_buf, len)
//再由of->kn得到kernfs_ops *ops
//sysfs_prealloc_kfops_rw={.read = sysfs_kf_read, .write = sysfs_kf_write, }
ops = kernfs_ops(of->kn)
ops->write(of, buf, len, *ppos)
// = dynamic_kobj_ktype.sysfs_ops = &kobj_sysfs_ops
//最后得到sysfs_ops
struct sysfs_ops *ops = sysfs_file_ops(of->kn)
kobj = of->kn->parent->priv
ops->store(kobj, of->kn->priv, buf, count)
struct kobj_attribute *kattr
kattr = container_of(attr, struct kobj_attribute, attr)
// static struct kobj_attribute foo_attribute = __ATTR(foo, 0664, foo_show, foo_store);
kattr->store(kobj, kattr, buf, count) // 完成attr定义时的最终调用
4 小结
目前,本博文对于sysfs的分析,主要聚焦在:sysfs实现了哪些功能、如何使用这些接口;这些只能算作皮毛,算作应用而已,还没有深入到文件系统底层。
文件系统的核心始终是超级块、inode、dentry和vfsmount等等,这方面的分析等《文件系统》系列博文再详细分析。