4.5. 设备模型
4.5.1. sysfs
sysfs是一个基于内存的虚拟的文件系统,有kernel提供,挂载到/sys目录下,负责以设备树的形式向user space提供直观的设备和驱动信息。
sysfs以不同的视角展示当前系统接入的设备:
/sys/block 历史遗留问题,存放块设备,提供一设备名(如sda)到/sys/devices的符号链接
/sys/bus 按总线类型分类,在某个总线目录之下可以找到链接该总线的设备的符号链接,指向/sys/devices. 某个总线目录之下的drivers目录 包含了该总线所需的所有驱动的符号链接。对应kernel中的struct bus_type
/sys/calss 按设备功能分类,如输入设备在/sys/class/input之下,图形设备在/sys/class/graphics之下,是指向/sys/devices的符号链接。 对应kernel中的struct class
/sys/dev 按设备驱动程序分层(字符设备 块设备),提供以major:minor为名到/sys/devices的符号链接。 对应Kernel中的struct device_driver
/sys/devices 包含所有被发现的注册在各种总线上的各种物理设备。 所有物理设备都按其在总线上的拓扑结构来显示,除了platform devices和system devices。 platform devices 一般是挂载在芯片内部高速或者低速总线上的各种控制器和外设,能被CPU直接寻址。 system devices不是外设,而是芯片内部的核心结构,比如CPU,timer等。对应kernel中的strcut device
/sys/firmware 提供对固件的查询和操作接口(关于固件有专用于固件加载的一套api)
/sys/fs 描述当前加载的文件系统,提供文件系统和文件系统已挂载的设备信息。
/sys/kernel 提供kernel所有可调整参数,但大多数可调整参数依然存放在sysctl(/proc/sys/kernel)
/sys/module 所有加载模块(包括内联、编译进kernel、外部的模块)信息,按模块类型分类。
/sys/power 电源选项,可用于控制整个机器的电源状态,如写入控制命令进行关机、重启等。
sysfs支持多视角查看,通过符号链接,同样的信息可出现在多个目录下。
以硬盘sda为例,既可以在块设备目录/sys/block下找到,又可以在/sys/devices/pci0000:00/0000:00:10.0/host32/target32:0:0/下找到
4.5.2. 统一设备模型
linux统一设备模型中, kset , kobject , ktype 是实现设备模型的三个重要概念,他们构成了设备模型的基石
4.5.2.1. kobject/kset/ktype
统一设备模型中最基本的对象
kobject (设备对象)
struct kobject{
const char *name; //名称,对应sysfs下的一个目录
struct list_head entry; //加入kset链表的结构
struct kobject *parent; //父节点指针,构成树状结构
struct kset *kset; //指向所属的kset
struct kobj_type *ktype; //类型
struct kernfs_node *sd; //VFS文件系统中的目录项,指向所属(sysfs)目录项
struct kref kref; //引用计数
unsigned int state_initialized:1; //是否已经初始化
unsigned int state_in_sysfs:1; //是否已经在sysfs中显示
unsigned int state_add_uevent_sent:1; //是否已经向user space发送add uevent
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1; //是否忽略上报
};
kobj_type
struct kobj_type{
void (*release)(struct kobject *kobj); //析构函数,kobject的引用计数为0时调用
const struct sysfs_ops *sysfs_ops; //操作函数,当用户读取sysfs属性时调用show(),写入sysfs属性时调用store()
struct attribute **default_attrs; //默认属性,体现为该kobject目录下的文件
const struct kobject_ns_type_operations *(*child_ns_type)(struct kobject *kobj); //namespace 操作函数
const void *(*namespace)(struct kobject *kobj);
};
实际上这里实现的类似与对kobject的派生,包含不同kobj_type的kobject可以看作不同的子类。通过实现相同的函数来实现多态。 每一个kobject的数据结构(如kset、device、device_driver等),都要实现自己的kobj_type,并实现其中的函数。
kobj_type的定义会如实的在sysfs中反映,其中的属性attribute会以attribute.name为文件名在该目录下创建文件,对该文件进行读写会调用 sysfs_ops 中定义的show()和store()
kset
kobject的容器,维护了其包含的kobject链表,链表的最后一项指向kset.kobj,用于表示某一类型的kobject
struct kset{
struct list_head list; //kobject链表头
spinlock_t list_lock; //自旋锁,保障操作安全
struct kobject kobj; //自身的kobject,也是归属于此kset的所有kobject的共同parent
const struct kset_uevent_ops *uevent_ops; //uevent 操作函数集。
};
注意和kobj_type的关联,kobject会利用成员kset找到自己所属的kset,设置自身的ktype为kset.kobj.ktype。当没有指定的kset成员时,才会用ktype来建立关系。
备注
kobject调用的是它所属的kset的uevent操作函数来发送uevent,如果kobject不属于任何kset,则无法发送uevent
4.5.2.2. device/driver/bus/class
linux设备模型的更上一层的表述是device/driver/bus/class。他们都定义在include/linux/device.h中
/sys/bus 目录下的每个子目录都是注册好了的总线类型,每个子目录下包含两个子目录(devices和driver文件夹)。其中devices下是该总线类型下的所有设备, 这些设备都是符号链接,指向/sys/devices。
/sys/devices 目录是是全局设备结构体系,包含所有被发现的注册在总线上的各种物理设备。
/sys/class 目录则是包含所有注册在kernel中的设备类型。
下图是一个usb设备的设备去驱动拓扑图
在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动,同样当我们向系统注册一个设备时,便会
向设备的管理链表插入我们的新设备。在插入的同时总线会执行一个 bus_type 结构体中的 match 方法对新插入的设备/驱动进行匹配。匹配成功后会调用
device_driver结构体中probe方法,移除驱动或者设备时调用device_driver结构体中的remove方法。
device
device描述了一项设备,对应数据结构device
struct device{
struct device *parent; //表示该设备的父对象
struct device_private *p;
strcut kobject kobj; //内嵌kobject,用于在sysfs中表达
const char *init_name; //指定设备名字
const struct device_type *type;
struct mutex mutex;
struct bus_type *bus;
struct device_driver *driver; //表示该设备对应的驱动
struct class *class; //表示该设备挂载在哪条总线,当我们注册设备时,内核便会将该设备注册到对应的总线。
};
可以使用以下函数注册和注销设备
int device_register(struct device *dev);
void device_unregister(struct device *dev);
其中维护了类型为device_private的指针p
struct device_private{
struct klist list_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct list_head deferred_probe;
struct device *device;
};
klist_node 用来作为所属driver链表,所属bus链表中的节点。
设备通过device_register来注册到系统中,通过device_unregister来从系统中卸载。
driver
设备依赖于driver来进行驱动,对应的数据结构为device_driver
struct device_driver{
const char *name; //驱动名称
struct bus_type *bus; //挂载在哪种总线上
strcut module *owner;
const char *mod_name;
bool suppress_bind_attrs;//用于指定是否通过sysfs导出bind与unbind文件,
//bind和unbind文件时驱动用于绑定/解绑关联的设备。
enum probe_type probe_type;
const strcut of_device_id *of_match_table; //指定驱动支持的设备类型,当内核使能设备树时,
//会利用该成员与设备树中的 ``compatible`` 属性进行比较
const struct acpi_device_id *acpi_match_table;
int(*probe)(strcut device *dev); //设备与驱动匹配后会执行该回调函数
int(*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int(*suspend)(struct device *dev, pm_message_t state);
int(*resume)(struct device *dev);
const struct attribute_group **groups; //表示驱动的属性
const struct dev_pm_ops *pm;
struct driver_private *p;
};
其中维护了类型为driver_private的指针p
struct driver_private{
struct kobject kobj;
struct klist klist_device;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
其维护了dirver自身的私有属性,比如由于他也是kobject的子类,因此包含了kobj。可通过driver_cteate_file/ dirver_remove_file来增删属性,属性将直接作用于p->kobj.
bus
设备总是挂载在某一条总线上的,对应的数据结构为bus_type
struct bus_type{
const char *name; //指定总线名称,会在/sys/bus目录下创建一个新的目录,目录名称就是该参数的值
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
//分别表示总线、设备、驱动的属性。这些属性可以时内部变量、字符串等等,
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
//当向总线注册一个新的设备或者新的驱动时,会调用该回调函数。
int (*match)(struct device *dev, struct device_driver *drv);
//当总线上的设备发生添加、移除就会调用该函数。
int (*uevent)(struct device *dev, struct kobj_uvent_env *env);
//设备和驱动匹配后会调用该函数。
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev,pm_message_t state);
int (*resume)(struct device *dev);
//该结构它用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线上的设备和驱动。
struct subsys_private *p;
struct lock_class_key lock_key;
};
通过以下函数注册和注销bus
int bus_register(struct bus_type *bus);
void bus_ungister(struct bus_type *bus);
其中维护了类型为subsys_private的指针P
struct subsys_private{
struct kset subsys;
struct kset *devices_kset;
struct list_head interface;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_nottifier_head bus_notifier;
unsigned int drivers_autoprober:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
它维护了bus自身的私有属性,它维护了挂载在该总线上的设备集合device_kset和与该总线相关的驱动程序集合drivers_kset
对应到sysfs中,每个bus_type对象对应/sys/bus目录下的一个子目录,子目录下必有devices和dirver文件夹,里面存放指向设备和驱动的符号链接。
下图是总线上关联设备和驱动之后的数据结构关系图:
class
class对应一种设备分类,对应的数据结构为class
struct class{
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
const struct attribute_group **dev_grpoups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev,struct kobj_uvent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev,pm_message_t state);
int (*resume)(struct device *dev);
int (*shutdown)(struct device *dev);
const struct kobj_ns_type_operation *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
class 只是一种抽象的概念,用于描述接口相似的一类设备。
小结
driver用于驱动device,其保存了所有能够被它所驱动的设备链表。
bus是连接driver和device的桥梁,其保存了所有挂载在它上面的设备链表和驱动这些设备的驱动链表。
class用于描述一类device,其保存了所有该类device的设备链表。
4.5.2.3. attribute
用于定义设备模型中的各项属性,基本属性有两种,分别为普通属性attribute和二进制属性bin_attribute
struct attribute{
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_ket skey;
#endif
};
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);
};
使用attribute生成的sysfs文件,只能用字符串的形式读写,而struct bin_attribute在attribute的基础上,增加了read、write函数,因此 它生成的sysfs文件可以用任何方式读写。
attribute_gpoup
顾名思义就是属性组,将一组属性打包成一个对象,其包含了以attribute和bin_attribute指针数组。
struct attribute_group{
const char *name;
umode_t (*is_visible)(struct kobject *,struct attribute *,int);
umode_t (*is_bin_visible)(struct kobject *, struct bin_attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
sysfs 映射
sysfs本质上是对统一设备模型中的各结构的映射。换句话说sysfs本质上就是通过vfs接口去读写kobject的层次结构后动态建立的内存文件系统。
int __init sysfs_init(void)
{
int err;
sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHEACK, NULL);
if(IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);
sysfs_root_kn = sysfs_root->kn;
err = register_filesystem(&sysfs_fs_type);
if(err)
{
kernfs_destroy_root(sysfs_root);
return err;
}
return 0;
}
sysfs_init通过kernfs_create_root创建新的kernfs层级,然后将其保存在静态全局变量中,供各处使用,然后通过register_filesystem将其注册到名 为sysfs的文件系统中。
目录映射
kobject 在sysfs中对应的是目录(dir)
当我们注册一个kobject时,会调用kobject_add 于是
kobject_add===>kobject_add_varg===>kobject_add_internal===>create_dir====>sysfs_create_dir_ns
如果kobj有parent,则它的父节点为kobj->parent->sd
属性映射
属性在sysfs中对应的是文件(file)
当需要为设备添加属性时,可以调用device_create_file,于是
deivice_create_file===>sysfs_create_file===>sysfs_create_file_ns====>sysfs_add_file_mode_ns===>__kernfs_create_file
创建的文件大小即为存放该属性值的长度,对于普通属性来说,大小为 PAGE_SIZE(4K),而对于二进制属性来说,大小由属性自定义,即 bin_attribute.size 指定。
当用户对属性文件进行读写时,会调用绑定的读写函数,比如对于 mode 为 SYSFS_PREALLOC 且 kobj->ktype->sysfs_ops 定义了 show 和 store 函数的属性, 绑定是 sysfs_prealloc_kfops_rw 。这里的 kobj 指的是该属性的父节点,也就是属性所属设备的 kobj。
于是在读文件时,调用 sysfs_kf_read ,它会根据属性文件找到其父节点类型对应的 sysfs_ops ,然后调用 sysfs_ops.show 。show 需要将输出写到传入的 buf 缓冲区中, 并返回写入的长度。
在写文件时,调用 sysfs_kf_write ,它会根据属性文件找到其父节点类型对应的 sysfs_ops ,然后调用 sysfs_ops.store 。 store 可以从传入的 buf 缓冲区中, 读取用户写入的长度为 len 的内容。
但是需要注意的是, sysfs_ops 中的 show 和 store 函数并非是读写我们属性所需要的 show 和 store 。因为一个设备只有一个类型,因此 sysfs_ops 打扰 show 和 store 只有一种实现,但实际上 show 和 store 应该根据属性的不同而不同。怎么办呢?绕个弯子:在调用 sysfs_ops.show 和 sysfs_ops.store 时传入属性 attribute 的指针, 然后在函数中将指针转换为设备类型对应属性的指针后调用属性的 show 和 store 函数。这也就是 device_attribute 、 class_attribute 或一些设备自定义属性 (比如 cpuidle_driver_attr) 中定义有 show 和 store 函数的原因。