From 5e0d4985829940f142bd3788b9c17bf21489bf2a Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Wed, 21 Jul 2021 19:16:30 -0700 Subject: [PATCH] hypervisor, vmm: Add dynamic control of logging dirty pages This patch extends slightly the current live-migration code path with the ability to dynamically start and stop logging dirty-pages, which relies on two new methods added to the `hypervisor::vm::Vm` Trait. This patch also contains a complete implementation of the two new methods based on `kvm` and placeholders for `mshv` in the `hypervisor` crate. Fixes: #2858 Signed-off-by: Bo Chen --- hypervisor/src/kvm/mod.rs | 53 ++++++++++++++++++++++++++++++++++++++ hypervisor/src/mshv/mod.rs | 28 ++++++++++++++++++++ hypervisor/src/vm.rs | 26 +++++++++++++++++++ vmm/src/lib.rs | 3 +++ vmm/src/memory_manager.rs | 35 +++++++++++++++++++++---- vmm/src/vm.rs | 4 +++ 6 files changed, 144 insertions(+), 5 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index fccd6145b..20d04aeed 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -383,6 +383,59 @@ impl vm::Vm for KvmVm { Ok(()) } + /// + /// Start logging dirty pages + /// + fn start_dirty_log( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + ) -> vm::Result<()> { + let region = self.make_user_memory_region( + slot, + guest_phys_addr, + memory_size, + userspace_addr, + false, + true, + ); + // Safe because guest regions are guaranteed not to overlap. + unsafe { + self.fd + .set_user_memory_region(region) + .map_err(|e| vm::HypervisorVmError::StartDirtyLog(e.into())) + } + } + + /// + /// Stop logging dirty pages + /// + fn stop_dirty_log( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + ) -> vm::Result<()> { + let region = self.make_user_memory_region( + slot, + guest_phys_addr, + memory_size, + userspace_addr, + false, + false, + ); + + // Safe because guest regions are guaranteed not to overlap. + unsafe { + self.fd + .set_user_memory_region(region) + .map_err(|e| vm::HypervisorVmError::StopDirtyLog(e.into())) + } + } + /// /// Get dirty pages bitmap (one bit per page) /// diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 395ef095d..edb5d804b 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -877,6 +877,34 @@ impl vm::Vm for MshvVm { Ok(()) } /// + /// Start logging dirty pages + /// + fn start_dirty_log( + &self, + _slot: u32, + _guest_phys_addr: u64, + _memory_size: u64, + _userspace_addr: u64, + ) -> vm::Result<()> { + Err(vm::HypervisorVmError::StartDirtyLog(anyhow!( + "functionality not implemented" + ))) + } + /// + /// Stop logging dirty pages + /// + fn stop_dirty_log( + &self, + _slot: u32, + _guest_phys_addr: u64, + _memory_size: u64, + _userspace_addr: u64, + ) -> vm::Result<()> { + Err(vm::HypervisorVmError::StopDirtyLog(anyhow!( + "functionality not implemented" + ))) + } + /// /// Get dirty pages bitmap (one bit per page) /// fn get_dirty_log(&self, _slot: u32, _memory_size: u64) -> vm::Result> { diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index d91016109..ca84e3a16 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -168,6 +168,16 @@ pub enum HypervisorVmError { #[error("Failed to write to IO Bus: {0}")] IoBusWrite(#[source] anyhow::Error), /// + /// Start dirty log error + /// + #[error("Failed to get dirty log: {0}")] + StartDirtyLog(#[source] anyhow::Error), + /// + /// Stop dirty log error + /// + #[error("Failed to get dirty log: {0}")] + StopDirtyLog(#[source] anyhow::Error), + /// /// Get dirty log error /// #[error("Failed to get dirty log: {0}")] @@ -270,6 +280,22 @@ pub trait Vm: Send + Sync { fn state(&self) -> Result; /// Set the VM state fn set_state(&self, state: VmState) -> Result<()>; + /// Start logging dirty pages + fn start_dirty_log( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + ) -> Result<()>; + /// Stop logging dirty pages + fn stop_dirty_log( + &self, + slot: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, + ) -> Result<()>; /// Get dirty pages bitmap fn get_dirty_log(&self, slot: u32, memory_size: u64) -> Result>; #[cfg(feature = "tdx")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index b50f85aeb..e597bdd42 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1049,6 +1049,9 @@ impl Vmm { // Send last batch of dirty pages Self::vm_maybe_send_dirty_pages(vm, &mut socket)?; + // Stop logging dirty pages + vm.stop_memory_dirty_log()?; + // Capture snapshot and send it let vm_snapshot = vm.snapshot()?; let snapshot_data = serde_json::to_vec(&vm_snapshot).unwrap(); diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 85215cc2c..01568960d 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1561,14 +1561,22 @@ impl MemoryManager { Ok(table) } - // The dirty log is cleared by the kernel by calling the KVM_GET_DIRTY_LOG ioctl. - // Just before we do a bulk copy we want to clear the dirty log so that + // Start the dirty log on guest RAM in the hypervisor (kvm/mshv). + // Also, reset the dirty bitmap logged by the vmm. + // Just before we do a bulk copy we want to start/clear the dirty log so that // pages touched during our bulk copy are tracked. pub fn start_memory_dirty_log(&self) -> std::result::Result<(), MigratableError> { for r in &self.guest_ram_mappings { - self.vm.get_dirty_log(r.slot, r.size).map_err(|e| { - MigratableError::MigrateSend(anyhow!("Error getting VM dirty log {}", e)) - })?; + let user_addr = self + .guest_memory() + .memory() + .get_host_address(GuestAddress(r.gpa)) + .unwrap(); + self.vm + .start_dirty_log(r.slot, r.gpa, r.size, user_addr as u64) + .map_err(|e| { + MigratableError::MigrateSend(anyhow!("Error starting VM dirty log {}", e)) + })?; } for r in self.guest_memory.memory().iter() { @@ -1577,6 +1585,23 @@ impl MemoryManager { Ok(()) } + + pub fn stop_memory_dirty_log(&self) -> std::result::Result<(), MigratableError> { + for r in &self.guest_ram_mappings { + let user_addr = self + .guest_memory() + .memory() + .get_host_address(GuestAddress(r.gpa)) + .unwrap(); + self.vm + .stop_dirty_log(r.slot, r.gpa, r.size, user_addr as u64) + .map_err(|e| { + MigratableError::MigrateSend(anyhow!("Error stopping VM dirty log {}", e)) + })?; + } + + Ok(()) + } } #[cfg(feature = "acpi")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 898adaf10..792c96468 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2156,6 +2156,10 @@ impl Vm { self.memory_manager.lock().unwrap().start_memory_dirty_log() } + pub fn stop_memory_dirty_log(&self) -> std::result::Result<(), MigratableError> { + self.memory_manager.lock().unwrap().stop_memory_dirty_log() + } + pub fn dirty_memory_range_table( &self, ) -> std::result::Result {