Lyndra's Blog

QEMU 命令参数解析

2024-10-29
编程开发 QEMU
11分钟
2023字
温馨提示:本文最后更新于 2025-03-11 ,部分信息可能因时间推移而不再适用,欢迎反馈。

QEMU 9.1.50 版本为例。

数据结构及初始化

  QEMU 在 softmmu/vl.c​ 文件中定义了 QEMUOption​ 结构体来描述不同的命令行参数,其代码如下:

1
typedef struct QEMUOption {
2
const char *name;
3
int flags;
4
int index;
5
uint32_t arch_mask;
6
} QEMUOption;

  其中 name​ 表示参数名称,flags​ 表示参数属性,为 1 表示拥有子参数,为 0 则表示无子参数,index​ 表示命令索引 (QEMU_OPTION_cmd),arch_mask​ 表示参数支持的架构。在 softmmu/vl.c​ 文件中还定义了一个全局 QEMUOption​ 数组 qemu_options​ 来描述 QEMU 的全部可用参数,具体如下:

1
enum {
2
#define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \
3
opt_enum,
4
#define DEFHEADING(text)
5
#define ARCHHEADING(text, arch_mask)
6
7
#include "qemu-options.def"
8
};
9
10
static const QEMUOption qemu_options[] = {
11
{ "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL },
12
13
#define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \
14
{ option, opt_arg, opt_enum, arch_mask },
15
#define DEFHEADING(text)
5 collapsed lines
16
#define ARCHHEADING(text, arch_mask)
17
18
#include "qemu-options.def"
19
{ /* end of list */ }
20
};

  可以看到,qemu_options​ 数组中首先定义了一个参数 h​,其使用方法为 qemu-system-riscv64 -h​,作用是打印帮助信息。其余所有的可用参数都都通过 DEF​ 宏定义在 <qemu_build_dir>/qemu-options.def​ 文件中。需要注意的是,qemu-options.def​ 文件是由 scripts/hxtool​ 脚本在编译时根据 qemu-options.hx​ 文件生成的,因此不在 QEMU 源代码目录中。

这里需要说明一下,在 QEMU 中常用的一种编程方式:将可重复利用的配置信息通过宏定义的方式放在一个文件中,在正式使用时,通过重新定义宏来实现同一个配置信息文件生成不同结构体或数组的功能。例如需要定义的 qemu_options​ 静态数组和各个选项的 enum​类型,其中对 DEF​ 宏进行了重新定义,并包含了同一个文件 “qemu-options.def”,但由于 DEF​ 两次定义的内容不同,最终生成的数据结构不同。实现一次配置,多次重复利用。

  ​QEMUOption​ 只定义了每一个大选项的名称、是否有子选项、支持的体系结构,但并没有定义子选项。子选项则是由文件 include/qemu/option_int.h​ 中定义的两个结构体 QemuOpt​、QemuOpts​进行描述。

1
struct QemuOpt {
2
char *name;
3
char *str;
4
5
const QemuOptDesc *desc;
6
union {
7
bool boolean;
8
uint64_t uint;
9
} value;
10
11
QemuOpts *opts;
12
QTAILQ_ENTRY(QemuOpt) next;
13
};
14
15
struct QemuOpts {
6 collapsed lines
16
char *id;
17
QemuOptsList *list;
18
Location loc;
19
QTAILQ_HEAD(, QemuOpt) head;
20
QTAILQ_ENTRY(QemuOpts) next;
21
};

  ​QemuOpt​ 保存每一个子选项的具体信息,每一个子选项会通过 TailQueue​ 连接成一个双向尾队列 QemuOpts​。因此,也可以将 QemuOpt​ 视作 QemuOpts​ 中的一个队列节点。其中所定义的 QTAILQ_ENTRY​ 是和 TailQueue​ 相关的数据结构,关于 TailQueue​ 的详细信息可查看 QEMU中的队列queue

  ​QemuOpts​ 是大选项中各个子选项的动态集合。QemuOpts​中保存的QTAILQ_HEAD(, QemuOpt) head;​ 是 QemuOpt​ 队列的头节点,能够访问所有的 QemuOpt​。

  以 -device​ 大选项为例,QEMU 中 -device​ 表示设备,设备有非常多种,每一种都是独立的,且可以重复添加。一个 QemuOpts​ 只能保存一种设备的子选项集合。那么多个相同种类的设备,但是参数不同,如何保存?这就需要再引入一个数据结构 QemuOptsList​,QemuOptsList​ 也是一个 TailQueue​,保存 QemuOpts​ 的集合。

1
struct QemuOptsList {
2
const char *name;
3
const char *implied_opt_name;
4
bool merge_lists; /* Merge multiple uses of option into a single list? */
5
QTAILQ_HEAD(, QemuOpts) head;
6
QemuOptDesc desc[];
7
};

  每一个 QemuOptsList​ 代表了大选项,QemuOptsList​ 中的每一个 QemuOpts​ 代表一类设备,由大选项中的子选项集合组成。

default

  下图给出完整体的数据结构的实例,将 QemuOptsList​ 中的数据结构和 QemuOpts​ 分开绘制,并在 QemuOpts​ 给出了 TailQueue​ 的细节,结合 QEMU中的队列queue 理解更佳。

default

  QEMU 在 util/qemu-config.c​ 中定义了一个全局的 QemuOptsList​ 数组 vm_config_groups​ 来储存所有可用的参数(即上图中提到的数组 vm_config_groups​):

1
static QemuOptsList *vm_config_groups[48];
2
static QemuOptsList *drive_config_groups[5];

  这两行代码说明了 QEMU 最多支持 48 个参数,5 个驱动器参数。这两个全局数组由位于 softmmu/vl.c​ 文件的 qemu_init​ 函数负责初始化:

1
qemu_add_opts(&qemu_drive_opts);
2
qemu_add_drive_opts(&qemu_legacy_drive_opts);
3
qemu_add_drive_opts(&qemu_common_drive_opts);
4
qemu_add_drive_opts(&qemu_drive_opts);
5
qemu_add_drive_opts(&bdrv_runtime_opts);
6
qemu_add_opts(&qemu_chardev_opts);
7
qemu_add_opts(&qemu_device_opts);
8
qemu_add_opts(&qemu_netdev_opts);
9
qemu_add_opts(&qemu_nic_opts);
10
qemu_add_opts(&qemu_net_opts);
11
qemu_add_opts(&qemu_rtc_opts);
12
qemu_add_opts(&qemu_global_opts);
13
qemu_add_opts(&qemu_mon_opts);
14
qemu_add_opts(&qemu_trace_opts);
15
qemu_plugin_add_opts();
16 collapsed lines
16
qemu_add_opts(&qemu_option_rom_opts);
17
qemu_add_opts(&qemu_accel_opts);
18
qemu_add_opts(&qemu_mem_opts);
19
qemu_add_opts(&qemu_smp_opts);
20
qemu_add_opts(&qemu_boot_opts);
21
qemu_add_opts(&qemu_add_fd_opts);
22
qemu_add_opts(&qemu_object_opts);
23
qemu_add_opts(&qemu_tpmdev_opts);
24
qemu_add_opts(&qemu_overcommit_opts);
25
qemu_add_opts(&qemu_msg_opts);
26
qemu_add_opts(&qemu_name_opts);
27
qemu_add_opts(&qemu_numa_opts);
28
qemu_add_opts(&qemu_icount_opts);
29
qemu_add_opts(&qemu_semihosting_config_opts);
30
qemu_add_opts(&qemu_fw_cfg_opts);
31
qemu_add_opts(&qemu_action_opts);

  其中,qemu_add_opts​ 和 qemu_add_drive_opts​ 函数的实现位于 util/qemu-config.c​ 文件中,主要负责将参数中传入的 OemuOptsList​ 添加到全局数组中:

1
void qemu_add_drive_opts(QemuOptsList *list)
2
{
3
int entries, i;
4
5
entries = ARRAY_SIZE(drive_config_groups);
6
entries--; /* keep list NULL terminated */
7
for (i = 0; i < entries; i++) {
8
if (drive_config_groups[i] == NULL) {
9
drive_config_groups[i] = list;
10
return;
11
}
12
}
13
fprintf(stderr, "ran out of space in drive_config_groups");
14
abort();
15
}
16 collapsed lines
16
17
void qemu_add_opts(QemuOptsList *list)
18
{
19
int entries, i;
20
21
entries = ARRAY_SIZE(vm_config_groups);
22
entries--; /* keep list NULL terminated */
23
for (i = 0; i < entries; i++) {
24
if (vm_config_groups[i] == NULL) {
25
vm_config_groups[i] = list;
26
return;
27
}
28
}
29
fprintf(stderr, "ran out of space in vm_config_groups");
30
abort();
31
}

命令行参数的解析

  在 qemu 中,参数解析在 vl.c​ 中的 qemu_init​ 函数中,参数的解析分为两部分:

  • 第一部分检查选项是否是 QEMU 中预定义的 QEMUOption​,并初始化 machine_opts_dict​ 数组,根据是否传入了 -no-user-config​ 参数来加载用户配置。
  • 真正解析具体参数并执行相应设置

第一阶段

  首先按照下标顺序依次读取终端传入的参数数组,跳过子选项,只解析主选项。通过 lookup_opt​ 函数查询主选项是否是预定义的 QEMUOption​,如果没找到,退出程序,如果找到,则返回找到的 QEMUOption​ 指针。虽然 lookup_opt​ 函数也会保存主选项对应的子选项参数数组的指针到 optarg​,但是第一阶段并不会使用。

  如果在解析主选项过程中,检查到有主选项-no-user-config​,后续就跳过加载用户配置,否则还会加载用户配置。然后会初始化 machine_opts_dict​ 数组,这里的 machine_opts_dict​ 是一个字典结构,主要用于存储终端传入的参数数组中的虚拟机选项和参数,包括 CPU 数量、内存大小、设备配置等。machine_opts_dict​ 的存在使得参数解析机制能够以一种结构化的方式管理和访问虚拟机参数,而不是使用分散的单独变量或者凌乱的数据结构。

1
/* first pass of option parsing */
2
optind = 1;
3
while (optind < argc) {
4
if (argv[optind][0] != '-') {
5
/* disk image */
6
optind++;
7
} else {
8
const QEMUOption *popt;
9
10
popt = lookup_opt(argc, argv, &optarg, &optind);
11
switch (popt->index) {
12
case QEMU_OPTION_nouserconfig:
13
userconfig = false;
14
break;
15
}
7 collapsed lines
16
}
17
}
18
19
machine_opts_dict = qdict_new();
20
if (userconfig) {
21
qemu_read_default_config_file(&error_fatal);
22
}

  ​lookup_opt​ 函数定义如下,由于qemu_options​定义时,最后一个元素为{ }​,在遍历时发现 popt->name​ 为空还没有匹配到主选项,就可以认为该主选项非法。lookup_opt​ 函数若匹配到当前的主选项,且主选项有子选项,则将子选项数组的指针保存到 poptarg​ 并返回给上层函数。lookup_opt​ 同时还会将已经遍历的 optind​ 的值也返回给上层函数,表示已经遍历过参数数组的这些参数。

1
static const QEMUOption *lookup_opt(int argc, char **argv,
2
const char **poptarg, int *poptind)
3
{
4
const QEMUOption *popt;
5
int optind = *poptind;
6
char *r = argv[optind];
7
const char *optarg;
8
9
loc_set_cmdline(argv, optind, 1);
10
optind++;
11
/* Treat --foo the same as -foo. */
12
if (r[1] == '-')
13
r++;
14
popt = qemu_options;
15
for(;;) {
24 collapsed lines
16
if (!popt->name) {
17
error_report("invalid option");
18
exit(1);
19
}
20
if (!strcmp(popt->name, r + 1))
21
break;
22
popt++;
23
}
24
if (popt->flags & HAS_ARG) {
25
if (optind >= argc) {
26
error_report("requires an argument");
27
exit(1);
28
}
29
optarg = argv[optind++];
30
loc_set_cmdline(argv, optind - 2, 2);
31
} else {
32
optarg = NULL;
33
}
34
35
*poptarg = optarg;
36
*poptind = optind;
37
38
return popt;
39
}

第二阶段

  真正开始解析参数。

1
/* second pass of option parsing */
2
optind = 1;
3
for(;;) {
4
if (optind >= argc)
5
break;
6
if (argv[optind][0] != '-') {
7
loc_set_cmdline(argv, optind, 1);
8
drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
9
} else {
10
const QEMUOption *popt;
11
12
popt = lookup_opt(argc, argv, &optarg, &optind);
13
if (!(popt->arch_mask & arch_type)) {
14
error_report("Option not supported for this target");
15
exit(1);
11 collapsed lines
16
}
17
switch(popt->index) {
18
case QEMU_OPTION_cpu:
19
...
20
break;
21
...
22
default:
23
...
24
}
25
}
26
}

  重新按照下标顺序依次遍历终端传入的参数数组,调用 lookup_opt​ 函数找到对应的 QEMUOption​,然后检查对应选项在当前架构下是否支持,最后使用 switch​ 语句根据 QEMUOption​ 的成员变量 index​ 的不同来执行不同的分支完成具体的设置。需要注意,主选项和子选项是成对出现的,在 lookup_opt​ 函数中也是成对解析,如果发现子选项进入了主循环,则默认为 driver​。

  ‍

参考资料

  ‍

本文标题:QEMU 命令参数解析
文章作者:Lyndra
发布时间:2024-10-29
总访问量
总访客数人次
Copyright 2025
站点地图