libvirt savevm 命令存储内存文件分析
来源:互联网 发布:linux mysql编程 编辑:程序博客网 时间:2024/06/12 22:29
在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, ¶ms);//存储虚拟机状态主要是有数据的设备存储。 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 0x01void 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 0x10static 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 0x02int 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 0x20static 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 0x08static 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 0x00void 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。
- libvirt savevm 命令存储内存文件分析
- Libvirt源码分析Part1-存储池和卷的表象与细节之存储池
- Libvirt源码分析Part1-存储池和卷的表象与细节之存储池
- libvirt(virsh命令介绍)
- libvirt(virsh命令介绍)
- libvirt(virsh命令介绍)
- libvirt(virsh命令介绍)
- libvirt-virsh命令
- libvirt(virsh命令介绍)
- Libvirt命令参数详解
- libvirt框架分析
- 内存分析的命令
- Android内存分析命令
- Android 内存分析命令
- Android 内存分析命令
- Android内存分析命令
- Android内存分析命令
- Android 内存分析命令
- ScrollView中嵌套ListView/GridView导致的问题总结
- 汉诺塔算法
- bzoj1036 树的统计 树链剖分
- [CF 513F2]躲藏
- 读取指定行数文件内容并显示
- libvirt savevm 命令存储内存文件分析
- PHPChina中的Yii2与Discuz会员打通(单点登录)
- 剑指Offer笔记<JAVA版>(一)
- UML图
- 接口测试的一些感悟
- 《跟我学Shiro》——张开涛(链接)
- Linux系统操作详解
- This project is a not MyEclipse Hibenate project assuming hibernate3 capabilites for
- iOS之在drawRect通过Quartz 2D画虚线圆。 函数为CGContextSetLineDash