QEMU源码全解析 —— 内存虚拟化(19)

接前一篇文章: QEMU源码全解析 —— 内存虚拟化(18)

本文内容参考:

《趣谈 Linux操作系统 》 —— 刘超, 极客时间

QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社

QEMU内存管理模型

浅谈QEMU Memory Region 与 Address Space

【QEMU系统分析之实例篇(七)】-CSDN博客

QEMU内存分析(一):内存虚拟化关键结构体 - Edver - 博客园

特此致谢!

2. QEMU虚拟机内存初始化

前边用了几回重点讲解了内存平坦化。按道理来说,接下来该往下继续讲了。但是,笔者在此并不想开新的内容,因为这几回实际上大都是参考了《QEMU/KVM源码解析与应用》,其中的内容笔者还是觉得不够细,没有完全涉及到路径中遇到的每一个函数,自己也还没有完全吃透。在此,笔者准备再次重走以下流程,把整个流程中涉及到的各个函数(最起码绝大多数函数)、尤其是之前没有展开讲的函数解析一遍,把这一流程用“磨蔓儿”的方式进行讲解,以使自己以及读者加深印象,一次就把这部分内容彻底掌握。

main()

---> qemu_init()

---> qemu_create_machine()

---> cpu_exec_init_all()

---> memory_map_init()

---> address_space_init()

---> address_space_update_topology()

(1)main函数

QEMU的入口main函数在softmmu/main.c中,代码如下:

int main(int argc, char **argv)
{
    qemu_init(argc, argv);
    return qemu_main();
}

(2)qemu_init函数

qemu_init函数在softmmu/vl.c中,函数很长,一共有将近1000行(qemu-8.1.4版本),这里只截取调用qemu_create_machine函数的一段:

static QDict *machine_opts_dict;
……

void qemu_init(int argc, char **argv)
{
    ……
    machine_opts_dict = qdict_new();
    if (userconfig) {
        qemu_read_default_config_file(&error_fatal);
    }

    /* 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_nographic:
                qdict_put_str(machine_opts_dict, "graphics", "off");
                nographic = true;
                dpy.type = DISPLAY_TYPE_NONE;
                break;
            ……
            case QEMU_OPTION_kernel:
                qdict_put_str(machine_opts_dict, "kernel", optarg);
                break;
            case QEMU_OPTION_initrd:
                qdict_put_str(machine_opts_dict, "initrd", optarg);
                break;
            case QEMU_OPTION_append:
                qdict_put_str(machine_opts_dict, "append", optarg);
                break;
            case QEMU_OPTION_dtb:
                qdict_put_str(machine_opts_dict, "dtb", optarg);
                break;
            ……
            case QEMU_OPTION_bios:
                qdict_put_str(machine_opts_dict, "firmware", optarg);
                break;
            ……
            case QEMU_OPTION_enable_kvm:
                qdict_put_str(machine_opts_dict, "accel", "kvm");
                break;
            case QEMU_OPTION_M:
            case QEMU_OPTION_machine:
                {
                    bool help;

                    keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);
                    if (help) {
                        machine_help_func(machine_opts_dict);
                        exit(EXIT_SUCCESS);
                    }
                    break;
                }
            ……
            case QEMU_OPTION_usb:
                qdict_put_str(machine_opts_dict, "usb", "on");
                break;
            case QEMU_OPTION_usbdevice:
                qdict_put_str(machine_opts_dict, "usb", "on");
                add_device_config(DEV_USB, optarg);
                break;
            ……
            case QEMU_OPTION_no_acpi:
                warn_report("-no-acpi is deprecated, use '-machine acpi=off' instead");
                qdict_put_str(machine_opts_dict, "acpi", "off");
                break;
            case QEMU_OPTION_no_hpet:
                warn_report("-no-hpet is deprecated, use '-machine hpet=off' instead");
                qdict_put_str(machine_opts_dict, "hpet", "off");
                break;
            ……
    }
    ……
    qemu_create_machine(machine_opts_dict);
    ……
}

(3)qemu_create_machine函数

qemu_create_machine函数在同文件(softmmu/vl.c)中,代码如下:

static void qemu_create_machine(QDict *qdict)
{
    MachineClass *machine_class = select_machine(qdict, &error_fatal);
    object_set_machine_compat_props(machine_class->compat_props);

    current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));
    object_property_add_child(object_get_root(), "machine",
                              OBJECT(current_machine));
    object_property_add_child(container_get(OBJECT(current_machine),
                                            "/unattached"),
                              "sysbus", OBJECT(sysbus_get_default()));

    if (machine_class->minimum_page_bits) {
        if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {
            /* This would be a board error: specifying a minimum smaller than
             * a target's compile-time fixed setting.
             */
            g_assert_not_reached();
        }
    }

    cpu_exec_init_all();
    page_size_init();

    if (machine_class->hw_version) {
        qemu_set_hw_version(machine_class->hw_version);
    }

    /*
     * Get the default machine options from the machine if it is not already
     * specified either by the configuration file or by the command line.
     */
    if (machine_class->default_machine_opts) {
        QDict *default_opts =
            keyval_parse(machine_class->default_machine_opts, NULL, NULL,
                         &error_abort);
        qemu_apply_legacy_machine_options(default_opts);
        object_set_properties_from_keyval(OBJECT(current_machine), default_opts,
                                          false, &error_abort);
        qobject_unref(default_opts);
    }
}

(4)cpu_exec_init_all函数

cpu_exec_init_all函数在softmmu/physmem.c中,代码如下:

void cpu_exec_init_all(void)
{
    qemu_mutex_init(&ram_list.mutex);
    /* The data structures we set up here depend on knowing the page size,
     * so no more changes can be made after this point.
     * In an ideal world, nothing we did before we had finished the
     * machine setup would care about the target page size, and we could
     * do this much later, rather than requiring board models to state
     * up front what their requirements are.
     */
    finalize_target_page_bits();
    io_mem_init();
    memory_map_init();
    qemu_mutex_init(&map_client_list_lock);
}

从cpu_exec_init_all函数开始,就步入正题了。可以看到,cpu_exec_init_all函数并无参数,因此它与之前的qemu_create_machine等函数实际上并无过多瓜葛,只是调用与被调用的关系。

前文书早已讲过,cpu_exec_init_all函数会进行一些初始化工作,其中io_mem_init()和memory_map_init()两个函数的调用与内存相关。其中,io_mem_init()用于对I/O内存区域进行初始化;而memory_map_init()则是对系统内存区域进行初始化。这里重点关注memory_map_init函数。

(5)memory_map_init函数

memory_map_init函数也在softmmu/physmem.c中,代码如下:

static void memory_map_init(void)
{
    system_memory = g_malloc(sizeof(*system_memory));

    memory_region_init(system_memory, NULL, "system", UINT64_MAX);
    address_space_init(&address_space_memory, system_memory, "memory");

    system_io = g_malloc(sizeof(*system_io));
    memory_region_init_io(system_io, NULL, &unassigned_io_ops, NULL, "io",
                          65536);
    address_space_init(&address_space_io, system_io, "I/O");
}

对于memory_map_init函数再一次的深入解析,请看下回。