QEMU 3.1.0 源码学习,更新中…
To be updated…
目录
1. QEMU 迁移
摘自
qemu-3.1.0/docs/devel/migration.rst
1.1 Migration
QEMU 中关于保存/恢复正在运行客户机的状态的代码,有两个相对应的操作:
Saving the state
Restoring a guest
因此,QEMU 需要在目的宿主机上以相同的参数启动客户机,并且客户机所拥有的设备需要与迁移前保存时所拥有的设备保持一致。
当我们可以保存/恢复客户机之后,还需要另一项功能,即迁移Migration
:
迁移意味着源宿主机上运行的 QEMU 可以被迁移至目标宿主机继续运行
而 KVM 虚拟机的迁移又可分为以下两种:
- 静态迁移
static migration
,又称冷迁移cold migration
- 动态迁移
live migration
,又称热迁移hot migration
其中动态迁移值得更多关注,因为运行中的客户机有很多的状态(例如RAM
),而动态迁移可以保证客户机在保持运行的情况下,将这些状态一并迁移至目标宿主机。
当然,客户机并不是真的一直处于运行态。当客户机所有的相关数据都已迁移至目标宿主机后,源宿主机上的客户机就会停止运行。而在目标宿主机上的客户机重新运行之前,还会有一段停机时间service down-time
,通常情况下为几百毫秒以内。
1.2 Transports
迁移的数据流一般都是字节流byte stream
,可以通过常见的协议进行传递:
tcp migration
:使用 TCP 套接字TCP Sockets
完成迁移unix migration
:使用 UNIX 套接字UNIX Sockets
完成迁移exec migration
:使用进程的标准输入/输出stdin/stdout
完成迁移fd migration
:使用传递给 QEMU 的文件描述符fd
完成迁移,并且 QEMU 不需要关心这个fd
是如何打开的
In addition, support is included for migration using
RDMA
, which transports the page data usingRDMA
, where the hardware takes care of transporting the pages, and the load on the CPU is much lower. While the internals ofRDMA
migration are a bit different, this isn’t really visible outside the RAM migration code.
所有的迁移协议使用相同的infrastructure
来保存/恢复虚拟机的设备。
1.3 Common infrastructure
持有迁移数据流的文件、套接字sockets
、文件描述符fd
都抽象在migration/qemu-file.h
中的QEMUFile
结构体中。
结构体QEMUFile
在qemu-file.c
中的定义如下:
#define IOV_MAX 1024 /* 定义在 include/qemu/osdep.h 中 */
...
#define IO_BUF_SIZE 32768
#define MAX_IOV_SIZE MIN(IOV_MAX, 64)
struct QEMUFile {
const QEMUFileOps *ops;
const QEMUFileHooks *hooks;
void *opaque;
int64_t bytes_xfer;
int64_t xfer_limit;
int64_t pos; /* start of buffer when writing, end of buffer
when reading */
int buf_index;
int buf_size; /* 0 when writing */
uint8_t buf[IO_BUF_SIZE];
DECLARE_BITMAP(may_free, MAX_IOV_SIZE);
struct iovec iov[MAX_IOV_SIZE];
unsigned int iovcnt;
int last_error;
};
结构体QIOChannel
在include/io/channel.h
中的定义如下:
/**
* QIOChannel:
*
* The QIOChannel defines the core API for a generic I/O channel
* class hierarchy. It is inspired by GIOChannel, but has the
* following differences
*
* - Use QOM to properly support arbitrary subclassing
* - Support use of iovecs for efficient I/O with multiple blocks
* - None of the character set translation, binary data exclusively
* - Direct support for QEMU Error object reporting
* - File descriptor passing
*
* This base class is abstract so cannot be instantiated. There
* will be subclasses for dealing with sockets, files, and higher
* level protocols such as TLS, WebSocket, etc.
*/
struct QIOChannel {
Object parent;
unsigned int features; /* bitmask of QIOChannelFeatures */
char *name;
AioContext *ctx;
Coroutine *read_coroutine;
Coroutine *write_coroutine;
#ifdef _WIN32
HANDLE event; /* For use with GSource on Win32 */
#endif
};
大多数情况下,
QEMUFile
都和QIOChannel
的subtype
相互关联,例如QIOChannelTLS
、QIOChannelFile
、QIOChannelSocket
1.4 Saving the state of one device
对于大多数的设备,只需要调用一次common infrastructure
即可,这些被称为non-iterative devices
。这些设备的数据在precopy migration
预拷贝迁移阶段的最后被传送,此时虚拟机的 CPU 处于暂停状态。
而对于iterative devices
,包含的数据量很大,例如内存RAM
或large tables
。
General advice for device developers
略
VMState
大部分的设备数据可以使用include/migration/vmstate.h
中的VMSTATE
宏定义来描述。
结构体VMStateDescription
在include/migration/vmstate.h
中定义如下:
struct VMStateDescription {
const char *name;
int unmigratable;
int version_id;
int minimum_version_id;
int minimum_version_id_old;
MigrationPriority priority;
LoadStateHandler *load_state_old;
int (*pre_load)(void *opaque);
int (*post_load)(void *opaque, int version_id);
int (*pre_save)(void *opaque);
bool (*needed)(void *opaque);
const VMStateField *fields;
const VMStateDescription **subsections;
};
而在hw/input/pckbd.c
中,vmstate_kdb
定义如下:
static const VMStateDescription vmstate_kbd = {
.name = "pckbd",
.version_id = 3,
.minimum_version_id = 3,
.post_load = kbd_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT8(write_cmd, KBDState),
VMSTATE_UINT8(status, KBDState),
VMSTATE_UINT8(mode, KBDState),
VMSTATE_UINT8(pending, KBDState),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription*[]) {
&vmstate_kbd_outport,
NULL
}
};
Legacy way
与VMState
相对应的是 QEMU 早期的实现方式:每个被迁移的设备需要注册两个函数,一个用来保存状态,另一个用来恢复状态。
函数register_savevm_live
在migration/savevm.c
中的定义如下:
/* TODO: Individual devices generally have very little idea about the rest
of the system, so instance_id should be removed/replaced.
Meanwhile pass -1 as instance_id if you do not already have a clearly
distinguishing id for all instances of your device class. */
int register_savevm_live(DeviceState *dev,
const char *idstr,
int instance_id,
int version_id,
SaveVMHandlers *ops,
void *opaque)
{
SaveStateEntry *se;
se = g_new0(SaveStateEntry, 1);
se->version_id = version_id;
se->section_id = savevm_state.global_section_id++;
se->ops = ops;
se->opaque = opaque;
se->vmsd = NULL;
/* if this is a live_savem then set is_ram */
if (ops->save_setup != NULL) {
se->is_ram = 1;
}
if (dev) {
char *id = qdev_get_dev_path(dev);
if (id) {
if (snprintf(se->idstr, sizeof(se->idstr), "%s/", id) >=
sizeof(se->idstr)) {
error_report("Path too long for VMState (%s)", id);
g_free(id);
g_free(se);
return -1;
}
g_free(id);
se->compat = g_new0(CompatEntry, 1);
pstrcpy(se->compat->idstr, sizeof(se->compat->idstr), idstr);
se->compat->instance_id = instance_id == -1 ?
calculate_compat_instance_id(idstr) : instance_id;
instance_id = -1;
}
}
pstrcat(se->idstr, sizeof(se->idstr), idstr);
if (instance_id == -1) {
se->instance_id = calculate_new_instance_id(se->idstr);
} else {
se->instance_id = instance_id;
}
assert(!se->compat || se->instance_id == 0);
savevm_state_handler_insert(se);
return 0;
}
而ops
是一个指向SaveVMHanlers
的指针对象,结构体SaveVMHandlers
在include/migration/register.h
中的定义如下:
typedef struct SaveVMHandlers {
/* This runs inside the iothread lock. */
SaveStateHandler *save_state;
void (*save_cleanup)(void *opaque);
int (*save_live_complete_postcopy)(QEMUFile *f, void *opaque);
int (*save_live_complete_precopy)(QEMUFile *f, void *opaque);
/* This runs both outside and inside the iothread lock. */
bool (*is_active)(void *opaque);
bool (*has_postcopy)(void *opaque);
/* is_active_iterate
* If it is not NULL then qemu_savevm_state_iterate will skip iteration if
* it returns false. For example, it is needed for only-postcopy-states,
* which needs to be handled by qemu_savevm_state_setup and
* qemu_savevm_state_pending, but do not need iterations until not in
* postcopy stage.
*/
bool (*is_active_iterate)(void *opaque);
/* This runs outside the iothread lock in the migration case, and
* within the lock in the savevm case. The callback had better only
* use data that is local to the migration thread or protected
* by other locks.
*/
int (*save_live_iterate)(QEMUFile *f, void *opaque);
/* This runs outside the iothread lock! */
int (*save_setup)(QEMUFile *f, void *opaque);
void (*save_live_pending)(QEMUFile *f, void *opaque,
uint64_t threshold_size,
uint64_t *res_precopy_only,
uint64_t *res_compatible,
uint64_t *res_postcopy_only);
/* Note for save_live_pending:
* - res_precopy_only is for data which must be migrated in precopy phase
* or in stopped state, in other words - before target vm start
* - res_compatible is for data which may be migrated in any phase
* - res_postcopy_only is for data which must be migrated in postcopy phase
* or in stopped state, in other words - after source vm stop
*
* Sum of res_postcopy_only, res_compatible and res_postcopy_only is the
* whole amount of pending data.
*/
LoadStateHandler *load_state;
int (*load_setup)(QEMUFile *f, void *opaque);
int (*load_cleanup)(void *opaque);
/* Called when postcopy migration wants to resume from failure */
int (*resume_prepare)(MigrationState *s, void *opaque);
} SaveVMHandlers;
可以看到有以下两个指针对象:
typedef struct SaveVMHandlers {
/* This runs inside the iothread lock. */
SaveStateHandler *save_state;
...
LoadStateHandler *load_state;
...
} SaveVMHandlers;
而在include/qemu/typedefs.h
中:
typedef void SaveStateHandler(QEMUFile *f, void *opaque);
typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id);
值得注意的是:
load_state
需要接收version_id
作为参数,以便确认正在接收的状态数据的格式。而save_state
不需要version_id
参数,因为它总是会保存最新的状态
QEMU 之后应该会逐渐用VMState
的方式来替代现有的VMState macros
:
Note that because the VMState macros still save the data in a raw format, in many cases it’s possible to replace legacy code with a carefully constructed VMState description that matches the byte layout of the existing code.
未完待续…
2. QEMU Detailed Study
先看一张图:
QEMU 作为设备模拟器,可以模拟多种处理器架构。其中,待模拟的架构称为Target
,而 QEMU 运行的系统环境称为Host
。
QEMU 中有一个模块叫做Tiny Code Generator
,简称TCG
,负责将Target Code
动态的翻译为Host Code
,也即TCG Target
。
因此我们也可以将在模拟处理器上运行的代码 (OS + UserTools) 称为Guest Code
。QEMU 的作用就是将Guest Code
提取出来,并将其转换为Host Specific Code
。
2.1 源码基本结构
启动过程
QEMU 的启动过程涉及以下几个重要的源文件:
vl.c
cpus.c
exec.c
cpu-exec.c
在vl.c
中定义了启动入口main
函数,负责根据传入的参数例如RAM
、CPU
、devices
来建立虚拟机的运行环境。CPU 的执行也是从此处开始的。
硬件设备的模拟
所有与虚拟硬件设备相关的代码都在hw/
目录下。
Guest (Target) 定义
在target/
目录下:
> pwd
/kvm/qemu-src/qemu-3.1.0/target
> ll
total 28K
drwxr-xr-x 2 ibm ibm 269 Dec 12 2018 alpha
drwxr-xr-x 2 ibm ibm 4.0K Dec 12 2018 arm
drwxr-xr-x 2 ibm ibm 296 Dec 12 2018 cris
drwxr-xr-x 2 ibm ibm 214 Dec 12 2018 hppa
drwxr-xr-x 3 ibm ibm 4.0K Dec 12 2018 i386
drwxr-xr-x 2 ibm ibm 219 Dec 12 2018 lm32
drwxr-xr-x 2 ibm ibm 299 Dec 12 2018 m68k
drwxr-xr-x 2 ibm ibm 210 Dec 12 2018 microblaze
drwxr-xr-x 2 ibm ibm 4.0K Dec 12 2018 mips
drwxr-xr-x 2 ibm ibm 164 Dec 12 2018 moxie
drwxr-xr-x 2 ibm ibm 166 Dec 12 2018 nios2
drwxr-xr-x 2 ibm ibm 319 Dec 12 2018 openrisc
drwxr-xr-x 3 ibm ibm 4.0K Dec 12 2018 ppc
drwxr-xr-x 2 ibm ibm 243 Dec 12 2018 riscv
drwxr-xr-x 2 ibm ibm 4.0K Dec 12 2018 s390x
drwxr-xr-x 2 ibm ibm 192 Dec 12 2018 sh4
drwxr-xr-x 2 ibm ibm 4.0K Dec 12 2018 sparc
drwxr-xr-x 2 ibm ibm 168 Dec 12 2018 tilegx
drwxr-xr-x 2 ibm ibm 223 Dec 12 2018 tricore
drwxr-xr-x 2 ibm ibm 179 Dec 12 2018 unicore32
drwxr-xr-x 8 ibm ibm 4.0K Dec 12 2018 xtensa
Host (TCG) 定义
在tcg
目录下:
> pwd
/kvm/qemu-src/qemu-3.1.0/tcg
> ll
total 576K
drwxr-xr-x 2 ibm ibm 74 Dec 12 2018 aarch64
drwxr-xr-x 2 ibm ibm 50 Dec 12 2018 arm
drwxr-xr-x 2 ibm ibm 74 Dec 12 2018 i386
-rw-r--r-- 1 ibm ibm 146 Dec 12 2018 LICENSE
drwxr-xr-x 2 ibm ibm 50 Dec 12 2018 mips
-rw-r--r-- 1 ibm ibm 48K Dec 12 2018 optimize.c
drwxr-xr-x 2 ibm ibm 50 Dec 12 2018 ppc
-rw-r--r-- 1 ibm ibm 22K Dec 12 2018 README
drwxr-xr-x 2 ibm ibm 50 Dec 12 2018 s390
drwxr-xr-x 2 ibm ibm 50 Dec 12 2018 sparc
-rw-r--r-- 1 ibm ibm 122K Dec 12 2018 tcg.c
-rw-r--r-- 1 ibm ibm 1.6K Dec 12 2018 tcg-common.c
-rw-r--r-- 1 ibm ibm 1.8K Dec 12 2018 tcg-gvec-desc.h
-rw-r--r-- 1 ibm ibm 47K Dec 12 2018 tcg.h
-rw-r--r-- 1 ibm ibm 3.0K Dec 12 2018 tcg-ldst.inc.c
-rw-r--r-- 1 ibm ibm 2.0K Dec 12 2018 tcg-mo.h
-rw-r--r-- 1 ibm ibm 94K Dec 12 2018 tcg-op.c
-rw-r--r-- 1 ibm ibm 11K Dec 12 2018 tcg-opc.h
-rw-r--r-- 1 ibm ibm 72K Dec 12 2018 tcg-op-gvec.c
-rw-r--r-- 1 ibm ibm 15K Dec 12 2018 tcg-op-gvec.h
-rw-r--r-- 1 ibm ibm 49K Dec 12 2018 tcg-op.h
-rw-r--r-- 1 ibm ibm 11K Dec 12 2018 tcg-op-vec.c
-rw-r--r-- 1 ibm ibm 5.2K Dec 12 2018 tcg-pool.inc.c
drwxr-xr-x 2 ibm ibm 64 Dec 12 2018 tci
-rw-r--r-- 1 ibm ibm 39K Dec 12 2018 tci.c
-rw-r--r-- 1 ibm ibm 394 Dec 12 2018 TODO
总结一下
/vl.c
:包含了main
函数,负责启动虚拟机、运行 vCPU。main_loop()
也存在于此文件中,虚拟机的转换是在这个循环中进行调用的/target/i386/translate.c
:负责提取Guest Code
并将其转换为平台无关的TCG ops
。转换过程中的单位元是一个TB
即Translation Block
,只有当一个TB
的转换执行结束后,才会轮到下一个TB
。这里是 TCG 的前端/tcg/tcg.c
:TCG 的主要实现代码,这里是 TCG 的后端/tcg/i386/tcg-target.inc.c
:将TCG ops
转换为Host Code
/accel/tcg/cpu-exec.c
:函数int cpu_exec(CPUState *cpu)
会调用函数tb_find()
在 Code Buffer 中查找下一个 TB,这里的 TB 指的是已经翻译成 Host 相关指令的 TB。如果找到了,就会调用cpu_loop_exec_tb()
来在 Host 上执行
QEMU 的作用就是,提取
Guest Code
,并将其转换为Host Code
整个转换过程由两部分组成:
第一步由前端完成,
Target Code
的代码块TB
被转换成TCG-ops
(独立于机器的中间代码)第二步由后端完成,利用 Host 架构对应的
TCG
,把由TB
生成的TCP-ops
转换成Host Code
2.2 main 流程分析
main(…)
定义在/vl.c
中,函数原型如下:
int main(int argc, char **argv, char **envp);
入口main
函数,解析传入 QEMU 的命令行参数,并以此初始化 VM,例如内存大小、磁盘大小、启动盘等。
3. QEMU 2.12.1 热迁移
部分内容参考 Living Migrating QEMU-KVM Virtual Machines | Red Hat Developer
3.1 热迁移的用法
QEMU/KVM 在早期版本中就引入了热迁移的支持。一般来说,热迁移需要迁移的src
和dst
可以同时访问虚拟机镜像。一个简单的例子,在同一台 Host 上将QEMU VM
迁移至另一台QEMU VM
。
首先在src
启动一台虚拟机vm1
:
qemu-system-x86_64 --accel kvm -m 2G -smp 2 -hda fedora30.qcow2
之后在dst
以相同的启动命令运行另一台虚拟机vm2
,指定相同的镜像文件,并添加-incoming
参数:
qemu-system-x86_64 --accel kvm -m 2G -smp 2 -hda fedora30.qcow2 -incoming tcp:0:6666
在vm1
中的QEMU monitor
中输入以下命令:
migrate tcp:localhost:6666
大概十几秒之后可以看到vm2
以vm1
暂停之前的状态继续运行,迁移成功。
3.2 热迁移的基本原理
- 现在来看迁移过程中涉及到的具体数据。首先我们需要迁移一些
Guest
的运行现状——内存区域,QEMU 将其视为the entire Guest
。不需要翻译任何关于内存区域的内容,这部分会被迁移代码当作一个黑盒Opaque
,只需将这些内容从src
发送到dst
。这个区域在上图中被标记为灰色 - 之后就是左边的区域,表示
Devices
即设备状态,这部分对Guest
来说是可见的,也即 QEMU 内部的状态(因为这些设备由 QEMU 进行模拟并提供给Guest
),因此 QEMU 使用自己的协议发送这部分数据,其中包含了所有对Guest
可见的设备状态 - 最后就是右边的部分,QEMU 本身的状态也即
Host
上的 QEMU 进程状态(例如通过-smp
指定的 CPU 核数、-m
指定的内存大小等),这部分由 Host 内核中的 KVM 模块提供,因此迁移过程不涉及这部分状态,但需要在迁移之前确保在src
和dst
上这部分状态保持一致,一般以相同的 QEMU 命令行参数启动 QEMU 即可实现
3.3 热迁移的前提条件
迁移的src
和dst
需要满足以下前提条件才可实现热迁移:
- 使用共享存储存储镜像文件,例如
NFS
- 主机时间同步(很重要),可通过
NTP
实现 - 主机的网络配置必须一致
- 主机的CPU 类型必须一致
- VM 的
machine type
(当进行跨 QEMU 版本的热迁移时很重要)、RAM
大小
3.4 热迁移的主要阶段
热迁移主要有以下三个阶段
- Stage 1:将
Guest
的所有RAM
标记为dirty
- Stage 2:持续迭代的将所有
dirty RAM page
发送至dst
,直到达到一定的终止条件 - Stage 3:停止
src
上的Guest
,继续传送剩余的dirty RAM page
以及device state
阶段一、二对应上图中的灰色区域,阶段三对应灰色区域和左边的区域
可以看到热迁移大部分的工作都是在进行RAM
传输,尤其是dirty page
的传输,所以很多对于热迁移的优化也是针对RAM
传输进行优化。
注:
dirty page
指的是在迁移过程中产生变化的memory page
,内存迁移是先把没有变化的内存传输过去,然后逐渐减小dirty page
的大小,最后有短暂的downtime
,把剩下的dirty page
一并传输过去
之后就可以在dst
上继续运行 QEMU 程序了。
注意:当从阶段二向阶段三过渡时,要做一个很重要的决策,即
Guest
会在阶段三暂停运行,所以在第三阶段要尽可能少的迁移页面,以减少停机时间
3.5 发送端源码分析
先来看在QEMU Monitor
输入migrate
命令后,经过的一些函数:
注意:除了
hmp.c
在根目录之外,其他源文件均在migration
目录下
void hmp_migrate() /* hmp.c */
-> void qmp_migrate() /* migration.c */
-> void tcp_start_outgoing_migration() /* socket.c */
-> static void socket_start_outgoing_migration() /* socket.c */
-> static void socket_outgoing_migration() /* socket.c */
-> void migration_channel_connect() /* channel.c */
-> QEMUFile *qemu_fopen_channel_output() /* qemu-file-channel.c */
-> void migrate_fd_connect() /* migration.c */
-> static void *migration_thread() /* migration.c */
在hmp-commands.hx
中可以看到migrate
命令对应的入口函数为hmp_migrate
:
ETEXI
{
.name = "migrate",
.args_type = "detach:-d,blk:-b,inc:-i,resume:-r,uri:s",
.params = "[-d] [-b] [-i] [-r] uri",
.help = "migrate to URI (using -d to not wait for completion)"
"\n\t\t\t -b for migration without shared storage with"
" full copy of disk\n\t\t\t -i for migration without "
"shared storage with incremental copy of disk "
"(base image shared between src and destination)"
"\n\t\t\t -r to resume a paused migration",
.cmd = hmp_migrate,
},
STEXI
@item migrate [-d] [-b] [-i] @var{uri}
@findex migrate
Migrate to @var{uri} (using -d to not wait for completion).
-b for migration with full copy of disk
-i for migration with incremental copy of disk (base image is shared)
ETEXI
函数hmp_migrate
在hmp.c
中定义:
void hmp_migrate(Monitor *mon, const QDict *qdict)
{
/* 省略部分代码 */
qmp_migrate(uri, !!blk, blk, !!inc, inc, false, false, &err);
if (err) {
hmp_handle_error(mon, &err);
return;
}
/* 省略部分代码 */
}
进行迁移逻辑处理的函数跳转到了qmp_migrate
,在migration.c
中定义:
void qmp_migrate(const char *uri, bool has_blk, bool blk,
bool has_inc, bool inc, bool has_detach, bool detach,
Error **errp)
{
Error *local_err = NULL;
MigrationState *s = migrate_get_current();
const char *p;
if (migration_is_setup_or_active(s->state) ||
s->state == MIGRATION_STATUS_CANCELLING ||
s->state == MIGRATION_STATUS_COLO) {
error_setg(errp, QERR_MIGRATION_ACTIVE);
return;
}
if (runstate_check(RUN_STATE_INMIGRATE)) {
error_setg(errp, "Guest is waiting for an incoming migration");
return;
}
if (migration_is_blocked(errp)) {
return;
}
/* 省略部分代码 */
migrate_init(s);
if (strstart(uri, "tcp:", &p)) {
tcp_start_outgoing_migration(s, p, &local_err);
#ifdef CONFIG_RDMA
} else if (strstart(uri, "rdma:", &p)) {
rdma_start_outgoing_migration(s, p, &local_err);
#endif
} else if (strstart(uri, "exec:", &p)) {
exec_start_outgoing_migration(s, p, &local_err);
} else if (strstart(uri, "unix:", &p)) {
unix_start_outgoing_migration(s, p, &local_err);
} else if (strstart(uri, "fd:", &p)) {
fd_start_outgoing_migration(s, p, &local_err);
} else {
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "uri",
"a valid migration protocol");
migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
MIGRATION_STATUS_FAILED);
block_cleanup_parameters(s);
return;
}
if (local_err) {
migrate_fd_error(s, local_err);
error_propagate(errp, local_err);
return;
}
}
简单说下这个函数:首先通过migrate_get_current()
获取当前的MigrationState
指针对象,之后检查当前是否已经有迁移进程存在。之后下面的语句:
if (migration_is_blocked(errp)) {
return;
}
...
/* migration.c 中定义 */
bool migration_is_blocked(Error **errp)
{
if (qemu_savevm_state_blocked(errp)) {
return true;
}
if (migration_blockers) {
error_propagate(errp, error_copy(migration_blockers->data));
return true;
}
return false;
}
这里通过qemu_savevm_state_blocked()
来判断当前虚拟机状态适不适合进行迁移。
最后直接来说上面函数调用栈最下面的migrate_fd_connect()
,通过qemu_thread_create
调用migration_thread
在src
上创建一个迁移线程:
void migrate_fd_connect(MigrationState *s, Error *error_in)
{
/* 省略之前的语句 */
qemu_thread_create(&s->thread, "live_migration", migration_thread, s,
QEMU_THREAD_JOINABLE);
s->migration_thread_running = true;
}
而migration_thread
同样在migration.c
中定义:
/*
* Master migration thread on the source VM.
* It drives the migration and pumps the data down the outgoing channel.
*/
static void *migration_thread(void *opaque)
{
/* 省略部分代码 */
/* 对应 Stage 1 */
qemu_savevm_state_setup(s->to_dst_file);
/* 省略部分代码 */
while (s->state == MIGRATION_STATUS_ACTIVE ||
s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
int64_t current_time;
if (!qemu_file_rate_limit(s->to_dst_file)) {
/* 对应 Stage 2 */
MigIterateState iter_state = migration_iteration_run(s);
if (iter_state == MIG_ITERATE_SKIP) {
continue;
} else if (iter_state == MIG_ITERATE_BREAK) {
break;
}
}
if (qemu_file_get_error(s->to_dst_file)) {
if (migration_is_setup_or_active(s->state)) {
migrate_set_state(&s->state, s->state,
MIGRATION_STATUS_FAILED);
}
trace_migration_thread_file_err();
break;
}
current_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
migration_update_counters(s, current_time);
if (qemu_file_rate_limit(s->to_dst_file)) {
/* usleep expects microseconds */
g_usleep((s->iteration_start_time + BUFFER_DELAY -
current_time) * 1000);
}
}
trace_migration_thread_after_loop();
/* 对应 Stage 3 */
migration_iteration_finish(s);
rcu_unregister_thread();
return NULL;
}
migration_thread
主要就是用来完成热迁移的三个步骤。
首先来看第一个步骤,qemu_savevm_state_setup
将标记所有的RAM
为dirty
:
void qemu_savevm_state_setup() /* savevm.c */
-> SaveVMHandlers.save_setup /* block-dirty-bitmap.c */
-> static int dirty_bitmap_save_setup() /* block-dirty-bitmap.c */
-> static int init_dirty_bitmap_migration() /* block-dirty-bitmap.c */
-> static void send_bitmap_start() /* block-dirty-bitmap.c */
-> static void qemu_put_bitmap_flags() /* block-dirty-bitmap.c */
未完待续…
4. KVM Migration 文档
迁移简介
KVM 目前支持savevm/loadvm
即快照、静态迁移、动态迁移,可通过快捷键Ctrl+Alt+2
调出qemu-monitor
,并在其中通过migrate
相关命令进行迁移操作。迁移成功完成后,VM 就可以在目标主机上继续运行。
注意:支持在
AMD
和Intel
主机之间进行迁移。通常情况下,64 位的 VM 仅可以被迁移至 64 位的 Host 运行,而 32 位的 VM 则可以迁移至 32 位或 64 位的 Host
未完待续…
参考文章
- QEMU
- QEMU Wiki
- KVM
- kvm/kvm.git
- KVM Documents
- Virt Tools | Blogging about open source virtualization
- Migration | KVM
- QEMU Detailed Study | PDF
- QEMU vl.c 源码学习 | CSDN
- QEMU 参数解析 | CSDN
- qemu-kvm 部分流程/源代码分析 | CSDN
- qemu 学习(一)———— qemu 整体流程解读 | CSDN
- qemu 学习(二)———— qemu 中对处理器大小端的设置 | CSDN
- qemu 学习(三)———— qemu 中反汇编操作解析 | CSDN
- QEMU 源码架构 | ChinaUnix
- QEMU 源码分析系列(二) | ChinaUnix
- QEMU-KVM 虚机动态迁移原理 | 51CTO
- 虚拟化在线迁移优化实践(一):KVM虚拟化跨机迁移原理 - UCloud 云计算 | 知乎
- QEMU 热迁移简介 | 不忘初心,方得始终
- Live Migrating QEMU-KVM Virtual Machines | Red Hat Developer
- 虚拟机迁移之热迁移(live_migrate) | 随便写写
- 上面文章博主关于虚拟化的文章列表 | 随便写写
- KVM 虚拟机静态和动态迁移 | bbsmax
- 虚拟机活迁移揭秘 | 博客园
- qemu-kvm-1.1.0 源码中关于迁移的代码分析 | CSDN
- QEMU 源码分析系列(一)| CSDN
- qemu-kvm 虚拟机 live 迁移源代码解读 | CSDN
- QEMU live migration 代码简单剖析 | CSDN
- qemu-kvm savevm/loadvm 流程 | CSDN
- KVM/QEMU 2.3.0 虚拟机动态迁移分析(一)| CSDN
- KVM/QEMU 2.3.0 虚拟机动态迁移分析(二)| CSDN
- KVM/QEMU 2.3.0 虚拟机动态迁移分析(三)| CSDN
- Qemu-KVM 虚拟机初始化及创建过程源码简要分析(一)| CSDN
- Qemu-KVM 虚拟机初始化及创建过程源码简要分析(二)| CSDN
- qemu-kvm 虚拟机live迁移源代码解读 | CSDN
- 北方南方的文章列表 | CSDN
- qemu 迁移代码分析 | 随便写写
- QEMU live migration 代码简单剖析 | ChinaUnix
- qemu-kvm 虚拟机 live 迁移源代码解读 | ChinaUnix
- qemu-kvm 磁盘读写的缓冲(cache)的五种模式 | jusonalien
- 关于追踪 qemu 源码函数路径的一个方法 | jusonalien
- QEMU main 流程分析 | CSDN
- QEMU 翻译块(TB)分析 | CSDN
- 我见过最全的剖析 QEMU 原理的文章 | CSDN