上一章我们对LINUX设备模型的概念进行了分析,也大概理清了bus、class、device、drivers之间的关联与区别,本章开始我们就进行具体概念的分析,首先分析总线的概念以及总线接口分析。在进行分析之前我们还是把上一章中的bus-device-drivers的关联图放上来,通过该图我们对总线接口的实现也会有一个大概的轮廓,即每一个具体的总线均包括设备与驱动两部分,而每一个具体总线的所有添加的设备均链接至device下,每一个总线的所有注册的驱动均链接至drivers,而bus接口所有实现的功能也可以大致分为总线的注册、设备的注册、驱动的注册这三个部分。因此本章也从这几个方面进行分析。
总线相关的结构体介绍
与总线相关的结构体主要包括bus_type、bus_attribute、device、driver、device_attribute、driver_attribute、
subsys_private、kset、kobject等,其中kset、kobject、subsys_private结构体我们之前已经介绍过,此处不再细说。
struct bus_type接口说明
该接口定义如下:
struct bus_type {
/*总线名称*/
const char *name;
/*总线对应设备名称*/
const char *dev_name;
/*该总线对应的device*/
struct device *dev_root;
struct bus_attribute *bus_attrs;/*总线属性*/
struct device_attribute *dev_attrs;/*设备属性*/
struct driver_attribute *drv_attrs;/*驱动属性*/
/*match接口,用于进行device与driver的匹配*/
int (*match)(struct device *dev, struct device_driver *drv);
/*uevent接口,用于发送kobject event,供应用层mdev/udev使用*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
/*总线的probe接口,该接口会调用具体驱动的probe接口*/
int (*probe)(struct device *dev);
/*总线的remove接口,一般该接口主要是调用具体驱动的remove接口*/
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 dev_pm_ops *pm;/*pm相关*/
struct iommu_ops *iommu_ops;
/*该接口包含了该bus对应kobject、device对应的kset、driver对应的kset、
链接所有设备的链表、链接所有驱动的链表等*/
struct subsys_private *p;
struct lock_class_key lock_key;
};
该结构体定义的目的也是实现上述bus-device-driver之间的关联,同时也与kobject、kset关联,进而实现与sysfs的关联。具体说明如下:一、bus-device-driver关联通过struct subsys_private类型的变量p,定义了device、driver对应的kset变量,完成将所有设备、驱动对应的kobject;
- 通过struct subsys_private类型的变量p,定义了device、driver对应的链表,将所有该总线注册或添加的驱动与设备链接在一起,实现上面图中所描述的bus-device-driver关联
二、总线自身属性以及与kobject、sysfs的关联
- 通过bus_attribute类型的变量,定义总线自身的属性参数(通过sysfs中的接口,完成sysfs文件的创建,并提供该属性的读写接口store/show等);
- 通过device_attribute类型的变量,完成该bus总线默认的设备属性参数(这些参数为该总线下每一个设备均包含的属性,通过sysfs中的接口,完成sysfs文件的创建,并提供该属性的读写接口store/show等);
- 通过driver_attribute类型的变量,完成该bus总线默认的驱动属性参数(这些参数为该总线下每一个驱动均包含的属性,通过sysfs中的接口,完成sysfs文件的创建,并提供该属性的读写接口store/show等);
- struct subsys_private类型的变量p,定义了该总线对应的kobject变量,从而为该bus总线在sysfs文件系统下创建该bus对应的目录,并借助该kobject->kref变量,提供针对该总线的引用计数;
三、总线相关的处理接口
- match接口完成总线中设备与驱动的匹配检测,匹配上的设备与驱动即完成绑定操作;
- probe接口,当设备与驱动匹配后,则调用总线的probe接口,进行设备的probe操作(该接口一般会调用驱动的probe接口,进行探测操作);
- remove接口,当移除驱动时,即会调用总线的remove接口,进行申请的remove操作(该接口一般会调用驱动的remove接口,进行移除操作);
struct bus_attribute结构体说明
该结构体通过包含通用属性结构体变量attr和store/show接口,实现针对bus类型属性的读写接口。
struct bus_attribute {
/*通用属性结构体,struct sys_ops中的show/store接口即传递该类型的变量*/
struct attribute attr;
/*属性查看接口*/
ssize_t (*show)(struct bus_type *bus, char *buf);
/*属性设置接口*/
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
为了理清该结构体变量与kobject、sysfs等的关联,特将它们之间的关联图画出。在调用总线注册接口bus_register时,会创建该bus总线默认的属性文件,并设置该bus总线对应kobject的kobj_type,而kobj_type中的操作接口指针即为 bus_sysfs_ops,其提供针对bus属性的通用读写接口,而创建的bus属性变量中,针对具体的属性定义特定接口,由bus_sysfs_ops中的读写接口调用。
其中的关联说明如下:
- 图中的kset对应bus_type->p->subsys,而kset中的kobject包含了引用计数以及kobj_type;
- 根据bus_type->p->subsys,通过调用sysfs中的kset_register,实现kset初始化,并根据其kobject变量在sysfs中创建目录,并调用sysfs_create_file将该bus相关的默认属性在sysfs中创建对应的文件;
- 应用层若要访问bus属性,则通过系统调用open、read、write接口,最终调用sysfs中注册的文件操作接口sysfs_open_file、sysfs_read_file、sysfs_write_file,最终调用bus_attribute中store/show接口(这块具体的实现可参考《
LINUX SYSFS文件系统分析之四 文件处理及相关系统调用分析》)。
上图没有画出文件描述符、VFS中dentry、inode节点与之的关联,在《
LINUX SYSFS文件系统分析之四 文件处理及相关系统调用分析》中有详尽的描述。
此处关联图是针对bus_type本身的kset、kobject、bus_attribute、sysfs_dirent、kobj_type、sysfs_ops的关联,而在bus_register时,也就是完成这些结构体类型变量之间的关联,实现bus目录、bus属性等在sysfs中表示,我们理解了下面这张图基本上也就属性了bus总线相关的功能了。
而针对bus总线对应kobject的通用kobj_type定义以及其sysfs_ops定义如下所示
static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->show)
ret = bus_attr->show(subsys_priv->bus, buf);
return ret;
}
static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->store)
ret = bus_attr->store(subsys_priv->bus, buf, count);
return ret;
}
static const struct sysfs_ops bus_sysfs_ops = {
.show = bus_attr_show,
.store = bus_attr_store,
};
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
};
struct device_attribute 结构体说明
该结构体的定义如下,其定义实现与bus_attribute类似,而在bus_type->dev_attrs中定义的默认属性,属于添加到该总线上的所有设备的默认属性,即每一个添加到该总线上的设备,其对应kobject上均会包含这些默认属性。
/* interface for exporting device attributes */
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);
};
在进行bus_add_device接口时,除了创建设备对应的kobject外,还会将该kobject链接到bus_type->klist_devices的list上,同时会调用sysfs_create_file将默认属性在该device对应目录下创建sysfs文件,其处理流程和上面bus_attribute类似,唯一不同的是此时创建的kobject是链接的kset->list上,而不是kset->kobj的,如下图所示。
针对device设备而言,其kobject对应的默认kobj_type以及默认通用sysfs_ops的定义如下
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 = kobj_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;
}
static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO;
if (dev_attr->store)
ret = dev_attr->store(dev, dev_attr, buf, count);
return ret;
}
static const struct sysfs_ops dev_sysfs_ops = {
.show = dev_attr_show,
.store = dev_attr_store,
};
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
};
struct driver_attribute 结构体说明
该结构体的定义与device_attribute类似,在bus_type->drv_attrs中定义的默认属性,属于添加到该总线上的所有设备的默认属性,即每一个添加到该总线上的设备,其对应kobject上均会包含这些默认属性。
在bus_add_driver中会进行driver的添加以及对应kobject、driver_attribute对应属性文件的创建等信息,其流程图与上面device的处理流程类似,此处不再赘述。
/* sysfs interface for exporting driver attributes */
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf,
size_t count);
};
上面分析了bus相关的结构体变量(没有分析struct device 、struct device_driver变量,后面分析),下面就开始进行相关接口的分析操作,主要设置bus注册、注销、bus模块初始化、设备添加到总线、驱动添加到总线上等。
总线模块初始化接口分析
该接口主要通过调用kset_create_and_add接口创建一个kset类型的变量bus_kset,并在sysfs的根目录下创建bus名称的目录(该接口的实现也比较简单,流程图如下所示,而关于kset_create_and_add接口的实现,可参考文件《LINUX SYSFS文件系统分析之五 kobject、kset相关接口分析》)。该kset变量会作为bus子模块的父目录,同时也会将所有注册的bus类型变量汇聚在一起,而bus_kset与各具体bus的kset的关联如下下图所示,通过kset->list将所有已注册的xxx_bus_kset变量链接在一起。
总线注册与注销接口分析说明
总线注册接口bus_register
总线注册接口的流程图如下,其实结合上面的结构体分析,我们也可以大概总结出总线注册接口所实现的功能,下面我们详细分析之。
- 创建subsys_private类型的变量,赋值给该bus变量的subsys_private类型的成员变量p;
- 设置bus总线对应的kset、kobject,并调用kset_register接口在sysfs文件系统的/bus/目录下创建该bus子目录,并将该bus对应的kobject链接到全局变量bus_kset的链表中;
- 调用bus_create_file接口,为该bus默认的bus_uevent属性创建对应的sysfs文件;
- 调用kset_create_and_add为该bus创建kset类型的devices_kset、drivers_kset,并在该bus目录下创建对应的devices、drivers 的sysfs子目录,而这两个变量,分别用于将注册到该总线上的所有设备、驱动链接在一起;
- 调用add_probe_files、bus_add_attrs接口,创建该bus相关的bus_attr_drivers_probe、bus_attr_drivers_autoprobe以及该bus默认属性bus_attrs对应的sysfs文件等。
至此,即完成了bus类型的注册。而针对bus注册相关的bus_ktype接口,其定义如下,主要为bus_ktype、bus_sysfs_ops的定义,针对bus_sysfs_ops为bus总线属性读写的通用接口,在上面bus_attribute结构体的定义中,也进行了详细说明,bus_ktype、bus_sysfs_ops与kobject以及sysfs模块相关的结构体结合后,即完成了针对bus属性相关的系统调用关联。关于关联的详细说明请参考《
LINUX SYSFS文件系统分析之四 文件处理及相关系统调用分析》
总线注销接口bus_unregister
针对该接口,主要就是bus_register中的相反操作,通过调用kset_unregister、bus_remove_file、bus_remove_attrs、device_unregister以及kfree接口实现针对bus_register中注册的kset以及创建的sysfs文件的remove操作,同时释放在bus_register中申请的subsys_private类型的内存空间。
至此完成了总线注册与注销接口的分析,而针对bus_add_device、bus_add_driver接口的分析以及device_add、device_init等接口的实现分析,将在后面的章节中介绍与说明。