代码版本:qemu1.5
一、qemu有哪些参数
1、qemu-options.hx文件
qemu可用参数位于qemu-options.hx文件中,例如:
DEF("kernel", HAS_ARG, QEMU_OPTION_kernel, \ "-kernel bzImage use 'bzImage' as kernel image\n", QEMU_ARCH_ALL) STEXI @item -kernel @var{bzImage} @findex -kernel Use @var{bzImage} as kernel image. The kernel can be either a Linux kernel or in multiboot format. ETEXI
用法:-kernel bzImage,这是个很简单的命令。
DEF中的第一个参数对应"-kernel"去掉'-';第二个参数表示-kernel命令是有参数的;第三个参数是一个index,唯一的;第四个是help信息;第五个表示支持哪些平台。
DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ "-machine [type=]name[,prop[=value][,...]]\n" " selects emulated machine ('-machine help' for list)\n" " property accel=accel1[:accel2[:...]] selects accelerator\n" " supported accelerators are kvm, xen, tcg (default: tcg)\n" " kernel_irqchip=on|off controls accelerated irqchip support\n" " kvm_shadow_mem=size of KVM shadow MMU\n" " dump-guest-core=on|off include guest memory in a core dump (default=on)\n" " mem-merge=on|off controls memory merge support (default: on)\n", QEMU_ARCH_ALL) STEXI @item -machine [type=]@var{name}[,prop=@var{value}[,...]] @findex -machine Select the emulated machine by @var{name}. Use @code{-machine help} to list available machines. Supported machine properties are: @table @option @item accel=@var{accels1}[:@var{accels2}[:...]] This is used to enable an accelerator. Depending on the target architecture, kvm, xen, or tcg can be available. By default, tcg is used. If there is more than one accelerator specified, the next one is used if the previous one fails to initialize. @item kernel_irqchip=on|off Enables in-kernel irqchip support for the chosen accelerator when available. @item kvm_shadow_mem=size Defines the size of the KVM shadow MMU. @item dump-guest-core=on|off Include guest memory in a core dump. The default is on. @item mem-merge=on|off Enables or disables memory merge support. This feature, when supported by the host, de-duplicates identical memory pages among VMs instances (enabled by default). @end table ETEXI
用法:-machine [type=]@var{name}[,prop=@var{value}[,...]],这种命令的使用和解析就比较复杂了。
2、qemu-options-wrapper.h
qemu-options-wrapper.h会include qemu-options.hx,根据QEMU_OPTIONS_GENERATE_ENUM、QEMU_OPTIONS_GENERATE_HELP、QEMU_OPTIONS_GENERATE_OPTIONS是否定义,实现三种不同的功能,分别是enum,打印help信息和定义参数。
#if defined(QEMU_OPTIONS_GENERATE_ENUM) #define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \ opt_enum, #define DEFHEADING(text) #define ARCHHEADING(text, arch_mask) #elif defined(QEMU_OPTIONS_GENERATE_HELP) #define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \ if ((arch_mask) & arch_type) \ fputs(opt_help, stdout); #define ARCHHEADING(text, arch_mask) \ if ((arch_mask) & arch_type) \ puts(stringify(text)); #define DEFHEADING(text) ARCHHEADING(text, QEMU_ARCH_ALL) #elif defined(QEMU_OPTIONS_GENERATE_OPTIONS) #define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \ { option, opt_arg, opt_enum, arch_mask }, #define DEFHEADING(text) #define ARCHHEADING(text, arch_mask) #else #error "qemu-options-wrapper.h included with no option defined" #endif #include "qemu-options.def"
enum
enum { #define QEMU_OPTIONS_GENERATE_ENUM #include "qemu-options-wrapper.h" };
打印help信息
static void help(int exitcode) { version(); printf("usage: %s [options] [disk_image]\n\n" "'disk_image' is a raw hard disk image for IDE hard disk 0\n\n", error_get_progname()); #define QEMU_OPTIONS_GENERATE_HELP #include "qemu-options-wrapper.h" printf("\nDuring emulation, the following keys are useful:\n" "ctrl-alt-f toggle full screen\n" "ctrl-alt-n switch to virtual console 'n'\n" "ctrl-alt toggle mouse and keyboard grab\n" "\n" "When using -nographic, press 'ctrl-a h' to get some help.\n"); exit(exitcode); }
定义命令
qemu_options数组保存了所有的合法的参数:
static const QEMUOption qemu_options[] = { { "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL }, #define QEMU_OPTIONS_GENERATE_OPTIONS #include "qemu-options-wrapper.h" { NULL }, };
二、解析简单参数示例
以-kernel bzImage为例说明简单参数的解析
QemuOptsList
qemu所有解析后的命令行参数都保存在QemuOptsList的数组vm_config_groups中:
static QemuOptsList *vm_config_groups[32];
每个QemuOptsList中都记录了一类参数,比如machine类的参数:
static QemuOptsList qemu_machine_opts = { .name = "machine", .implied_opt_name = "type", .merge_lists = true, .head = QTAILQ_HEAD_INITIALIZER(qemu_machine_opts.head), .desc = { { .name = "type", .type = QEMU_OPT_STRING, .help = "emulated machine" }, { .name = "accel", .type = QEMU_OPT_STRING, .help = "accelerator list", }, { .name = "kernel_irqchip", .type = QEMU_OPT_BOOL, .help = "use KVM in-kernel irqchip", }, { .name = "kvm_shadow_mem", .type = QEMU_OPT_SIZE, .help = "KVM shadow MMU size", }, { .name = "kernel", .type = QEMU_OPT_STRING, .help = "Linux kernel image file", }, { .name = "initrd", .type = QEMU_OPT_STRING, .help = "Linux initial ramdisk file", }, { .name = "append", .type = QEMU_OPT_STRING, .help = "Linux kernel command line", }, { .name = "dtb", .type = QEMU_OPT_STRING, .help = "Linux kernel device tree file", }, { .name = "dumpdtb", .type = QEMU_OPT_STRING, .help = "Dump current dtb to a file and quit", }, { .name = "phandle_start", .type = QEMU_OPT_STRING, .help = "The first phandle ID we may generate dynamically", }, { .name = "dt_compatible", .type = QEMU_OPT_STRING, .help = "Overrides the \"compatible\" property of the dt root node", }, { .name = "dump-guest-core", .type = QEMU_OPT_BOOL, .help = "Include guest memory in a core dump", }, { .name = "mem-merge", .type = QEMU_OPT_BOOL, .help = "enable/disable memory merge support", },{ .name = "usb", .type = QEMU_OPT_BOOL, .help = "Set on/off to enable/disable usb", }, { /* End of list */ } }, };
向vm_config_groups中添加东西的代码:
qemu_add_opts(&qemu_drive_opts); qemu_add_opts(&qemu_chardev_opts); qemu_add_opts(&qemu_device_opts); qemu_add_opts(&qemu_netdev_opts); qemu_add_opts(&qemu_net_opts); qemu_add_opts(&qemu_rtc_opts); qemu_add_opts(&qemu_global_opts); qemu_add_opts(&qemu_mon_opts); qemu_add_opts(&qemu_trace_opts); qemu_add_opts(&qemu_option_rom_opts); qemu_add_opts(&qemu_machine_opts); qemu_add_opts(&qemu_boot_opts); qemu_add_opts(&qemu_sandbox_opts); qemu_add_opts(&qemu_add_fd_opts); qemu_add_opts(&qemu_object_opts); qemu_add_opts(&qemu_tpmdev_opts); qemu_add_opts(&qemu_realtime_opts);
void qemu_add_opts(QemuOptsList *list) { int entries, i; entries = ARRAY_SIZE(vm_config_groups); entries--; /* keep list NULL terminated */ for (i = 0; i < entries; i++) { if (vm_config_groups[i] == NULL) { vm_config_groups[i] = list; return; } } fprintf(stderr, "ran out of space in vm_config_groups"); abort(); }
QemuOptsList是QemuOpts的链表,QemuOptsList可以有多个QemuOpts,每一个使用QemuOpts中的id去区分,如果QemuOptsList中的merge_lists为true,那么只有一个QemuOpts。
QemuOpts是QemuOpt的链表,每一个QemuOpt记录了一个最基本的name,str(or value)。
-kernel bzImage被代码:
case QEMU_OPTION_kernel: qemu_opts_set(qemu_find_opts("machine"), 0, "kernel", optarg); break;
解析为:
vm_config_groups->QemuOptsList(为qemu_machine_opts)->QemuOpts(merge_lists=true,所以是唯一一个QemuOpts)->QemuOpt(name=kernel,str=bzImage)
先找到machine对应的QemuOptsList:
QemuOptsList *qemu_find_opts(const char *group) { QemuOptsList *ret; Error *local_err = NULL; ret = find_list(vm_config_groups, group, &local_err); if (error_is_set(&local_err)) { error_report("%s", error_get_pretty(local_err)); error_free(local_err); } return ret; }
根据QemuOptsList的name来匹配:
static QemuOptsList *find_list(QemuOptsList **lists, const char *group, Error **errp) { int i; for (i = 0; lists[i] != NULL; i++) { if (strcmp(lists[i]->name, group) == 0) break; } if (lists[i] == NULL) { error_set(errp, QERR_INVALID_OPTION_GROUP, group); } return lists[i]; }
QemuOpts
找到QemuOptsList后,先创建(or 查找)QemuOpts。然后在QemuOpts中插入QemuOpt,name为kernel,value为bzImage文件名:
int qemu_opts_set(QemuOptsList *list, const char *id, const char *name, const char *value) { QemuOpts *opts; Error *local_err = NULL; opts = qemu_opts_create(list, id, 1, &local_err); if (error_is_set(&local_err)) { qerror_report_err(local_err); error_free(local_err); return -1; } return qemu_opt_set(opts, name, value); }
根据id创建(or 查找)QemuOpts。id_wellformed检查是否有非法字符,merge_lists表示QemuOptsList中的QemuOpts是否唯一:
QemuOpts *qemu_opts_create(QemuOptsList *list, const char *id, int fail_if_exists, Error **errp) { QemuOpts *opts = NULL; if (id) { if (!id_wellformed(id)) { error_set(errp,QERR_INVALID_PARAMETER_VALUE, "id", "an identifier"); #if 0 /* conversion from qerror_report() to error_set() broke this: */ error_printf_unless_qmp("Identifiers consist of letters, digits, '-', '.', '_', starting with a letter.\n"); #endif return NULL; } opts = qemu_opts_find(list, id); if (opts != NULL) { if (fail_if_exists && !list->merge_lists) { error_set(errp, QERR_DUPLICATE_ID, id, list->name); return NULL; } else { return opts; } } } else if (list->merge_lists) { opts = qemu_opts_find(list, NULL); if (opts) { return opts; } } opts = g_malloc0(sizeof(*opts)); opts->id = g_strdup(id); opts->list = list; loc_save(&opts->loc); QTAILQ_INIT(&opts->head); QTAILQ_INSERT_TAIL(&list->head, opts, next); return opts; }
struct QemuOpts { char *id; QemuOptsList *list; Location loc; QTAILQ_HEAD(QemuOptHead, QemuOpt) head; QTAILQ_ENTRY(QemuOpts) next; };
QemuOpt
在QemuOpts中插入QemuOpt,name为kernel,value为bzImage文件名:
int qemu_opt_set(QemuOpts *opts, const char *name, const char *value) { Error *local_err = NULL; opt_set(opts, name, value, false, &local_err); if (error_is_set(&local_err)) { qerror_report_err(local_err); error_free(local_err); return -1; } return 0; }
static void opt_set(QemuOpts *opts, const char *name, const char *value, bool prepend, Error **errp) { QemuOpt *opt; const QemuOptDesc *desc; Error *local_err = NULL; desc = find_desc_by_name(opts->list->desc, name); if (!desc && !opts_accepts_any(opts)) { error_set(errp, QERR_INVALID_PARAMETER, name); return; } opt = g_malloc0(sizeof(*opt)); opt->name = g_strdup(name); opt->opts = opts; if (prepend) { QTAILQ_INSERT_HEAD(&opts->head, opt, next); } else { QTAILQ_INSERT_TAIL(&opts->head, opt, next); } opt->desc = desc; opt->str = g_strdup(value); qemu_opt_parse(opt, &local_err); if (error_is_set(&local_err)) { error_propagate(errp, local_err); qemu_opt_del(opt); } }
看看desc中kernel对应value的类型是啥,字符串,bool or int:
static const QemuOptDesc *find_desc_by_name(const QemuOptDesc *desc, const char *name) { int i; for (i = 0; desc[i].name != NULL; i++) { if (strcmp(desc[i].name, name) == 0) { return &desc[i]; } } return NULL; }
static QemuOptsList qemu_machine_opts = { .name = "machine", .implied_opt_name = "type", .merge_lists = true, .head = QTAILQ_HEAD_INITIALIZER(qemu_machine_opts.head), .desc = { // ... { .name = "kernel", .type = QEMU_OPT_STRING, .help = "Linux kernel image file", } // ... , { /* End of list */ } }, };
真正的插入QemuOpt的操作:
static void qemu_opt_parse(QemuOpt *opt, Error **errp) { if (opt->desc == NULL) return; switch (opt->desc->type) { case QEMU_OPT_STRING: /* nothing */ return; case QEMU_OPT_BOOL: parse_option_bool(opt->name, opt->str, &opt->value.boolean, errp); break; case QEMU_OPT_NUMBER: parse_option_number(opt->name, opt->str, &opt->value.uint, errp); break; case QEMU_OPT_SIZE: parse_option_size(opt->name, opt->str, &opt->value.uint, errp); break; default: abort(); } }
struct QemuOpt { const char *name; const char *str; const QemuOptDesc *desc; union { bool boolean; uint64_t uint; } value; QemuOpts *opts; QTAILQ_ENTRY(QemuOpt) next; };
三、解析复杂参数示例
类似于这种参数: -aaa key1=value2,key2=value2,key3=value3。如果key1没有指定,那么就是implied_opt_name。将被解析为:
vm_config_groups->QemuOptsList->QemuOpts->QemuOpt(name=key1,str=value1)
->QemuOpt(name=key2,str=value2)
->QemuOpt(name=key3,str=value3)
复杂的参数由以下两种函数解析,params就是"key1=value2,key2=value2,key3=value3":
QemuOpts *qemu_opts_parse(QemuOptsList *list, const char *params, int permit_abbrev) { return opts_parse(list, params, permit_abbrev, false); } void qemu_opts_set_defaults(QemuOptsList *list, const char *params, int permit_abbrev) { QemuOpts *opts; opts = opts_parse(list, params, permit_abbrev, true); assert(opts); }
可能会使用解析到的id去创建QemuOpts,也可能使用默认的QemuOpts。得到QemuOpts之后,使用opts_do_parse去解析params:
static QemuOpts *opts_parse(QemuOptsList *list, const char *params, int permit_abbrev, bool defaults) { const char *firstname; char value[1024], *id = NULL; const char *p; QemuOpts *opts; Error *local_err = NULL; assert(!permit_abbrev || list->implied_opt_name); firstname = permit_abbrev ? list->implied_opt_name : NULL; if (strncmp(params, "id=", 3) == 0) { get_opt_value(value, sizeof(value), params+3); id = value; } else if ((p = strstr(params, ",id=")) != NULL) { get_opt_value(value, sizeof(value), p+4); id = value; } if (defaults) { if (!id && !QTAILQ_EMPTY(&list->head)) { opts = qemu_opts_find(list, NULL); } else { opts = qemu_opts_create(list, id, 0, &local_err); } } else { opts = qemu_opts_create(list, id, 1, &local_err); } if (opts == NULL) { if (error_is_set(&local_err)) { qerror_report_err(local_err); error_free(local_err); } return NULL; } if (opts_do_parse(opts, params, firstname, defaults) != 0) { qemu_opts_del(opts); return NULL; } return opts; }
逗号为分隔符,如果key1没有指定,那么就是QemuOptsList中的implied_opt_name。flag类的参数没有value,解析后添加on或者off,如果以no开头,会把name开头的no去掉,value设置为off。
static int opts_do_parse(QemuOpts *opts, const char *params, const char *firstname, bool prepend) { char option[128], value[1024]; const char *p,*pe,*pc; Error *local_err = NULL; for (p = params; *p != '\0'; p++) { pe = strchr(p, '='); pc = strchr(p, ','); if (!pe || (pc && pc < pe)) { /* found "foo,more" */ if (p == params && firstname) { /* implicitly named first option */ pstrcpy(option, sizeof(option), firstname); p = get_opt_value(value, sizeof(value), p); } else { /* option without value, probably a flag */ p = get_opt_name(option, sizeof(option), p, ','); if (strncmp(option, "no", 2) == 0) { memmove(option, option+2, strlen(option+2)+1); pstrcpy(value, sizeof(value), "off"); } else { pstrcpy(value, sizeof(value), "on"); } } } else { /* found "foo=bar,more" */ p = get_opt_name(option, sizeof(option), p, '='); if (*p != '=') { break; } p++; p = get_opt_value(value, sizeof(value), p); } if (strcmp(option, "id") != 0) { /* store and parse */ opt_set(opts, option, value, prepend, &local_err); if (error_is_set(&local_err)) { qerror_report_err(local_err); error_free(local_err); return -1; } } if (*p != ',') { break; } } return 0; }