接前一篇文章: 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函数的详情,且看下回分解。