系列文章目录
第十五章 QEMU系统仿真的导出虚拟机分析
文章目录
前言
本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的启动过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。
本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。
一、QEMU是什么?
QEMU 是一个通用且开源的机器模拟器和虚拟机。
其官方主页是:https://www.qemu.org/
二、QEMU系统仿真的启动分析
1.系统仿真的初始化代码
QEMU 作为系统仿真工具,其入口代码在 system/main.c 文件中,初始化函数 qemu_init() 的实现在 system/vl.c 文件中,在完成 QEMU 虚拟机的内存设备及NUMA初始化后,系统将处理导出信息的设置,本篇文章将完成以下代码部分的分析。
2.主循环数据初始化
这部分代码在 system/vl.c 文件中,实现如下:
void qemu_init(int argc, char **argv)
{
...
if (vmstate_dump_file) {
/* dump and exit */
module_load_qom_all();
dump_vmstate_json_to_file(vmstate_dump_file);
exit(0);
}
...
}
3. module_load_qom_all()
此函数在 /util/module.c 文件中,定义如下:
void module_load_qom_all(void)
{
const QemuModinfo *modinfo;
Error *local_err = NULL;
if (module_loaded_qom_all) {
return;
}
for (modinfo = module_info; modinfo->name != NULL; modinfo++) {
if (!modinfo->objs) {
continue;
}
if (!module_check_arch(modinfo)) {
continue;
}
if (module_load("", modinfo->name, &local_err) < 0) {
error_report_err(local_err);
}
}
module_loaded_qom_all = true;
}
module_check_arch()
函数 module_check_arch() 定义如下:
static bool module_check_arch(const QemuModinfo *modinfo)
{
if (modinfo->arch) {
if (!module_arch) {
/* no arch set -> ignore all */
return false;
}
if (strcmp(module_arch, modinfo->arch) != 0) {
/* mismatch */
return false;
}
}
return true;
}
module_load()
函数 module_load(),定义如下:
int module_load(const char *prefix, const char *name, Error **errp)
{
int rv = -1;
#ifdef CONFIG_MODULE_UPGRADES
char *version_dir;
#endif
const char *search_dir;
char *dirs[5];
char *module_name;
int i = 0, n_dirs = 0;
bool export_symbols = false;
static GHashTable *loaded_modules;
const QemuModinfo *modinfo;
const char **sl;
if (!g_module_supported()) {
error_setg(errp, "%s", "this platform does not support GLib modules");
return -1;
}
if (!loaded_modules) {
loaded_modules = g_hash_table_new(g_str_hash, g_str_equal);
}
/* allocate all resources managed by the out: label here */
module_name = g_strdup_printf("%s%s", prefix, name);
if (g_hash_table_contains(loaded_modules, module_name)) {
g_free(module_name);
return 2; /* module already loaded */
}
g_hash_table_add(loaded_modules, module_name);
search_dir = getenv("QEMU_MODULE_DIR");
if (search_dir != NULL) {
dirs[n_dirs++] = g_strdup_printf("%s", search_dir);
}
dirs[n_dirs++] = get_relocated_path(CONFIG_QEMU_MODDIR);
#ifdef CONFIG_MODULE_UPGRADES
version_dir = g_strcanon(g_strdup(QEMU_PKGVERSION),
G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "+-.~",
'_');
dirs[n_dirs++] = g_strdup_printf("/var/run/qemu/%s", version_dir);
#endif
assert(n_dirs <= ARRAY_SIZE(dirs));
/* end of resources managed by the out: label */
for (modinfo = module_info; modinfo->name != NULL; modinfo++) {
if (modinfo->arch) {
if (strcmp(modinfo->name, module_name) == 0) {
if (!module_check_arch(modinfo)) {
error_setg(errp, "module arch does not match: "
"expected '%s', got '%s'", module_arch, modinfo->arch);
goto out;
}
}
}
if (modinfo->deps) {
if (strcmp(modinfo->name, module_name) == 0) {
/* we depend on other module(s) */
for (sl = modinfo->deps; *sl != NULL; sl++) {
int subrv = module_load("", *sl, errp);
if (subrv <= 0) {
rv = subrv;
goto out;
}
}
} else {
for (sl = modinfo->deps; *sl != NULL; sl++) {
if (strcmp(module_name, *sl) == 0) {
/* another module depends on us */
export_symbols = true;
}
}
}
}
}
for (i = 0; i < n_dirs; i++) {
char *fname = g_strdup_printf("%s/%s%s",
dirs[i], module_name, CONFIG_HOST_DSOSUF);
int ret = access(fname, F_OK);
if (ret != 0 && (errno == ENOENT || errno == ENOTDIR)) {
/*
* if we don't find the module in this dir, try the next one.
* If we don't find it in any dir, that can be fine too: user
* did not install the module. We will return 0 in this case
* with no error set.
*/
g_free(fname);
continue;
} else if (ret != 0) {
/* most common is EACCES here */
error_setg_errno(errp, errno, "error trying to access %s", fname);
} else if (module_load_dso(fname, export_symbols, errp)) {
rv = 1; /* module successfully loaded */
}
g_free(fname);
goto out;
}
rv = 0; /* module not found */
out:
if (rv <= 0) {
g_hash_table_remove(loaded_modules, module_name);
g_free(module_name);
}
for (i = 0; i < n_dirs; i++) {
g_free(dirs[i]);
}
return rv;
}
函数 module_load(),定义如下:
/* Load a module from a DSO */
static CONF_MODULE *module_load_dso(const CONF *cnf,
const char *name, const char *value)
{
DSO *dso = NULL;
conf_init_func *ifunc;
conf_finish_func *ffunc;
const char *path = NULL;
int errcode = 0;
CONF_MODULE *md;
/* Look for alternative path in module section */
path = _CONF_get_string(cnf, value, "path");
if (path == NULL) {
path = name;
}
dso = DSO_load(NULL, path, NULL, 0);
if (dso == NULL) {
errcode = CONF_R_ERROR_LOADING_DSO;
goto err;
}
ifunc = (conf_init_func *)DSO_bind_func(dso, DSO_mod_init_name);
if (ifunc == NULL) {
errcode = CONF_R_MISSING_INIT_FUNCTION;
goto err;
}
ffunc = (conf_finish_func *)DSO_bind_func(dso, DSO_mod_finish_name);
/* All OK, add module */
md = module_add(dso, name, ifunc, ffunc);
if (md == NULL)
goto err;
return md;
err:
DSO_free(dso);
ERR_raise_data(ERR_LIB_CONF, errcode, "module=%s, path=%s", name, path);
return NULL;
}
4.dump_vmstate_json_to_file(vmstate_dump_file)
函数 dump_vmstate_json_to_file() 定义如下:
void dump_vmstate_json_to_file(FILE *out_file)
{
GSList *list, *elt;
bool first;
fprintf(out_file, "{\n");
dump_machine_type(out_file);
first = true;
list = object_class_get_list(TYPE_DEVICE, true);
for (elt = list; elt; elt = elt->next) {
DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data,
TYPE_DEVICE);
const char *name;
int indent = 2;
if (!dc->vmsd) {
continue;
}
if (!first) {
fprintf(out_file, ",\n");
}
name = object_class_get_name(OBJECT_CLASS(dc));
fprintf(out_file, "%*s\"%s\": {\n", indent, "", name);
indent += 2;
fprintf(out_file, "%*s\"Name\": \"%s\",\n", indent, "", name);
fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
dc->vmsd->version_id);
fprintf(out_file, "%*s\"minimum_version_id\": %d,\n", indent, "",
dc->vmsd->minimum_version_id);
dump_vmstate_vmsd(out_file, dc->vmsd, indent, false);
fprintf(out_file, "\n%*s}", indent - 2, "");
first = false;
}
fprintf(out_file, "\n}\n");
fclose(out_file);
g_slist_free(list);
}
函数 dump_vmstate_vmsd() 定义如下:
static void dump_vmstate_vmsd(FILE *out_file,
const VMStateDescription *vmsd, int indent,
bool is_subsection)
{
if (is_subsection) {
fprintf(out_file, "%*s{\n", indent, "");
} else {
fprintf(out_file, "%*s\"%s\": {\n", indent, "", "Description");
}
indent += 2;
fprintf(out_file, "%*s\"name\": \"%s\",\n", indent, "", vmsd->name);
fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
vmsd->version_id);
fprintf(out_file, "%*s\"minimum_version_id\": %d", indent, "",
vmsd->minimum_version_id);
if (vmsd->fields != NULL) {
const VMStateField *field = vmsd->fields;
bool first;
fprintf(out_file, ",\n%*s\"Fields\": [\n", indent, "");
first = true;
while (field->name != NULL) {
if (field->flags & VMS_MUST_EXIST) {
/* Ignore VMSTATE_VALIDATE bits; these don't get migrated */
field++;
continue;
}
if (!first) {
fprintf(out_file, ",\n");
}
dump_vmstate_vmsf(out_file, field, indent + 2);
field++;
first = false;
}
assert(field->flags == VMS_END);
fprintf(out_file, "\n%*s]", indent, "");
}
if (vmsd->subsections != NULL) {
const VMStateDescription **subsection = vmsd->subsections;
bool first;
fprintf(out_file, ",\n%*s\"Subsections\": [\n", indent, "");
first = true;
while (*subsection != NULL) {
if (!first) {
fprintf(out_file, ",\n");
}
dump_vmstate_vmss(out_file, subsection, indent + 2);
subsection++;
first = false;
}
fprintf(out_file, "\n%*s]", indent, "");
}
fprintf(out_file, "\n%*s}", indent - 2, "");
}
函数 dump_vmstate_vmsf() 定义如下:
static void dump_vmstate_vmsf(FILE *out_file, const VMStateField *field,
int indent)
{
fprintf(out_file, "%*s{\n", indent, "");
indent += 2;
fprintf(out_file, "%*s\"field\": \"%s\",\n", indent, "", field->name);
fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
field->version_id);
fprintf(out_file, "%*s\"field_exists\": %s,\n", indent, "",
field->field_exists ? "true" : "false");
if (field->flags & VMS_ARRAY) {
fprintf(out_file, "%*s\"num\": %d,\n", indent, "", field->num);
}
fprintf(out_file, "%*s\"size\": %zu", indent, "", field->size);
if (field->vmsd != NULL) {
fprintf(out_file, ",\n");
dump_vmstate_vmsd(out_file, field->vmsd, indent, false);
}
fprintf(out_file, "\n%*s}", indent - 2, "");
}
函数 dump_vmstate_vmss() 定义如下:
static void dump_vmstate_vmss(FILE *out_file,
const VMStateDescription **subsection,
int indent)
{
if (*subsection != NULL) {
dump_vmstate_vmsd(out_file, *subsection, indent, true);
}
}
总结
以上分析了 QEMU 系统仿真在启动过程中,QEMU 系统仿真导出虚拟机状态参数到文件中的代码实现。