[Qemu-devel] [PULL 44/57] Postcopy: Use helpers to map pages during migration
From: "Dr. David Alan Gilbert"In postcopy, the destination guest is running at the same time as it's receiving pages; as we receive new pages we must put them into the guests address space atomically to avoid a running CPU accessing a partially written page. Use the helpers in postcopy-ram.c to map these pages. qemu_get_buffer_in_place is used to avoid a copy out of qemu_file in the case that postcopy is going to do a copy anyway. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Juan Quintela Signed-off-by: Juan Quintela --- migration/ram.c | 130 +++- trace-events| 1 + 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/migration/ram.c b/migration/ram.c index d09d5ab..af5f369 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1932,6 +1932,14 @@ static int load_xbzrle(QEMUFile *f, ram_addr_t addr, void *host) /* Must be called from within a rcu critical section. * Returns a pointer from within the RCU-protected ram_list. */ +/* + * Read a RAMBlock ID from the stream f, find the host address of the + * start of that block and add on 'offset' + * + * f: Stream to read from + * offset: Offset within the block + * flags: Page flags (mostly to see if it's a continuation of previous block) + */ static inline void *host_from_stream_offset(QEMUFile *f, ram_addr_t offset, int flags) @@ -2077,11 +2085,126 @@ int ram_postcopy_incoming_init(MigrationIncomingState *mis) return postcopy_ram_incoming_init(mis, ram_pages); } +/* + * Called in postcopy mode by ram_load(). + * rcu_read_lock is taken prior to this being called. + */ +static int ram_load_postcopy(QEMUFile *f) +{ +int flags = 0, ret = 0; +bool place_needed = false; +bool matching_page_sizes = qemu_host_page_size == TARGET_PAGE_SIZE; +MigrationIncomingState *mis = migration_incoming_get_current(); +/* Temporary page that is later 'placed' */ +void *postcopy_host_page = postcopy_get_tmp_page(mis); + +while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) { +ram_addr_t addr; +void *host = NULL; +void *page_buffer = NULL; +void *place_source = NULL; +uint8_t ch; +bool all_zero = false; + +addr = qemu_get_be64(f); +flags = addr & ~TARGET_PAGE_MASK; +addr &= TARGET_PAGE_MASK; + +trace_ram_load_postcopy_loop((uint64_t)addr, flags); +place_needed = false; +if (flags & (RAM_SAVE_FLAG_COMPRESS | RAM_SAVE_FLAG_PAGE)) { +host = host_from_stream_offset(f, addr, flags); +if (!host) { +error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); +ret = -EINVAL; +break; +} +page_buffer = host; +/* + * Postcopy requires that we place whole host pages atomically. + * To make it atomic, the data is read into a temporary page + * that's moved into place later. + * The migration protocol uses, possibly smaller, target-pages + * however the source ensures it always sends all the components + * of a host page in order. + */ +page_buffer = postcopy_host_page + + ((uintptr_t)host & ~qemu_host_page_mask); +/* If all TP are zero then we can optimise the place */ +if (!((uintptr_t)host & ~qemu_host_page_mask)) { +all_zero = true; +} + +/* + * If it's the last part of a host page then we place the host + * page + */ +place_needed = (((uintptr_t)host + TARGET_PAGE_SIZE) & + ~qemu_host_page_mask) == 0; +place_source = postcopy_host_page; +} + +switch (flags & ~RAM_SAVE_FLAG_CONTINUE) { +case RAM_SAVE_FLAG_COMPRESS: +ch = qemu_get_byte(f); +memset(page_buffer, ch, TARGET_PAGE_SIZE); +if (ch) { +all_zero = false; +} +break; + +case RAM_SAVE_FLAG_PAGE: +all_zero = false; +if (!place_needed || !matching_page_sizes) { +qemu_get_buffer(f, page_buffer, TARGET_PAGE_SIZE); +} else { +/* Avoids the qemu_file copy during postcopy, which is + * going to do a copy later; can only do it when we + * do this read in one go (matching page sizes) + */ +qemu_get_buffer_in_place(f, (uint8_t **)_source, + TARGET_PAGE_SIZE); +} +break; +case RAM_SAVE_FLAG_EOS: +/* normal exit */ +break; +default: +
[Qemu-devel] [PULL 44/57] Postcopy: Use helpers to map pages during migration
From: "Dr. David Alan Gilbert"In postcopy, the destination guest is running at the same time as it's receiving pages; as we receive new pages we must put them into the guests address space atomically to avoid a running CPU accessing a partially written page. Use the helpers in postcopy-ram.c to map these pages. qemu_get_buffer_in_place is used to avoid a copy out of qemu_file in the case that postcopy is going to do a copy anyway. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Juan Quintela Signed-off-by: Juan Quintela --- migration/ram.c | 130 +++- trace-events| 1 + 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/migration/ram.c b/migration/ram.c index d09d5ab..af5f369 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1932,6 +1932,14 @@ static int load_xbzrle(QEMUFile *f, ram_addr_t addr, void *host) /* Must be called from within a rcu critical section. * Returns a pointer from within the RCU-protected ram_list. */ +/* + * Read a RAMBlock ID from the stream f, find the host address of the + * start of that block and add on 'offset' + * + * f: Stream to read from + * offset: Offset within the block + * flags: Page flags (mostly to see if it's a continuation of previous block) + */ static inline void *host_from_stream_offset(QEMUFile *f, ram_addr_t offset, int flags) @@ -2077,11 +2085,126 @@ int ram_postcopy_incoming_init(MigrationIncomingState *mis) return postcopy_ram_incoming_init(mis, ram_pages); } +/* + * Called in postcopy mode by ram_load(). + * rcu_read_lock is taken prior to this being called. + */ +static int ram_load_postcopy(QEMUFile *f) +{ +int flags = 0, ret = 0; +bool place_needed = false; +bool matching_page_sizes = qemu_host_page_size == TARGET_PAGE_SIZE; +MigrationIncomingState *mis = migration_incoming_get_current(); +/* Temporary page that is later 'placed' */ +void *postcopy_host_page = postcopy_get_tmp_page(mis); + +while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) { +ram_addr_t addr; +void *host = NULL; +void *page_buffer = NULL; +void *place_source = NULL; +uint8_t ch; +bool all_zero = false; + +addr = qemu_get_be64(f); +flags = addr & ~TARGET_PAGE_MASK; +addr &= TARGET_PAGE_MASK; + +trace_ram_load_postcopy_loop((uint64_t)addr, flags); +place_needed = false; +if (flags & (RAM_SAVE_FLAG_COMPRESS | RAM_SAVE_FLAG_PAGE)) { +host = host_from_stream_offset(f, addr, flags); +if (!host) { +error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); +ret = -EINVAL; +break; +} +page_buffer = host; +/* + * Postcopy requires that we place whole host pages atomically. + * To make it atomic, the data is read into a temporary page + * that's moved into place later. + * The migration protocol uses, possibly smaller, target-pages + * however the source ensures it always sends all the components + * of a host page in order. + */ +page_buffer = postcopy_host_page + + ((uintptr_t)host & ~qemu_host_page_mask); +/* If all TP are zero then we can optimise the place */ +if (!((uintptr_t)host & ~qemu_host_page_mask)) { +all_zero = true; +} + +/* + * If it's the last part of a host page then we place the host + * page + */ +place_needed = (((uintptr_t)host + TARGET_PAGE_SIZE) & + ~qemu_host_page_mask) == 0; +place_source = postcopy_host_page; +} + +switch (flags & ~RAM_SAVE_FLAG_CONTINUE) { +case RAM_SAVE_FLAG_COMPRESS: +ch = qemu_get_byte(f); +memset(page_buffer, ch, TARGET_PAGE_SIZE); +if (ch) { +all_zero = false; +} +break; + +case RAM_SAVE_FLAG_PAGE: +all_zero = false; +if (!place_needed || !matching_page_sizes) { +qemu_get_buffer(f, page_buffer, TARGET_PAGE_SIZE); +} else { +/* Avoids the qemu_file copy during postcopy, which is + * going to do a copy later; can only do it when we + * do this read in one go (matching page sizes) + */ +qemu_get_buffer_in_place(f, (uint8_t **)_source, + TARGET_PAGE_SIZE); +} +break; +case RAM_SAVE_FLAG_EOS: +/* normal exit */ +break; +default: +