一直在纠结这块要不要写出来,毕竟自己没有看懂,后来还是鼓起梁静茹给的勇气,看多少写多少吧。

操作方式

首先先说我们操作内存热扩容的命令操作,采用的是qemu monitor的方式,执行如下两条命令:

两条命令

# 此处一定要加share=on,不然会出现qemu扩的内存和dpdk映射的内存不是同一块
# 导致内存浪费和网络通信的问题
object_add memory-backend-file,id=mem1,size=4G,mem-path=/mnt/huge,share=on
device_add pc-dimm,id=dimm1,memdev=mem1

从文件hmp-commands.hx中我们可以看一下这两条命令针对的函数调用:

    {
        .name       = "object_add",
        .args_type  = "object:O",
        .params     = "[qom-type=]type,id=str[,prop=value][,...]",
        .help       = "create QOM object",
        .mhandler.cmd = hmp_object_add,
        .command_completion = object_add_completion,
    },

STEXI
@item object_add
@findex object_add
Create QOM object.
ETEXI

    {
        .name       = "device_add",
        .args_type  = "device:O",
        .params     = "driver[,prop=value][,...]",
        .help       = "add device, like -device on the command line",
        .mhandler.cmd = hmp_device_add,
        .command_completion = device_add_completion,
    },

STEXI
@item device_add @var{config}
@findex device_add
Add device.
ETEXI

monitor_init-->monitor_command_cb-->handle_hmp_command最后会调用到命令行执行的函数,可以看到第一条命令对应的函数hmp_object_add,第二条命令对应的函数hmp_device_add,接下来我们针对这两个函数进行分析。

内存扩容

void hmp_object_add(Monitor *mon, const QDict *qdict)
{

    /* 解析object的参数 */
    QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err);

    /* 创建一些函数指针,下个函数会用到 */
    OptsVisitor *ov = opts_visitor_new(opts);

    /* 创建实例,并且申请配置的实例大小 */
    Object *obj = user_creatable_add(qdict, opts_get_visitor(ov), &err);
}
Object *user_creatable_add(const QDict *qdict,
                           Visitor *v, Error **errp)
{
    char *type = NULL;
    char *id = NULL;
    Object *obj = NULL;
    Error *local_err = NULL, *end_err = NULL;

    /* clone一份 */
    QDict *pdict = qdict_clone_shallow(qdict);

    /* 调用opts_start_struct创建参数存储 */
    visit_start_struct(v, NULL, NULL, 0, &local_err);

    /* 删除参数,并且解析参数出来 */
    qdict_del(pdict, "qom-type");
    visit_type_str(v, "qom-type", &type, &local_err);

    /* 同上 */
    qdict_del(pdict, "id");
    visit_type_str(v, "id", &id, &local_err);

    /* 创建目标,并且根据需求做相应的操作,这里我们就是申请内存 */
    obj = user_creatable_add_type(type, id, pdict, v, &local_err);

    return obj;
}
Object *user_creatable_add_type(const char *type, const char *id,
                                const QDict *qdict,
                                Visitor *v, Error **errp)
{
    /* 根据type类型获取class */
    ObjectClass *klass = object_class_by_name(type);

    const QDictEntry *e;
    /* 创建目标 */
    Object *obj = object_new(type);

    /* 调用host_memory_backend_memory_complete */
    user_creatable_complete(obj, &local_err);

    return obj;
}
static void
host_memory_backend_memory_complete(UserCreatable *uc, Error **errp)
{
    HostMemoryBackend *backend = MEMORY_BACKEND(uc);
    HostMemoryBackendClass *bc = MEMORY_BACKEND_GET_CLASS(uc);

    if (bc->alloc) {
        /* 申请memory region,我们是通过调用file_backend_memory_alloc */
        bc->alloc(backend, &local_err);

        /* 获取memory region的可用地址 */
        void *ptr = memory_region_get_ram_ptr(&backend->mr);

        /* 获取memory region的大小 */
        uint64_t sz = memory_region_size(&backend->mr);

        /* 对该段地址进行一些特性的设置,比如可以合并,不允许dump */
        if (backend->merge) {
            qemu_madvise(ptr, sz, QEMU_MADV_MERGEABLE);
        }
        if (!backend->dump) {
            qemu_madvise(ptr, sz, QEMU_MADV_DONTDUMP);
        }
#ifdef CONFIG_NUMA
        /* 没细看,我觉得应该是计算有几个node,我们一般是两个 */
        unsigned long lastbit = find_last_bit(backend->host_nodes, MAX_NODES);
        unsigned long maxnode = (lastbit + 1) % (MAX_NODES + 1);
        unsigned flags = MPOL_MF_STRICT | MPOL_MF_MOVE;

        /* 设置内存策略,内核尝试移动所有内存范围内的页面,如果无法移动,则失败报错EIO */
        mbind(ptr, sz, backend->policy,
              maxnode ? backend->host_nodes : NULL, maxnode + 1, flags);
#endif
        /* Preallocate memory after the NUMA policy has been instantiated.
         * This is necessary to guarantee memory is allocated with
         * specified NUMA policy in place.
         */
        if (backend->prealloc) {
            /* 安装信号处理函数,并且清空内存内容 */
            os_mem_prealloc(memory_region_get_fd(&backend->mr), ptr, sz);
        }
    }
}

内存设备插入

hmp_device_add-->qmp_device_add

void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
{
    /* 解析device的参数 */
    QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &local_err);

    DeviceState *dev = qdev_device_add(opts, &local_err);
}
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
{
    BusState *bus = NULL;

    /* 查找驱动,并根据驱动找到设备的class */
    const char *driver = qemu_opt_get(opts, "driver");
    DeviceClass *dc = qdev_get_device_class(&driver, errp);

    /* find bus */
    const char *path = qemu_opt_get(opts, "bus");
    if (path != NULL) {
        bus = qbus_find(path, errp);
    } else if (dc->bus_type != NULL) {
        bus = qbus_find_recursive(sysbus_get_default(), NULL, dc->bus_type);
    }

    /* create device */
    DeviceState *dev = DEVICE(object_new(driver));

    if (bus) {
        qdev_set_parent_bus(dev, bus);
    }

    const char *id = qemu_opts_id(opts);
    if (id) {
        dev->id = id;
    }

    if (dev->id) {
        object_property_add_child(qdev_get_peripheral(), dev->id,
                                  OBJECT(dev), NULL);
    } else {
        static int anon_count;
        gchar *name = g_strdup_printf("device[%d]", anon_count++);
        object_property_add_child(qdev_get_peripheral_anon(), name,
                                  OBJECT(dev), NULL);
        g_free(name);
    }

    /* set properties */
    qemu_opt_foreach(opts, set_property, dev, &err);

    dev->opts = opts;
    object_property_set_bool(OBJECT(dev), true, "realized", &err);
    return dev;
}

看不下去了,直接写上路径的了。

hmp_device_add-->qmp_device_add-->qdev_device_add-->object_property_set_bool-->object_property_set_qobject-->object_property_set-->property_set_bool-->device_set_realized-->hotplug_handler_plug-->pc_machine_device_plug_cb-->pc_dimm_plug-->pc_dimm_memory_plug-->memory_region_add_subregion-->memory_region_add_subregion_common-->memory_region_update_container_subregions-->memory_region_transaction_commit-->vhost_commit-->vhost_user_set_mem_table

此处函数大概的意思是对DPDK进行一些配置,主要是传递过去作为共享内存的文件描述符fd,并且进行之前qemu扩的内存的mmap,这里需要注意的有两点:

  • 文件描述符通过unix socket domain传递的时候可能是在DPDK的进程中重新做了一个fd值映射相同的文件,所以qemu端发送的fd的值和DPDK端收到的值不一样,但是映射的是同一个文件。
  • 因为每次扩容,都会将每个region的fd都传一次,所以老的region(非扩容部分)也会传来一个新的fd,这块相当于对映射文件做了多次打开,所以需要注意fd的关闭,保证文件的所有fd都关闭,防止内存泄露,做法就是每次新传来老region的fd,要么关闭新的,要么关闭旧的,保证每个region只打开一个fd。
最后修改:2021 年 08 月 18 日
如果觉得我的文章对你有用,请随意赞赏