vm-migration: better naming + unittests

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
This commit is contained in:
Philipp Schuster
2025-11-12 11:18:35 +01:00
committed by Rob Bradford
parent b6c266c880
commit a7fa3a0c86
4 changed files with 56 additions and 18 deletions

View File

@@ -393,7 +393,7 @@ impl BlocksState {
// TODO We can avoid creating a new bitmap here, if we switch the code
// to use Vec<u64> to keep dirty bits and just pass it as is.
MemoryRangeTable::from_bitmap(bitmap, start_addr, VIRTIO_MEM_DEFAULT_BLOCK_SIZE)
MemoryRangeTable::from_dirty_bitmap(bitmap, start_addr, VIRTIO_MEM_DEFAULT_BLOCK_SIZE)
}
}

View File

@@ -575,7 +575,7 @@ impl VhostUserHandle {
let ptr = region.as_ptr() as *const u64;
std::slice::from_raw_parts(ptr, len)
};
Ok(MemoryRangeTable::from_bitmap(
Ok(MemoryRangeTable::from_dirty_bitmap(
bitmap.iter().copied(),
0,
4096,

View File

@@ -207,19 +207,28 @@ impl Response {
}
#[repr(C)]
#[derive(Clone, Default, Serialize, Deserialize)]
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryRange {
pub gpa: u64,
pub length: u64,
}
impl MemoryRange {
/// Turn an iterator over the dirty bitmap into an iterator of dirty ranges.
pub fn dirty_ranges(
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct MemoryRangeTable {
data: Vec<MemoryRange>,
}
impl MemoryRangeTable {
/// Converts an iterator over a dirty bitmap into an iterator of dirty
/// [`MemoryRange`]s, merging consecutive dirty pages into contiguous ranges.
///
/// A memory page (i.e., a range) is marked dirty when its corresponding bit
/// is set.
fn dirty_ranges_iter(
bitmap: impl IntoIterator<Item = u64>,
start_addr: u64,
page_size: u64,
) -> impl Iterator<Item = Self> {
) -> impl Iterator<Item = MemoryRange> {
bitmap
.into_iter()
.bit_positions()
@@ -233,26 +242,23 @@ impl MemoryRange {
Err((prev, curr))
}
})
.map(move |r| Self {
.map(move |r| MemoryRange {
gpa: start_addr + r.start * page_size,
length: (r.end - r.start) * page_size,
})
}
}
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct MemoryRangeTable {
data: Vec<MemoryRange>,
}
impl MemoryRangeTable {
pub fn from_bitmap(
/// Creates a new [`MemoryRangeTable`] from a bitmap (represented as
/// multiple `u64`) where each bit corresponds to a dirty memory page.
///
/// Only dirty ranges are represented in the resulting bitmap.
pub fn from_dirty_bitmap(
bitmap: impl IntoIterator<Item = u64>,
start_addr: u64,
page_size: u64,
) -> Self {
Self {
data: MemoryRange::dirty_ranges(bitmap, start_addr, page_size).collect(),
data: Self::dirty_ranges_iter(bitmap, start_addr, page_size).collect(),
}
}
@@ -312,3 +318,35 @@ impl MemoryRangeTable {
Self { data }
}
}
#[cfg(test)]
mod tests {
use crate::protocol::{MemoryRange, MemoryRangeTable};
#[test]
fn test_memory_range_table_from_dirty_ranges_iter() {
let input = [0b1111_1110_1110, 0b1_0000];
let start_gpa = 0x1000;
let page_size = 0x1000;
let range = MemoryRangeTable::from_dirty_bitmap(input, start_gpa, page_size);
assert_eq!(
range.regions(),
&[
MemoryRange {
gpa: start_gpa + page_size,
length: page_size * 3,
},
MemoryRange {
gpa: start_gpa + 5 * page_size,
length: page_size * 7,
},
MemoryRange {
gpa: start_gpa + (64 + 4) * page_size,
length: page_size,
}
]
);
}
}

View File

@@ -2605,7 +2605,7 @@ impl Migratable for MemoryManager {
.zip(vmm_dirty_bitmap.iter())
.map(|(x, y)| x | y);
let sub_table = MemoryRangeTable::from_bitmap(dirty_bitmap, r.gpa, 4096);
let sub_table = MemoryRangeTable::from_dirty_bitmap(dirty_bitmap, r.gpa, 4096);
if sub_table.regions().is_empty() {
info!("Dirty Memory Range Table is empty");