dragonball: introduce virtio-mem device

We introduce virtio-mem device to support memory resize. virtio-mem
device could hot-plug more memory blocks to guest and could also
hot-unplug them from guest.

Fixes: #6719

Signed-off-by: Helin Guo <helinguo@linux.alibaba.com>
This commit is contained in:
Helin Guo 2023-05-31 10:13:09 +08:00
parent a8e0f51c52
commit 7ed9494973
7 changed files with 885 additions and 10 deletions

View File

@ -209,11 +209,12 @@ dependencies = [
[[package]] [[package]]
name = "dbs-address-space" name = "dbs-address-space"
version = "0.2.2" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bcc37dc0b8ffae1c5911d13ae630dc7a9020fa0de0edd178d6ab71daf56c8fc" checksum = "95e20d28a9cd13bf00d0ecd1bd073d242242b04f0acb663d7adfc659f8879322"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"lazy_static",
"libc", "libc",
"nix 0.23.2", "nix 0.23.2",
"thiserror", "thiserror",
@ -300,9 +301,9 @@ dependencies = [
[[package]] [[package]]
name = "dbs-upcall" name = "dbs-upcall"
version = "0.2.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "699e62afa444ae4b00d474fd91bc37785ba050acdfbe179731c81898e32efc3f" checksum = "ea3a78128fd0be8b8b10257675c262b378dc5d00b1e18157736a6c27e45ce4fb"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dbs-utils", "dbs-utils",
@ -330,9 +331,9 @@ dependencies = [
[[package]] [[package]]
name = "dbs-virtio-devices" name = "dbs-virtio-devices"
version = "0.2.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88e5c6c48b766afb95851b04b6b193871a59d0b2a3ed19990d4f8f651ae5c668" checksum = "24d671cc3e5f98b84ef6b6bed007d28f72f16d3aea8eb38e2d42b00b2973c1d8"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"caps", "caps",
@ -346,7 +347,7 @@ dependencies = [
"kvm-ioctls", "kvm-ioctls",
"libc", "libc",
"log", "log",
"nix 0.23.2", "nix 0.24.3",
"nydus-api", "nydus-api",
"nydus-blobfs", "nydus-blobfs",
"nydus-rafs", "nydus-rafs",

View File

@ -12,16 +12,16 @@ edition = "2018"
[dependencies] [dependencies]
arc-swap = "1.5.0" arc-swap = "1.5.0"
bytes = "1.1.0" bytes = "1.1.0"
dbs-address-space = "0.2.0" dbs-address-space = "0.3.0"
dbs-allocator = "0.1.0" dbs-allocator = "0.1.0"
dbs-arch = "0.2.0" dbs-arch = "0.2.0"
dbs-boot = "0.4.0" dbs-boot = "0.4.0"
dbs-device = "0.2.0" dbs-device = "0.2.0"
dbs-interrupt = { version = "0.2.0", features = ["kvm-irq"] } dbs-interrupt = { version = "0.2.0", features = ["kvm-irq"] }
dbs-legacy-devices = "0.1.0" dbs-legacy-devices = "0.1.0"
dbs-upcall = { version = "0.2.0", optional = true } dbs-upcall = { version = "0.3.0", optional = true }
dbs-utils = "0.2.0" dbs-utils = "0.2.0"
dbs-virtio-devices = { version = "0.2.0", optional = true, features = ["virtio-mmio"] } dbs-virtio-devices = { version = "0.3.1", optional = true, features = ["virtio-mmio"] }
kvm-bindings = "0.6.0" kvm-bindings = "0.6.0"
kvm-ioctls = "0.12.0" kvm-ioctls = "0.12.0"
lazy_static = "1.2" lazy_static = "1.2"
@ -55,3 +55,4 @@ virtio-blk = ["dbs-virtio-devices/virtio-blk", "virtio-queue"]
virtio-net = ["dbs-virtio-devices/virtio-net", "virtio-queue"] virtio-net = ["dbs-virtio-devices/virtio-net", "virtio-queue"]
# virtio-fs only work on atomic-guest-memory # virtio-fs only work on atomic-guest-memory
virtio-fs = ["dbs-virtio-devices/virtio-fs", "virtio-queue", "atomic-guest-memory"] virtio-fs = ["dbs-virtio-devices/virtio-fs", "virtio-queue", "atomic-guest-memory"]
virtio-mem = ["dbs-virtio-devices/virtio-mem", "virtio-queue", "atomic-guest-memory"]

View File

@ -27,6 +27,8 @@ pub use crate::device_manager::blk_dev_mgr::{
pub use crate::device_manager::fs_dev_mgr::{ pub use crate::device_manager::fs_dev_mgr::{
FsDeviceConfigInfo, FsDeviceConfigUpdateInfo, FsDeviceError, FsDeviceMgr, FsMountConfigInfo, FsDeviceConfigInfo, FsDeviceConfigUpdateInfo, FsDeviceError, FsDeviceMgr, FsMountConfigInfo,
}; };
#[cfg(feature = "virtio-mem")]
pub use crate::device_manager::mem_dev_mgr::{MemDeviceConfigInfo, MemDeviceError};
#[cfg(feature = "virtio-net")] #[cfg(feature = "virtio-net")]
pub use crate::device_manager::virtio_net_dev_mgr::{ pub use crate::device_manager::virtio_net_dev_mgr::{
VirtioNetDeviceConfigInfo, VirtioNetDeviceConfigUpdateInfo, VirtioNetDeviceError, VirtioNetDeviceConfigInfo, VirtioNetDeviceConfigUpdateInfo, VirtioNetDeviceError,
@ -97,6 +99,15 @@ pub enum VmmActionError {
/// The action `ResizeVcpu` Failed /// The action `ResizeVcpu` Failed
#[error("vcpu resize error : {0}")] #[error("vcpu resize error : {0}")]
ResizeVcpu(#[source] VcpuResizeError), ResizeVcpu(#[source] VcpuResizeError),
/// Cannot access address space.
#[error("Cannot access address space.")]
AddressSpaceNotInitialized,
#[cfg(feature = "virtio-mem")]
/// Mem device related errors.
#[error("virtio-mem device error: {0}")]
Mem(#[source] MemDeviceError),
} }
/// This enum represents the public interface of the VMM. Each action contains various /// This enum represents the public interface of the VMM. Each action contains various
@ -172,6 +183,10 @@ pub enum VmmAction {
#[cfg(feature = "hotplug")] #[cfg(feature = "hotplug")]
/// Resize Vcpu number in the guest. /// Resize Vcpu number in the guest.
ResizeVcpu(VcpuResizeInfo), ResizeVcpu(VcpuResizeInfo),
#[cfg(feature = "virtio-mem")]
/// Add a new mem device or update one that already exists using the `MemDeviceConfig` as input.
InsertMemDevice(MemDeviceConfigInfo),
} }
/// The enum represents the response sent by the VMM in case of success. The response is either /// The enum represents the response sent by the VMM in case of success. The response is either
@ -274,6 +289,8 @@ impl VmmService {
} }
#[cfg(feature = "hotplug")] #[cfg(feature = "hotplug")]
VmmAction::ResizeVcpu(vcpu_resize_cfg) => self.resize_vcpu(vmm, vcpu_resize_cfg), VmmAction::ResizeVcpu(vcpu_resize_cfg) => self.resize_vcpu(vmm, vcpu_resize_cfg),
#[cfg(feature = "virtio-mem")]
VmmAction::InsertMemDevice(mem_cfg) => self.add_mem_device(vmm, event_mgr, mem_cfg),
}; };
debug!("send vmm response: {:?}", response); debug!("send vmm response: {:?}", response);
@ -648,6 +665,32 @@ impl VmmService {
Ok(VmmData::Empty) Ok(VmmData::Empty)
} }
#[cfg(feature = "virtio-mem")]
fn add_mem_device(
&mut self,
vmm: &mut Vmm,
event_mgr: &mut EventManager,
config: MemDeviceConfigInfo,
) -> VmmRequestResult {
let vm = vmm.get_vm_mut().ok_or(VmmActionError::InvalidVMID)?;
let ctx = vm
.create_device_op_context(Some(event_mgr.epoll_manager()))
.map_err(|e| {
if let StartMicroVmError::UpcallServerNotReady = e {
VmmActionError::UpcallServerNotReady
} else {
VmmActionError::StartMicroVm(e)
}
})?;
vm.device_manager_mut()
.mem_manager
.insert_or_update_device(ctx, config)
.map(|_| VmmData::Empty)
.map_err(VmmActionError::Mem)
}
} }
fn handle_cpu_topology( fn handle_cpu_topology(
@ -1456,4 +1499,44 @@ mod tests {
t.check_request(); t.check_request();
} }
} }
#[cfg(feature = "virtio-mem")]
#[test]
fn test_vmm_action_insert_mem_device() {
skip_if_not_root!();
let tests = &mut [
// hotplug unready
TestData::new(
VmmAction::InsertMemDevice(MemDeviceConfigInfo::default()),
InstanceState::Running,
&|result| {
assert!(matches!(
result,
Err(VmmActionError::StartMicroVm(
StartMicroVmError::UpcallMissVsock
))
));
let err_string = format!("{}", result.unwrap_err());
let expected_err = String::from(
"failed to boot the VM: \
the upcall client needs a virtio-vsock device for communication",
);
assert_eq!(err_string, expected_err);
},
),
// success
TestData::new(
VmmAction::InsertMemDevice(MemDeviceConfigInfo::default()),
InstanceState::Uninitialized,
&|result| {
assert!(result.is_ok());
},
),
];
for t in tests.iter_mut() {
t.check_request();
}
}
} }

View File

@ -871,6 +871,8 @@ mod tests {
Some(vm.vm_as().unwrap().clone()), Some(vm.vm_as().unwrap().clone()),
None, None,
false, false,
Some(vm.vm_config().clone()),
vm.shared_info().clone(),
); );
let dummy_file = TempFile::new().unwrap(); let dummy_file = TempFile::new().unwrap();
@ -907,6 +909,8 @@ mod tests {
Some(vm.vm_as().unwrap().clone()), Some(vm.vm_as().unwrap().clone()),
None, None,
false, false,
Some(vm.vm_config().clone()),
vm.shared_info().clone(),
); );
vm.device_manager_mut() vm.device_manager_mut()

View File

@ -0,0 +1,733 @@
// Copyright 2020 Alibaba Cloud. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::io;
use std::sync::{Arc, Mutex};
use dbs_address_space::{
AddressSpace, AddressSpaceError, AddressSpaceRegion, MPOL_MF_MOVE, MPOL_PREFERRED, USABLE_END,
};
use dbs_utils::epoll_manager::EpollManager;
use dbs_virtio_devices as virtio;
use kvm_bindings::kvm_userspace_memory_region;
use kvm_ioctls::VmFd;
use nix::sys::mman;
use serde_derive::{Deserialize, Serialize};
use slog::{debug, error, info, warn};
use virtio::mem::{Mem, MemRegionFactory};
use virtio::Error as VirtIoError;
use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestRegionMmap, GuestUsize, MmapRegion,
};
use crate::address_space_manager::GuestAddressSpaceImpl;
use crate::config_manager::{ConfigItem, DeviceConfigInfo, DeviceConfigInfos};
use crate::device_manager::DbsMmioV2Device;
use crate::device_manager::{DeviceManager, DeviceMgrError, DeviceOpContext};
use crate::vm::VmConfigInfo;
// The flag of whether to use the shared irq.
const USE_SHARED_IRQ: bool = true;
// The flag of whether to use the generic irq.
const USE_GENERIC_IRQ: bool = false;
const HUGE_PAGE_2M: usize = 0x200000;
// max numa node ids on host
const MAX_NODE: u32 = 64;
/// Errors associated with `MemDeviceConfig`.
#[derive(Debug, thiserror::Error)]
pub enum MemDeviceError {
/// The mem device was already used.
#[error("the virtio-mem ID was already added to a different device")]
MemDeviceAlreadyExists,
/// Cannot perform the requested operation after booting the microVM.
#[error("the update operation is not allowed after boot")]
UpdateNotAllowedPostBoot,
/// insert mem device error
#[error("cannot add virtio-mem device, {0}")]
InsertDeviceFailed(#[source] DeviceMgrError),
/// create mem device error
#[error("cannot create virito-mem device, {0}")]
CreateMemDevice(#[source] DeviceMgrError),
/// create mmio device error
#[error("cannot create virito-mem mmio device, {0}")]
CreateMmioDevice(#[source] DeviceMgrError),
/// resize mem device error
#[error("failure while resizing virtio-mem device, {0}")]
ResizeFailed(#[source] VirtIoError),
/// mem device does not exist
#[error("mem device does not exist")]
DeviceNotExist,
/// address space region error
#[error("address space region error, {0}")]
AddressSpaceRegion(#[source] AddressSpaceError),
/// Cannot initialize a mem device or add a device to the MMIO Bus.
#[error("failure while registering mem device: {0}")]
RegisterMemDevice(#[source] DeviceMgrError),
/// The mem device id doesn't exist.
#[error("invalid mem device id '{0}'")]
InvalidDeviceId(String),
/// The device manager errors.
#[error("DeviceManager error: {0}")]
DeviceManager(#[source] DeviceMgrError),
}
/// Configuration information for a virtio-mem device.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct MemDeviceConfigInfo {
/// Unique identifier of the pmem device
pub mem_id: String,
/// Memory size mib
pub size_mib: u64,
/// Memory capacity mib
pub capacity_mib: u64,
/// Use multi_region or not
pub multi_region: bool,
/// host numa node id
pub host_numa_node_id: Option<u32>,
/// guest numa node id
pub guest_numa_node_id: Option<u16>,
/// Use shared irq
pub use_shared_irq: Option<bool>,
/// Use generic irq
pub use_generic_irq: Option<bool>,
}
impl ConfigItem for MemDeviceConfigInfo {
type Err = MemDeviceError;
fn id(&self) -> &str {
&self.mem_id
}
fn check_conflicts(&self, other: &Self) -> Result<(), MemDeviceError> {
if self.mem_id.as_str() == other.mem_id.as_str() {
Err(MemDeviceError::MemDeviceAlreadyExists)
} else {
Ok(())
}
}
}
/// Mem Device Info
pub type MemDeviceInfo = DeviceConfigInfo<MemDeviceConfigInfo>;
impl ConfigItem for MemDeviceInfo {
type Err = MemDeviceError;
fn id(&self) -> &str {
&self.config.mem_id
}
fn check_conflicts(&self, other: &Self) -> Result<(), MemDeviceError> {
if self.config.mem_id.as_str() == other.config.mem_id.as_str() {
Err(MemDeviceError::MemDeviceAlreadyExists)
} else {
Ok(())
}
}
}
/// Wrapper for the collection that holds all the Mem Devices Configs
#[derive(Clone)]
pub struct MemDeviceMgr {
/// A list of `MemDeviceConfig` objects.
info_list: DeviceConfigInfos<MemDeviceConfigInfo>,
pub(crate) use_shared_irq: bool,
}
impl MemDeviceMgr {
/// Inserts `mem_cfg` in the virtio-mem device configuration list.
/// If an entry with the same id already exists, it will attempt to update
/// the existing entry.
pub fn insert_or_update_device(
&mut self,
mut ctx: DeviceOpContext,
mem_cfg: MemDeviceConfigInfo,
) -> std::result::Result<(), MemDeviceError> {
if !cfg!(feature = "hotplug") && ctx.is_hotplug {
error!(ctx.logger(), "hotplug feature has been disabled.";
"subsystem" => "virito-mem");
return Err(MemDeviceError::UpdateNotAllowedPostBoot);
}
let epoll_mgr = ctx.get_epoll_mgr().map_err(MemDeviceError::DeviceManager)?;
// If the id of the drive already exists in the list, the operation is update.
if let Some(index) = self.get_index_of_mem_dev(&mem_cfg.mem_id) {
// Update an existing memory device
if ctx.is_hotplug {
info!(
ctx.logger(),
"update memory device: {}, size: 0x{:x}MB.",
mem_cfg.mem_id,
mem_cfg.size_mib;
"subsystem" => "virito-mem"
);
self.update_memory_size(index, mem_cfg.size_mib)?;
}
self.info_list.insert_or_update(&mem_cfg)?;
} else {
// Create a new memory device
if !ctx.is_hotplug {
self.info_list.insert_or_update(&mem_cfg)?;
return Ok(());
}
info!(
ctx.logger(),
"hot-add memory device: {}, size: 0x{:x}MB.", mem_cfg.mem_id, mem_cfg.size_mib;
"subsystem" => "virito-mem"
);
let device = Self::create_memory_device(&mem_cfg, &ctx, &epoll_mgr)
.map_err(MemDeviceError::CreateMemDevice)?;
let mmio_device =
DeviceManager::create_mmio_virtio_device_with_device_change_notification(
Box::new(device),
&mut ctx,
mem_cfg.use_shared_irq.unwrap_or(self.use_shared_irq),
mem_cfg.use_generic_irq.unwrap_or(USE_GENERIC_IRQ),
)
.map_err(MemDeviceError::CreateMmioDevice)?;
#[cfg(not(test))]
ctx.insert_hotplug_mmio_device(&mmio_device, None)
.map_err(|e| {
error!(
ctx.logger(),
"failed to hot-add virtio-mem device {}, {}", &mem_cfg.mem_id, e;
"subsystem" => "virito-mem"
);
MemDeviceError::InsertDeviceFailed(e)
})?;
let index = self.info_list.insert_or_update(&mem_cfg)?;
self.info_list[index].set_device(mmio_device);
}
Ok(())
}
/// Attaches all virtio-mem devices from the MemDevicesConfig.
pub fn attach_devices(
&mut self,
ctx: &mut DeviceOpContext,
) -> std::result::Result<(), MemDeviceError> {
let epoll_mgr = ctx.get_epoll_mgr().map_err(MemDeviceError::DeviceManager)?;
for info in self.info_list.iter_mut() {
let config = &info.config;
info!(
ctx.logger(),
"attach virtio-mem device {}, size 0x{:x}.", config.mem_id, config.size_mib;
"subsystem" => "virito-mem"
);
// Ignore virtio-mem device with zero memory capacity.
if config.size_mib == 0 {
debug!(
ctx.logger(),
"ignore zero-sizing memory device {}.", config.mem_id;
"subsystem" => "virito-mem"
);
continue;
}
let device = Self::create_memory_device(config, ctx, &epoll_mgr)
.map_err(MemDeviceError::CreateMemDevice)?;
let mmio_device =
DeviceManager::create_mmio_virtio_device_with_device_change_notification(
Box::new(device),
ctx,
config.use_shared_irq.unwrap_or(self.use_shared_irq),
config.use_generic_irq.unwrap_or(USE_GENERIC_IRQ),
)
.map_err(MemDeviceError::RegisterMemDevice)?;
info.set_device(mmio_device);
}
Ok(())
}
fn get_index_of_mem_dev(&self, mem_id: &str) -> Option<usize> {
self.info_list
.iter()
.position(|info| info.config.mem_id.eq(mem_id))
}
fn create_memory_device(
config: &MemDeviceConfigInfo,
ctx: &DeviceOpContext,
epoll_mgr: &EpollManager,
) -> std::result::Result<virtio::mem::Mem<GuestAddressSpaceImpl>, DeviceMgrError> {
let factory = Arc::new(Mutex::new(MemoryRegionFactory::new(
ctx,
config.mem_id.clone(),
config.host_numa_node_id,
)?));
let mut capacity_mib = config.capacity_mib;
if capacity_mib == 0 {
capacity_mib = *USABLE_END >> 20;
}
// get boot memory size for calculate alignment
let boot_mem_size = {
let boot_size = (ctx.get_vm_config()?.mem_size_mib << 20) as u64;
// increase 1G memory because of avoiding mmio hole
match boot_size {
x if x > dbs_boot::layout::MMIO_LOW_START => x + (1 << 30),
_ => boot_size,
}
};
virtio::mem::Mem::new(
config.mem_id.clone(),
capacity_mib,
config.size_mib,
config.multi_region,
config.guest_numa_node_id,
epoll_mgr.clone(),
factory,
boot_mem_size,
)
.map_err(DeviceMgrError::Virtio)
}
/// Removes all virtio-mem devices
pub fn remove_devices(&self, ctx: &mut DeviceOpContext) -> Result<(), DeviceMgrError> {
for info in self.info_list.iter() {
if let Some(device) = &info.device {
DeviceManager::destroy_mmio_virtio_device(device.clone(), ctx)?;
}
}
Ok(())
}
fn update_memory_size(
&self,
index: usize,
size_mib: u64,
) -> std::result::Result<(), MemDeviceError> {
let device = self.info_list[index]
.device
.as_ref()
.ok_or_else(|| MemDeviceError::DeviceNotExist)?;
if let Some(mmio_dev) = device.as_any().downcast_ref::<DbsMmioV2Device>() {
let guard = mmio_dev.state();
let inner_dev = guard.get_inner_device();
if let Some(mem_dev) = inner_dev
.as_any()
.downcast_ref::<Mem<GuestAddressSpaceImpl>>()
{
return mem_dev
.set_requested_size(size_mib)
.map_err(MemDeviceError::ResizeFailed);
}
}
Ok(())
}
}
impl Default for MemDeviceMgr {
/// Create a new `MemDeviceMgr` object..
fn default() -> Self {
MemDeviceMgr {
info_list: DeviceConfigInfos::new(),
use_shared_irq: USE_SHARED_IRQ,
}
}
}
struct MemoryRegionFactory {
mem_id: String,
vm_as: GuestAddressSpaceImpl,
address_space: AddressSpace,
vm_config: VmConfigInfo,
vm_fd: Arc<VmFd>,
logger: Arc<slog::Logger>,
host_numa_node_id: Option<u32>,
instance_id: String,
}
impl MemoryRegionFactory {
fn new(
ctx: &DeviceOpContext,
mem_id: String,
host_numa_node_id: Option<u32>,
) -> Result<Self, DeviceMgrError> {
let vm_as = ctx.get_vm_as()?;
let address_space = ctx.get_address_space()?;
let vm_config = ctx.get_vm_config()?;
let logger = Arc::new(ctx.logger().new(slog::o!()));
let shared_info = ctx.shared_info.read().unwrap();
let instance_id = shared_info.id.clone();
Ok(MemoryRegionFactory {
mem_id,
vm_as,
address_space,
vm_config,
vm_fd: ctx.vm_fd.clone(),
logger,
host_numa_node_id,
instance_id,
})
}
fn configure_anon_mem(&self, mmap_reg: &MmapRegion) -> Result<(), VirtIoError> {
unsafe {
mman::madvise(
mmap_reg.as_ptr() as *mut libc::c_void,
mmap_reg.size(),
mman::MmapAdvise::MADV_DONTFORK,
)
}
.map_err(VirtIoError::Madvise)?;
Ok(())
}
fn configure_numa(&self, mmap_reg: &MmapRegion, node_id: u32) -> Result<(), VirtIoError> {
let nodemask = 1_u64
.checked_shl(node_id)
.ok_or(VirtIoError::InvalidInput)?;
let res = unsafe {
libc::syscall(
libc::SYS_mbind,
mmap_reg.as_ptr() as *mut libc::c_void,
mmap_reg.size(),
MPOL_PREFERRED,
&nodemask as *const u64,
MAX_NODE,
MPOL_MF_MOVE,
)
};
if res < 0 {
warn!(
self.logger,
"failed to mbind memory to host_numa_node_id {}: this may affect performance",
node_id;
"subsystem" => "virito-mem"
);
}
Ok(())
}
fn configure_thp(&mut self, mmap_reg: &MmapRegion) -> Result<(), VirtIoError> {
debug!(
self.logger,
"Setting MADV_HUGEPAGE on AddressSpaceRegion addr {:x?} len {:x?}",
mmap_reg.as_ptr(),
mmap_reg.size();
"subsystem" => "virito-mem"
);
// Safe because we just create the MmapRegion
unsafe {
mman::madvise(
mmap_reg.as_ptr() as *mut libc::c_void,
mmap_reg.size(),
mman::MmapAdvise::MADV_HUGEPAGE,
)
}
.map_err(VirtIoError::Madvise)?;
Ok(())
}
fn map_to_kvm(
&mut self,
slot: u32,
reg: &Arc<AddressSpaceRegion>,
mmap_reg: &MmapRegion,
) -> Result<(), VirtIoError> {
let host_addr = mmap_reg.as_ptr() as u64;
let flags = 0u32;
let mem_region = kvm_userspace_memory_region {
slot,
guest_phys_addr: reg.start_addr().raw_value(),
memory_size: reg.len(),
userspace_addr: host_addr,
flags,
};
// Safe because the user mem region is just created, and kvm slot is allocated
// by resource allocator.
unsafe { self.vm_fd.set_user_memory_region(mem_region) }
.map_err(VirtIoError::SetUserMemoryRegion)?;
Ok(())
}
}
impl MemRegionFactory for MemoryRegionFactory {
fn create_region(
&mut self,
guest_addr: GuestAddress,
region_len: GuestUsize,
kvm_slot: u32,
) -> std::result::Result<Arc<GuestRegionMmap>, VirtIoError> {
// create address space region
let mem_type = self.vm_config.mem_type.as_str();
let mut mem_file_path = self.vm_config.mem_file_path.clone();
let mem_file_name = format!(
"/virtiomem_{}_{}",
self.instance_id.as_str(),
self.mem_id.as_str()
);
mem_file_path.push_str(mem_file_name.as_str());
let region = Arc::new(
AddressSpaceRegion::create_default_memory_region(
guest_addr,
region_len,
self.host_numa_node_id,
mem_type,
mem_file_path.as_str(),
false,
true,
)
.map_err(|e| {
error!(self.logger, "failed to insert address space region: {}", e);
// dbs-virtio-devices should not depend on dbs-address-space.
// So here io::Error is used instead of AddressSpaceError directly.
VirtIoError::IOError(io::Error::new(
io::ErrorKind::Other,
format!(
"invalid address space region ({0:#x}, {1:#x})",
guest_addr.0, region_len
),
))
})?,
);
info!(
self.logger,
"VM: mem_type: {} mem_file_path: {}, numa_node_id: {:?} file_offset: {:?}",
mem_type,
mem_file_path,
self.host_numa_node_id,
region.file_offset();
"subsystem" => "virito-mem"
);
let mmap_region = MmapRegion::build(
region.file_offset().cloned(),
region_len as usize,
region.prot_flags(),
region.perm_flags(),
)
.map_err(VirtIoError::NewMmapRegion)?;
let host_addr: u64 = mmap_region.as_ptr() as u64;
// thp
if mem_type == "hugeanon" || mem_type == "hugeshmem" {
self.configure_thp(&mmap_region)?;
}
// Handle numa
if let Some(numa_node_id) = self.host_numa_node_id {
self.configure_numa(&mmap_region, numa_node_id)?;
}
// add to guest memory mapping
self.map_to_kvm(kvm_slot, &region, &mmap_region)?;
info!(
self.logger,
"kvm set user memory region: slot: {}, flags: {}, guest_phys_addr: {:X}, memory_size: {}, userspace_addr: {:X}",
kvm_slot,
0,
guest_addr.raw_value(),
region_len,
host_addr;
"subsystem" => "virito-mem"
);
// All value should be valid.
let memory_region = Arc::new(
GuestRegionMmap::new(mmap_region, guest_addr).map_err(VirtIoError::InsertMmap)?,
);
let vm_as_new = self
.vm_as
.memory()
.insert_region(memory_region.clone())
.map_err(VirtIoError::InsertMmap)?;
self.vm_as.lock().unwrap().replace(vm_as_new);
self.address_space.insert_region(region).map_err(|e| {
error!(self.logger, "failed to insert address space region: {}", e);
// dbs-virtio-devices should not depend on dbs-address-space.
// So here io::Error is used instead of AddressSpaceError directly.
VirtIoError::IOError(io::Error::new(
io::ErrorKind::Other,
format!(
"invalid address space region ({0:#x}, {1:#x})",
guest_addr.0, region_len
),
))
})?;
Ok(memory_region)
}
fn restore_region_addr(
&self,
guest_addr: GuestAddress,
) -> std::result::Result<*mut u8, VirtIoError> {
let memory = self.vm_as.memory();
// NOTE: We can't clone `GuestRegionMmap` reference directly!!!
//
// Since an important role of the member `mapping` (type is
// `MmapRegion`) in `GuestRegionMmap` is to mmap the memory during
// construction and munmap the memory during drop. However, when the
// life time of cloned data is over, the drop operation will be
// performed, which will munmap the origional mmap memory, which will
// cause some memory in dragonall to be inaccessable. And remember the
// data structure that was cloned is still alive now, when its life time
// is over, it will perform the munmap operation again, which will cause
// a memory exception!
memory
.get_host_address(guest_addr)
.map_err(VirtIoError::GuestMemory)
}
fn get_host_numa_node_id(&self) -> Option<u32> {
self.host_numa_node_id
}
fn set_host_numa_node_id(&mut self, host_numa_node_id: Option<u32>) {
self.host_numa_node_id = host_numa_node_id;
}
}
#[cfg(test)]
mod tests {
use vm_memory::GuestMemoryRegion;
use super::*;
use crate::test_utils::tests::create_vm_for_test;
impl Default for MemDeviceConfigInfo {
fn default() -> Self {
MemDeviceConfigInfo {
mem_id: "".to_string(),
size_mib: 0,
capacity_mib: 1024,
multi_region: true,
host_numa_node_id: None,
guest_numa_node_id: None,
use_generic_irq: None,
use_shared_irq: None,
}
}
}
#[test]
fn test_mem_config_check_conflicts() {
let config = MemDeviceConfigInfo::default();
let mut config2 = MemDeviceConfigInfo::default();
assert!(config.check_conflicts(&config2).is_err());
config2.mem_id = "dummy_mem".to_string();
assert!(config.check_conflicts(&config2).is_ok());
}
#[test]
fn test_create_mem_devices_configs() {
let mgr = MemDeviceMgr::default();
assert_eq!(mgr.info_list.len(), 0);
assert_eq!(mgr.get_index_of_mem_dev(""), None);
}
#[test]
fn test_mem_insert_or_update_device() {
// Init vm for test.
let mut vm = create_vm_for_test();
// We don't need to use virtio-mem before start vm
// Test for standard config with hotplug
let device_op_ctx = DeviceOpContext::new(
Some(vm.epoll_manager().clone()),
vm.device_manager(),
Some(vm.vm_as().unwrap().clone()),
vm.vm_address_space().cloned(),
true,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let dummy_mem_device = MemDeviceConfigInfo::default();
vm.device_manager_mut()
.mem_manager
.insert_or_update_device(device_op_ctx, dummy_mem_device)
.unwrap();
assert_eq!(vm.device_manager().mem_manager.info_list.len(), 1);
}
#[test]
fn test_mem_attach_device() {
// Init vm and insert mem config for test.
let mut vm = create_vm_for_test();
let dummy_mem_device = MemDeviceConfigInfo::default();
vm.device_manager_mut()
.mem_manager
.info_list
.insert_or_update(&dummy_mem_device)
.unwrap();
assert_eq!(vm.device_manager().mem_manager.info_list.len(), 1);
// Test for standard config
let mut device_op_ctx = DeviceOpContext::new(
Some(vm.epoll_manager().clone()),
vm.device_manager(),
Some(vm.vm_as().unwrap().clone()),
vm.vm_address_space().cloned(),
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
vm.device_manager_mut()
.mem_manager
.attach_devices(&mut device_op_ctx)
.unwrap();
assert_eq!(vm.device_manager().mem_manager.info_list.len(), 1);
}
#[test]
fn test_mem_create_region() {
let vm = create_vm_for_test();
let ctx = DeviceOpContext::new(
Some(vm.epoll_manager().clone()),
vm.device_manager(),
Some(vm.vm_as().unwrap().clone()),
vm.vm_address_space().cloned(),
true,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let mem_id = String::from("mem0");
let guest_addr = GuestAddress(0x1_0000_0000);
let region_len = 0x1000_0000;
let kvm_slot = 2;
// no vfio manager, no numa node
let mut factory = MemoryRegionFactory::new(&ctx, mem_id, None).unwrap();
let region_opt = factory.create_region(guest_addr, region_len, kvm_slot);
assert_eq!(region_opt.unwrap().len(), region_len);
}
}

View File

@ -55,6 +55,7 @@ impl DeviceVirtioRegionHandler {
None, None,
file_offset, file_offset,
region.flags(), region.flags(),
region.prot(),
false, false,
)); ));

View File

@ -90,6 +90,12 @@ mod memory_region_handler;
#[cfg(feature = "virtio-fs")] #[cfg(feature = "virtio-fs")]
pub use self::memory_region_handler::*; pub use self::memory_region_handler::*;
#[cfg(feature = "virtio-mem")]
/// Device manager for virtio-mem devices.
pub mod mem_dev_mgr;
#[cfg(feature = "virtio-mem")]
use self::mem_dev_mgr::MemDeviceMgr;
macro_rules! info( macro_rules! info(
($l:expr, $($args:tt)+) => { ($l:expr, $($args:tt)+) => {
slog::info!($l, $($args)+; slog::o!("subsystem" => "device_manager")) slog::info!($l, $($args)+; slog::o!("subsystem" => "device_manager"))
@ -311,6 +317,27 @@ impl DeviceOpContext {
} }
} }
pub(crate) fn get_vm_config(&self) -> Result<VmConfigInfo> {
match self.vm_config.as_ref() {
Some(v) => Ok(v.clone()),
None => Err(DeviceMgrError::InvalidOperation),
}
}
pub(crate) fn get_address_space(&self) -> Result<AddressSpace> {
match self.address_space.as_ref() {
Some(v) => Ok(v.clone()),
None => Err(DeviceMgrError::InvalidOperation),
}
}
pub(crate) fn get_epoll_mgr(&self) -> Result<EpollManager> {
match self.epoll_mgr.as_ref() {
Some(v) => Ok(v.clone()),
None => Err(DeviceMgrError::InvalidOperation),
}
}
pub(crate) fn logger(&self) -> &slog::Logger { pub(crate) fn logger(&self) -> &slog::Logger {
&self.logger &self.logger
} }
@ -498,6 +525,9 @@ pub struct DeviceManager {
#[cfg(feature = "virtio-fs")] #[cfg(feature = "virtio-fs")]
fs_manager: Arc<Mutex<FsDeviceMgr>>, fs_manager: Arc<Mutex<FsDeviceMgr>>,
#[cfg(feature = "virtio-mem")]
pub(crate) mem_manager: MemDeviceMgr,
} }
impl DeviceManager { impl DeviceManager {
@ -530,6 +560,8 @@ impl DeviceManager {
virtio_net_manager: VirtioNetDeviceMgr::default(), virtio_net_manager: VirtioNetDeviceMgr::default(),
#[cfg(feature = "virtio-fs")] #[cfg(feature = "virtio-fs")]
fs_manager: Arc::new(Mutex::new(FsDeviceMgr::default())), fs_manager: Arc::new(Mutex::new(FsDeviceMgr::default())),
#[cfg(feature = "virtio-mem")]
mem_manager: MemDeviceMgr::default(),
} }
} }
@ -899,6 +931,24 @@ impl DeviceManager {
) )
} }
/// Create an Virtio MMIO transport layer device for the virtio backend device with configure
/// change notification enabled.
pub fn create_mmio_virtio_device_with_device_change_notification(
device: DbsVirtioDevice,
ctx: &mut DeviceOpContext,
use_shared_irq: bool,
use_generic_irq: bool,
) -> std::result::Result<Arc<DbsMmioV2Device>, DeviceMgrError> {
let features = DRAGONBALL_FEATURE_PER_QUEUE_NOTIFY;
DeviceManager::create_mmio_virtio_device_with_features(
device,
ctx,
Some(features),
use_shared_irq,
use_generic_irq,
)
}
/// Create an Virtio MMIO transport layer device for the virtio backend device with specified /// Create an Virtio MMIO transport layer device for the virtio backend device with specified
/// features. /// features.
pub fn create_mmio_virtio_device_with_features( pub fn create_mmio_virtio_device_with_features(
@ -1077,6 +1127,8 @@ mod tests {
virtio_net_manager: VirtioNetDeviceMgr::default(), virtio_net_manager: VirtioNetDeviceMgr::default(),
#[cfg(feature = "virtio-vsock")] #[cfg(feature = "virtio-vsock")]
vsock_manager: VsockDeviceMgr::default(), vsock_manager: VsockDeviceMgr::default(),
#[cfg(feature = "virtio-mem")]
mem_manager: MemDeviceMgr::default(),
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
mmio_device_info: HashMap::new(), mmio_device_info: HashMap::new(),