在libvirt 使用save命令对虚拟机进行存储后,将libvirt的信息,虚拟机的内存,cpu以及其他设备的信息都存储了起来。

先把文件格式大致的画出来。

libvirt 存储文件信息
+-------------+-----+-----+-----+-----+
|Libvirt-Magic|版本 | xml |运行 |是否 |
| | |长度 |状态 |压缩 |
|8字节 |4字节|4字节|4字节|4字节|
+-------------+-----+-----+-----+-----+
| 保留字段(60字节) |
+-------------------------------------+
| xml 详细信息(占用xml长度个字节) |
+-------------------------------------+
qemu文件开头存储信息。
+-----+-----+--------+
|QEMV |版本 |设备信息|
| | | |
|4字节|4字节| |
+-----+-----+--------+

qemu中savevm_handlers中的设备信息,可能有多个设备。

设备循环,一个设备信息如下。
+-------+-------+------+------+------+------+--------+------+
|section|section|设备名|设备名|实例id|版本id|子设备信|循环 |
|start | id | 长度 | | | | 息 |设备 |
|1字节 | 4字节 |1字节 |n字节 |4字节 |4字节 | | |
+-------+-------+------+------+------+------+--------+------+

子设备信息存储 这里以ram设备 为例
+--------------------------------------+
|共占用内存字节数 8字节 |
+------+------+------+--------+--------+
|设备名|设备名|设备内|循环前面|ram存储 |
| 长度 | |存长度|信息 |结束标记|
|1字节 |n字节 |8字节 | |8字节 |
+------+------+------+--------+--------+


开始存储设备详细信息
+-------+-------+------+------+
|section|section|设备详|循环 |
|part | id |细信息|设备 |
|1字节 |4字节 |n字节 | |
+-------+-------+------+------+

处理存储结束
+-------+-------+------+------+
|section|section|设备结|循环 |
|end | id |束信息|设备 |
|1字节 |4字节 |n字节 | |
+-------+-------+------+------+
存储设备状态
+-------+-------+------+------+
|section|section|设备状|循环 |
|full | id |态信息|设备 |
|1字节 |4字节 |n字节 | |
+-------+-------+------+------+

+-----+
|结束 |
|标记 |
|1字节|
+-----+

这里就对这一文件进行分析。

首先将存储文件的2进制信息发出来。

00000000  4c 69 62 76 69 72 74 51  65 6d 75 64 53 61 76 65  |LibvirtQemudSave|
00000010 02 00 00 00 07 12 00 00 01 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 3c 64 6f 6d |............<dom|

这是存储文件中的一部分信息,结合代码来看一下这个文件究竟是什么

#define QEMU_SAVE_MAGIC   "LibvirtQemudSave"

typedef enum {
QEMU_SAVE_FORMAT_RAW = 0,
QEMU_SAVE_FORMAT_GZIP = 1,
QEMU_SAVE_FORMAT_BZIP2 = 2,
/*
* Deprecated by xz and never used as part of a release
* QEMU_SAVE_FORMAT_LZMA
*/

QEMU_SAVE_FORMAT_XZ = 3,
QEMU_SAVE_FORMAT_LZOP = 4,
/* Note: add new members only at the end.
These values are used in the on-disk format.
Do not change or re-use numbers. */


QEMU_SAVE_FORMAT_LAST
} virQEMUSaveFormat;

struct _virQEMUSaveHeader {
char magic[sizeof(QEMU_SAVE_MAGIC)-1]; //libvirt 存储内存文件的魔数LibvirtQemudSave 16个字节
uint32_t version; //版本信息 上述例子是02
uint32_t xml_len; //存储的xml配置信息长度0x1207
uint32_t was_running;//01 表示恢复时运行,00表示恢复时处于暂停状态
uint32_t compressed;//表示压缩方式,0表示raw,其他格式根据virQEMUSaveFormat定义。
uint32_t unused[15];
};

首先,文件最开始存储了_virQEMUSaveHeader 文件的头信息。 头信息总共占用了0x5b个字节。

忽略libvirt存储配置的一部分,然后直接到达qemu开始存储文件的位置。 根据上面头文件信息,可以得知,qemu存储文件的起始位置为0x1207+0x5b=0x1262

00001250  63 6c 61 62 65 6c 3e 0a  3c 2f 64 6f 6d 61 69 6e  |clabel>.</domain|
00001260 3e 0a 00 51 45 56 4d 00 00 00 03 01 00 00 00 02 |>..QEVM.........|
00001270 03 72 61 6d 00 00 00 00 00 00 00 04 00 00 00 00 |.ram............|
00001280 80 8d 10 04 06 70 63 2e 72 61 6d 00 00 00 00 80 |.....pc.ram.....|
00001290 00 00 00 08 76 67 61 2e 76 72 61 6d 00 00 00 00 |....vga.vram....|
000012a0 00 80 00 00 07 70 63 2e 62 69 6f 73 00 00 00 00 |.....pc.bios....|
000012b0 00 04 00 00 1f 30 30 30 30 3a 30 30 3a 30 33 2e |.....0000:00:03.|
000012c0 30 2f 76 69 72 74 69 6f 2d 6e 65 74 2d 70 63 69 |0/virtio-net-pci|
000012d0 2e 72 6f 6d 00 00 00 00 00 04 00 00 06 70 63 2e |.rom.........pc.|
000012e0 72 6f 6d 00 00 00 00 00 02 00 00 14 2f 72 6f 6d |rom........./rom|
000012f0 40 65 74 63 2f 61 63 70 69 2f 74 61 62 6c 65 73 |@etc/acpi/tables|
00001300 00 00 00 00 00 02 00 00 1b 30 30 30 30 3a 30 30 |.........0000:00|
00001310 3a 30 32 2e 30 2f 63 69 72 72 75 73 5f 76 67 61 |:02.0/cirrus_vga|
00001320 2e 72 6f 6d 00 00 00 00 00 01 00 00 15 2f 72 6f |.rom........./ro|
00001330 6d 40 65 74 63 2f 74 61 62 6c 65 2d 6c 6f 61 64 |m@etc/table-load|
00001340 65 72 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |er..............|
00001350 00 10 02 00 00 00 02 00 00 00 00 00 00 00 08 06 |................|
00001360 70 63 2e 72 61 6d 53 ff 00 f0 53 ff 00 f0 c3 e2 |pc.ramS...S.....|

可以看到从0x1263开始就是qemu存储的文件了。接下来就分析qemu存储文件格式。

这是存储qemu虚拟机状态的函数,这里可以看按到大致流程

static int qemu_savevm_state(QEMUFile *f)
{
int ret;
MigrationParams params = {
.blk = 0,
.shared = 0
};

if (qemu_savevm_state_blocked(NULL)) {
return -EINVAL;
}

qemu_mutex_unlock_iothread();
qemu_savevm_state_begin(f, &params);//存储虚拟机状态主要是有数据的设备存储。
qemu_mutex_lock_iothread();

while (qemu_file_get_error(f) == 0) {
if (qemu_savevm_state_iterate(f) > 0) { //存储具体信息。
break;
}
}

ret = qemu_file_get_error(f);
if (ret == 0) {
qemu_savevm_state_complete(f);//进行有是护具的存储设备结尾,同时对有状态的设备进行存储。
ret = qemu_file_get_error(f);
}
if (ret != 0) {
qemu_savevm_state_cancel();
}
return ret;
}
#define QEMU_VM_FILE_MAGIC           0x5145564d
#define QEMU_VM_FILE_VERSION 0x00000003

#define QEMU_VM_SECTION_START 0x01

void qemu_savevm_state_begin(QEMUFile *f,
const MigrationParams *params)
{
SaveStateEntry *se;
int ret;

trace_savevm_state_begin();
QTAILQ_FOREACH(se, &savevm_handlers, entry) {
if (!se->ops || !se->ops->set_params) {
continue;
}
se->ops->set_params(params, se->opaque);
}

qemu_put_be32(f, QEMU_VM_FILE_MAGIC);//qemu文件起始QEVM 4字节
qemu_put_be32(f, QEMU_VM_FILE_VERSION);//qemu文件的版本 4字节

QTAILQ_FOREACH(se, &savevm_handlers, entry) {
int len;

if (!se->ops || !se->ops->save_live_setup) {
continue;
}
if (se->ops && se->ops->is_active) {
if (!se->ops->is_active(se->opaque)) {
continue;
}
}
/* Section type */
qemu_put_byte(f, QEMU_VM_SECTION_START);//设备起始·1字节
qemu_put_be32(f, se->section_id);//设备的section_id 4字节

/* ID string */
len = strlen(se->idstr);
qemu_put_byte(f, len);//设备字符串长度1字节
qemu_put_buffer(f, (uint8_t *)se->idstr, len);//设备字符

qemu_put_be32(f, se->instance_id);//实例id 4字节
qemu_put_be32(f, se->version_id);//版本id 4字节

ret = se->ops->save_live_setup(f, se->opaque);//进入具体的设备存储信息,这里首先是ram
if (ret < 0) {
qemu_file_set_error(f, ret);
break;
}
}
}

首先是qemu中的魔数(4字节),qemu文件版本(4字节), 然后就是需要save的设备的信息,首先是section start的标记(1字节) 然后是section_id。(4字节)设备名字长度1字节,设备名字,然后实例id(4字节)(版本id)4字节

#define RAM_SAVE_FLAG_EOS      0x10

static int ram_save_setup(QEMUFile *f, void *opaque)
{
RAMBlock *block;
int64_t ram_bitmap_pages; /* Size of bitmap in pages, including gaps */

mig_throttle_on = false;
dirty_rate_high_cnt = 0;
bitmap_sync_count = 0;
migration_bitmap_sync_init();

if (migrate_use_xbzrle()) {
XBZRLE_cache_lock();
XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
TARGET_PAGE_SIZE,
TARGET_PAGE_SIZE);
if (!XBZRLE.cache) {
XBZRLE_cache_unlock();
error_report("Error creating cache");
return -1;
}
XBZRLE_cache_unlock();

/* We prefer not to abort if there is no memory */
XBZRLE.encoded_buf = g_try_malloc0(TARGET_PAGE_SIZE);
if (!XBZRLE.encoded_buf) {
error_report("Error allocating encoded_buf");
return -1;
}

XBZRLE.current_buf = g_try_malloc(TARGET_PAGE_SIZE);
if (!XBZRLE.current_buf) {
error_report("Error allocating current_buf");
g_free(XBZRLE.encoded_buf);
XBZRLE.encoded_buf = NULL;
return -1;
}

acct_clear();
}

qemu_mutex_lock_iothread();
qemu_mutex_lock_ramlist();
bytes_transferred = 0;
reset_ram_globals();

ram_bitmap_pages = last_ram_offset() >> TARGET_PAGE_BITS;
migration_bitmap = bitmap_new(ram_bitmap_pages);
bitmap_set(migration_bitmap, 0, ram_bitmap_pages);

/*
* Count the total number of pages used by ram blocks not including any
* gaps due to alignment or unplugs.
*/

migration_dirty_pages = 0;
QTAILQ_FOREACH(block, &ram_list.blocks, next) {
uint64_t block_pages;

block_pages = block->length >> TARGET_PAGE_BITS;
migration_dirty_pages += block_pages;
}

memory_global_dirty_log_start();
migration_bitmap_sync();
qemu_mutex_unlock_iothread();

qemu_put_be64(f, ram_bytes_total() | RAM_SAVE_FLAG_MEM_SIZE);//存了8位用来表示内存站了多少个字节

QTAILQ_FOREACH(block, &ram_list.blocks, next) {
qemu_put_byte(f, strlen(block->idstr));//1个字节长度
qemu_put_buffer(f, (uint8_t *)block->idstr, strlen(block->idstr));//设备名字
qemu_put_be64(f, block->length);//设备占用长度。//8字节
}

qemu_mutex_unlock_ramlist();

ram_control_before_iterate(f, RAM_CONTROL_SETUP);
ram_control_after_iterate(f, RAM_CONTROL_SETUP);

qemu_put_be64(f, RAM_SAVE_FLAG_EOS); //存储结尾8字节

return 0;
}

0x127b开始,就是ram的save函数进行记录的信息。 首先是存储了8字节 ram总共占用了多少字节 这里是0x808d1004。这里除了guest的内存还有其他设备的寄存器也在这里保存。 接下来是pc.ram 内存,我们的虚拟机使用了2G内存,使用了0x80000000。 vga.vram 显卡的显存 8M 使用了0x800000 pc.bios bios寄存器 256k 0x40000 virtio-net-pci寄存器 256k 0x40000 acpi表 128k 0x20000 cirrus_vga显存 64k 0x010000 acpi的table-loader 4k 0x1000 存储结尾标记 RAM_SAVE_FLAG_EOS 这里到达0x1351结束,接下来就是要存储的设备进行一一存储了。

#define QEMU_VM_SECTION_PART         0x02
int qemu_savevm_state_iterate(QEMUFile *f)
{
SaveStateEntry *se;
int ret = 1;

trace_savevm_state_iterate();
QTAILQ_FOREACH(se, &savevm_handlers, entry) {
if (!se->ops || !se->ops->save_live_iterate) {
continue;
}
if (se->ops && se->ops->is_active) {
if (!se->ops->is_active(se->opaque)) {
continue;
}
}
if (qemu_file_rate_limit(f)) {
return 0;
}
trace_savevm_section_start(se->idstr, se->section_id);
/* Section type */
qemu_put_byte(f, QEMU_VM_SECTION_PART);// 开始标记1字节
qemu_put_be32(f, se->section_id);//section id

ret = se->ops->save_live_iterate(f, se->opaque);
trace_savevm_section_end(se->idstr, se->section_id);

if (ret < 0) {
qemu_file_set_error(f, ret);
}
if (ret <= 0) {
/* Do not proceed to the next vmstate before this one reported
completion of the current stage. This serializes the migration
and reduces the probability that a faster changing state is
synchronized over and over again. */

break;
}
}
return ret;
}

记录了设备的其实信息后,就会调用设备自身来进行数据存储到达0x1356

static int ram_save_iterate(QEMUFile *f, void *opaque)
{
int ret;
int i;
int64_t t0;
int total_sent = 0;

qemu_mutex_lock_ramlist();

if (ram_list.version != last_version) {
reset_ram_globals();
}

ram_control_before_iterate(f, RAM_CONTROL_ROUND);

t0 = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
i = 0;
while ((ret = qemu_file_rate_limit(f)) == 0) {
int bytes_sent;

bytes_sent = ram_find_and_save_block(f, false);//开始存储
/* no more blocks to sent */
if (bytes_sent == 0) {
break;
}
total_sent += bytes_sent;
acct_info.iterations++;
check_guest_throttling();
/* we want to check in the 1st loop, just in case it was the 1st time
and we had to sync the dirty bitmap.
qemu_get_clock_ns() is a bit expensive, so we only check each some
iterations
*/

if ((i & 63) == 0) {
uint64_t t1 = (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - t0) / 1000000;
if (t1 > MAX_WAIT) {
DPRINTF("big wait: %" PRIu64 " milliseconds, %d iterations\n",
t1, i);
break;
}
}
i++;
}

qemu_mutex_unlock_ramlist();

/*
* Must occur before EOS (or any QEMUFile operation)
* because of RDMA protocol.
*/

ram_control_after_iterate(f, RAM_CONTROL_ROUND);

bytes_transferred += total_sent;

/*
* Do not count these 8 bytes into total_sent, so that we can
* return 0 if no page had been dirtied.
*/

qemu_put_be64(f, RAM_SAVE_FLAG_EOS);//8字节的结束标记
bytes_transferred += 8;

ret = qemu_file_get_error(f);
if (ret < 0) {
return ret;
}

return total_sent;
}

在ram_find_and_save_block中 进行了实际的存储。

static int ram_find_and_save_block(QEMUFile *f, bool last_stage)
{
RAMBlock *block = last_seen_block;
ram_addr_t offset = last_offset;
bool complete_round = false;
int bytes_sent = 0;
MemoryRegion *mr;

if (!block)
block = QTAILQ_FIRST(&ram_list.blocks);

while (true) {
mr = block->mr;
offset = migration_bitmap_find_and_reset_dirty(mr, offset);//从dirty页中拿出一页
if (complete_round && block == last_seen_block &&
offset >= last_offset) {
break;
}
if (offset >= block->length) {
offset = 0;
block = QTAILQ_NEXT(block, next);
if (!block) {
block = QTAILQ_FIRST(&ram_list.blocks);
complete_round = true;
ram_bulk_stage = false;
}
} else {
bytes_sent = ram_save_page(f, block, offset, last_stage);//存储page

/* if page is unmodified, continue to the next */
if (bytes_sent > 0) {
last_sent_block = block;//标记上一次存储的设备
break;
}
}
}
last_seen_block = block;
last_offset = offset;

return bytes_sent;
}

ram_save_page 继续执行存储page

#define RAM_SAVE_FLAG_CONTINUE 0x20

static int ram_save_page(QEMUFile *f, RAMBlock* block, ram_addr_t offset,
bool last_stage)
{
int bytes_sent;
int cont;
ram_addr_t current_addr;
MemoryRegion *mr = block->mr;
uint8_t *p;
int ret;
bool send_async = true;

cont = (block == last_sent_block) ? RAM_SAVE_FLAG_CONTINUE : 0;//这里进行了比较如果新的block,这里标记cont

p = memory_region_get_ram_ptr(mr) + offset;//获取要存储页的地址


...
} else if (is_zero_range(p, TARGET_PAGE_SIZE)) {//如果页面全0就会采用压缩方式。
acct_info.dup_pages++;
bytes_sent = save_block_hdr(f, block, offset, cont,
RAM_SAVE_FLAG_COMPRESS);
qemu_put_byte(f, 0);//并存入一个字节0
bytes_sent++;

...

/* XBZRLE overflow or normal page */
if (bytes_sent == -1) {
bytes_sent = save_block_hdr(f, block, offset, cont, RAM_SAVE_FLAG_PAGE);//存储页的偏移
if (send_async) {
qemu_put_buffer_async(f, p, TARGET_PAGE_SIZE);//实际存储数据异步
} else {
qemu_put_buffer(f, p, TARGET_PAGE_SIZE);//实际存储数据同步
}
bytes_sent += TARGET_PAGE_SIZE;
acct_info.norm_pages++;
}

XBZRLE_cache_unlock();

return bytes_sent;
}

这里来看一下如何存储页面偏移。

#define RAM_SAVE_FLAG_PAGE     0x08

static size_t save_block_hdr(QEMUFile *f, RAMBlock *block, ram_addr_t offset,
int cont, int flag)
{
size_t size;

qemu_put_be64(f, offset | cont | flag); //一个8字节的标记位
size = 8;

if (!cont) {//如果是第一次存储,会存储设备的名字。
qemu_put_byte(f, strlen(block->idstr));
qemu_put_buffer(f, (uint8_t *)block->idstr,
strlen(block->idstr));
size += 1 + strlen(block->idstr);
}
return size;
}

这样就可以看到 0x1356后接下来是存储8个字节page的标记位 0x00000008 就是offset=0 cont=0 flag=RAM_SAVE_FLAG_PAGE,因为是第一次,所以存储了 设备的名字。 从0x1366开始就是存储4k的页面, 在ram_find_and_save_block中 会循环执行ram_save_page函数,直到全部存储完所有内存。 因此存储模式就是 页偏移+页内容 所以接下来直接查看0x2366的信息。

00002360  00 00 00 00 00 00 00 00  00 00 00 00 10 22 00 00  |............."..|
00002370 00 00 00 00 00 20 22 00 00 00 00 00 00 00 30 22 |..... "
.......0"|
00002380 00 00 00 00 00 00 00 40 22 00 00 00 00 00 00 00 |.......@"
.......|
00002390 50 22 00 00 00 00 00 00 00 60 28 00 00 00 00 00 |P".......`(.....|
000023a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000023b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000023c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000023d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000023e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000023f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

可以看到第二个页的标记是0x1022, 由于第二个页面是全0页,所以做了压缩处理。第二个页offset=0x10000,cont=0x20,flag=RAM_SAVE_FLAG_COMPRESS。 然后存入1字节0 紧接着0x236e是第三个页面是0x2022 后面的就不过多说明了。 pc.ram 是2G,当pc.ram存储完后,会接着存储其他设备。

00939ec0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00939ed0 00 00 00 02 08 76 67 61 2e 76 72 61 6d 00 00 00 |.....vga.vram...|
00939ee0 00 00 00 00 10 22 00 00 00 00 00 00 00 20 22 00 |....."....... ".|
00939ef0 00 00 00 00 00 00 30 22 00 00 00 00 00 00 00 40 |......0".......@|
00939f00 22 00 00 00 00 00 00 00 50 22 00 00 00 00 00 00 |"
.......P"......|

可以看到从0x00939cb开始就是存储vag.ram的信息。由于第一个页就是0页。值是0x00000002. 所以标记offset=0,cont=0,flag=RAM_SAVE_FLAG_COMPRESS。由于第一次存储该设备,所以存入设备名字。

当这些设备全部存储完毕后,就是存储结尾。

00a72d40  00 00 00 00 00 e0 22 00  00 00 00 00 00 00 f0 22  |......"........"|
00a72d50 00 00 00 00 00 00 00 00 08 15 2f 72 6f 6d 40 65 |........../rom@e|
00a72d60 74 63 2f 74 61 62 6c 65 2d 6c 6f 61 64 65 72 01 |tc/table-loader.|
00a72d70 00 00 00 65 74 63 2f 61 63 70 69 2f 72 73 64 70 |...etc/acpi/rsdp|

这里从最后一个 设备开始查找,最后一个设备占用4k,所以从0x00a72d6d开始向后4k来查找。

00a73d50  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| 
00a73d60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00a73d70 00 00 00 00 00 00 10 03 00 00 00 02 00 00 00 00 |................|
00a73d80 00 00 00 10 04 00 00 00 00 05 74 69 6d 65 72 00 |..........timer.|
00a73d90 00 00 00 00 00 00 02 00 00 00 05 9f e1 15 85 00 |................|
00a73da0 00 00 00 00 00 00 00 00 00 00 02 73 7e b6 7e 04 |...........s~.~.|
00a73db0 00 00 00 03 0a 63 70 75 5f 63 6f 6d 6d 6f 6e 00 |.....cpu_common.|

从0x00a73d6e开始设备信息查找完成,然后存入ram结束信息RAM_SAVE_FLAG_EOS 8字节 0x00000010 。 从0x00a73d77开始 ,就是进行最后一次存储,此时会存储cpu信息。

#define QEMU_VM_SECTION_END          0x03
#define QEMU_VM_SECTION_FULL 0x04
#define QEMU_VM_EOF 0x00

void qemu_savevm_state_complete(QEMUFile *f)
{
SaveStateEntry *se;
int ret;

trace_savevm_state_complete();

cpu_synchronize_all_states();//同步此时的cpu状态。

QTAILQ_FOREACH(se, &savevm_handlers, entry) {
if (!se->ops || !se->ops->save_live_complete) {
continue;
}
if (se->ops && se->ops->is_active) {
if (!se->ops->is_active(se->opaque)) {
continue;
}
}
trace_savevm_section_start(se->idstr, se->section_id);
/* Section type */
qemu_put_byte(f, QEMU_VM_SECTION_END);
qemu_put_be32(f, se->section_id);

ret = se->ops->save_live_complete(f, se->opaque);//调用设备的complete函数
trace_savevm_section_end(se->idstr, se->section_id);
if (ret < 0) {
qemu_file_set_error(f, ret);
return;
}
}
//开始存储具有状态的设备。
QTAILQ_FOREACH(se, &savevm_handlers, entry) {
int len;

if ((!se->ops || !se->ops->save_state) && !se->vmsd) {
continue;
}
trace_savevm_section_start(se->idstr, se->section_id);
/* Section type */
qemu_put_byte(f, QEMU_VM_SECTION_FULL); //1字节section full
qemu_put_be32(f, se->section_id);

/* ID string */
len = strlen(se->idstr);
qemu_put_byte(f, len);
qemu_put_buffer(f, (uint8_t *)se->idstr, len);

qemu_put_be32(f, se->instance_id);
qemu_put_be32(f, se->version_id);

vmstate_save(f, se);
trace_savevm_section_end(se->idstr, se->section_id);
}

qemu_put_byte(f, QEMU_VM_EOF);
qemu_fflush(f);
}
static int ram_save_complete(QEMUFile *f, void *opaque)
{
qemu_mutex_lock_ramlist();
migration_bitmap_sync();

ram_control_before_iterate(f, RAM_CONTROL_FINISH);

/* try transferring iterative blocks of memory */
ram_control_before_iterate(f, RAM_CONTROL_FINISH);

/* try transferring iterative blocks of memory */

/* flush all remaining blocks regardless of rate limiting */
while (true) {
int bytes_sent;

bytes_sent = ram_find_and_save_block(f, true);
/* no more blocks to sent */
if (bytes_sent == 0) {
break;
}
bytes_transferred += bytes_sent;
}

ram_control_after_iterate(f, RAM_CONTROL_FINISH);
migration_end();

qemu_mutex_unlock_ramlist();
qemu_put_be64(f, RAM_SAVE_FLAG_EOS);//存入8字节 ram结束标记。

return 0;
}

0x00a73d78 是03 是标记存储结束。sectionid是 0x00000002。然后存入表示RAM_SAVE_FLAG_EOS 8字节0x00000010

从 0x00a73d83开始 存储QEMU_VM_SECTION_FULL 1字节 0x04 然后sectionid 0x00000000 然后是设备的名字,设备instance_id和version_id。然后使用vmstate_save来存储信息。

从0x00a73d89开始是timer设备的信息。

static const VMStateDescription vmstate_timers = {
.name = "timer",
.version_id = 2,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_INT64(cpu_ticks_offset, TimersState),
VMSTATE_INT64(dummy, TimersState),
VMSTATE_INT64_V(cpu_clock_offset, TimersState, 2),
VMSTATE_END_OF_LIST()
},
.subsections = (VMStateSubsection[]) {
{
.vmsd = &icount_vmstate_timers,
.needed = icount_state_needed,
}, {
/* empty */
}
}
};

void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque)
{
VMStateField *field = vmsd->fields;

if (vmsd->pre_save) {
vmsd->pre_save(opaque);
}
while (field->name) {
if (!field->field_exists ||
field->field_exists(opaque, vmsd->version_id)) {
void *base_addr = vmstate_base_addr(opaque, field, false);
int i, n_elems = vmstate_n_elems(opaque, field);
int size = vmstate_size(opaque, field);//获取state的size

for (i = 0; i < n_elems; i++) {
void *addr = base_addr + size * i;

if (field->flags & VMS_ARRAY_OF_POINTER) {
addr = *(void **)addr;
}
if (field->flags & VMS_STRUCT) {
vmstate_save_state(f, field->vmsd, addr);
} else {
field->info->put(f, addr, size);//这里对state进行存储
}
}
} else {
if (field->flags & VMS_MUST_EXIST) {
fprintf(stderr, "Output state validation failed: %s/%s\n",
vmsd->name, field->name);
assert(!(field->flags & VMS_MUST_EXIST));
}
}
field++;
}
vmstate_subsection_save(f, vmsd, opaque);
}

timer设备的state有3个VMSTATE_INT64组成,即24字节。 从00a73d97到00a73dae用存储timer的state信息。

所以对于有状态设备来说,就在这里就进行存储。

后面的设备就不一一分析。

当存储完成后,存入QEMU_VM_EOF。