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;
|
size_t page_size;
|
||||||
/* dirty bitmap used during migration */
|
/* dirty bitmap used during migration */
|
||||||
unsigned long *bmap;
|
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 */
|
/* bitmap of already received pages in postcopy */
|
||||||
unsigned long *receivedmap;
|
unsigned long *receivedmap;
|
||||||
|
|
||||||
|
131
migration/ram.c
131
migration/ram.c
@ -94,6 +94,18 @@
|
|||||||
#define RAM_SAVE_FLAG_MULTIFD_FLUSH 0x200
|
#define RAM_SAVE_FLAG_MULTIFD_FLUSH 0x200
|
||||||
/* We can't use any flag that is bigger than 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;
|
XBZRLECacheStats xbzrle_counters;
|
||||||
|
|
||||||
/* used by the search for pages to send */
|
/* used by the search for pages to send */
|
||||||
@ -1126,12 +1138,18 @@ static int save_zero_page(RAMState *rs, PageSearchStatus *pss,
|
|||||||
return 0;
|
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);
|
len += save_page_header(pss, file, pss->block, offset | RAM_SAVE_FLAG_ZERO);
|
||||||
qemu_put_byte(file, 0);
|
qemu_put_byte(file, 0);
|
||||||
len += 1;
|
len += 1;
|
||||||
ram_release_page(pss->block->idstr, offset);
|
ram_release_page(pss->block->idstr, offset);
|
||||||
|
|
||||||
stat64_add(&mig_stats.zero_pages, 1);
|
|
||||||
ram_transferred_add(len);
|
ram_transferred_add(len);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1189,14 +1207,20 @@ static int save_normal_page(PageSearchStatus *pss, RAMBlock *block,
|
|||||||
{
|
{
|
||||||
QEMUFile *file = pss->pss_channel;
|
QEMUFile *file = pss->pss_channel;
|
||||||
|
|
||||||
ram_transferred_add(save_page_header(pss, pss->pss_channel, block,
|
if (migrate_mapped_ram()) {
|
||||||
offset | RAM_SAVE_FLAG_PAGE));
|
qemu_put_buffer_at(file, buf, TARGET_PAGE_SIZE,
|
||||||
if (async) {
|
block->pages_offset + offset);
|
||||||
qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE,
|
set_bit(offset >> TARGET_PAGE_BITS, block->file_bmap);
|
||||||
migrate_release_ram() &&
|
|
||||||
migration_in_postcopy());
|
|
||||||
} else {
|
} 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);
|
ram_transferred_add(TARGET_PAGE_SIZE);
|
||||||
stat64_add(&mig_stats.normal_pages, 1);
|
stat64_add(&mig_stats.normal_pages, 1);
|
||||||
@ -2411,6 +2435,8 @@ static void ram_save_cleanup(void *opaque)
|
|||||||
block->clear_bmap = NULL;
|
block->clear_bmap = NULL;
|
||||||
g_free(block->bmap);
|
g_free(block->bmap);
|
||||||
block->bmap = NULL;
|
block->bmap = NULL;
|
||||||
|
g_free(block->file_bmap);
|
||||||
|
block->file_bmap = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
xbzrle_cleanup();
|
xbzrle_cleanup();
|
||||||
@ -2778,6 +2804,9 @@ static void ram_list_init_bitmaps(void)
|
|||||||
*/
|
*/
|
||||||
block->bmap = bitmap_new(pages);
|
block->bmap = bitmap_new(pages);
|
||||||
bitmap_set(block->bmap, 0, 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_shift = shift;
|
||||||
block->clear_bmap = bitmap_new(clear_bmap_size(pages, 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
|
* Each of ram_save_setup, ram_save_iterate and ram_save_complete has
|
||||||
* long-running RCU critical section. When rcu-reclaims in the code
|
* 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()) {
|
if (migrate_ignore_shared()) {
|
||||||
qemu_put_be64(f, block->mr->addr);
|
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);
|
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
|
* ram_save_iterate: iterative stage for migration
|
||||||
*
|
*
|
||||||
@ -3186,6 +3287,18 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
|
|||||||
return ret;
|
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()) {
|
if (migrate_multifd() && !migrate_multifd_flush_after_each_section()) {
|
||||||
qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH);
|
qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user