QEMU源码分析-虚拟外设创建(以GPIO为例)
本文最后更新于:4 个月前
一个板子上有很多硬件:芯片,LED、按键、LCD、触摸屏、网卡等等。芯片里面也有很多部件,比如CPU、GPIO、SD控制器、中断控制器等等。
这些硬件,或是部件,各有不同。怎么描述它们?
如何描述硬件
每一个都使用一个TypeInfo
结构体来描述。
对于KVM
有这样的结构体
1 |
|
对于sifive_gpio
有这样的结构体
1 |
|
这些结构体在运行时会被注册进程序里,保存在一个链表中备用,为什么是备用,因为不是每一个硬件都会被用到。
如何注册硬件
当虚拟机真的要使用物理资源的时候,对下面的物理机上的资源要进行请求,所以它的工作模式有点儿类似操作系统对接驱动。驱动要符合一定的格式,才能算操作系统的一个模块。同理,qemu
为了模拟各种各样的设备,也需要管理各种各样的模块,这些模块也需要符合一定的格式。
怎么注册这些TypeInfo
结构体呢?不需要我们去调用注册函数,以GPIO
为例,在hw/gpio/sifive_gpio.c
中有如下代码,一般在最后一行:
1 |
|
F12
找到这个宏定义,我们追根溯源,调用过程如下
1 |
|
type_init
是个宏定义,调用了__attribute__((constructor))
函数,我们知道这个C语言中位数不多的在main
函数执行前,执行的函数。函数中调用了register_module_init
注册函数,说明在main
函数执行前,已经注册好硬件了。该函数将一个新的ModuleEntry
加到链表里。
初始化设备
了解了设备的注册机制,得到了ModuleEntry
链表,那么ModuleEntry
里面的init
函数在合适被调用呢?
1 |
|
1 |
|
在 module_call_init
中,我们会找到 MODULE_INIT_QOM
这种类型对应的 ModuleTypeList
,找出列表中所有的 ModuleEntry
,然后调用每个 ModuleEntry
的 init
函数。
这些xxx_register_types
执行后,又得到了什么?
- 分配一个
TypeImpl
结构体,使用TypeInfo
来设置它:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49static void kvm_type_init(void)
{
type_register_static(&kvm_accel_type);
}
||
TypeImpl *type_register_static(const TypeInfo *info)
{
return type_register(info);
}
||
TypeImpl *type_register(const TypeInfo *info)
{
assert(info->parent);
return type_register_internal(info);
}
||
static TypeImpl *type_register_internal(const TypeInfo *info)
{
TypeImpl *ti;
ti = type_new(info);
type_table_add(ti);
return ti;
}
||
static TypeImpl *type_new(const TypeInfo *info)
{
TypeImpl *ti = g_malloc0(sizeof(*ti));
int i;
if (type_table_lookup(info->name) != NULL) {
}
ti->name = g_strdup(info->name);
ti->parent = g_strdup(info->parent);
ti->class_size = info->class_size;
ti->instance_size = info->instance_size;
ti->class_init = info->class_init;
ti->class_base_init = info->class_base_init;
ti->class_data = info->class_data;
ti->instance_init = info->instance_init;
ti->instance_post_init = info->instance_post_init;
ti->instance_finalize = info->instance_finalize;
ti->abstract = info->abstract;
for (i = 0; info->interfaces && info->interfaces[i].type; i++) {
ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);
}
ti->num_interfaces = i;
return ti;
} - 把
TypeImpl
放入链表:type_table
。
在qemu
里面,有一个全局的哈希表type_table
,用来存放所有定义的类。在type_new
里面,我们先从全局表里面根据名字type_table_lookup
查找找这个类。如果找到,说明这个类曾经被注册过,就报错;如果没有找到,说明这是一个新的类,则将TypeInfo
里面信息填到TypeImpl
里面。type_table_add
会将这个类注册到全局的表里面。到这里,我们注意,class_init
还没有被调用,也即这个类现在还处于纸面的状态。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static void type_table_add(TypeImpl *ti)
{
assert(!enumerating_types);
g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}
static GHashTable *type_table_get(void)
{
static GHashTable *type_table;
if (type_table == NULL) {
type_table = g_hash_table_new(g_str_hash, g_str_equal);
}
return type_table;
}
这点更加像 Java 的反射机制了。在 Java 里面,对于一个类,首先我们写代码的时候要写一个 class xxx
的定义,编译好就放在XXX.class
文件中,这也是出于纸面的状态。然后,Java 会有一个 Class
对象,用于读取和表示这个纸面上的 class xxx
,可以生成真正的对象。
相同的过程在后面的代码中我们也可以看到,class_init
会生成XXXClass
,就相当于 Java 里面的 Class
对象,TypeImpl
还会有一个 instance_init
函数,相当于构造函数,用于根据 XXXClass
生成 Object
,这就相当于 Java 反射里面最终创建的对象。和构造函数对应的还有 instance_finalize
,相当于析构函数。
实例化设备
上面提到,目前设备还处于纸面状态,它只有被实例化后,才表示QEMU模拟的板子上有了这些硬件设备。那么到底是如何实例化的呢?
在程序的type_table
链表中,有很多TypeImpl
结构体,比如CPU
、GPIO
、LCD
对应的TypeImpl
结构体。
但是这并不表示QEMU模拟的板子上有这些硬件,必竟它们只是“TypeImpl”,表示“类型”,需要在“实例化”之后,才表示板子上有了这些硬件。
以GPIO
为例,代码为hw/gpio/sifive_gpio.c
,里面声明了一个A15MPPrivState
结构体,还定义了一个TypeInfo
结构体:
1 |
|
SIFIVEGPIOState
用来表示GPIO
外设,你要在板子上添加GPIO
设备,就必须分配,设置一个SIFIVEGPIOState
结构体。
板子上不止有一个GPIO
端口,假设有10个,那么应该有对应的10个GPIO
结构体。这10个GPIO
是类似的,同属于某类:用TypeImpl
来描述。
1 |
|
谁来分配、设置SIFIVEGPIOState
结构体?
- 分配:
根据TypeInfo
中的instrance_size
来malloc
出结构体。 - 设置:
调用TypeInfo
中的instrance_init
函数来设置刚malloc
出的结构体。
方法一:object_initialize_child
参考hw/arm/fsl-imx6ul.c
,比如:
1 |
|
object_initialize_child
该函数的第5
个参数是type
,表示type name
,
它会被用来找到对应的TypeImpl
;
找到后,会分配instance_size
大小的结构体;
然后调用TypeImpl
中的class_init
函数,这一般是设置dc->realize
;
最后调用TypeImpl
中的instance_init
函数。
调用流程如下:
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!