sysfs文件系统
参考博客:
一、kobject的作用
c语言是面向过程的语言,然后在linux内核中也有很多共用的结构体,比如kobject,kobject可以理解为面向对象语言中的父类。kobject结构体包含了大量的设备信息,三大类设备驱动都需要继承自kobject结构体,一个kobject对象往往就对应于sysfs目录下的一个目录,kobject是组成设备模型的基本结构,kobject结构体有以下功能:
1)对象的引用计数,作用类似于android中的sp/wp
2)sysfs表述,对应sysfs下面的一个目录
3)数据结构关联,作为父类被继承
4)热插拔的关键结构体,热插拔时kobject子系统会向上层上报事件
二、kobject和相关数据结构
include/linux/kobject.h
struct kobject { const char *name; // 该Kobject的名称,同时也是sysfs中的目录名称 struct list_head entry; // 用于将kobject加入到Kset中的list_head struct kobject *parent; // 指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构) struct kset *kset; // kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent struct kobj_type *ktype; // 该Kobject属于的kobj_type。每个Kobject必须有一个ktype struct kernfs_node *sd; /* sysfs directory entry */ struct kref kref; // 引用计数 #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif unsigned int state_initialized:1; // 指示该Kobject是否已经初始化 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; }; struct kset { struct list_head list; // 用于保存该kset下所有的kobject的链表 spinlock_t list_lock; // 自旋锁 struct kobject kobj; // kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现) const struct kset_uevent_ops *uevent_ops; // kset的uevent操作函数集,用于处理kset中的kobject对象的热插拔操作 } struct kobj_type { void (*release)(struct kobject *kobj); // 通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。 const struct sysfs_ops *sysfs_ops; // 该种类型的Kobject的sysfs文件系统接口 struct attribute **default_attrs; // 该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中 const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid); };
在kobject结构体中,存在kobject, kset和ktype这三个概念。
kobj_type是对kobject的特性描述。ktype数据结构初始化将会在随后的kobject_init流程中看到。
kset是很多kobject的集合。一般来说,如果一个kobject属于某个kset,即kset->kobj是存在的,那么这个kset->kobj将作为该kobject的父对象。这一点在随后的kobject_add流程中体现。
kref引用计数来标示kobject的生命周期,在对象初始化时,调用kref_init()来使kref->refcount被原子置1(这一步将会在随后的kobject_init流程中看到)。引用了该kobject的代码都要进行引用计数加一。只要这个refcount不为0,该kobject就会继续被保留在内存中,否则对象将被撤销、内存被释放。
三、kobject相关api函数
// 初始化一个object extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype); // 将kobject加入到分层结构 int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); // 直接初始化并进行添加操作 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...); // kobj的删除函数 void kobject_del(struct kobject *kobj); // 动态注册kobject并生成sysfs struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
kobject结构体如下(include/linux/kobject.h):
struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct kernfs_node *sd; /* sysfs directory entry */ struct kref kref; #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif 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; };
对于这几个常用的object的api函数分析可以查看下面的博客:
下图也来源于 折木H.O. 的博客:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240325173612638-1849095840.png)
四、kobject创建sys节点的demo
#include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/stat.h> /*声明release、show、store函数*/ void obj_test_release(struct kobject *kobject); ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf); ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count); /*对应于kobject的目录下的一个文件,Name成员就是文件名*/ struct attribute test_attr = { .name = "kobj_config", .mode = S_IRWXUGO, }; static struct attribute *def_attrs[] = { &test_attr, NULL, }; //kobject对象的操作 struct sysfs_ops obj_test_sysops = { .show = kobj_test_show, .store = kobj_test_store, }; /*定义kobject对象的一些属性及对应的操作*/ struct kobj_type ktype = { .release = obj_test_release, .sysfs_ops=&obj_test_sysops, .default_attrs=def_attrs, }; /*release方法释放该kobject对象*/ void obj_test_release(struct kobject *kobject) { printk("eric_test: release .\n"); } /*当读文件时执行的操作*/ ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf) { printk("have show.\n"); printk("attrname:%s.\n", attr->name); sprintf(buf,"%s\n",attr->name); return strlen(attr->name)+2; } /*当写文件时执行的操作*/ ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count) { printk("havestore\n"); printk("write: %s\n",buf); return count; } struct kobject kobj;//声明kobject对象 static int kobj_test_init(void) { printk("kboject test init.\n"); kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");//初始化kobject对象kobj,并将其注册到linux系统 //kobject_init(&kobj); //kobj.ktype = &ktype; //kobj.parent = NULL; //kobject_set_name(&kobj, "kobject_test"); //err = kobject_add(&kobj); return 0; } static void kobj_test_exit(void) { printk("kobject test exit.\n"); kobject_del(&kobj); } module_init(kobj_test_init); module_exit(kobj_test_exit);
demo测试结果如下:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240325173612526-186161872.png)
五、有关创建节点的一些函数
1)kobject 可看做是所有总线、设备和驱动的抽象基类,1个kobject 对应sysfs 中的一个目录。总线、设备和驱动中的各个attribute 则对应sysfs 中的1个文件。attribute 会伴随着show() 和store() 这两个函数,分别用于读写该attribute 对应的sysfs 文件。
2)linux用户空间和kernel空间是分开,所以上层需要和某个模块驱动交流的时候,就需要驱动来创建一个文件节点。
3)以下三种方法,第一种和第二种方法使用时一般流程就是open、read/write、ioctl等,第三种方法则可以通过echo/cat命令来读写。
4)sysfs 中的目录来源于bus_type, device_driver, device, 而目录中的文件则来源于attribute。
sys节点多种创建方法,创建属性文件节点的接口有五个:
sysfs_create_files //通过kobject创建sysfs的节点(后面3个接口最终都会调用此接口,参数attr中没有读写操作的接口,所以不使用此接口)。 device_create_file //为设备创建sys的节点。 bus_create_file //为总线创建sys的节点。 driver_create_file //为驱动创建sys的节点。 sysfs_create_group //创建一个sysfs目录 // 为设备创建sys的节点 int device_create_file(struct device *dev, const struct device_attribute *attr) // 创建一个sysfs目录 int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp) { return internal_create_group(kobj, 0, grp); }
先来看一下DEVICE_ATTR宏定义:
#define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define __ATTR(_name, _mode, _show, _store) { \ .attr = {.name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ .show = _show, \ .store = _store, \ } 比如我们将 static DEVICE_ATTR(device_test, 0666, show_device, set_device); 展开则变成了: struct device_attribute dev_attr_device_test = { .attr = { .name = "device_test", .mode = 0666, } .show = show_device, .store = set_device, }
继续往下查看device_attribute等有关结构体:
struct device_attribute { struct attribute attr; // device_attribute包含attribute结构体 ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); // 读取函数,对应cat内容 ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); // 写入函数,对应echo内容 }; struct attribute { const char *name; // 属性的名字,对应sysfs中文件的名字 umode_t mode; // 应用于属性(文件)的保护位,与文件的权限相同 #ifdef CONFIG_DEBUG_LOCK_ALLOC bool ignore_lockdep:1; struct lock_class_key *key; struct lock_class_key skey; #endif };
1、device_create_file和DEVICE_ATTR结合使用创建目录和文件
模板如下:
ssize_t show_device(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "show_device"); } ssize_t set_device(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { printk("set_device"); return 1; } ... static DEVICE_ATTR(device_test, 0666, show_device, set_device); ... // &dev来源于设计的device device_create_file(&dev, &dev_attr_device_test);
说明:目录和文件创建在sys/device/总线类型/目录下,在sys/class下可以找到,device_test是文件名。
2、sysfs_create_group、kobject_create_and_add、DEVICE_ATTR创建目录和文件
ssize_t show_device1(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "show_device1"); } ssize_t set_device1(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { printk("set_device1"); return 1; } ssize_t show_device2(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "show_device2"); } ssize_t set_device2(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { printk("set_device2"); return 2; } static DEVICE_ATTR(test1, 0666, show_device1, set_device1); static DEVICE_ATTR(test2, 0666, show_device2, set_device2); static struct attribute *tests_attrs[] = { &dev_attr_test1.attr, &dev_attr_test2.attr, NULL, }; static struct attribute_group test_attr_group = { .attrs = tests_attrs, }; static struct kobject *test_kobj; test_kobj = kobject_create_and_add("test", kernel_kobj); sysfs_create_group(test_kobj, &test_attr_group); ...
kobject_create_and_add函数的第二个参数确定了创建sys节点的位置。第二个参数为NULL的话,创建出来的路径为/sys/,如果第二个参数为kernel_kobj,创建出来的路径是/sys/kernel/。通常和sysfs_create_group进行配合使用。
3、sysfs_create_files创建多个属性文件
可参考下面博客:
4、常见的sysfs目录
/sys 下的子目录
|
所包含的内容
|
/sys/devices
|
这是内核对系统中所有设备的分层表达模型,也是 /sys 文件系统管理设备的最重要的目录结构,下文会对它的内部结构作进一步分析;
|
/sys/dev
|
这个目录下维护一个按字符设备和块设备的主次设备号(major:minor)链接到真实的设备(/sys/devices 下)的符号链接文件,它是在内核 2.6.26 首次引入;
|
/sys/bus
|
这是内核设备按总线类型分层放置的目录结构, device 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号连接,它也是构成 Linux 统一设备模型的一部分;
|
/sys/class
|
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;
|
/sys/block
|
这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更核识,但只是由于历史遗留因素而一直存在于 /sys/block,但从 2.6.22 开始就已标记为过时,只有在打开了 CONFIG_SYSFS_DEPRECATED 配置下编译才会有这个目录的存在,并且在 2.6.26 内核中已正式移到 /sys/class/block,旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真是设备的符号链接文件;
|
/sys/firmware
|
这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套 API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍;
|
/sys/fs
|
这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,ext4 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl(/proc/sys/fs)接口中;
|
/sys/kernel
|
这里是内核所有可调整参数的位置,有些内核可调整参数仍然位于 sysctl(/proc/sys/fs)接口中;
|
/sts/module
|
这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko 文件),都可能会出现在 /sys/module 中:编译为外部模块(ko 文件)在加载后会出现对应的 /sys/module//,并且在这个目录下会出现一些属性文件和属性目录来表示次外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;编译为内联方式的模块则只在当它有非 0 属性的模块参数时会出现对应的 /sys/module/,这些模块的可用参数会出现在 /sys/module//parameters/ 中,如 /sys/module/printk/parameters/time 这个可读可写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀;所有内联模块的参数也可以由 ".=" 的形式写在内核启动参数上,如启动内核时加上参数 "printk.time = 1" 与向 "/sys/module/printk/parameters/time" 写入 1 的效果相同;没有非 0 属性参数的内联模快不会出现于此;
|
/sys/power
|
这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。
|