QEMU源码全解析5 —— QEMU参数解析(5)

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

猜你喜欢

转载自blog.csdn.net/phmatthaus/article/details/131692003