|
Linux 设备模型为系统结构提供了一般性抽象描述,并通过 sysfs 虚拟文件系统向外界展示。
Linux 设备模型涉及的内容很多且比较复杂,属于 Linux 设备驱动中的高级内容。但一般情况下驱动开发人员并不需要具体了解这部分内容,这都得利于内核抽象为 kobject 和 kset 类,并对其实现进行了封装。所以,一般驱动开发人员只要继承 kobject 和 kset 并调用其方法就可以了。
本文不会将 Linux 设备模型涉及的内容详细展开,而是会聚焦在如何通过kobject、kset 来实现 sysfs 的树形结构,从而来了解 Linux 是如何用 C 语言实现面向对象的抽象、封装和继承的。
设备模型示意
图:设备模型
我们来看一个环境中实例。一种查看的方法是在/sys下运行tree,当然结果是一个非常长的树形结构。我只摘取其中 I2C 适配器相关的部分:
.
├── bus
│ ├── platform
│ │ ├── devices
│ │ │ ├── fef04500.i2c -> ../../../devices/platform/soc/fef04500.i2c
│ │ │ ├── fef09500.i2c -> ../../../devices/platform/soc/fef09500.i2c
│ │ ├── drivers
│ │ │ ├── brcmstb-i2c
......
├── devices
│ ├── platform
│ │ ├── soc
│ │ │ ├── fef04500.i2c
│ │ │ ├── fef09500.i2c面向对象的实现思路
观察上面的示意图和实例,我们很容易发现整个设备模型就是一个树形结构,而组成这个树形结构的是一个个的文件夹。所以, Linux 为这个树形结构中的文件夹抽象了一个类,这个类的名字叫 kobject,对应到代码中就是 struct kobject。
(注:C语言没有为“类”提供专门的语法,但可以通过结构体来实现“类”。本文中提到“struct kobject”或者“kobject 类”或者“kobject”指的都是一个东西。)
kobject 提供了一系列 public 方法,比如:
int kobject_add(struct kobject *kobj,
struct kobject *parent, const char *fmt, ...);
void kobject_del(struct kobject *kobj);
int kobject_init_and_add(struct kobject *kobj,
struct kobj_type *ktype, struct kobject *parent,
const char *fmt, ...);
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)kobject 的 public 方法远不止上面这几个,这里只是列了几个作为例子,更多的方法可到 include/linux/kobject.h 中查看。
观察上述方法,我们会发现一个特点,每个函数的第一个参数都是 struct kobject 的指针。这是因为 C 语言没有提供在方法中直接使用 this 的语法,所以我们需要把 struct kobject 指针用参数传进去。 上述方法是不支持多态的,所以可以直接以函数的形式定义。如果要支持多态,那么我们需要把方法作为函数指针放在 struct kobject 中,该指针指向的函数在不同的对象实例中可以有不同的定义,从而实现多态。多态不是本文要讲的内容,这里只是提一下。
我们再进一步观察上面的示意图和实例,我们会发现这个树形结构有两种文件夹:
一种文件夹表示的是一个具体的东西(图中标识为“椭圆形”的文件夹),比如:brcmstb-i2c 是一个驱动,fef04500.i2c 是一个设备,fef09500.i2c 也是一个设备,它们都是一个具体的东西,它们分别是一个文件夹。
另一种文件夹表示的是集合,(图中标识为“方形”的文件夹)比如:bus/platform/drivers 这里的 drivers 就是一组驱动的集合,它并不代表哪个具体的驱动。同理 bus/platform/devices 是一组设备的集合,它也不代表哪个具体的设备。Linux 为这种文件夹定义了一个名叫 set 的集合,对应到代码中就是 struct kset。
struct kset {
struct list_head list; // 该链表的成员类型为 kobject
spinlock_t list_lock;
struct kobject kobj; // 继承kobject
const struct kset_uevent_ops *uevent_ops;
};上面的代码段是 kset 的定义。这里我们重点看里面的 kobj、list 这两个成员:
kobj:一个 kset 的实例在 sysfs 下也是一个文件夹(比如:上面例子中的 devices、drivers)。因为 sysfs 下的文件夹已经被抽象为 kobject 了,所以 kset 只需要继承 kobject 就可以实现了。因此我们可以看到 kset 中嵌套了 kobject。(注:C语言没有为“继承”提供专门的语法,但可以通过结构体嵌套来实现“继承”,被嵌套的结构体就是被继承的类。)
list:一个 kset 是一个集合,所以 kset 需要一个链表把这组集合收纳起来,所以我们可以看到 kset 中有个名叫list的链表。
kset 提供了一系列 public 方法,比如:
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj);
int kset_register(struct kset *kset);看到这里,有人可能会有一个疑问:kset_create_and_add() 的第一个参数怎么不是 struct kset 指针?我们前面不是讲到方法的第一个参数就是这个类的结构体指针吗? 因为 kset_create_and_add() 相当于是 kset 类的构造函数,它会创建一个 kset 类的实例,并且返回这个实例的指针,后面使用其它方法的时候就要传入这里返回的指针了。 前面讲的 kobject 类也有类似的构造函数 kobject_create() 和 kobject_create_and_add(),只是本文前面没有把它列出来而已。
具体的实现代码
下面我们通过几段代码调用关系来看看具体的实现。这里的具体实现是指如何通过继承 koject 和 kset 来实现 sysfs 中的展示,不是指 koject 和 kset 自身的实现。
我们先看/sys/bus中的bus。以下都仅截取了与示例强相关的代码调用关系,本文中代码的注释很长,请拖动代码框的滚动条查看: ./drivers/base/bus.c
buses_init()
|--> kset_create_and_add("bus", &bus_uevent_ops, NULL) // 创建一个名为“bus”的 kset 实例,并把它加到/sys,它的父节点为 NULL,所以直接创建在 /sys 下。我们再看 platform,我们从本文前面的 /sys 的树形结构的例子中可以看到有两个地方有 platform: 一个是 /sys/bus/platform。这里的 platform 对应到“图:设备模型”中“xx总线”,它代表该总线下的devices和drivers的集合,所以它是一个 kset。 另一个是 /sys/devices/platform。这里的 platform 对应到“图:设备模型”中的“yyy设备”或“zzz设备”(只不过它还有更下层的设备,这点我们先不管它),所以它是一个 kobject。 这两个地方的 platform 的代码都在 platform_bus_init 中,如下: ./drivers/base/platform.c
struct device platform_bus = {
.init_name = "platform",
};
struct bus_type platform_bus_type = {
.name = "platform",
......
}
platform_bus_init
|--> device_register(&platform_bus)
|--> device_initialize(dev) // 这一层的 dev 就是 platform_bus 的指针。device_initialize() 中将 dev->kobj.kset 置为 devices_kset,即 /sys/devices
|--> device_add(dev)
|--> dev_set_name(dev, "%s", dev->init_name); // 将 dev->kobj.name 置为 dev->init_name,即“platform”
|--> kobject_add(&dev->kobj, dev->kobj.parent, NULL); // 将名为 dev->kobj.name(即“platform”)加到 sysfs 中。它的父节点是 dev->kobj.kset 或 dev->kobj.parent 对应的节点,此处 dev->kobj.kset 为 /sys/devices,而 dev->kobj.parent 为 NULL,所以创建为 /sys/devices/platform。
|--> bus_register(&platform_bus_type)
|--> kobject_set_name(&priv->subsys.kobj, "%s", bus->name); // 这一层的 bus 就是 platform_bus_type 的指针。priv->subsys 就是即将创建的 /sys/bus/platform 这个 kset。
priv->subsys.kobj.kset = bus_kset; // 将 priv->subsys 的父节点置为 bus_kset,即/sys/bus
priv->subsys.kobj.ktype = &bus_ktype;
|--> kset_register(&priv->subsys); // 将名为 platform 的 kset 加到 sysfs 中,它的父节点是前一步设置的 bus_kset,即/sys/bus
|--> kset_create_and_add("devices", NULL, &priv->subsys.kobj); // 创建名为 devices 的 kset,它的父节点是 priv->subsys.kobj,即/sys/bus/platform
|--> kset_create_and_add("drivers", NULL, &priv->subsys.kobj); // 创建名为 drivers 的 kset,它的父节点是 priv->subsys.kobj,即/sys/bus/platform上面的代码调用关系已经把 kobject 和 kset 的都覆盖到了,就不再多举例了。 我们从上面的代码调用关系中可以看到,这些代码并不需要关心 kobject 和 kset 的具体实现,只需要嵌套 struct kobject 或 struct kset 并使用它们方法就可以实现 sysfs 树形结构。这就是继承和封装。
总结
本文基于 Linux 设备模型的实现,讲解如何用 C 语言实现面向对象的抽象、封装和继承。最后再回顾一下:
抽象
将 sysfs 中的一个文件夹抽象为 kobject。 将 sysfs 中的一个代表一组集合的文件夹抽象为 kset。
继承
因为 kset 也是一个文件夹,所以它也是一个 kobject,从这个层面上看 kset 继承了 kobject,所以我们可以看到 struct kset 中嵌套了一个 struct koject。 其它要继承 kobject 和 kset 的地方也只需要把对应的结构体嵌套在自身的结构体中就可以实现继承。
封装
koject 和 kset 将实现 sysfs 树形结构的方法定义成函数的形式,继承 kobject 和 kset 的地方只需要调用函数,不需要知道实现细节。 |
|