Extend memory page saving and loading functions to utilize information available in checkpoints to avoid sending full pages over the network.
Signed-off-by: Bohdan Trach <bohdan.tr...@mailbox.tu-dresden.de> --- migration/ram.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- trace-events | 5 +++ 2 files changed, 127 insertions(+), 9 deletions(-) diff --git a/migration/ram.c b/migration/ram.c index 379a381..79cb143 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -203,6 +203,16 @@ void init_checksum_lookup_table(const char *checkpoint_path) cmp_hash_offset_entry); g_free(pg); } + +static int is_outgoing_with_checkpoint(void) { + return (fd_checkpoint != -1); +} + +static uint32_t get_page_nr(uint64_t addr) { + assert((addr % TARGET_PAGE_SIZE) == 0); + return (addr / TARGET_PAGE_SIZE); +} + static int dirty_rate_high_cnt; static uint64_t bitmap_sync_count; @@ -219,6 +229,7 @@ static uint64_t bitmap_sync_count; #define RAM_SAVE_FLAG_XBZRLE 0x40 /* 0x80 is reserved in migration.h start with 0x100 next */ #define RAM_SAVE_FLAG_COMPRESS_PAGE 0x100 +#define RAM_SAVE_FLAG_HASH 0x200 static const uint8_t ZERO_TARGET_PAGE[TARGET_PAGE_SIZE]; @@ -887,6 +898,7 @@ static int ram_save_page(QEMUFile *f, RAMBlock* block, ram_addr_t offset, uint8_t *p; int ret; bool send_async = true; + uint8_t hash[SHA256_DIGEST_LENGTH]; p = block->host + offset; @@ -935,16 +947,32 @@ static int ram_save_page(QEMUFile *f, RAMBlock* block, ram_addr_t offset, /* XBZRLE overflow or normal page */ if (pages == -1) { - *bytes_transferred += save_page_header(f, block, - offset | 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); + if (is_outgoing_with_checkpoint()) { + SHA256(p, TARGET_PAGE_SIZE, hash); + + if (bsearch(hash, hashes, hashes_entries, + SHA256_DIGEST_LENGTH, uint256_compare) != NULL) { + + *bytes_transferred += save_page_header(f, block, offset | RAM_SAVE_FLAG_HASH); + qemu_put_buffer(f, hash, SHA256_DIGEST_LENGTH); + *bytes_transferred += SHA256_DIGEST_LENGTH; + pages = 1; + trace_ram_load_send_hash(offset&TARGET_PAGE_MASK, (offset | RAM_SAVE_FLAG_HASH)& ~TARGET_PAGE_MASK, sha256s(hash)); + } + } + if (pages == -1) { + *bytes_transferred += save_page_header(f, block, + offset | 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_transferred += TARGET_PAGE_SIZE; + pages = 1; + acct_info.norm_pages++; + trace_ram_load_send_page(offset&TARGET_PAGE_MASK, (offset | RAM_SAVE_FLAG_PAGE)& ~TARGET_PAGE_MASK); } - *bytes_transferred += TARGET_PAGE_SIZE; - pages = 1; - acct_info.norm_pages++; } XBZRLE_cache_unlock(); @@ -2540,6 +2568,58 @@ static int ram_load_postcopy(QEMUFile *f) return ret; } +/** + * If migration source determined we already have the chunk, it only + * sends a hash of the page's content. Read it from local storage, + * e.g., an old checkpoint. + * @param host Address which, after this function, should have a content matching the functions 2nd parameter. + * @param hash The hash value. + * @param size Size of the memory region in bytes. Typically, size is a single page, e.g., 4 KiB. + * @param fd file descriptor of checkpoint file + */ +static void ram_handle_hash(void *host, uint64_t guest_phy_addr, uint8_t *hash, uint64_t size) +{ + assert(fd_checkpoint != -1); + + /* fprintf(stdout, "ram_handle_hash: incoming has %u!\n", hash); */ + uint8_t local_page_hash[SHA256_DIGEST_LENGTH]; + SHA256(host, TARGET_PAGE_SIZE, local_page_hash); + + if (0 != memcmp(local_page_hash, hash, SHA256_DIGEST_LENGTH)) { + /* Computed hash does not match the hash the migration source + sent us for this page. */ + hash_offset_entry* v = bsearch(hash, hash_offset_array, hash_offset_entries, + sizeof(hash_offset_entry), cmp_hash_offset_entry); + if (v == NULL) { + /* For some reason the source thought the destination + already has this block. But it doesn't. Hmmm ... */ + trace_ram_handle_hash_unknown(sha256s(hash), guest_phy_addr); + assert(0); + } + + trace_ram_handle_hash(guest_phy_addr, sha256s(hash), v->offset); + + off_t offset_actual = lseek(fd_checkpoint, v->offset, SEEK_SET); + assert(offset_actual == v->offset); + + ssize_t read_actual = read(fd_checkpoint, host, TARGET_PAGE_SIZE); + assert(read_actual == TARGET_PAGE_SIZE); + SHA256(host, TARGET_PAGE_SIZE, local_page_hash); + if (0 != memcmp(local_page_hash, hash, SHA256_DIGEST_LENGTH)) { + trace_ram_handle_hash_mismatch(sha256s(local_page_hash)); + assert(0); + } + } +} + +static void add_remote_hash(ram_addr_t addr, uint8_t *hash) { + uint64_t page_nr = get_page_nr(addr); + memcpy(&hashes[page_nr * SHA256_DIGEST_LENGTH], + hash, + SHA256_DIGEST_LENGTH); +} + + static int ram_load(QEMUFile *f, void *opaque, int version_id) { int flags = 0, ret = 0; @@ -2572,6 +2652,7 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) ram_addr_t addr, total_ram_bytes; void *host = NULL; uint8_t ch; + uint8_t hash[SHA256_DIGEST_LENGTH]; addr = qemu_get_be64(f); flags = addr & ~TARGET_PAGE_MASK; @@ -2627,10 +2708,34 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) case RAM_SAVE_FLAG_COMPRESS: ch = qemu_get_byte(f); ram_handle_compressed(host, ch, TARGET_PAGE_SIZE); + if (fd_checkpoint != -1) { + if (ch != 0) { + SHA256(host, TARGET_PAGE_SIZE, hash); + add_remote_hash(addr, hash); + } else { + add_remote_hash(addr, all_zeroes_hash); + } + } break; + case RAM_SAVE_FLAG_HASH: + host = host_from_stream_offset(f, addr, flags); + if (!host) { + error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); + ret = -EINVAL; + break; + } + qemu_get_buffer(f, hash, SHA256_DIGEST_LENGTH); + + ram_handle_hash(host, addr, hash, TARGET_PAGE_SIZE); + add_remote_hash(addr, hash); + break; case RAM_SAVE_FLAG_PAGE: qemu_get_buffer(f, host, TARGET_PAGE_SIZE); + if (fd_checkpoint != -1) { + SHA256(host, TARGET_PAGE_SIZE, hash); + add_remote_hash(addr, hash); + } break; case RAM_SAVE_FLAG_COMPRESS_PAGE: @@ -2642,6 +2747,10 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) } qemu_get_buffer(f, compressed_data_buf, len); decompress_data_with_multi_threads(compressed_data_buf, host, len); + if (fd_checkpoint != -1) { + SHA256(host, TARGET_PAGE_SIZE, hash); + add_remote_hash(addr, hash); + } break; case RAM_SAVE_FLAG_XBZRLE: @@ -2651,6 +2760,10 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) ret = -EINVAL; break; } + if (fd_checkpoint != -1) { + SHA256(host, TARGET_PAGE_SIZE, hash); + add_remote_hash(addr, hash); + } break; case RAM_SAVE_FLAG_EOS: /* normal exit */ diff --git a/trace-events b/trace-events index eee060b..e543821 100644 --- a/trace-events +++ b/trace-events @@ -1267,6 +1267,11 @@ ram_save_queue_pages(const char *rbname, size_t start, size_t len) "%s: start: % allocate_checksum_table(uint64_t npages) "pages=%" PRIu64 init_checksum_lookup_table_start(uint64_t ram_size) "ram_size=%" PRIu64 init_checksum_lookup_table_hash(const char* hash, uint64_t offset) "hash=%s offset=%" PRIu64 +ram_load_send_hash(uint64_t addr, uint64_t flags, const char *hash) "addr=%" PRIx64 " flags=%" PRIx64 " hash=%s" +ram_load_send_page(uint64_t addr, uint64_t flags) "addr=%" PRIx64 " flags=%" PRIx64 +ram_handle_hash(uint64_t addr, const char *hash, uint64_t offset) "addr=%" PRIx64 " hash=%s offset=%" PRIx64 +ram_handle_hash_mismatch(const char *hash) "mismatch: hash=%s" +ram_handle_hash_unknown(const char *hash, uint64_t offset) "unknown hash %s at guest addr %08" PRIx64 # hw/display/qxl.c disable qxl_interface_set_mm_time(int qid, uint32_t mm_time) "%d %d" -- 2.4.10