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 协议 ,转载请注明出处!