Linux的设备模型在设计上采用了许多面向对象的思想,但是由于C语言的限制它们只能通过一些技巧和对高级语言的模仿来实现。这些技巧使得代码结构变得更加复杂,并使得对代码的理解变得困难。本文尝试完全以面向对象的角度来分析整个设备模型的结构。所参考的代码为Linux
3.10。
如何用C模拟面向对象
从代码来看基本有两种方法:
1.
通过内嵌结构体实现继承。例如kset结构体内嵌了kobject结构,可以理解为从kobject派生了kset类。
2.
通过对结构体内的函数指针进行赋值,实现类似对纯虚父类的虚函数重载的效果。例如在bus_type结构体中含有match函数指针,变量usb_bus_type变量对其赋值,这样usb_bus_type变量可以看成是对bus_type父类的继承,并重载了虚函数match。
struct bus_type {
int (*match)(struct device *dev, struct device_driver
*drv);
}
struct bus_type usb_bus_type = {
.match = usb_device_match,
}
The Big Picture
上面是一张用UML表示的设备模型关系图。最上面两行的kobject, kobj_type,
attribute和kset是最基本的类。从devices_kset和bus_kset开始到usb_bus_type这行(不包含)之间是设备无关层。这一层最重要的三个结构就是分别表示bus,
device, driver的but_type,
device和device_driver类。usb_bus_type及其之下是usb设备相关的结构。
kobject
每一个/sys下的目录(注意是目录不是文件)都有一个相对应的kobject,目录的名字就是kobject的name字段。parent指向父目录,例如/sys/bus/usb的父目录是/sys/bus。
kobject_add()用于将一个kobject加入到系统中(加入到/sys中)。每一个继承自kobject的类的实例在register的时候最终都会调用到kobject_add(),例如通过bus_register()将一个bus_type(代表一种总线)注册到系统中时,kobject_add()就会被调用。
kobj_type
kobject的指针ktype指向一个kobj_type结构。该结构表示kobject属于哪一种类型。例如ktype将会派生出bus_ktype,
device_ktype和driver_ktype三种子类,表示了kobject属于一种bus还是device还是driver。
kobj_type中的sysfs_op包含了一组函数指针用于attribute的读写。所谓attribute就是/sys下的文件,例如/sys/bus/usb/uevent文件。每当有进程读写attribute时相应的函数就会被调用。
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
};
static const struct sysfs_ops bus_sysfs_ops = {
.show =
bus_attr_show,
.store = bus_attr_store,
};
进一步跟踪bus_attr_show()的实现可以看出该函数的参数包含一个kobject和一个attribute。这个*kobj就是attribute所在的目录,而*attr就是所访问的文件(attribute)。
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;
}
每一个目录下的文件都是通过类似sysfs_create_file()这样的函数调用关联到相应的kobject。通过每一个kobject的成员sysfs_dirent
*sd可以找到所有挂在该kobject下的attribute。由于具有多对一的关系,kobject和attribute直接使用表示组合关系的线连接在一起。
int sysfs_create_file(struct kobject * kobj, const struct
attribute * attr)
kobj_type中的另外一个重要成员是release()函数指针。当kobject的引用计数为零时该函数指针所指向的函数就会被调用,用于释放相应的系统资源(一般来说是free
memory)。由于kobject一般都是内嵌成员,在释放内存时应当释放所有的内存而非kobject自己,所以release指针会被kobj_ktype的派生类bus_ktype或device_ktype或driver_ktype重载。由于这种派生不属于内嵌对象,所以在kobj_ktype和派生类直接的连线上没有标注。
kset
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
由于kset中内嵌了kobject,这两个结构使用表示继承关系的连线表示。在连线处标注了kobj,表示kobject在kset中的内嵌对象名为kobj。
kset是一种容器类型,它的list成员串起了多个kobject,其中每个kobject的kset
*kset指针又指向了kset。
kobject的parent和ket指针具有不同的含义。parent指向其在/sys里的父目录,而kset只是表示它所处的容器(可以为空)。例如每一个device的parent都指向父device,而从图上可以看出kset都指向devices_kset(表示包含所有device的容器)。
在调用kobject_add()时,如果parent为空,则parent将会被赋值为kobject的kset指针,这时两个指针(parent和kset)就指向相同的object。在图中,如果一个结构体的parent指针没有标出,则代表kset指针与parent指针相同。例如对bus_type结构体,它的parent和kset指针都指向bus_kset变量。
bus_type
图中的bus_type类其实表示的是实际代码中的bus_type结构体加上subsys_private结构体。由于这两个结构体共同描述了一条bus,并且两个结构体中分别有指针指向对方,所以将它们作为一个整体考虑。
所有的bus_type的kset和parent指针(其实这些指针包含在bus_type所内嵌的kobject结构内)都指向一个叫做bus_kset的变量。这个变量就代表了/sys/bus目录。因此我们能够在该目录下找到所有注册过的bus的目录。
在but_type中有两个kset成员分别是devices_kset和drivers_kset,它们代表了相应bus目录下的devices和drivers目录。例如在/sys/bus/usb/下就有devices和drivers目录。
另外还有两个klist类型的成员是klist_devices和klist_drivers。这两个链表分别串起了该bus所包含的的所有设备和驱动。
bus_type相关的三个重要API分别是bus_register,
device_register和driver_register。通过命名它们的含义已经非常清晰。
device
图中的device结构代表了实际代码中的device结构和device_private结构。将两者合并的原因与合并bus_type和subsys_private的原因一致。
每一个设备都内嵌(或者说继承)了一个device结构体。device结构中的bus指针指向相应的bus_type结构。driver指针则指向相应的device_driver结构。在表示多对一的关系时(一端为菱形的直线),标注表示了参与该关系的成员名称。以device和device_driver的关系为例,标注为"n:driver 1:klist_devices",其中"n:driver"表示多的一方(device)中的driver指针指向一的一方(device_driver),其中"1:klist_devices"表示一的一方(device_driver)中的klist_devices串起了多的一方(device)。
device结构的kset指针指向devices_kset变量,该变量代表了/sys/devices目录。
device结构的parent指针则指向了它的父device。
device结构中的p指针指向device_private结构,其定义如下所示。其中klist_children包含了所有该device的子device。前面谈到过bus和driver都有一个链表连接了所有相关的device,device_private中的knode_*就放在该链表中。driver_data指向driver相关的数据结构。
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct list_head deferred_probe;
void *driver_data;
struct device *device;
};
device_driver
图中的device_driver结构其实代表了实际代码中的device_driver结构和driver_private结构。将两者合并的原因与合并bus_type和subsys_private的原因一致。每一个device
driver都由一个device_driver结构代表。该结构的bus指针指向相应的bus_type结构。
该结构包含许多函数指针成员用于被不同的设备驱动重载,以实现相应的功能。
bus_ktype, device_ktype, driver_ktype
这三种类型都派生自kobj_type类。每一种派生类都与处在同一行并具有相同颜色的结构有着紧密的关系。例如bus_type的ktype指针(包含于嵌入结构kobject)就指向bus_ktype。bus_ktype包含了与bus相关的sysfs
operation函数指针,并且包含用于释放bus_ktype的release函数指针。
bus_attribute, device_attribute, driver_attribute
以bus_attribute为例,它通过包含attribute结构实现了对它的继承。每一个attribute都代表了/sys下的一个文件(又称作attribute)。struct
attribute定义了这个文件的名称、权限等信息。另外两个函数指针则指向了读写该文件(attribute)时将会调用的函数。
struct bus_attribute {
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);
};
bus_attribute与bus_ktype是一种紧密协作的关系。以bus_register()中的代码为例,当下面的bus_create_file()被调用时,bus_attr_uevent将被作为bus的一个attribute注册在内核里,同时会有一个uevent文件添加在bus的目录下。
int bus_register(struct bus_type *bus)
{
// ...
retval = bus_create_file(bus, &bus_attr_uevent);
}
struct bus_attribute bus_attr_uevent = {
{.name = "uevent", .mode = S_IWUSR},
.show = NULL,
.store = bus_uevnet_store,
}
当进程写uevent文件时,bus_type所对应的ktype(即bus_ktype)中的sysfs_op指向的函数就会被调用(例如bus_attr_show())。传入的kobj和attr参数分别是bus_type和bus_attribute的内嵌成员。bus_attr_show()就会相应的将它们转换成相应类型(即bus_type和bus_attribute)的指针,并且调用bus_attribute里的store函数指针,也就是bus_uevent_store。
---------------------
作者:colddown
来源:CSDN
原文:https://blog.csdn.net/colddown/article/details/32163511
版权声明:本文为博主原创文章,转载请附上博文链接!