第 3 章 内核对象

内核对象,或 Kobj 为内核提供了一个面向对象的 C 编程系统。因此,正在操作的数据包含了如何操作它的描述。这允许在运行时添加和删除接口的操作,并且不会破坏二进制兼容性。

3.1 术语

对象

一组数据 - 数据结构 - 数据分配。

方法

一个操作 - 函数。

一个或多个方法。

接口

一组标准的一个或多个方法。

3.2 Kobj 操作

Kobj 通过生成方法描述来工作。每个描述都包含一个唯一的 ID 以及一个默认函数。描述的地址用于在类的“方法表”中唯一标识方法。

类是通过创建方法表来构建的,该方法表将一个或多个函数与方法描述相关联。在使用之前,类会被编译。编译会分配一个缓存并将其与类关联。如果另一个引用类的编译尚未为方法描述分配唯一 ID,则会为方法表中的每个方法描述分配一个唯一 ID。对于要使用的每个方法,都会通过脚本生成一个函数,以限定参数并自动引用方法描述以进行查找。生成的函数通过使用与方法描述关联的唯一 ID 作为哈希值来查找方法,该哈希值位于与对象类关联的缓存中。如果方法未被缓存,则生成的函数将继续使用类的表来查找方法。如果找到该方法,则使用类中的关联函数;否则,将使用与方法描述关联的默认函数。

这些间接引用可以可视化为如下所示

object->cache<->class

3.3 使用 Kobj

3.3.1 结构体

struct kobj_method

3.3.2 函数

void kobj_class_compile(kobj_class_t cls);
void kobj_class_compile_static(kobj_class_t cls, kobj_ops_t ops);
void kobj_class_free(kobj_class_t cls);
kobj_t kobj_create(kobj_class_t cls, struct malloc_type *mtype, int mflags);
void kobj_init(kobj_t obj, kobj_class_t cls);
void kobj_delete(kobj_t obj, struct malloc_type *mtype);

3.3.3 宏

KOBJ_CLASS_FIELDS
KOBJ_FIELDS
DEFINE_CLASS(name, methods, size)
KOBJMETHOD(NAME, FUNC)

3.3.4 头文件

<sys/param.h>
<sys/kobj.h>

3.3.5 创建接口模板

使用 Kobj 的第一步是创建一个接口。创建接口涉及创建脚本 src/sys/kern/makeobjops.pl 可以用来生成方法声明和方法查找函数的头文件和代码的模板。

在此模板中,使用以下关键字:#includeINTERFACECODEEPILOGHEADERMETHODPROLOGSTATICMETHODDEFAULT

#include 语句及其后的内容将逐字复制到生成代码文件的开头。

例如

#include <sys/foo.h>

INTERFACE 关键字用于定义接口名称。此名称与每个方法名称连接,格式为 [接口名称]_[方法名称]。其语法为 INTERFACE [接口名称];。

例如

INTERFACE foo;

CODE 关键字将其参数逐字复制到代码文件中。其语法为 CODE { [任何内容] };

例如

CODE {
	struct foo * foo_alloc_null(struct bar *)
	{
		return NULL;
	}
};

HEADER 关键字将其参数逐字复制到头文件中。其语法为 HEADER { [任何内容] };

例如

HEADER {
        struct mumble;
        struct grumble;
};

METHOD 关键字描述一个方法。其语法为 METHOD [返回类型] [方法名称] { [对象 [, 参数]] };

例如

METHOD int bar {
	struct object *;
	struct foo *;
	struct bar;
};

DEFAULT 关键字可以跟随 METHOD 关键字。它扩展了 METHOD 关键字以包含方法的默认函数。扩展语法为 METHOD [返回类型] [方法名称] { [对象; [其他参数]] }DEFAULT [默认函数];

例如

METHOD int bar {
	struct object *;
	struct foo *;
	int bar;
} DEFAULT foo_hack;

STATICMETHOD 关键字的使用方式类似于 METHOD 关键字,但 kobj 数据不在对象结构的开头,因此转换为 kobj_t 将是不正确的。相反,STATICMETHOD 依赖于 Kobj 数据作为“ops”进行引用。这对于直接从类的“方法表”中调用方法也很有用。

PROLOGEPILOG 关键字在与其关联的 METHOD 之前或之后立即插入代码。此功能主要用于分析情况,在这些情况下难以通过其他方式获取信息。

其他完整示例

src/sys/kern/bus_if.m
src/sys/kern/device_if.m

3.3.6 创建类

使用 Kobj 的第二步是创建一个类。类由名称、方法表以及对象大小(如果使用 Kobj 的对象处理功能)组成。要创建类,请使用宏 DEFINE_CLASS()。要创建方法表,请创建一个以 NULL 条目结尾的 kobj_method_t 数组。每个非 NULL 条目都可以使用宏 KOBJMETHOD() 创建。

例如

DEFINE_CLASS(fooclass, foomethods, sizeof(struct foodata));

kobj_method_t foomethods[] = {
	KOBJMETHOD(bar_doo, foo_doo),
	KOBJMETHOD(bar_foo, foo_foo),
	{ NULL, NULL}
};

类必须“编译”。根据系统在初始化类时所处的状态,必须使用静态分配的缓存(“操作表”)。这可以通过声明一个 struct kobj_ops 并使用 kobj_class_compile_static(); 来实现,否则应使用 kobj_class_compile()

3.3.7 创建对象

使用 Kobj 的第三步涉及如何定义对象。Kobj 对象创建例程假定 Kobj 数据位于对象的开头。如果这不太合适,您将必须自己分配对象,然后在它的 Kobj 部分上使用 kobj_init();否则,您可以使用 kobj_create() 自动分配和初始化对象的 Kobj 部分。kobj_init() 也可以用来更改对象使用的类。

要将 Kobj 集成到对象中,应使用宏 KOBJ_FIELDS。

例如

struct foo_data {
	KOBJ_FIELDS;
	foo_foo;
	foo_bar;
};

3.3.8 调用方法

使用 Kobj 的最后一步是简单地使用生成的函数来使用对象类中的所需方法。这与使用接口名称和方法名称一样简单,只需进行一些修改。接口名称应使用“_”连接方法名称,全部大写。

例如,如果接口名称为 foo,方法为 bar,则调用将为

[return value = ] FOO_BAR(object [, other parameters]);

3.3.9. 清理

当不再需要通过 kobj_create() 分配的对象时,可以对其调用 kobj_delete(),当不再使用某个类时,可以对其调用 kobj_class_free()


上次修改时间:2024年3月9日 由 Danilo G. Baio 修改