vmm: memory: Restore with Copy-on-Write when possible

This patch extends the previous behavior on the restore codepath.
Instead of copying the memory regions content from the snapshot files
into the new memory regions, the VMM will use the snapshot region files
as the backing files behind each mapped region. This is done in order to
reduce the time for the VM to be restored.

When the source VM has been initially started with a backing file, this
means it has been mapped with the MAP_SHARED flag. For this case, we
cannot use the CoW trick to speed up the VM restore path and we simply
fallback onto the copy of the memory regions content.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2020-04-07 11:27:49 +02:00
parent d771223b2f
commit b2cdee80b6

View File

@ -384,8 +384,6 @@ impl MemoryManager {
config: &MemoryConfig,
source_url: &str,
) -> Result<Arc<Mutex<MemoryManager>>, Error> {
let memory_manager = MemoryManager::new(fd, config, None)?;
let url = Url::parse(source_url).unwrap();
/* url must be valid dir which is verified in recv_vm_snapshot() */
let vm_snapshot_path = url.to_file_path().unwrap();
@ -405,35 +403,54 @@ impl MemoryManager {
}
};
memory_manager
.lock()
.unwrap()
.guest_memory
.memory()
.with_regions(|index, region| {
let mut memory_region_path = vm_snapshot_path.clone();
memory_region_path
.push(mem_snapshot.memory_regions[index].backing_file.clone());
let mut ext_regions = mem_snapshot.memory_regions;
for region in ext_regions.iter_mut() {
let mut memory_region_path = vm_snapshot_path.clone();
memory_region_path.push(region.backing_file.clone());
region.backing_file = memory_region_path;
}
// Create the snapshot file for the region
let mut memory_region_file = OpenOptions::new()
.read(true)
.open(memory_region_path)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
// In case there was no backing file, we can safely use CoW by
// mapping the source files provided for restoring. This case
// allows for a faster VM restoration and does not require us to
// fill the memory content, hence we can return right away.
if config.file.is_none() {
return MemoryManager::new(fd, config, Some(ext_regions));
};
region
.read_from(
MemoryRegionAddress(0),
&mut memory_region_file,
region.len().try_into().unwrap(),
)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
let memory_manager = MemoryManager::new(fd, config, None)?;
let guest_memory = memory_manager.lock().unwrap().guest_memory();
Ok(())
})?;
// In case the previous config was using a backing file, this means
// it was MAP_SHARED, therefore we must copy the content into the
// new regions so that we can still use MAP_SHARED when restoring
// the VM.
guest_memory.memory().with_regions(|index, region| {
// Open (read only) the snapshot file for the given region.
let mut memory_region_file = OpenOptions::new()
.read(true)
.open(&ext_regions[index].backing_file)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
// Fill the region with the file content.
region
.read_from(
MemoryRegionAddress(0),
&mut memory_region_file,
region.len().try_into().unwrap(),
)
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
Ok(())
})?;
Ok(memory_manager)
} else {
Err(Error::Restore(MigratableError::Restore(anyhow!(
"Could not find {}-section from snapshot",
MEMORY_MANAGER_SNAPSHOT_ID
))))
}
Ok(memory_manager)
}
fn create_ram_region(