第六章 QEMU系统仿真的启动参数校验和后台运行
文章目录
- 系列文章目录
- 第六章 QEMU系统仿真的启动参数校验和后台运行
- 前言
- 一、QEMU是什么?
- 二、QEMU系统仿真的启动分析
- 1.系统仿真的初始化代码
- 2.配置项合法性校验
- qemu_validate_options()
- qemu_process_sugar_options()
- qemu_process_early_options()
- qemu_process_help_options()
- qemu_maybe_daemonize()
- 总结
前言
本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的启动过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。
本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。
一、QEMU是什么?
QEMU 是一个通用且开源的机器模拟器和虚拟机。
其官方主页是:https://www.qemu.org/
二、QEMU系统仿真的启动分析
1.系统仿真的初始化代码
QEMU 作为系统仿真工具,其入口代码在 system/main.c 文件中,初始化函数 qemu_init() 的实现在 system/vl.c 文件中,在完成命令行启动项配置解析后,程序进入解析后的启动项校验工作,本文继续前上一篇文章的工作,完成以下代码部分的分析。
2.配置项合法性校验
这部分代码在 system/vl.c 文件中,实现如下:
void qemu_init(int argc, char **argv)
{
...
/*
* Clear error location left behind by the loop.
* Best done right after the loop. Do not insert code here!
*/
loc_set_none();
qemu_validate_options(machine_opts_dict);
qemu_process_sugar_options();
/*
* These options affect everything else and should be processed
* before daemonizing.
*/
qemu_process_early_options();
qemu_process_help_options();
qemu_maybe_daemonize(pid_file);
...
}
代码首先通过函数 loc_set_none() 设置错误报告位置为 none,然后调用函数 qemu_validate_options() 对机器配置参数字典进行校验。
qemu_validate_options()
此函数在 /system/vl.c 文件中,定义如下:
static void qemu_validate_options(const QDict *machine_opts)
{
const char *kernel_filename = qdict_get_try_str(machine_opts, "kernel");
const char *initrd_filename = qdict_get_try_str(machine_opts, "initrd");
const char *kernel_cmdline = qdict_get_try_str(machine_opts, "append");
if (kernel_filename == NULL) {
if (kernel_cmdline != NULL) {
error_report("-append only allowed with -kernel option");
exit(1);
}
if (initrd_filename != NULL) {
error_report("-initrd only allowed with -kernel option");
exit(1);
}
}
if (loadvm && incoming) {
error_report("'incoming' and 'loadvm' options are mutually exclusive");
exit(EXIT_FAILURE);
}
if (loadvm && preconfig_requested) {
error_report("'preconfig' and 'loadvm' options are "
"mutually exclusive");
exit(EXIT_FAILURE);
}
if (incoming && preconfig_requested && strcmp(incoming, "defer") != 0) {
error_report("'preconfig' supports '-incoming defer' only");
exit(EXIT_FAILURE);
}
#ifdef CONFIG_CURSES
if (is_daemonized() && dpy.type == DISPLAY_TYPE_CURSES) {
error_report("curses display cannot be used with -daemonize");
exit(1);
}
#endif
}
函数校验了 Linux 系统启动参数的配置合法性。
qemu_process_sugar_options()
此函数在 /system/vl.c 文件中,定义如下:
static void qemu_process_sugar_options(void)
{
if (mem_prealloc) {
QObject *smp = qdict_get(machine_opts_dict, "smp");
if (smp && qobject_type(smp) == QTYPE_QDICT) {
QObject *cpus = qdict_get(qobject_to(QDict, smp), "cpus");
if (cpus && qobject_type(cpus) == QTYPE_QSTRING) {
const char *val = qstring_get_str(qobject_to(QString, cpus));
object_register_sugar_prop("memory-backend", "prealloc-threads",
val, false);
}
}
object_register_sugar_prop("memory-backend", "prealloc", "on", false);
}
}
函数校验了 CPU 启动参数的配置合法性。
qemu_process_early_options()
此函数在 /system/vl.c 文件中,定义如下:
static void qemu_process_early_options(void)
{
qemu_opts_foreach(qemu_find_opts("name"),
parse_name, NULL, &error_fatal);
object_option_foreach_add(object_create_pre_sandbox);
#ifdef CONFIG_SECCOMP
QemuOptsList *olist = qemu_find_opts_err("sandbox", NULL);
if (olist) {
qemu_opts_foreach(olist, parse_sandbox, NULL, &error_fatal);
}
#endif
if (qemu_opts_foreach(qemu_find_opts("action"),
process_runstate_actions, NULL, &error_fatal)) {
exit(1);
}
#ifndef _WIN32
qemu_opts_foreach(qemu_find_opts("add-fd"),
parse_add_fd, NULL, &error_fatal);
qemu_opts_foreach(qemu_find_opts("add-fd"),
cleanup_add_fd, NULL, &error_fatal);
#endif
/* Open the logfile at this point and set the log mask if necessary. */
{
int mask = 0;
if (log_mask) {
mask = qemu_str_to_log_mask(log_mask);
if (!mask) {
qemu_print_log_usage(stdout);
exit(1);
}
}
qemu_set_log_filename_flags(log_file, mask, &error_fatal);
}
qemu_add_default_firmwarepath();
}
程序首先通过函数 parse_name() 处理 "name"类启动项,代码如下:
static int parse_name(void *opaque, QemuOpts *opts, Error **errp)
{
const char *proc_name;
if (qemu_opt_get(opts, "debug-threads")) {
qemu_thread_naming(qemu_opt_get_bool(opts, "debug-threads", false));
}
qemu_name = qemu_opt_get(opts, "guest");
proc_name = qemu_opt_get(opts, "process");
if (proc_name) {
os_set_proc_name(proc_name);
}
return 0;
}
接下来调用函数 object_option_foreach_add(),完成在创建 sandbox 之前的所需要的对象创建工作。
object_option_foreach_add(object_create_pre_sandbox);
函数 object_option_foreach_add() 定义如下:
static void object_option_foreach_add(bool (*type_opt_predicate)(const char *))
{
ObjectOption *opt, *next;
QTAILQ_FOREACH_SAFE(opt, &object_opts, next, next) {
const char *type = ObjectType_str(opt->opts->qom_type);
if (type_opt_predicate(type)) {
user_creatable_add_qapi(opt->opts, &error_fatal);
qapi_free_ObjectOptions(opt->opts);
QTAILQ_REMOVE(&object_opts, opt, next);
g_free(opt);
}
}
}
而作为上述函数参数的 object_create_pre_sandbox() 定义如下:
/*
* Very early object creation, before the sandbox options have been activated.
*/
static bool object_create_pre_sandbox(const char *type)
{
/*
* Objects should in general not get initialized "too early" without
* a reason. If you add one, state the reason in a comment!
*/
/*
* Reason: -sandbox on,resourcecontrol=deny disallows setting CPU
* affinity of threads.
*/
if (g_str_equal(type, "thread-context")) {
return true;
}
return false;
}
接着,如果定义了 CONFIG_SECCOMP ,则执行以下代码,调用 parse_sandbox() 函数创建 sandbox 类对象。
#ifdef CONFIG_SECCOMP
QemuOptsList *olist = qemu_find_opts_err("sandbox", NULL);
if (olist) {
qemu_opts_foreach(olist, parse_sandbox, NULL, &error_fatal);
}
#endif
函数 parse_sandbox() 在 /system/qemu-seccomp.c 文件中,定义如下:
int parse_sandbox(void *opaque, QemuOpts *opts, Error **errp)
{
if (qemu_opt_get_bool(opts, "enable", false)) {
uint32_t seccomp_opts = QEMU_SECCOMP_SET_DEFAULT
| QEMU_SECCOMP_SET_OBSOLETE;
const char *value = NULL;
value = qemu_opt_get(opts, "obsolete");
if (value) {
if (g_str_equal(value, "allow")) {
seccomp_opts &= ~QEMU_SECCOMP_SET_OBSOLETE;
} else if (g_str_equal(value, "deny")) {
/* this is the default option, this if is here
* to provide a little bit of consistency for
* the command line */
} else {
error_setg(errp, "invalid argument for obsolete");
return -1;
}
}
value = qemu_opt_get(opts, "elevateprivileges");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED;
} else if (g_str_equal(value, "children")) {
seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED;
/* calling prctl directly because we're
* not sure if host has CAP_SYS_ADMIN set*/
if (prctl(PR_SET_NO_NEW_PRIVS, 1)) {
error_setg(errp, "failed to set no_new_privs aborting");
return -1;
}
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for elevateprivileges");
return -1;
}
}
value = qemu_opt_get(opts, "spawn");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_SPAWN;
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for spawn");
return -1;
}
}
value = qemu_opt_get(opts, "resourcecontrol");
if (value) {
if (g_str_equal(value, "deny")) {
seccomp_opts |= QEMU_SECCOMP_SET_RESOURCECTL;
} else if (g_str_equal(value, "allow")) {
/* default value */
} else {
error_setg(errp, "invalid argument for resourcecontrol");
return -1;
}
}
if (seccomp_start(seccomp_opts, errp) < 0) {
return -1;
}
}
return 0;
}
最后调用函数 qemu_add_default_firmwarepath() 添加固件查找默认路径,定义如下:
void qemu_add_default_firmwarepath(void)
{
static const char * const dirs[] = {
CONFIG_QEMU_FIRMWAREPATH
NULL
};
size_t i;
/* add configured firmware directories */
for (i = 0; dirs[i] != NULL; i++) {
qemu_add_data_dir(get_relocated_path(dirs[i]));
}
/* try to find datadir relative to the executable path */
qemu_add_data_dir(get_relocated_path(CONFIG_QEMU_DATADIR));
}
qemu_process_help_options()
接下来处理获取启动项的帮助信息的工作,此时我们已经完成命令行启动项分析,如果所带参数是显示对应启动项的帮助信息,此处就完整所需工作。函数 qemu_process_help_options() 定义如下:
static void qemu_process_help_options(void)
{
/*
* Check for -cpu help and -device help before we call select_machine(),
* which will return an error if the architecture has no default machine
* type and the user did not specify one, so that the user doesn't need
* to say '-cpu help -machine something'.
*/
if (cpu_option && is_help_option(cpu_option)) {
list_cpus();
exit(0);
}
if (qemu_opts_foreach(qemu_find_opts("device"),
device_help_func, NULL, NULL)) {
exit(0);
}
/* -L help lists the data directories and exits. */
if (list_data_dirs) {
qemu_list_data_dirs();
exit(0);
}
}
qemu_maybe_daemonize()
接下来如果系统支持后台运行,则完成后台化操作,函数 qemu_maybe_daemonize() 在 /system/vl.c 文件中,定义如下:
static void qemu_maybe_daemonize(const char *pid_file)
{
Error *err = NULL;
os_daemonize();
rcu_disable_atfork();
if (pid_file) {
char *pid_file_realpath = NULL;
if (!qemu_write_pidfile(pid_file, &err)) {
error_reportf_err(err, "cannot create PID file: ");
exit(1);
}
pid_file_realpath = g_malloc0(PATH_MAX);
if (!realpath(pid_file, pid_file_realpath)) {
if (errno != ENOENT) {
warn_report("not removing PID file on exit: cannot resolve PID "
"file path: %s: %s", pid_file, strerror(errno));
}
return;
}
qemu_unlink_pidfile_notifier = (struct UnlinkPidfileNotifier) {
.notifier = {
.notify = qemu_unlink_pidfile,
},
.pid_file_realpath = pid_file_realpath,
};
qemu_add_exit_notifier(&qemu_unlink_pidfile_notifier.notifier);
}
}
至此,如果宿主机系统支持后台运行,则 QEMU 进程完成后台化运行的动作
总结
以上分析了 QEMU 系统仿真在启动过程中,对启动参数的合法性校验以及进入后台运行的操作,完成这些操作后,QEMU 进程可以脱离命令行终端进入后台运行。