qemu基本知识
前言
这里简单介绍一些QEMU相关的基本知识,从而方便后续更深入的研究QEMU
QOM(QEMU Object Model)
QEMU提供了一套面向对象编程的模型,从而实现各种具有继承关系的设备的模拟。
面向对象编程通常涵盖了类和对象这两个关键概念,其中类是对象的抽象定义,而对象则是类的具体实例。在QOM中,这些概念得到了实现和体现。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 struct OjbectProperty
┌────────────┐
│ │
└─────▲──────┘
│ ┌──┬──────────────┬──┐
│ │ │ class ├──┼──────►┌──┬──────────────┬──┐
│ │ ├──────────────┤ │ │ │ type ├──┼────►┌──────────────┐
◄─────────┴──────────┼──┤ properties │ │ │ ├──────────────┤ │ │ ◄─────┐
│ ├──────────────┤ │ ┌───┼──┤ properties │ │ └──────────────┘ │
│ │ ... │ │ │ │ ├──────────────┤ │ struct TypeImpl │
│ └──────────────┘ │ │ │ │ ... │ │ │
│ parent_obj │ │ │ └──────────────┘ │ │
├────────────────────┤ │ │ parent_class │ ┌───────────────┐ │
│ ...... │ │ ├────────────────────┤ │ │ │
└────────────────────┘ │ │ ...... │ └───────────────┘ │
struct DeviceState │ └────────────────────┘ struct TypeInfo────┘
│ struct DeviceClass
│
│
│
│ struct OjbectProperty
│ ┌────────────┐
├──────►│ │
│ └────────────┘
▼
类
QOM使用struct TypeInfo和Class结构体类型共同描述一个类。
其中struct TypeInfo描述类的基本属性,Class结构体类型描述静态成员。
struct TypeInfo
QOM中使用struct TypeInfo描述诸如类的名称、父类名称、实例大小和静态成员情况
1 | /** |
Class结构体
QOM使用用户自定义的Class结构体描述诸如函数表、静态成员等类的静态内容,因此所有的对象只能共享一份Class结构体和struct TypeInfo数据。
考虑到类会继承父类的成员内容,因此需要在Class结构体中包含父类的Class数据来实现继承关系。同时为了能安全的实现面向对象编程中的向上转型,总是将父类的数据放在Class结构体的最开始,如struct PCIDeviceClass所示。
1 | /* |
对象
QOM使用用户自定义的Object结构体描述非静态成员,也就是对象的数据,因此所有的对象都有自己的Object结构数据。
类似于Class结构体,考虑到对象同样会继承父类对象的成员内容,因此需要在Object结构体中包含父类的Object数据来实现继承,父类的数据同样应放在Object结构体的最开始以实现向上转型,如struct PCIDevice所示。
1 | /* |
初始化
类的初始化
根据前面章节的介绍,QOM使用struct TypeInfo和Class结构体共同来描述类,类的初始化也就是这两个数据的初始化,包括如下几个步骤
注册类
QOM使用type_init宏注册类信息,如下所示
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/*
* #0 register_module_init (fn=0x555555a9b5a8 <pci_register_types>, type=MODULE_INIT_QOM) at ../../qemu/util/module.c:75
* #1 0x0000555555a9b63c in do_qemu_init_pci_register_types () at ../../qemu/hw/pci/pci.c:2851
* #2 0x00007ffff7829ebb in call_init (env=<optimized out>, argv=0x7fffffffdc58, argc=29) at ../csu/libc-start.c:145
* #3 __libc_start_main_impl (main=0x555555e92809 <main>, argc=29, argv=0x7fffffffdc58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdc48) at ../csu/libc-start.c:379
* #4 0x000055555586ba15 in _start ()
*/
/* This should not be used directly. Use block_init etc. instead. */
void register_module_init(void (*fn)(void), module_init_type type)
{
ModuleEntry *e;
ModuleTypeList *l;
e = g_malloc0(sizeof(*e));
e->init = fn;
e->type = type;
l = find_type(type);
QTAILQ_INSERT_TAIL(l, e, node);
}
static const TypeInfo pci_device_type_info = {
.name = TYPE_PCI_DEVICE,
.parent = TYPE_DEVICE,
.instance_size = sizeof(PCIDevice),
.abstract = true,
.class_size = sizeof(PCIDeviceClass),
.class_init = pci_device_class_init,
.class_base_init = pci_device_class_base_init,
};
static void pci_register_types(void)
{
...
type_register_static(&pci_device_type_info);
}
type_init(pci_register_types)可以看到,QOM通过
__attribute((constructor))
标记让do_qemu_init_X()
函数在main()
函数之前运行。而do_qemu_init_X()
函数是将用户自定义函数(这里是pci_register_types()
函数)插入到init_type_list[MODULE_INIT_QOM]
链表上生成struct TypeImpl
在
main()
中,init_type_list[MODULE_INIT_QOM]
链表上所有的之前插入的用户自定义函数都会在module_call_init()中执行,调用栈如下所示1
2
3
4
5
6
7
8
9
10
11/*
* #0 type_register_static (info=0x555556ea5540 <pci_device_type_info>) at ../../qemu/qom/object.c:195
* #1 0x0000555555a9b619 in pci_register_types () at ../../qemu/hw/pci/pci.c:2848
* #2 0x00005555560b0d54 in module_call_init (type=MODULE_INIT_QOM) at ../../qemu/util/module.c:109
* #3 0x0000555555bd3ce4 in qemu_init_subsystems () at ../../qemu/system/runstate.c:818
* #4 0x0000555555bdb08b in qemu_init (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/vl.c:2786
* #5 0x0000555555e9282d in main (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/main.c:47
* #6 0x00007ffff7829d90 in __libc_start_call_main (main=main@entry=0x555555e92809 <main>, argc=argc@entry=29, argv=argv@entry=0x7fffffffdc58) at ../sysdeps/nptl/libc_start_call_main.h:58
* #7 0x00007ffff7829e40 in __libc_start_main_impl (main=0x555555e92809 <main>, argc=29, argv=0x7fffffffdc58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdc48) at ../csu/libc-start.c:392
* #8 0x000055555586ba15 in _start ()
*/用户自定义函数通过type_register_static()生成struct TypeImpl数据,并将其插入到一个全局GHashTable中,如下所示。随后可以通过类的名称调用type_table_lookup获取struct TypeImpl数据,struct TypeImpl数据包含了struct TypeInfo数据和Class结构体数据(此时还未初始化),也就是类的全部信息。
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73/*
* #0 type_register_internal (info=0x555556ea5540 <pci_device_type_info>) at ../../qemu/qom/object.c:176
* #1 0x0000555555e9df0a in type_register (info=0x555556ea5540 <pci_device_type_info>) at ../../qemu/qom/object.c:190
* #2 0x0000555555e9df30 in type_register_static (info=0x555556ea5540 <pci_device_type_info>) at ../../qemu/qom/object.c:195
* #3 0x0000555555a9b619 in pci_register_types () at ../../qemu/hw/pci/pci.c:2848
* #4 0x00005555560b0d54 in module_call_init (type=MODULE_INIT_QOM) at ../../qemu/util/module.c:109
* #5 0x0000555555bd3ce4 in qemu_init_subsystems () at ../../qemu/system/runstate.c:818
* #6 0x0000555555bdb08b in qemu_init (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/vl.c:2786
* #7 0x0000555555e9282d in main (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/main.c:47
* #8 0x00007ffff7829d90 in __libc_start_call_main (main=main@entry=0x555555e92809 <main>, argc=argc@entry=29, argv=argv@entry=0x7fffffffdc58) at ../sysdeps/nptl/libc_start_call_main.h:58
* #9 0x00007ffff7829e40 in __libc_start_main_impl (main=0x555555e92809 <main>, argc=29, argv=0x7fffffffdc58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdc48) at ../csu/libc-start.c:392
* #10 0x000055555586ba15 in _start ()
*/
static TypeImpl *type_register_internal(const TypeInfo *info)
{
TypeImpl *ti;
if (!type_name_is_valid(info->name)) {
fprintf(stderr, "Registering '%s' with illegal type name\n", info->name);
abort();
}
ti = type_new(info);
type_table_add(ti);
return ti;
}
static 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;
}
struct TypeImpl
{
const char *name;
size_t class_size;
size_t instance_size;
size_t instance_align;
void (*class_init)(ObjectClass *klass, void *data);
void (*class_base_init)(ObjectClass *klass, void *data);
void *class_data;
void (*instance_init)(Object *obj);
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);
bool abstract;
const char *parent;
TypeImpl *parent_type;
ObjectClass *class;
int num_interfaces;
InterfaceImpl interfaces[MAX_INTERFACES];
};初始化Class结构体
这里初始化类的最后一部分数据,即Class结构体。其往往在生成struct TypeImpl之后且对象初始化之前通过type_initialize()进行,如下所示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/*
* #0 pci_device_class_init (klass=0x555557108080, data=0x0) at ../../qemu/hw/pci/pci.c:2630
* #1 0x0000555555e9e904 in type_initialize (ti=0x5555570997c0) at ../../qemu/qom/object.c:418
* #2 0x0000555555e9e65d in type_initialize (ti=0x555557091ba0) at ../../qemu/qom/object.c:366
* #3 0x0000555555e9e65d in type_initialize (ti=0x555557091f60) at ../../qemu/qom/object.c:366
* #4 0x0000555555ea02b7 in object_class_foreach_tramp (key=0x5555570920e0, value=0x555557091f60, opaque=0x7fffffffd8a0) at ../../qemu/qom/object.c:1133
* #5 0x00007ffff7b9d6b8 in g_hash_table_foreach () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
* #6 0x0000555555ea03a7 in object_class_foreach (fn=0x555555ea0532 <object_class_get_list_tramp>, implements_type=0x555556274512 "machine", include_abstract=false, opaque=0x7fffffffd8f0) at ../../qemu/qom/object.c:1155
* #7 0x0000555555ea05c0 in object_class_get_list (implements_type=0x555556274512 "machine", include_abstract=false) at ../../qemu/qom/object.c:1212
* #8 0x0000555555bd8192 in select_machine (qdict=0x5555570e5ce0, errp=0x55555705bca0 <error_fatal>) at ../../qemu/system/vl.c:1661
* #9 0x0000555555bd935b in qemu_create_machine (qdict=0x5555570e5ce0) at ../../qemu/system/vl.c:2101
* #10 0x0000555555bdd50f in qemu_init (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/vl.c:3664
* #11 0x0000555555e9282d in main (argc=29, argv=0x7fffffffdc58) at ../../qemu/system/main.c:47
* #12 0x00007ffff7829d90 in __libc_start_call_main (main=main@entry=0x555555e92809 <main>, argc=argc@entry=29, argv=argv@entry=0x7fffffffdc58) at ../sysdeps/nptl/libc_start_call_main.h:58
* #13 0x00007ffff7829e40 in __libc_start_main_impl (main=0x555555e92809 <main>, argc=29, argv=0x7fffffffdc58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdc48) at ../csu/libc-start.c:392
* #14 0x000055555586ba15 in _start ()
*/
static void type_initialize(TypeImpl *ti)
{
TypeImpl *parent;
if (ti->class) {
return;
}
...
ti->class = g_malloc0(ti->class_size);
parent = type_get_parent(ti);
if (parent) {
type_initialize(parent);
memcpy(ti->class, parent->class, parent->class_size);
...
}
ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
object_property_free);
ti->class->type = ti;
while (parent) {
if (parent->class_base_init) {
parent->class_base_init(ti->class, ti->class_data);
}
parent = type_get_parent(parent);
}
if (ti->class_init) {
ti->class_init(ti->class, ti->class_data);
}
}type_initialize()首先填充struct TypeImpl和Class结构体相关的字段,此时struct TypeImpl才完整的包含了类的所有信息。之后初始化所有父类的Class结构体,并依次调用所有父类的class_base_init()和自己的class_init()从而最终完成类的初始化。
对象初始化
根据前面章节的介绍,QOM使用Object结构体来描述对象,则对象的初始化也就是该数据结构的初始化。
QOM根据对象的类名称调用type_table_lookup()获取类对应的struct TypeImpl,然后使用object_initialize_with_type()来创建并初始化一个对象实例,如下所示
1 | /* |
object_initialize_with_type()首先调用type_initialize()确保类被初始化,然后调用object_init_with_type()和objet_post_init_with_type(),从而递归调用对象和对象所有父类的对象初始化相关函数。
类型转换
QOM同样实现了面向对象编程中的cast概念。根据类和对象章节的介绍,相关结构体在起始偏移处存放了父类的结构体,因此向上转型始终是安全的;而为了实现向下转型,QOM通过OBJECT_DECLARE_TYPE()宏声明了诸多的helper函数,如下所示
1 | /** |
可以看到,QOM提供了OBJ_NAME()将任何一个struct Object转换为Object结构体、OBJ_NAME##_GET_CLASS()从struct Object提取Class结构体和OBJ_NAME##_CLASS从struct ObjectClass转换为Class结构体的函数。
由于struct Object的class字段指向对应的struct ObjectClass,而struct ObjectClass的type字段指向真实的struct TypeImpl内容,基于此,QOM通过object_dynamic_cast_assert()和object_class_dynamic_cast_assert(),检查目标类或对象是否为这些指针的祖先从而进行安全的转换,如下所示
1 | Object *object_dynamic_cast_assert(Object *obj, const char *typename, |
属性
类似于linux中的sysfs,考虑到QOM的每个类的Class结构体基类是struct ObjectClass,每个对象的Object结构体基类是Object,为了提供一套类和对象的公用对外接口,QOM为struct ObjectClass和struct Object添加了properties域,即属性名称到struct ObjectProperty的哈希表,如下所示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
49
50
51
52
53
54
55
56
57
58
59
60struct ObjectProperty
{
char *name;
char *type;
char *description;
ObjectPropertyAccessor *get;
ObjectPropertyAccessor *set;
ObjectPropertyResolve *resolve;
ObjectPropertyRelease *release;
ObjectPropertyInit *init;
void *opaque;
QObject *defval;
};
/**
* typedef ObjectPropertyAccessor:
* @obj: the object that owns the property
* @v: the visitor that contains the property data
* @name: the name of the property
* @opaque: the object property opaque
* @errp: a pointer to an Error that is filled if getting/setting fails.
*
* Called when trying to get/set a property.
*/
typedef void (ObjectPropertyAccessor)(Object *obj,
Visitor *v,
const char *name,
void *opaque,
Error **errp);
/**
* typedef ObjectPropertyResolve:
* @obj: the object that owns the property
* @opaque: the opaque registered with the property
* @part: the name of the property
*
* Resolves the #Object corresponding to property @part.
*
* The returned object can also be used as a starting point
* to resolve a relative path starting with "@part".
*
* Returns: If @path is the path that led to @obj, the function
* returns the #Object corresponding to "@path/@part".
* If "@path/@part" is not a valid object path, it returns #NULL.
*/
typedef Object *(ObjectPropertyResolve)(Object *obj,
void *opaque,
const char *part);
/**
* typedef ObjectPropertyRelease:
* @obj: the object that owns the property
* @name: the name of the property
* @opaque: the opaque registered with the property
*
* Called when a property is removed from a object.
*/
typedef void (ObjectPropertyRelease)(Object *obj,
const char *name,
void *opaque);
其中,name
表示属性的名称,type
表示属性的类型,而opaque
指向一个属性的具体类型的结构体信息,如下图所示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18┌────────────────┐
│ │
│ │
├────────────────┤
│ properties ├───────────────┬──────────────────────────────────────►
├────────────────┤ │
│ │ │
│ │ ┌──────▼──────┐
└────────────────┘ │ name │
struct ObjectClass ├─────────────┤
│ type ├─────────►"bool"
├─────────────┤
│ ... │
├─────────────┤
│ opaque ├─────────►┌──────────┐
└─────────────┘ │ │
struct ObjectProperty └──────────┘
struct BoolProperty
QOM分别使用object_class_property_find()、object_class_property_add()和object_property_find()、object_property_add()来查找和设置这些属性,如下所示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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115ObjectProperty *object_class_property_find(ObjectClass *klass, const char *name)
{
ObjectClass *parent_klass;
parent_klass = object_class_get_parent(klass);
if (parent_klass) {
ObjectProperty *prop =
object_class_property_find(parent_klass, name);
if (prop) {
return prop;
}
}
return g_hash_table_lookup(klass->properties, name);
}
ObjectProperty *
object_class_property_add(ObjectClass *klass,
const char *name,
const char *type,
ObjectPropertyAccessor *get,
ObjectPropertyAccessor *set,
ObjectPropertyRelease *release,
void *opaque)
{
ObjectProperty *prop;
assert(!object_class_property_find(klass, name));
prop = g_malloc0(sizeof(*prop));
prop->name = g_strdup(name);
prop->type = g_strdup(type);
prop->get = get;
prop->set = set;
prop->release = release;
prop->opaque = opaque;
g_hash_table_insert(klass->properties, prop->name, prop);
return prop;
}
ObjectProperty *object_property_find(Object *obj, const char *name)
{
ObjectProperty *prop;
ObjectClass *klass = object_get_class(obj);
prop = object_class_property_find(klass, name);
if (prop) {
return prop;
}
return g_hash_table_lookup(obj->properties, name);
}
bool object_property_set(Object *obj, const char *name, Visitor *v,
Error **errp)
{
ERRP_GUARD();
ObjectProperty *prop = object_property_find_err(obj, name, errp);
if (prop == NULL) {
return false;
}
if (!prop->set) {
error_setg(errp, "Property '%s.%s' is not writable",
object_get_typename(obj), name);
return false;
}
prop->set(obj, v, name, prop->opaque, errp);
return !*errp;
}
ObjectProperty *
object_property_add(Object *obj, const char *name, const char *type,
ObjectPropertyAccessor *get,
ObjectPropertyAccessor *set,
ObjectPropertyRelease *release,
void *opaque)
{
return object_property_try_add(obj, name, type, get, set, release,
opaque, &error_abort);
}
ObjectProperty *
object_property_try_add(Object *obj, const char *name, const char *type,
ObjectPropertyAccessor *get,
ObjectPropertyAccessor *set,
ObjectPropertyRelease *release,
void *opaque, Error **errp)
{
ObjectProperty *prop;
...
if (object_property_find(obj, name) != NULL) {
error_setg(errp, "attempt to add duplicate property '%s' to object (type '%s')",
name, object_get_typename(obj));
return NULL;
}
prop = g_malloc0(sizeof(*prop));
prop->name = g_strdup(name);
prop->type = g_strdup(type);
prop->get = get;
prop->set = set;
prop->release = release;
prop->opaque = opaque;
g_hash_table_insert(obj->properties, prop->name, prop);
return prop;
}
参数
QEMU允许用户通过命令行参数来自定义虚拟机的设置,如qemu-system-x86_64 -nic user,model=virtio-net-pci
,这里介绍一下QEMU的参数的相关机制。
数据结构
QEMU参数的数据结构整体关系如下所示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 ┌────┬─────────────┐
│name│"nic" │
├────┼─────────────┤
│head│ ├────┐
└────┴─────────────┘ │
struct QemuOptsList◄─┐ │
│ │
│ │
┌────┬─────────────┐ │ │
│id │ │ │ │
├────┼─────────────┤ │ │
│list│ ├──┘ │
├────┼─────────────┤ │
│next│ │◄───┘
├────┼─────────────┤
┌────┤head│ │
│ └────┴─────────────┘
│ ┌─► struct QemuOpts ◄──────────┐
│ │ │
│ │ │
│ │ │
│ │ │
│ │ ┌────┬────────┐ │ ┌────┬────────────────┐
│ │ │name│"user" │ │ │name│"model" │
│ │ ├────┼────────┤ │ ├────┼────────────────┤
│ │ │str │ │ │ │str │"virtio-net-pci"│
│ │ ├────┼────────┤ │ ├────┼────────────────┤
│ └─┤opts│ │ └────┤opts│ │
│ ├────┼────────┤ ├────┼────────────────┤
└───►│next│ ├───────────────────►│next│ │
└────┴────────┘ └────┴────────────────┘
struct QemuOpt struct QemuOpt
struct QemuOptsList
QEMU将所有参数分成了几个大选项,如-nic
、-cpu
等,每一个大选项使用结构体struct QemuOptsList表示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct QemuOptsList {
const char *name;
const char *implied_opt_name;
bool merge_lists; /* Merge multiple uses of option into a single list? */
QTAILQ_HEAD(, QemuOpts) head;
QemuOptDesc desc[];
};
// -nic选项的样例
QemuOptsList qemu_nic_opts = {
.name = "nic",
.implied_opt_name = "type",
.head = QTAILQ_HEAD_INITIALIZER(qemu_nic_opts.head),
.desc = {
/*
* no elements => accept any params
* validation will happen later
*/
{ /* end of list */ }
},
};
struct QemuOpt
每个struct QemuOptsList大选项下还支持多个小选项,如-nic
下的user
和model
等小选项,每个小选项由struct QemuOpt表示1
2
3
4
5
6
7
8
9
10
11
12
13struct QemuOpt {
char *name;
char *str;
const QemuOptDesc *desc;
union {
bool boolean;
uint64_t uint;
} value;
QemuOpts *opts;
QTAILQ_ENTRY(QemuOpt) next;
};
其中name表示小选项的字符串表示,str表示对应的值。需要注意的是,struct QemuOpt并不和struct QemuOptsList直接联系,这是因为QEMU命令行可能会指定创建两个相同参数的设备,因此会使用struct QemuOpts连接。
struct QemuOpts
struct QemuOpts用于连接struct QemuOpt和struct QemuOptsList,可以理解为一个大选项的实例1
2
3
4
5
6
7struct QemuOpts {
char *id;
QemuOptsList *list;
Location loc;
QTAILQ_HEAD(, QemuOpt) head;
QTAILQ_ENTRY(QemuOpts) next;
};
解析
QEMU会在qemu_init()中解析命令行参数,并填充相关的数据结构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
49
50void qemu_init(int argc, char **argv)
{
...
/* first pass of option parsing */
optind = 1;
while (optind < argc) {
if (argv[optind][0] != '-') {
/* disk image */
optind++;
} else {
const QEMUOption *popt;
popt = lookup_opt(argc, argv, &optarg, &optind);
switch (popt->index) {
case QEMU_OPTION_nouserconfig:
userconfig = false;
break;
}
}
}
...
/* second pass of option parsing */
optind = 1;
for(;;) {
if (optind >= argc)
break;
if (argv[optind][0] != '-') {
loc_set_cmdline(argv, optind, 1);
drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
} else {
const QEMUOption *popt;
popt = lookup_opt(argc, argv, &optarg, &optind);
if (!(popt->arch_mask & arch_type)) {
error_report("Option not supported for this target");
exit(1);
}
switch(popt->index) {
...
case QEMU_OPTION_nic:
default_net = 0;
net_client_parse(qemu_find_opts("nic"), optarg);
break;
...
}
}
}
}
QEMU首先在lookup_opt()中解析出所属的大选项,然后再继续解析出一个大选项的实例struct QemuOpts。
以-nic
大选项为例,其在net_client_parse()中解析出struct QemuOpts并插入在nic对应的struct QemuOptsList中,如下所示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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95static bool opts_do_parse(QemuOpts *opts, const char *params,
const char *firstname,
bool warn_on_flag, bool *help_wanted, Error **errp)
{
char *option, *value;
const char *p;
QemuOpt *opt;
for (p = params; *p;) {
p = get_opt_name_value(p, firstname, warn_on_flag, help_wanted, &option, &value);
if (help_wanted && *help_wanted) {
g_free(option);
g_free(value);
return false;
}
firstname = NULL;
if (!strcmp(option, "id")) {
g_free(option);
g_free(value);
continue;
}
opt = opt_create(opts, option, value);
g_free(option);
if (!opt_validate(opt, errp)) {
qemu_opt_del(opt);
return false;
}
}
return true;
}
static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
bool permit_abbrev,
bool warn_on_flag, bool *help_wanted, Error **errp)
{
const char *firstname;
char *id = opts_parse_id(params);
QemuOpts *opts;
assert(!permit_abbrev || list->implied_opt_name);
firstname = permit_abbrev ? list->implied_opt_name : NULL;
opts = qemu_opts_create(list, id, !list->merge_lists, errp);
g_free(id);
if (opts == NULL) {
return NULL;
}
if (!opts_do_parse(opts, params, firstname,
warn_on_flag, help_wanted, errp)) {
qemu_opts_del(opts);
return NULL;
}
return opts;
}
/**
* Create a QemuOpts in @list and with options parsed from @params.
* If @permit_abbrev, the first key=value in @params may omit key=,
* and is treated as if key was @list->implied_opt_name.
* Report errors with error_report_err(). This is inappropriate in
* QMP context. Do not use this function there!
* Return the new QemuOpts on success, null pointer on error.
*/
QemuOpts *qemu_opts_parse_noisily(QemuOptsList *list, const char *params,
bool permit_abbrev)
{
Error *err = NULL;
QemuOpts *opts;
bool help_wanted = false;
opts = opts_parse(list, params, permit_abbrev, true,
opts_accepts_any(list) ? NULL : &help_wanted,
&err);
if (!opts) {
assert(!!err + !!help_wanted == 1);
if (help_wanted) {
qemu_opts_print_help(list, true);
} else {
error_report_err(err);
}
}
return opts;
}
void net_client_parse(QemuOptsList *opts_list, const char *optstr)
{
if (!qemu_opts_parse_noisily(opts_list, optstr, true)) {
exit(1);
}
}
可以看到,QEMU在opts_parse()中使用qemu_opts_create()创建struct QemuOpts实例并插入到struct QemuOptsList中,然后使用opts_do_parse()解析所有struct QemuOpt并插入到struct QemuOpts中。
至于检查参数的正确性,则推迟到初始化nic设备时在进行即可,如下所示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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89/*
* #0 visit_check_struct (v=0x5555570ea430, errp=0x5555570ea550) at ../../qemu/qapi/qapi-visit-core.c:62
* #1 0x0000555556055353 in visit_type_Netdev (v=0x5555570ea430, name=0x0, obj=0x7fffffffda18, errp=0x55555705bca0 <error_fatal>) at qapi/qapi-visit-net.c:1327
* #2 0x0000555555c3d0b4 in net_client_init (opts=0x5555570e7f60, is_netdev=true, errp=0x55555705bca0 <error_fatal>) at ../../qemu/net/net.c:1427
* #3 0x0000555555c3e0f6 in net_param_nic (dummy=0x0, opts=0x5555570e7f60, errp=0x55555705bca0 <error_fatal>) at ../../qemu/net/net.c:1822
* #4 0x00005555560b9a7c in qemu_opts_foreach (list=0x555556f485e0 <qemu_nic_opts>, func=0x555555c3dd92 <net_param_nic>, opaque=0x0, errp=0x55555705bca0 <error_fatal>) at ../../qemu/util/qemu-option.c:1135
* #5 0x0000555555c3e2bb in net_init_clients () at ../../qemu/net/net.c:1860
* #6 0x0000555555bd8ef8 in qemu_create_late_backends () at ../../qemu/system/vl.c:2011
* #7 0x0000555555bdd5f7 in qemu_init (argc=15, argv=0x7fffffffde08) at ../../qemu/system/vl.c:3712
* #8 0x0000555555e9282d in main (argc=15, argv=0x7fffffffde08) at ../../qemu/system/main.c:47
* #9 0x00007ffff7829d90 in __libc_start_call_main (main=main@entry=0x555555e92809 <main>, argc=argc@entry=15, argv=argv@entry=0x7fffffffde08) at ../sysdeps/nptl/libc_start_call_main.h:58
* #10 0x00007ffff7829e40 in __libc_start_main_impl (main=0x555555e92809 <main>, argc=15, argv=0x7fffffffde08, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffddf8) at ../csu/libc-start.c:392
* #11 0x000055555586ba15 in _start ()
*/
bool visit_type_NetLegacyNicOptions_members(Visitor *v, NetLegacyNicOptions *obj, Error **errp)
{
bool has_netdev = !!obj->netdev;
bool has_macaddr = !!obj->macaddr;
bool has_model = !!obj->model;
bool has_addr = !!obj->addr;
if (visit_optional(v, "netdev", &has_netdev)) {
if (!visit_type_str(v, "netdev", &obj->netdev, errp)) {
return false;
}
}
if (visit_optional(v, "macaddr", &has_macaddr)) {
if (!visit_type_str(v, "macaddr", &obj->macaddr, errp)) {
return false;
}
}
if (visit_optional(v, "model", &has_model)) {
if (!visit_type_str(v, "model", &obj->model, errp)) {
return false;
}
}
if (visit_optional(v, "addr", &has_addr)) {
if (!visit_type_str(v, "addr", &obj->addr, errp)) {
return false;
}
}
if (visit_optional(v, "vectors", &obj->has_vectors)) {
if (!visit_type_uint32(v, "vectors", &obj->vectors, errp)) {
return false;
}
}
return true;
}
bool visit_type_Netdev_members(Visitor *v, Netdev *obj, Error **errp)
{
...
switch (obj->type) {
...
case NET_CLIENT_DRIVER_NIC:
return visit_type_NetLegacyNicOptions_members(v, &obj->u.nic, errp);
...
default:
abort();
}
return true;
}
bool visit_type_Netdev(Visitor *v, const char *name,
Netdev **obj, Error **errp)
{
bool ok = false;
if (!visit_start_struct(v, name, (void **)obj, sizeof(Netdev), errp)) {
return false;
}
if (!*obj) {
/* incomplete */
assert(visit_is_dealloc(v));
ok = true;
goto out_obj;
}
if (!visit_type_Netdev_members(v, *obj, errp)) {
goto out_obj;
}
ok = visit_check_struct(v, errp);
out_obj:
visit_end_struct(v, (void **)obj);
if (!ok && visit_is_input(v)) {
qapi_free_Netdev(*obj);
*obj = NULL;
}
return ok;
}
QEMU在visit_type_Netdev()完成参数的认证,其通过visit_type_Netdev_members()解析nic大选项预设的小选项,并将struct QemuOpts中剩余的小选项当做非法小选项即可。其中visit_type_Netdev()函数是通过qapi-gen.py基于net.json自动生成的函数。