diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index db84542e3..1a9c53dd3 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1187,7 +1187,7 @@ impl Vmm { // Send the current dirty pages let transfer_begin = Instant::now(); - migration_transport::vm_send_dirty_pages(vm, socket, &iteration_table)?; + migration_transport::send_memory_ranges(&vm.guest_memory(), &iteration_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); @@ -1329,7 +1329,7 @@ impl Vmm { ctx.update_metrics_before_transfer(iteration_begin, &final_table); let transfer_begin = Instant::now(); - migration_transport::vm_send_dirty_pages(vm, socket, &final_table)?; + migration_transport::send_memory_ranges(&vm.guest_memory(), &final_table, socket)?; let transfer_duration = transfer_begin.elapsed(); ctx.update_metrics_after_transfer(transfer_begin, transfer_duration); ctx.iteration += 1; diff --git a/vmm/src/migration_transport.rs b/vmm/src/migration_transport.rs index fd3e035e1..22dac6f71 100644 --- a/vmm/src/migration_transport.rs +++ b/vmm/src/migration_transport.rs @@ -12,11 +12,11 @@ use std::result::Result; use anyhow::{Context, anyhow}; use log::info; use serde_json; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{MigratableError, Snapshot}; -use crate::vm::Vm; -use crate::{SocketStream, VmMigrationConfig}; +use crate::{GuestMemoryMmap, SocketStream, VmMigrationConfig}; /// Extract a UNIX socket path from a "unix:" migration URL. fn socket_url_to_path(url: &str) -> Result { @@ -137,29 +137,55 @@ pub(crate) fn send_state( ) } -/// Transmits the given [`MemoryRangeTable`] over the wire if there is at -/// least one region. +/// Transmits the given [`MemoryRangeTable`] and the corresponding guest memory +/// content over the wire if there is at least one range. /// /// Sends a memory migration request, the range table, and the corresponding -/// guest memory regions over the given socket. Waits for acknowledgment +/// guest memory range over the given socket. Waits for acknowledgment /// from the destination. -pub(crate) fn vm_send_dirty_pages( - vm: &mut Vm, +pub(crate) fn send_memory_ranges( + guest_memory: &GuestMemoryAtomic, + ranges: &MemoryRangeTable, socket: &mut SocketStream, - table: &MemoryRangeTable, ) -> Result<(), MigratableError> { - if table.regions().is_empty() { + if ranges.regions().is_empty() { return Ok(()); } - Request::memory(table.length()).write_to(socket)?; - table.write_to(socket)?; + // Send the memory table + Request::memory(ranges.length()).write_to(socket)?; + ranges.write_to(socket)?; + // And then the memory itself - vm.send_memory_regions(table, socket)?; + let mem = guest_memory.memory(); + for range in ranges.regions() { + let mut offset: u64 = 0; + // Here we are manually handling the retry in case we can't read the + // whole region at once because we can't use the implementation + // from vm-memory::GuestMemory of write_all_to() as it is not + // following the correct behavior. For more info about this issue + // see: https://github.com/rust-vmm/vm-memory/issues/174 + loop { + let bytes_written = mem + .write_volatile_to( + GuestAddress(range.gpa + offset), + socket, + (range.length - offset) as usize, + ) + .map_err(|e| { + MigratableError::MigrateSend(anyhow!( + "Error transferring memory to socket: {e}" + )) + })?; + offset += bytes_written as u64; + + if offset == range.length { + break; + } + } + } expect_ok_response( socket, MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")), - )?; - - Ok(()) + ) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 7a5a19133..c0eaba7ed 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -68,7 +68,7 @@ use vm_device::Bus; use vm_memory::GuestMemory; #[cfg(feature = "tdx")] use vm_memory::{Address, ByteValued, GuestMemoryRegion, ReadVolatile}; -use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, WriteVolatile}; +use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::protocol::{MemoryRangeTable, Request, Response}; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, snapshot_from_id, @@ -2853,49 +2853,6 @@ impl Vm { Ok(()) } - /// Writes the contents of the given guest memory regions to the provided sink. - /// Used, for example, during VM live migration to transfer memory to a socket. - pub fn send_memory_regions( - &mut self, - ranges: &MemoryRangeTable, - fd: &mut F, - ) -> std::result::Result<(), MigratableError> - where - F: WriteVolatile, - { - let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory(); - let mem = guest_memory.memory(); - - for range in ranges.regions() { - let mut offset: u64 = 0; - // Here we are manually handling the retry in case we can't the - // whole region at once because we can't use the implementation - // from vm-memory::GuestMemory of write_all_to() as it is not - // following the correct behavior. For more info about this issue - // see: https://github.com/rust-vmm/vm-memory/issues/174 - loop { - let bytes_written = mem - .write_volatile_to( - GuestAddress(range.gpa + offset), - fd, - (range.length - offset) as usize, - ) - .map_err(|e| { - MigratableError::MigrateSend(anyhow!( - "Error transferring memory to socket: {e}" - )) - })?; - offset += bytes_written as u64; - - if offset == range.length { - break; - } - } - } - - Ok(()) - } - pub fn memory_range_table(&self) -> std::result::Result { self.memory_manager .lock() @@ -2903,6 +2860,10 @@ impl Vm { .memory_range_table(false) } + pub fn guest_memory(&self) -> GuestMemoryAtomic { + self.memory_manager.lock().unwrap().guest_memory() + } + pub fn device_tree(&self) -> Arc> { self.device_manager.lock().unwrap().device_tree() }