migration/ram: Add outgoing 'mapped-ram' migration
Implement the outgoing migration side for the 'mapped-ram' capability. A bitmap is introduced to track which pages have been written in the migration file. Pages are written at a fixed location for every ramblock. Zero pages are ignored as they'd be zero in the destination migration as well. The migration stream is altered to put the dirty pages for a ramblock after its header instead of having a sequential stream of pages that follow the ramblock headers. Without mapped-ram (current): With mapped-ram (new): --------------------- -------------------------------- | ramblock 1 header | | ramblock 1 header | --------------------- -------------------------------- | ramblock 2 header | | ramblock 1 mapped-ram header | --------------------- -------------------------------- | ... | | padding to next 1MB boundary | --------------------- | ... | | ramblock n header | -------------------------------- --------------------- | ramblock 1 pages | | RAM_SAVE_FLAG_EOS | | ... | --------------------- -------------------------------- | stream of pages | | ramblock 2 header | | (iter 1) | -------------------------------- | ... | | ramblock 2 mapped-ram header | --------------------- -------------------------------- | RAM_SAVE_FLAG_EOS | | padding to next 1MB boundary | --------------------- | ... | | stream of pages | -------------------------------- | (iter 2) | | ramblock 2 pages | | ... | | ... | --------------------- -------------------------------- | ... | | ... | --------------------- -------------------------------- | RAM_SAVE_FLAG_EOS | -------------------------------- | ... | -------------------------------- where: - ramblock header: the generic information for a ramblock, such as idstr, used_len, etc. - ramblock mapped-ram header: the new information added by this feature: bitmap of pages written, bitmap size and offset of pages in the migration file. Signed-off-by: Nikolay Borisov <nborisov@suse.com> Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20240229153017.2221-10-farosas@suse.de Signed-off-by: Peter Xu <peterx@redhat.com>
This commit is contained in:
parent
8d9e0d4100
commit
c2d5c4a7cb
@ -44,6 +44,19 @@ struct RAMBlock {
|
||||
size_t page_size;
|
||||
/* dirty bitmap used during migration */
|
||||
unsigned long *bmap;
|
||||
|
||||
/*
|
||||
* Below fields are only used by mapped-ram migration
|
||||
*/
|
||||
/* bitmap of pages present in the migration file */
|
||||
unsigned long *file_bmap;
|
||||
/*
|
||||
* offset in the file pages belonging to this ramblock are saved,
|
||||
* used only during migration to a file.
|
||||
*/
|
||||
off_t bitmap_offset;
|
||||
uint64_t pages_offset;
|
||||
|
||||
/* bitmap of already received pages in postcopy */
|
||||
unsigned long *receivedmap;
|
||||
|
||||
|
131
migration/ram.c
131
migration/ram.c
@ -94,6 +94,18 @@
|
||||
#define RAM_SAVE_FLAG_MULTIFD_FLUSH 0x200
|
||||
/* We can't use any flag that is bigger than 0x200 */
|
||||
|
||||
/*
|
||||
* mapped-ram migration supports O_DIRECT, so we need to make sure the
|
||||
* userspace buffer, the IO operation size and the file offset are
|
||||
* aligned according to the underlying device's block size. The first
|
||||
* two are already aligned to page size, but we need to add padding to
|
||||
* the file to align the offset. We cannot read the block size
|
||||
* dynamically because the migration file can be moved between
|
||||
* different systems, so use 1M to cover most block sizes and to keep
|
||||
* the file offset aligned at page size as well.
|
||||
*/
|
||||
#define MAPPED_RAM_FILE_OFFSET_ALIGNMENT 0x100000
|
||||
|
||||
XBZRLECacheStats xbzrle_counters;
|
||||
|
||||
/* used by the search for pages to send */
|
||||
@ -1126,12 +1138,18 @@ static int save_zero_page(RAMState *rs, PageSearchStatus *pss,
|
||||
return 0;
|
||||
}
|
||||
|
||||
stat64_add(&mig_stats.zero_pages, 1);
|
||||
|
||||
if (migrate_mapped_ram()) {
|
||||
/* zero pages are not transferred with mapped-ram */
|
||||
clear_bit(offset >> TARGET_PAGE_BITS, pss->block->file_bmap);
|
||||
return 1;
|
||||
}
|
||||
|
||||
len += save_page_header(pss, file, pss->block, offset | RAM_SAVE_FLAG_ZERO);
|
||||
qemu_put_byte(file, 0);
|
||||
len += 1;
|
||||
ram_release_page(pss->block->idstr, offset);
|
||||
|
||||
stat64_add(&mig_stats.zero_pages, 1);
|
||||
ram_transferred_add(len);
|
||||
|
||||
/*
|
||||
@ -1189,14 +1207,20 @@ static int save_normal_page(PageSearchStatus *pss, RAMBlock *block,
|
||||
{
|
||||
QEMUFile *file = pss->pss_channel;
|
||||
|
||||
ram_transferred_add(save_page_header(pss, pss->pss_channel, block,
|
||||
offset | RAM_SAVE_FLAG_PAGE));
|
||||
if (async) {
|
||||
qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE,
|
||||
migrate_release_ram() &&
|
||||
migration_in_postcopy());
|
||||
if (migrate_mapped_ram()) {
|
||||
qemu_put_buffer_at(file, buf, TARGET_PAGE_SIZE,
|
||||
block->pages_offset + offset);
|
||||
set_bit(offset >> TARGET_PAGE_BITS, block->file_bmap);
|
||||
} else {
|
||||
qemu_put_buffer(file, buf, TARGET_PAGE_SIZE);
|
||||
ram_transferred_add(save_page_header(pss, pss->pss_channel, block,
|
||||
offset | RAM_SAVE_FLAG_PAGE));
|
||||
if (async) {
|
||||
qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE,
|
||||
migrate_release_ram() &&
|
||||
migration_in_postcopy());
|
||||
} else {
|
||||
qemu_put_buffer(file, buf, TARGET_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
ram_transferred_add(TARGET_PAGE_SIZE);
|
||||
stat64_add(&mig_stats.normal_pages, 1);
|
||||
@ -2411,6 +2435,8 @@ static void ram_save_cleanup(void *opaque)
|
||||
block->clear_bmap = NULL;
|
||||
g_free(block->bmap);
|
||||
block->bmap = NULL;
|
||||
g_free(block->file_bmap);
|
||||
block->file_bmap = NULL;
|
||||
}
|
||||
|
||||
xbzrle_cleanup();
|
||||
@ -2778,6 +2804,9 @@ static void ram_list_init_bitmaps(void)
|
||||
*/
|
||||
block->bmap = bitmap_new(pages);
|
||||
bitmap_set(block->bmap, 0, pages);
|
||||
if (migrate_mapped_ram()) {
|
||||
block->file_bmap = bitmap_new(pages);
|
||||
}
|
||||
block->clear_bmap_shift = shift;
|
||||
block->clear_bmap = bitmap_new(clear_bmap_size(pages, shift));
|
||||
}
|
||||
@ -2915,6 +2944,60 @@ void qemu_guest_free_page_hint(void *addr, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
#define MAPPED_RAM_HDR_VERSION 1
|
||||
struct MappedRamHeader {
|
||||
uint32_t version;
|
||||
/*
|
||||
* The target's page size, so we know how many pages are in the
|
||||
* bitmap.
|
||||
*/
|
||||
uint64_t page_size;
|
||||
/*
|
||||
* The offset in the migration file where the pages bitmap is
|
||||
* stored.
|
||||
*/
|
||||
uint64_t bitmap_offset;
|
||||
/*
|
||||
* The offset in the migration file where the actual pages (data)
|
||||
* are stored.
|
||||
*/
|
||||
uint64_t pages_offset;
|
||||
} QEMU_PACKED;
|
||||
typedef struct MappedRamHeader MappedRamHeader;
|
||||
|
||||
static void mapped_ram_setup_ramblock(QEMUFile *file, RAMBlock *block)
|
||||
{
|
||||
g_autofree MappedRamHeader *header = NULL;
|
||||
size_t header_size, bitmap_size;
|
||||
long num_pages;
|
||||
|
||||
header = g_new0(MappedRamHeader, 1);
|
||||
header_size = sizeof(MappedRamHeader);
|
||||
|
||||
num_pages = block->used_length >> TARGET_PAGE_BITS;
|
||||
bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
|
||||
|
||||
/*
|
||||
* Save the file offsets of where the bitmap and the pages should
|
||||
* go as they are written at the end of migration and during the
|
||||
* iterative phase, respectively.
|
||||
*/
|
||||
block->bitmap_offset = qemu_get_offset(file) + header_size;
|
||||
block->pages_offset = ROUND_UP(block->bitmap_offset +
|
||||
bitmap_size,
|
||||
MAPPED_RAM_FILE_OFFSET_ALIGNMENT);
|
||||
|
||||
header->version = cpu_to_be32(MAPPED_RAM_HDR_VERSION);
|
||||
header->page_size = cpu_to_be64(TARGET_PAGE_SIZE);
|
||||
header->bitmap_offset = cpu_to_be64(block->bitmap_offset);
|
||||
header->pages_offset = cpu_to_be64(block->pages_offset);
|
||||
|
||||
qemu_put_buffer(file, (uint8_t *) header, header_size);
|
||||
|
||||
/* prepare offset for next ramblock */
|
||||
qemu_set_offset(file, block->pages_offset + block->used_length, SEEK_SET);
|
||||
}
|
||||
|
||||
/*
|
||||
* Each of ram_save_setup, ram_save_iterate and ram_save_complete has
|
||||
* long-running RCU critical section. When rcu-reclaims in the code
|
||||
@ -2964,6 +3047,10 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
|
||||
if (migrate_ignore_shared()) {
|
||||
qemu_put_be64(f, block->mr->addr);
|
||||
}
|
||||
|
||||
if (migrate_mapped_ram()) {
|
||||
mapped_ram_setup_ramblock(f, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2997,6 +3084,20 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
|
||||
return qemu_fflush(f);
|
||||
}
|
||||
|
||||
static void ram_save_file_bmap(QEMUFile *f)
|
||||
{
|
||||
RAMBlock *block;
|
||||
|
||||
RAMBLOCK_FOREACH_MIGRATABLE(block) {
|
||||
long num_pages = block->used_length >> TARGET_PAGE_BITS;
|
||||
long bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
|
||||
|
||||
qemu_put_buffer_at(f, (uint8_t *)block->file_bmap, bitmap_size,
|
||||
block->bitmap_offset);
|
||||
ram_transferred_add(bitmap_size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ram_save_iterate: iterative stage for migration
|
||||
*
|
||||
@ -3186,6 +3287,18 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (migrate_mapped_ram()) {
|
||||
ram_save_file_bmap(f);
|
||||
|
||||
if (qemu_file_get_error(f)) {
|
||||
Error *local_err = NULL;
|
||||
int err = qemu_file_get_error_obj(f, &local_err);
|
||||
|
||||
error_reportf_err(local_err, "Failed to write bitmap to file: ");
|
||||
return -err;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrate_multifd() && !migrate_multifd_flush_after_each_section()) {
|
||||
qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user