crosvm: implement lock-guest-memory feature.

This CL adds a command-line flag to crosm that locks the entire guest
memory, per http://go/host-assisted-vm-swap.
The change relies on existing balloon device code that punches holes
within the guest memory region upon "inflate".

BUG=b:236210703

TEST=manual startup, roblox+instagram and various chrome sessions

Change-Id: I11e0e5b0b0cb6450f47124a6a8b6c4befd9de6ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3725731
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Reviewed-by: David Stevens <stevensd@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Alexandre Marciano Gimenez <raging@google.com>
Auto-Submit: Alexandre Marciano Gimenez <raging@google.com>
This commit is contained in:
Alex Gimenez 2022-06-28 18:28:41 +09:00 committed by Chromeos LUCI
parent 10574227dc
commit ed071b6edd
5 changed files with 71 additions and 2 deletions

View file

@ -12,6 +12,8 @@ use std::{
ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned},
};
use log::warn;
use crate::{
external_mapping::ExternalMapping, AsRawDescriptor, Descriptor,
MemoryMapping as CrateMemoryMapping, MemoryMappingBuilder, RawDescriptor, SafeDescriptor,
@ -703,6 +705,34 @@ impl MemoryMapping {
}
}
/// Disable host swap for this mapping.
pub fn lock_all(&self) -> Result<()> {
let ret = unsafe {
// Safe because MLOCK_ONFAULT only affects the swap behavior of the kernel, so it
// has no impact on rust semantics.
// TODO(raging): use the explicit declaration of mlock2, which was being merged
// as of when the call below was being worked on.
libc::syscall(
libc::SYS_mlock2,
(self.addr as usize) as *const libc::c_void,
self.size(),
libc::MLOCK_ONFAULT,
)
};
if ret < 0 {
let errno = super::Error::last();
warn!(
"failed to mlock at {:#x} with length {}: {}",
(self.addr as usize) as u64,
self.size(),
errno,
);
Err(Error::SystemCallFailed(errno))
} else {
Ok(())
}
}
// Check that offset+count is valid and return the sum.
fn range_end(&self, offset: usize, count: usize) -> Result<usize> {
let mem_end = offset.checked_add(count).ok_or(Error::InvalidAddress)?;
@ -990,13 +1020,19 @@ impl CrateMemoryMapping {
}
pub trait Unix {
/// Remove the specified range from the mapping.
fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()>;
/// Disable host swap for this mapping.
fn lock_all(&self) -> Result<()>;
}
impl Unix for CrateMemoryMapping {
fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()> {
self.mapping.remove_range(mem_offset, count)
}
fn lock_all(&self) -> Result<()> {
self.mapping.lock_all()
}
}
pub trait MemoryMappingBuilderUnix<'a> {

View file

@ -651,6 +651,9 @@ pub struct RunCommand {
#[argh(option, long = "kvm-device", arg_name = "PATH")]
/// path to the KVM device. (default /dev/kvm)
pub kvm_device_path: Option<PathBuf>,
#[argh(switch)]
/// disable host swap on guest VM pages.
pub lock_guest_memory: bool,
#[cfg(unix)]
#[argh(option, arg_name = "MAC", long = "mac")]
/// MAC address for VM
@ -1197,6 +1200,8 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.hugepages = cmd.hugepages;
cfg.lock_guest_memory = cmd.lock_guest_memory;
#[cfg(feature = "audio")]
{
cfg.ac97_parameters = cmd.ac97;

View file

@ -1720,6 +1720,7 @@ pub struct Config {
pub itmt: bool,
pub jail_config: Option<JailConfig>,
pub kvm_device_path: PathBuf,
pub lock_guest_memory: bool,
pub mac_address: Option<net_util::MacAddress>,
pub memory: Option<u64>,
pub memory_file: Option<PathBuf>,
@ -1856,6 +1857,7 @@ impl Default for Config {
None
},
kvm_device_path: PathBuf::from(KVM_PATH),
lock_guest_memory: false,
mac_address: None,
memory: None,
memory_file: None,
@ -2137,6 +2139,10 @@ pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
return Err("'balloon-control' requires enabled balloon".to_string());
}
if cfg.lock_guest_memory && cfg.jail_config.is_none() {
return Err("'lock-guest-memory' and 'disable-sandbox' are mutually exclusive".to_string());
}
set_default_serial_parameters(
&mut cfg.serial_parameters,
!cfg.vhost_user_console.is_empty(),

View file

@ -1201,6 +1201,10 @@ pub fn run_config(cfg: Config) -> Result<ExitState> {
if components.hugepages {
mem_policy |= MemoryPolicy::USE_HUGEPAGES;
}
if cfg.lock_guest_memory {
mem_policy |= MemoryPolicy::LOCK_GUEST_MEMORY;
}
guest_mem.set_memory_policy(mem_policy);
if cfg.kvm_device_path.exists() {

View file

@ -11,6 +11,7 @@ use crate::{Error, GuestAddress, GuestMemory, Result};
bitflags! {
pub struct MemoryPolicy: u32 {
const USE_HUGEPAGES = 1;
const LOCK_GUEST_MEMORY = (1 << 1);
}
}
@ -41,14 +42,31 @@ impl GuestMemory {
/// Handles guest memory policy hints/advices.
pub fn set_memory_policy(&self, mem_policy: MemoryPolicy) {
if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) {
for (_, region) in self.regions.iter().enumerate() {
if mem_policy.is_empty() {
return;
}
for (_, region) in self.regions.iter().enumerate() {
if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) {
let ret = region.mapping.use_hugepages();
if let Err(err) = ret {
println!("Failed to enable HUGEPAGE for mapping {}", err);
}
}
if mem_policy.contains(MemoryPolicy::LOCK_GUEST_MEMORY) {
// This is done in coordination with remove_range() calls, which are
// performed by the virtio-balloon process (they must be performed by
// a different process from the one that issues the locks).
// We also prevent this from happening in single-process configurations,
// when we compute configuration flags.
let ret = region.mapping.lock_all();
if let Err(err) = ret {
println!("Failed to lock memory for mapping {}", err);
}
}
}
}
}