[PATCH v3 06/11] migration: add RAMBlock field and helper for fast snapshot load

Aadeshveer Singh posted 11 patches 2 months ago
Maintainers: Peter Xu <peterx@redhat.com>, Fabiano Rosas <farosas@suse.de>, Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>, Paolo Bonzini <pbonzini@redhat.com>, "Philippe Mathieu-Daudé" <philmd@mailo.com>, Laurent Vivier <lvivier@redhat.com>
There is a newer version of this series
[PATCH v3 06/11] migration: add RAMBlock field and helper for fast snapshot load
Posted by Aadeshveer Singh 2 months ago
Add pending_bmap field per RAMBlock which is a Bitmap to store
internal state of which pages have been read by some thread to ensure
coordination between fault thread and eager load thread.

Modify parse_ramblock_mapped_ram(), to not load the actual RAMBlocks
data in postcopy case as that will be loaded by fault thread and eager
thread after the VM starts running.

Change ram_load() to use new function ram_should_load_postcopy_pages()
to decide how to load/read RAM.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 include/system/ramblock.h |  6 ++++
 migration/ram.c           | 59 +++++++++++++++++++++++++++++++++++----
 2 files changed, 59 insertions(+), 6 deletions(-)

diff --git a/include/system/ramblock.h b/include/system/ramblock.h
index 4435f8d55f..83187bf44c 100644
--- a/include/system/ramblock.h
+++ b/include/system/ramblock.h
@@ -60,6 +60,12 @@ struct RAMBlock {
 
     /* Bitmap of already received pages.  Only used on destination side. */
     unsigned long *receivedmap;
+    /*
+     * Bitmap for pages that are yet to be read from disk. It is required for
+     * fault thread and eager thread to keep note of which pages are currently
+     * being read. Used by fast snapshot load.
+     */
+    unsigned long *pending_bmap;
 
     /*
      * bitmap to track already cleared dirty bitmap.  When the bit is
diff --git a/migration/ram.c b/migration/ram.c
index 967db7c0db..330fceaa43 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -263,6 +263,18 @@ static void ramblock_file_bmap_init(void)
     }
 }
 
+static void ramblock_pending_bmap_init(void)
+{
+    RAMBlock *rb;
+
+    RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
+        assert(!rb->pending_bmap);
+        size_t size = rb->max_length >> qemu_target_page_bits();
+        rb->pending_bmap = bitmap_new(size);
+        bitmap_set(rb->pending_bmap, 0, size);
+    }
+}
+
 static void ramblock_recv_map_init(void)
 {
     RAMBlock *rb;
@@ -3762,6 +3774,10 @@ static int ram_load_setup(QEMUFile *f, void *opaque, Error **errp)
     ramblock_recv_map_init();
     if (migrate_mapped_ram()) {
         ramblock_file_bmap_init();
+        if (migrate_postcopy_ram()) {
+            /* fast snapshot load */
+            ramblock_pending_bmap_init();
+        }
     }
 
     return 0;
@@ -3782,6 +3798,7 @@ static int ram_load_cleanup(void *opaque)
     RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
         g_clear_pointer(&rb->receivedmap, g_free);
         g_clear_pointer(&rb->file_bmap, g_free);
+        g_clear_pointer(&rb->pending_bmap, g_free);
     }
 
     return 0;
@@ -4201,9 +4218,12 @@ static void parse_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
         return;
     }
 
-    if (!read_ramblock_mapped_ram(f, block, num_pages, block->file_bmap,
-                                  errp)) {
-        return;
+    if (!migrate_postcopy_ram()) {
+        /* Do not load RAM during setup for fast snapshot load */
+        if (!read_ramblock_mapped_ram(f, block, num_pages, block->file_bmap,
+                                      errp)) {
+            return;
+        }
     }
 
     /* Skip pages array */
@@ -4475,15 +4495,42 @@ static int ram_load_precopy(QEMUFile *f)
     return ret;
 }
 
+static bool ram_should_load_postcopy_pages(void)
+{
+    /* This is pure precopy, we don't need to load pages in postcopy way */
+    if (!postcopy_is_running()) {
+        return false;
+    }
+
+    /*
+     * This is postcopy, but when with mapped-ram, pages are not loaded in the
+     * migration stream here, but done separately in a thread eagerly reading
+     * pages from the snapshot.  Here, we only need to read the ram headers,
+     * reusing the precopy code.
+     * TODO: when we have separate function to parse RAM headers we should
+     * switch to that.
+     */
+    if (migrate_mapped_ram()) {
+        return false;
+    }
+
+    /*
+     * Genuine network postcopy, we will load pages in this current stream and
+     * they need to be done in postcopy way.
+     */
+    return true;
+}
+
 static int ram_load(QEMUFile *f, void *opaque, int version_id)
 {
     int ret = 0;
     static uint64_t seq_iter;
     /*
      * If system is running in postcopy mode, page inserts to host memory must
-     * be atomic
+     * be atomic. However, fast snapshot load uses the mapped ram precopy like
+     * path to read block headers and populating bitmaps.
      */
-    bool postcopy_running = postcopy_is_running();
+    bool load_postcopy_pages = ram_should_load_postcopy_pages();
 
     seq_iter++;
 
@@ -4499,7 +4546,7 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
      */
     trace_ram_load_start();
     WITH_RCU_READ_LOCK_GUARD() {
-        if (postcopy_running) {
+        if (load_postcopy_pages) {
             /*
              * Note!  Here RAM_CHANNEL_PRECOPY is the precopy channel of
              * postcopy migration, we have another RAM_CHANNEL_POSTCOPY to
-- 
2.55.0
Re: [PATCH v3 06/11] migration: add RAMBlock field and helper for fast snapshot load
Posted by Peter Xu 1 month, 3 weeks ago
On Tue, Jul 14, 2026 at 07:45:42PM +0530, Aadeshveer Singh wrote:
> +static void ramblock_pending_bmap_init(void)
> +{
> +    RAMBlock *rb;
> +
> +    RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
> +        assert(!rb->pending_bmap);
> +        size_t size = rb->max_length >> qemu_target_page_bits();

I forgot one thing here: I think it should use qemu_ram_pagesize(), for
huge pages when the fault thread or eager load thread want to figure how
who to do the work, it should always be in host psize, not guest-psize,
because that's the granule userfaultfd's UFFDIO_COPY/ZEROPAGE uses.

> +        rb->pending_bmap = bitmap_new(size);
> +        bitmap_set(rb->pending_bmap, 0, size);
> +    }
> +}

-- 
Peter Xu
Re: [PATCH v3 06/11] migration: add RAMBlock field and helper for fast snapshot load
Posted by Aadeshveer Singh 1 month, 3 weeks ago
On Tue, Jul 21, 2026 at 12:09 AM Peter Xu <peterx@redhat.com> wrote:
>
> On Tue, Jul 14, 2026 at 07:45:42PM +0530, Aadeshveer Singh wrote:
> > +static void ramblock_pending_bmap_init(void)
> > +{
> > +    RAMBlock *rb;
> > +
> > +    RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
> > +        assert(!rb->pending_bmap);
> > +        size_t size = rb->max_length >> qemu_target_page_bits();
>
> I forgot one thing here: I think it should use qemu_ram_pagesize(), for
> huge pages when the fault thread or eager load thread want to figure how
> who to do the work, it should always be in host psize, not guest-psize,
> because that's the granule userfaultfd's UFFDIO_COPY/ZEROPAGE uses.
>

Hi Peter,

Thanks for pointing this out; I think I noted that but missed changing it.
I will address this in the next version.

> > +        rb->pending_bmap = bitmap_new(size);
> > +        bitmap_set(rb->pending_bmap, 0, size);
> > +    }
> > +}
>
> --
> Peter Xu
>
Re: [PATCH v3 06/11] migration: add RAMBlock field and helper for fast snapshot load
Posted by Peter Xu 1 month, 3 weeks ago
On Tue, Jul 14, 2026 at 07:45:42PM +0530, Aadeshveer Singh wrote:
> Add pending_bmap field per RAMBlock which is a Bitmap to store
> internal state of which pages have been read by some thread to ensure
> coordination between fault thread and eager load thread.

The other way to do this is squashing this three core patches (starting
from this one) into one big patch implementing the idea.  Otherwise it
might be hard to review a newly added bitmap when it got allocated but
never used.

But it's still fine when implementing a major feature like this - people
may have different opinion on how to split patches, you can keep it like
this if nobody else complains.

> 
> Modify parse_ramblock_mapped_ram(), to not load the actual RAMBlocks
> data in postcopy case as that will be loaded by fault thread and eager
> thread after the VM starts running.
> 
> Change ram_load() to use new function ram_should_load_postcopy_pages()
> to decide how to load/read RAM.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>

Reviewed-by: Peter Xu <peterx@redhat.com>

-- 
Peter Xu