接前一篇文章:QEMU源码全解析4 —— QEMU参数解析(4)
本文内容参考:
《趣谈Linux操作系统》 —— 刘超,极客时间
《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社
特此致谢!
本篇文章以-device参数项为例简单分析QEMU参数的处理过程。
softmmu/vl.c的main函数(qemu_init函数)中有一个很长的循环来解析参数,代码片段如下所示:
/* 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_cpu:
/* hw initialization will check this */
cpu_option = optarg;
break;
case QEMU_OPTION_hda:
case QEMU_OPTION_hdb:
case QEMU_OPTION_hdc:
case QEMU_OPTION_hdd:
drive_add(IF_DEFAULT, popt->index - QEMU_OPTION_hda, optarg,
HD_OPTS);
break;
case QEMU_OPTION_blockdev:
{
Visitor *v;
BlockdevOptionsQueueEntry *bdo;
v = qobject_input_visitor_new_str(optarg, "driver",
&error_fatal);
bdo = g_new(BlockdevOptionsQueueEntry, 1);
visit_type_BlockdevOptions(v, NULL, &bdo->bdo,
&error_fatal);
visit_free(v);
loc_save(&bdo->loc);
QSIMPLEQ_INSERT_TAIL(&bdo_queue, bdo, entry);
break;
}
case QEMU_OPTION_drive:
opts = qemu_opts_parse_noisily(qemu_find_opts("drive"),
optarg, false);
if (opts == NULL) {
exit(1);
}
break;
……
case QEMU_OPTION_msg:
opts = qemu_opts_parse_noisily(qemu_find_opts("msg"), optarg,
false);
if (!opts) {
exit(1);
}
configure_msg(opts);
break;
case QEMU_OPTION_dump_vmstate:
if (vmstate_dump_file) {
error_report("only one '-dump-vmstate' "
"option may be given");
exit(1);
}
vmstate_dump_file = fopen(optarg, "w");
if (vmstate_dump_file == NULL) {
error_report("open %s: %s", optarg, strerror(errno));
exit(1);
}
break;
case QEMU_OPTION_enable_sync_profile:
qsp_enable();
break;
case QEMU_OPTION_nouserconfig:
/* Nothing to be parsed here. Especially, do not error out below. */
break;
default:
if (os_parse_cmd_args(popt->index, optarg)) {
error_report("Option not supported in this build");
exit(1);
}
}
}
}
从注释中可以看到,是第2次选项解析,那么第1次解析是什么?其实就是本系列第2篇文章中讲到的lookup_opt函数,其在主函数中的代码片段如下:
/* 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;
}
}
}
machine_opts_dict = qdict_new();
if (userconfig) {
qemu_read_default_config_file(&error_fatal);
}
为例便于理解,再贴一下lookup_opt函数代码,在softmmu/vl.c中:
static const QEMUOption *lookup_opt(int argc, char **argv,
const char **poptarg, int *poptind)
{
const QEMUOption *popt;
int optind = *poptind;
char *r = argv[optind];
const char *optarg;
loc_set_cmdline(argv, optind, 1);
optind++;
/* Treat --foo the same as -foo. */
if (r[1] == '-')
r++;
popt = qemu_options;
for(;;) {
if (!popt->name) {
error_report("invalid option");
exit(1);
}
if (!strcmp(popt->name, r + 1))
break;
popt++;
}
if (popt->flags & HAS_ARG) {
if (optind >= argc) {
error_report("requires an argument");
exit(1);
}
optarg = argv[optind++];
loc_set_cmdline(argv, optind - 2, 2);
} else {
optarg = NULL;
}
*poptarg = optarg;
*poptind = optind;
return popt;
}
还是回到第2次选项解析中来。这个循环用很长都不够表达了,那是“相当的长”,有1000行左右。这个循环中包含了一个一个的选项,其中就包括-device。当解析到“-device”时,对应的分支处理代码如下:
case QEMU_OPTION_device:
if (optarg[0] == '{') {
QObject *obj = qobject_from_json(optarg, &error_fatal);
DeviceOption *opt = g_new0(DeviceOption, 1);
opt->opts = qobject_to(QDict, obj);
loc_save(&opt->loc);
assert(opt->opts != NULL);
QTAILQ_INSERT_TAIL(&device_opts, opt, next);
} else {
if (!qemu_opts_parse_noisily(qemu_find_opts("device"),
optarg, true)) {
exit(1);
}
}
break;
核心的代码为:!qemu_opts_parse_noisily(qemu_find_opts("device"), optarg, true))。其中包括2个函数,qemu_opts_parse_noisily和qemu_find_opts。
先来看后者。qemu_find_opts函数在util/qemu-config.c中,代码如下:
QemuOptsList *qemu_find_opts(const char *group)
{
QemuOptsList *ret;
Error *local_err = NULL;
ret = find_list(vm_config_groups, group, &local_err);
if (local_err) {
error_report_err(local_err);
}
return ret;
}
find_list函数就在qemu_find_opts函数上边,代码如下:
static QemuOptsList *find_list(QemuOptsList **lists, const char *group,
Error **errp)
{
int i;
qemu_load_module_for_opts(group);
for (i = 0; lists[i] != NULL; i++) {
if (strcmp(lists[i]->name, group) == 0)
break;
}
if (lists[i] == NULL) {
error_setg(errp, "There is no option group '%s'", group);
}
return lists[i];
}
简单来讲,qemu_find_opts函数从全局变量vm_config_groups中找到刚才插入的device QemuOptsList并返回。
再来看前者,即qemu_opts_parse_noisily函数。其在util/qemu-option.c中实现,代码如下:
/**
* 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;
}
qemu_opts_parse_noisily函数只是简单调用了opt_parse函数,后者解析出一个QemuOpts。预知opt_parse函数的详情,且看下回分解。