Commit fc1ea4b513 for qemu.org
commit fc1ea4b51373a3f7778219c297e7c1fe07696a55
Author: Aadeshveer Singh <aadeshveer07@gmail.com>
Date: Sun Aug 16 23:16:28 2026 +0530
migration: add eager load thread and setup for fast snapshot load
In fast snapshot load a thread is needed for actively loading in pages
along with the fault path so that the guest is not dependent on fault
thread indefinitely. Considering the difference from usual network
postcopy where major chunk of RAM is already loaded here entire RAM
needs to be loaded later. Existance of background pages which are not
really accessed by the guest might never be loaded and system will be
locked in migration for indefinite time. As there should be no
assumption about how guest accesses memory, the load times can be
indefinite.
Add postcopy_ram_eager_load_thread(), for the eager thread which
iterates over all non ignored blocks calling ram_block_load_eager()
on each. ram_block_load_eager then iterates to load in all pages using
postcopy_mapped_ram_load_page(), with a different channel, which takes
care of not loading in pages already loaded by fault thread. On
completion the thread schedules postcopy_incoming_complete_bh() to
destroy the incoming migration state.
Add postcopy_ram_eager_load_setup() to create the thread. Added joining
logic in postcopy_incoming_cleanup().
Add tracepoints for entry and exit to eager load thread.
When both mapped-ram and postcopy-ram are set, divert from
qemu_loadvm_state to run fast snapshot load
Initialize postcopy RAM state and register RAM Blocks with userfaultfd
via ram_postcopy_incoming_init() and postcopy_ram_incoming_setup() in
process_incoming_migration_co(). Fault thread needs to be launched
before VM to serve faults for some hardwares emulation that need to read
RAM (like vapic devices). Populate bitmaps and offset tables while
reading file in qemu_loadvm_state_main.
Add function qemu_loadvm_run_fast_snapshot_load() which starts the VM
using loadvm_postcopy_handle_run_bh() and launches eager load thread.
Skip scheduling process_incoming_migration_bh() in
process_incoming_migration_co(), for fast snapshot load as the state
cleanup is managed by eager load thread on completion.
Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
diff --git a/migration/migration.c b/migration/migration.c
index a30779f37c..59cdc1a169 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -710,6 +710,11 @@ static void process_incoming_migration_bh(void *opaque)
migration_incoming_state_destroy();
}
+static bool migration_incoming_has_postcopy_thread(MigrationIncomingState *mis)
+{
+ return mis->have_listen_thread || mis->have_eager_load_thread;
+}
+
static void coroutine_fn
process_incoming_migration_co(void *opaque)
{
@@ -739,17 +744,44 @@ process_incoming_migration_co(void *opaque)
migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
MIGRATION_STATUS_ACTIVE);
+ /*
+ * When loading snapshot with postcopy enabled, setup the postcopy
+ * infrastructure before loading the major part of device states.
+ * It's required because qemu_loadvm_state() may access guest memory
+ * while loading device states, which can cause page faults already.
+ */
+ if (migrate_postcopy_ram() && migrate_mapped_ram()) {
+ migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
+ MIGRATION_STATUS_POSTCOPY_DEVICE);
+
+ if (ram_postcopy_incoming_init(mis, &local_err)) {
+ goto fail;
+ }
+
+ postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
+ if (postcopy_ram_incoming_setup(mis, &local_err)) {
+ goto fail;
+ }
+ }
+
mis->loadvm_co = qemu_coroutine_self();
ret = qemu_loadvm_state(mis->from_src_file, &local_err);
mis->loadvm_co = NULL;
+ if (ret < 0) {
+ goto fail;
+ }
+
+ if (migrate_postcopy_ram() && migrate_mapped_ram()) {
+ qemu_loadvm_run_fast_snapshot_load(mis->from_src_file, mis);
+ }
trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
trace_process_incoming_migration_co_end(ret);
- if (mis->have_listen_thread) {
+ if (migration_incoming_has_postcopy_thread(mis)) {
/*
* Postcopy was started, cleanup should happen at the end of the
- * postcopy listen thread.
+ * postcopy listen thread or eager load thread.
*/
trace_process_incoming_migration_co_postcopy_end_main();
goto out;
diff --git a/migration/migration.h b/migration/migration.h
index 631421c784..e47ff4e3d1 100644
--- a/migration/migration.h
+++ b/migration/migration.h
@@ -42,6 +42,7 @@
#define MIGRATION_THREAD_DST_FAULT "mig/dst/fault"
#define MIGRATION_THREAD_DST_LISTEN "mig/dst/listen"
#define MIGRATION_THREAD_DST_PREEMPT "mig/dst/preempt"
+#define MIGRATION_THREAD_DST_SNAPSHOT_LOAD "mig/dst/snapshot_load"
struct PostcopyBlocktimeContext;
typedef struct ThreadPool ThreadPool;
@@ -120,6 +121,10 @@ struct MigrationIncomingState {
bool have_listen_thread;
QemuThread listen_thread;
+ /* Thread to load pages eagerly in fast snapshot load case */
+ bool have_eager_load_thread;
+ QemuThread eager_load_thread;
+
/* For the kernel to send us notifications */
int userfault_fd;
/* To notify the fault_thread to wake, e.g., when need to quit */
diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 430b5f24e1..3b66c1f6d4 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -39,6 +39,8 @@
#include "qemu/mmap-alloc.h"
#include "options.h"
+static void postcopy_incoming_complete_bh(void *opaque);
+
/* Arbitrary limit on size of each discard command,
* keeps them around ~200 bytes
*/
@@ -1867,6 +1869,70 @@ int postcopy_place_page_zero(MigrationIncomingState *mis, void *host,
}
}
+/*
+ * Called by postcopy_ram_eager_load_thread over all blocks to load in all the
+ * pending pages of given ram block
+ */
+static int ram_block_load_eager(RAMBlock *rb, void *opaque)
+{
+ MigrationIncomingState *mis = migration_incoming_get_current();
+ MigrationState *s = migrate_get_current();
+ Error *errp = NULL;
+ void *host = qemu_ram_get_host_addr(rb);
+ void *target;
+
+ for (ram_addr_t page_loc = 0; page_loc < rb->used_length;
+ page_loc += qemu_ram_pagesize(rb)) {
+ target = (uint8_t *)host + page_loc;
+ if (!postcopy_mapped_ram_load_page(mis, rb, page_loc, (uint64_t)target,
+ RAM_CHANNEL_PRECOPY, &errp)) {
+ migrate_error_propagate(s, errp);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Used by fast snapshot load to eagerly load in all pages of RAM and schedule
+ * cleanup after entire RAM is loaded
+ */
+static void *postcopy_ram_eager_load_thread(void *opaque)
+{
+ MigrationIncomingState *mis = opaque;
+ MigrationStatus next_state;
+
+ trace_postcopy_ram_eager_load_thread_entry();
+ rcu_register_thread();
+ qemu_event_set(&mis->thread_sync_event);
+
+ if (foreach_not_ignored_block(ram_block_load_eager, NULL)) {
+ next_state = MIGRATION_STATUS_FAILED;
+ } else {
+ next_state = MIGRATION_STATUS_COMPLETED;
+ }
+ migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
+ next_state);
+
+ postcopy_state_set(POSTCOPY_INCOMING_END);
+ migration_bh_schedule(postcopy_incoming_complete_bh, mis);
+
+ rcu_unregister_thread();
+ trace_postcopy_ram_eager_load_thread_exit();
+ return NULL;
+}
+
+/*
+ * Create thread for eager loading in fast snapshot load case
+ */
+void postcopy_ram_eager_load_setup(MigrationIncomingState *mis)
+{
+ postcopy_thread_create(
+ mis, &mis->eager_load_thread, MIGRATION_THREAD_DST_SNAPSHOT_LOAD,
+ postcopy_ram_eager_load_thread, QEMU_THREAD_JOINABLE);
+ mis->have_eager_load_thread = true;
+}
+
#else
/* No target OS support, stubs just fail */
void fill_destination_postcopy_migration_info(MigrationInfo *info)
@@ -1937,6 +2003,11 @@ bool try_mark_postcopy_blocktime_begin(MigrationIncomingState *mis,
g_assert_not_reached();
return false;
}
+
+void postcopy_ram_eager_load_setup(MigrationIncomingState *mis)
+{
+ g_assert_not_reached();
+}
#endif
/* ------------------------------------------------------------------------- */
@@ -2410,6 +2481,11 @@ int postcopy_incoming_cleanup(MigrationIncomingState *mis)
mis->have_listen_thread = false;
}
+ if (mis->have_eager_load_thread) {
+ qemu_thread_join(&mis->eager_load_thread);
+ mis->have_eager_load_thread = false;
+ }
+
if (migrate_postcopy_ram()) {
rc = postcopy_ram_incoming_cleanup(mis);
}
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index ea879f0bc6..c58a60bc8a 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -205,4 +205,6 @@ void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid,
int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp);
int postcopy_incoming_cleanup(MigrationIncomingState *mis);
+void postcopy_ram_eager_load_setup(MigrationIncomingState *mis);
+
#endif
diff --git a/migration/savevm.c b/migration/savevm.c
index 34dd06f9f7..161a9d090f 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -3004,6 +3004,22 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
return true;
}
+/*
+ * Starts the VM and launches the eager thread for fast snapshot load
+ */
+void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
+ MigrationIncomingState *mis)
+{
+ postcopy_state_set(POSTCOPY_INCOMING_RUNNING);
+
+ migration_bh_schedule(loadvm_postcopy_handle_run_bh, mis);
+
+ migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_DEVICE,
+ MIGRATION_STATUS_POSTCOPY_ACTIVE);
+
+ postcopy_ram_eager_load_setup(mis);
+}
+
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
Error **errp)
{
diff --git a/migration/savevm.h b/migration/savevm.h
index f7dcdd7d1b..9988dd4b8a 100644
--- a/migration/savevm.h
+++ b/migration/savevm.h
@@ -70,6 +70,8 @@ void qemu_savevm_send_postcopy_ram_discard(QEMUFile *f, const char *name,
int qemu_save_device_state(QEMUFile *f, Error **errp);
int qemu_loadvm_state(QEMUFile *f, Error **errp);
void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
+void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
+ MigrationIncomingState *mis);
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
Error **errp);
int qemu_load_device_state(QEMUFile *f, Error **errp);
diff --git a/migration/trace-events b/migration/trace-events
index af0e784535..4ae1c226f9 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -315,6 +315,8 @@ postcopy_blocktime_tid_cpu_map(int cpu, uint32_t tid) "cpu: %d, tid: %u"
postcopy_blocktime_begin(uint64_t addr, uint64_t time, int cpu, bool exists) "addr: 0x%" PRIx64 ", time: %" PRIu64 ", cpu: %d, exist: %d"
postcopy_blocktime_end(uint64_t addr, uint64_t time, int affected_cpu, int affected_non_cpus) "addr: 0x%" PRIx64 ", time: %" PRIu64 ", affected_cpus: %d, affected_non_cpus: %d"
postcopy_blocktime_end_one(int cpu, uint8_t left_faults) "cpu: %d, left_faults: %" PRIu8
+postcopy_ram_eager_load_thread_entry(void) ""
+postcopy_ram_eager_load_thread_exit(void) ""
# exec.c
migration_exec_outgoing(const char *cmd) "cmd=%s"