@samitouri / QOSamiQemu / commits / fc1ea4b513

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>

Aadeshveer Singh committed Aug 16, 2026 at 23:16 UTC fc1ea4b51373a3f7778219c297e7c1fe07696a55
7 files changed +137 -2
migration/migration.c
+34 -2
@@ -710,6 +710,11 @@ static void process_incoming_migration_bh(void *opaque)
710 migration_incoming_state_destroy();
711 }
712
713 +static bool migration_incoming_has_postcopy_thread(MigrationIncomingState *mis)
714 +{
715 + return mis->have_listen_thread || mis->have_eager_load_thread;
716 +}
717 +
718 static void coroutine_fn
719 process_incoming_migration_co(void *opaque)
720 {
@@ -739,17 +744,44 @@ process_incoming_migration_co(void *opaque)
744 migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
745 MIGRATION_STATUS_ACTIVE);
746
747 + /*
748 + * When loading snapshot with postcopy enabled, setup the postcopy
749 + * infrastructure before loading the major part of device states.
750 + * It's required because qemu_loadvm_state() may access guest memory
751 + * while loading device states, which can cause page faults already.
752 + */
753 + if (migrate_postcopy_ram() && migrate_mapped_ram()) {
754 + migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
755 + MIGRATION_STATUS_POSTCOPY_DEVICE);
756 +
757 + if (ram_postcopy_incoming_init(mis, &local_err)) {
758 + goto fail;
759 + }
760 +
761 + postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
762 + if (postcopy_ram_incoming_setup(mis, &local_err)) {
763 + goto fail;
764 + }
765 + }
766 +
767 mis->loadvm_co = qemu_coroutine_self();
768 ret = qemu_loadvm_state(mis->from_src_file, &local_err);
769 mis->loadvm_co = NULL;
770 + if (ret < 0) {
771 + goto fail;
772 + }
773 +
774 + if (migrate_postcopy_ram() && migrate_mapped_ram()) {
775 + qemu_loadvm_run_fast_snapshot_load(mis->from_src_file, mis);
776 + }
777
778 trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
779
780 trace_process_incoming_migration_co_end(ret);
749 - if (mis->have_listen_thread) {
781 + if (migration_incoming_has_postcopy_thread(mis)) {
782 /*
783 * Postcopy was started, cleanup should happen at the end of the
752 - * postcopy listen thread.
784 + * postcopy listen thread or eager load thread.
785 */
786 trace_process_incoming_migration_co_postcopy_end_main();
787 goto out;
migration/migration.h
+5
@@ -42,6 +42,7 @@
42 #define MIGRATION_THREAD_DST_FAULT "mig/dst/fault"
43 #define MIGRATION_THREAD_DST_LISTEN "mig/dst/listen"
44 #define MIGRATION_THREAD_DST_PREEMPT "mig/dst/preempt"
45 +#define MIGRATION_THREAD_DST_SNAPSHOT_LOAD "mig/dst/snapshot_load"
46
47 struct PostcopyBlocktimeContext;
48 typedef struct ThreadPool ThreadPool;
@@ -120,6 +121,10 @@ struct MigrationIncomingState {
121 bool have_listen_thread;
122 QemuThread listen_thread;
123
124 + /* Thread to load pages eagerly in fast snapshot load case */
125 + bool have_eager_load_thread;
126 + QemuThread eager_load_thread;
127 +
128 /* For the kernel to send us notifications */
129 int userfault_fd;
130 /* To notify the fault_thread to wake, e.g., when need to quit */
migration/postcopy-ram.c
+76
@@ -39,6 +39,8 @@
39 #include "qemu/mmap-alloc.h"
40 #include "options.h"
41
42 +static void postcopy_incoming_complete_bh(void *opaque);
43 +
44 /* Arbitrary limit on size of each discard command,
45 * keeps them around ~200 bytes
46 */
@@ -1867,6 +1869,70 @@ int postcopy_place_page_zero(MigrationIncomingState *mis, void *host,
1869 }
1870 }
1871
1872 +/*
1873 + * Called by postcopy_ram_eager_load_thread over all blocks to load in all the
1874 + * pending pages of given ram block
1875 + */
1876 +static int ram_block_load_eager(RAMBlock *rb, void *opaque)
1877 +{
1878 + MigrationIncomingState *mis = migration_incoming_get_current();
1879 + MigrationState *s = migrate_get_current();
1880 + Error *errp = NULL;
1881 + void *host = qemu_ram_get_host_addr(rb);
1882 + void *target;
1883 +
1884 + for (ram_addr_t page_loc = 0; page_loc < rb->used_length;
1885 + page_loc += qemu_ram_pagesize(rb)) {
1886 + target = (uint8_t *)host + page_loc;
1887 + if (!postcopy_mapped_ram_load_page(mis, rb, page_loc, (uint64_t)target,
1888 + RAM_CHANNEL_PRECOPY, &errp)) {
1889 + migrate_error_propagate(s, errp);
1890 + return -1;
1891 + }
1892 + }
1893 + return 0;
1894 +}
1895 +
1896 +/*
1897 + * Used by fast snapshot load to eagerly load in all pages of RAM and schedule
1898 + * cleanup after entire RAM is loaded
1899 + */
1900 +static void *postcopy_ram_eager_load_thread(void *opaque)
1901 +{
1902 + MigrationIncomingState *mis = opaque;
1903 + MigrationStatus next_state;
1904 +
1905 + trace_postcopy_ram_eager_load_thread_entry();
1906 + rcu_register_thread();
1907 + qemu_event_set(&mis->thread_sync_event);
1908 +
1909 + if (foreach_not_ignored_block(ram_block_load_eager, NULL)) {
1910 + next_state = MIGRATION_STATUS_FAILED;
1911 + } else {
1912 + next_state = MIGRATION_STATUS_COMPLETED;
1913 + }
1914 + migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
1915 + next_state);
1916 +
1917 + postcopy_state_set(POSTCOPY_INCOMING_END);
1918 + migration_bh_schedule(postcopy_incoming_complete_bh, mis);
1919 +
1920 + rcu_unregister_thread();
1921 + trace_postcopy_ram_eager_load_thread_exit();
1922 + return NULL;
1923 +}
1924 +
1925 +/*
1926 + * Create thread for eager loading in fast snapshot load case
1927 + */
1928 +void postcopy_ram_eager_load_setup(MigrationIncomingState *mis)
1929 +{
1930 + postcopy_thread_create(
1931 + mis, &mis->eager_load_thread, MIGRATION_THREAD_DST_SNAPSHOT_LOAD,
1932 + postcopy_ram_eager_load_thread, QEMU_THREAD_JOINABLE);
1933 + mis->have_eager_load_thread = true;
1934 +}
1935 +
1936 #else
1937 /* No target OS support, stubs just fail */
1938 void fill_destination_postcopy_migration_info(MigrationInfo *info)
@@ -1937,6 +2003,11 @@ bool try_mark_postcopy_blocktime_begin(MigrationIncomingState *mis,
2003 g_assert_not_reached();
2004 return false;
2005 }
2006 +
2007 +void postcopy_ram_eager_load_setup(MigrationIncomingState *mis)
2008 +{
2009 + g_assert_not_reached();
2010 +}
2011 #endif
2012
2013 /* ------------------------------------------------------------------------- */
@@ -2410,6 +2481,11 @@ int postcopy_incoming_cleanup(MigrationIncomingState *mis)
2481 mis->have_listen_thread = false;
2482 }
2483
2484 + if (mis->have_eager_load_thread) {
2485 + qemu_thread_join(&mis->eager_load_thread);
2486 + mis->have_eager_load_thread = false;
2487 + }
2488 +
2489 if (migrate_postcopy_ram()) {
2490 rc = postcopy_ram_incoming_cleanup(mis);
2491 }
migration/postcopy-ram.h
+2
@@ -205,4 +205,6 @@ void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid,
205 int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp);
206 int postcopy_incoming_cleanup(MigrationIncomingState *mis);
207
208 +void postcopy_ram_eager_load_setup(MigrationIncomingState *mis);
209 +
210 #endif
migration/savevm.c
+16
@@ -3004,6 +3004,22 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
3004 return true;
3005 }
3006
3007 +/*
3008 + * Starts the VM and launches the eager thread for fast snapshot load
3009 + */
3010 +void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
3011 + MigrationIncomingState *mis)
3012 +{
3013 + postcopy_state_set(POSTCOPY_INCOMING_RUNNING);
3014 +
3015 + migration_bh_schedule(loadvm_postcopy_handle_run_bh, mis);
3016 +
3017 + migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_DEVICE,
3018 + MIGRATION_STATUS_POSTCOPY_ACTIVE);
3019 +
3020 + postcopy_ram_eager_load_setup(mis);
3021 +}
3022 +
3023 int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
3024 Error **errp)
3025 {
migration/savevm.h
+2
@@ -70,6 +70,8 @@ void qemu_savevm_send_postcopy_ram_discard(QEMUFile *f, const char *name,
70 int qemu_save_device_state(QEMUFile *f, Error **errp);
71 int qemu_loadvm_state(QEMUFile *f, Error **errp);
72 void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
73 +void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
74 + MigrationIncomingState *mis);
75 int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
76 Error **errp);
77 int qemu_load_device_state(QEMUFile *f, Error **errp);
migration/trace-events
+2
@@ -315,6 +315,8 @@ postcopy_blocktime_tid_cpu_map(int cpu, uint32_t tid) "cpu: %d, tid: %u"
315 postcopy_blocktime_begin(uint64_t addr, uint64_t time, int cpu, bool exists) "addr: 0x%" PRIx64 ", time: %" PRIu64 ", cpu: %d, exist: %d"
316 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"
317 postcopy_blocktime_end_one(int cpu, uint8_t left_faults) "cpu: %d, left_faults: %" PRIu8
318 +postcopy_ram_eager_load_thread_entry(void) ""
319 +postcopy_ram_eager_load_thread_exit(void) ""
320
321 # exec.c
322 migration_exec_outgoing(const char *cmd) "cmd=%s"