mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: Refactor restore_by_uffd behind UffdMemorySource trait
Extract the page content provider out of the userfaultfd handler so it can be plugged with different backends in followup commits. No functional change intended. Signed-off-by: Sebastien Boeuf <sboeuf@meta.com> Assisted-by: Claude:claude-opus-4-7
This commit is contained in:
+40
-114
@@ -11,7 +11,6 @@ use std::io::{self, Seek, SeekFrom};
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::{BitAnd, Not, Sub};
|
use std::ops::{BitAnd, Not, Sub};
|
||||||
use std::os::fd::{AsFd, OwnedFd};
|
use std::os::fd::{AsFd, OwnedFd};
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
@@ -57,8 +56,9 @@ use crate::coredump::{
|
|||||||
};
|
};
|
||||||
use crate::migration::url_to_path;
|
use crate::migration::url_to_path;
|
||||||
use crate::sparse::{next_data_extent, write_region_sparse};
|
use crate::sparse::{next_data_extent, write_region_sparse};
|
||||||
|
use crate::uffd::{self, FaultResolution, FileUffdMemorySource, UffdMemorySource, UffdRange};
|
||||||
use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig};
|
use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig};
|
||||||
use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID, uffd};
|
use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID};
|
||||||
|
|
||||||
struct UffdHandler {
|
struct UffdHandler {
|
||||||
stop_event: EventFd,
|
stop_event: EventFd,
|
||||||
@@ -66,34 +66,6 @@ struct UffdHandler {
|
|||||||
handle: thread::JoinHandle<()>,
|
handle: thread::JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UffdRange {
|
|
||||||
host_addr: u64,
|
|
||||||
length: u64,
|
|
||||||
file_offset: u64,
|
|
||||||
page_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UffdRange {
|
|
||||||
fn num_pages(&self) -> u64 {
|
|
||||||
self.length.div_ceil(self.page_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn page_addr(&self, page_idx: u64) -> u64 {
|
|
||||||
self.host_addr + page_idx * self.page_size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_pos(&self, page_idx: u64) -> u64 {
|
|
||||||
self.file_offset + page_idx * self.page_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the page index containing `addr` if it falls within this range.
|
|
||||||
fn page_index_of(&self, addr: u64) -> Option<u64> {
|
|
||||||
let page_addr = addr & !(self.page_size - 1);
|
|
||||||
(page_addr >= self.host_addr && page_addr < self.host_addr + self.length)
|
|
||||||
.then(|| (page_addr - self.host_addr) / self.page_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MEMORY_MANAGER_ACPI_SIZE: usize = 0x18;
|
pub const MEMORY_MANAGER_ACPI_SIZE: usize = 0x18;
|
||||||
|
|
||||||
const DEFAULT_MEMORY_ZONE: &str = "mem0";
|
const DEFAULT_MEMORY_ZONE: &str = "mem0";
|
||||||
@@ -944,6 +916,19 @@ impl MemoryManager {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let snapshot_file = File::open(file_path).map_err(Error::SnapshotOpen)?;
|
||||||
|
let source: Box<dyn UffdMemorySource> = Box::new(FileUffdMemorySource::new(snapshot_file));
|
||||||
|
self.register_uffd_handler(saved_regions, exit_evt, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register every range against userfaultfd, then spawn the
|
||||||
|
/// handler thread that resolves faults through the memory source.
|
||||||
|
fn register_uffd_handler(
|
||||||
|
&mut self,
|
||||||
|
saved_regions: &MemoryRangeTable,
|
||||||
|
exit_evt: &EventFd,
|
||||||
|
source: Box<dyn UffdMemorySource>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let guest_memory = self.guest_memory.memory();
|
let guest_memory = self.guest_memory.memory();
|
||||||
let required_uffd_features = self.required_uffd_features();
|
let required_uffd_features = self.required_uffd_features();
|
||||||
|
|
||||||
@@ -963,8 +948,6 @@ impl MemoryManager {
|
|||||||
return Err(UffdError::UnalignedRanges.into());
|
return Err(UffdError::UnalignedRanges.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshot_file = File::open(file_path).map_err(Error::SnapshotOpen)?;
|
|
||||||
|
|
||||||
let uffd_fd = uffd::create(required_uffd_features).map_err(UffdError::Create)?;
|
let uffd_fd = uffd::create(required_uffd_features).map_err(UffdError::Create)?;
|
||||||
|
|
||||||
let mut handler_ranges: Vec<UffdRange> = Vec::new();
|
let mut handler_ranges: Vec<UffdRange> = Vec::new();
|
||||||
@@ -1005,7 +988,7 @@ impl MemoryManager {
|
|||||||
handler_ranges.push(UffdRange {
|
handler_ranges.push(UffdRange {
|
||||||
host_addr,
|
host_addr,
|
||||||
length: range.length,
|
length: range.length,
|
||||||
file_offset,
|
source_offset: file_offset,
|
||||||
page_size: range_page_size,
|
page_size: range_page_size,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1027,17 +1010,11 @@ impl MemoryManager {
|
|||||||
.name("uffd-handler".to_string())
|
.name("uffd-handler".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
|
std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
|
||||||
let max_page_size = handler_ranges
|
|
||||||
.iter()
|
|
||||||
.map(|r| r.page_size)
|
|
||||||
.max()
|
|
||||||
.unwrap_or(base_page_size);
|
|
||||||
let result = Self::uffd_handler_loop(
|
let result = Self::uffd_handler_loop(
|
||||||
uffd_fd,
|
uffd_fd,
|
||||||
thread_stop_event,
|
thread_stop_event,
|
||||||
snapshot_file,
|
source,
|
||||||
&handler_ranges,
|
&handler_ranges,
|
||||||
max_page_size,
|
|
||||||
&ready_tx,
|
&ready_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1102,25 +1079,17 @@ impl MemoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Poll the UFFD fd and serve page faults from the snapshot file, while
|
/// Serve UFFD faults via `source`, prefaulting one page per idle
|
||||||
/// opportunistically prefaulting the remaining pages in between faults.
|
/// iteration.
|
||||||
///
|
|
||||||
/// Runs until the fd is closed (EPOLLHUP), `stop_event` fires, or an
|
|
||||||
/// unrecoverable error occurs. Each fault triggers a read from the
|
|
||||||
/// snapshot file followed by a `UFFDIO_COPY` to resolve the fault and
|
|
||||||
/// wake the faulting thread. When no fault is pending, one prefault
|
|
||||||
/// page is copied per loop iteration.
|
|
||||||
#[expect(clippy::needless_pass_by_value)]
|
#[expect(clippy::needless_pass_by_value)]
|
||||||
fn uffd_handler_loop(
|
fn uffd_handler_loop(
|
||||||
uffd_fd: OwnedFd,
|
uffd_fd: OwnedFd,
|
||||||
stop_event: EventFd,
|
stop_event: EventFd,
|
||||||
snapshot_file: File,
|
mut source: Box<dyn UffdMemorySource>,
|
||||||
ranges: &[UffdRange],
|
ranges: &[UffdRange],
|
||||||
page_size: u64,
|
|
||||||
ready_tx: &SyncSender<()>,
|
ready_tx: &SyncSender<()>,
|
||||||
) -> Result<(), io::Error> {
|
) -> Result<(), io::Error> {
|
||||||
let uffd_raw_fd = uffd_fd.as_raw_fd();
|
let uffd_raw_fd = uffd_fd.as_raw_fd();
|
||||||
let mut page_buf = vec![0u8; page_size as usize];
|
|
||||||
|
|
||||||
let total_pages: u64 = ranges.iter().map(UffdRange::num_pages).sum();
|
let total_pages: u64 = ranges.iter().map(UffdRange::num_pages).sum();
|
||||||
let mut pages_served: u64 = 0;
|
let mut pages_served: u64 = 0;
|
||||||
@@ -1245,39 +1214,19 @@ impl MemoryManager {
|
|||||||
let Some(page_idx) = range.page_index_of(fault_addr) else {
|
let Some(page_idx) = range.page_index_of(fault_addr) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let page_addr = range.page_addr(page_idx);
|
|
||||||
let file_pos = range.file_pos(page_idx);
|
|
||||||
|
|
||||||
snapshot_file
|
|
||||||
.read_exact_at(&mut page_buf[..range.page_size as usize], file_pos)?;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match uffd::copy(
|
match source.resolve(uffd_fd.as_fd(), range, page_idx)? {
|
||||||
uffd_fd.as_fd(),
|
FaultResolution::Served => {
|
||||||
page_addr,
|
|
||||||
page_buf.as_ptr(),
|
|
||||||
range.page_size,
|
|
||||||
) {
|
|
||||||
Ok(()) => {
|
|
||||||
pages_served += 1;
|
pages_served += 1;
|
||||||
served_bitmap[range_idx].set_bit(page_idx as usize);
|
served_bitmap[range_idx].set_bit(page_idx as usize);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) if e.raw_os_error() == Some(libc::EEXIST) => {
|
FaultResolution::Retry => {
|
||||||
if let Err(e) =
|
// The kernel reported a transient state while the fault
|
||||||
uffd::wake(uffd_fd.as_fd(), page_addr, range.page_size)
|
// is being resolved; yield and retry instead of aborting.
|
||||||
{
|
|
||||||
warn!("UFFDIO_WAKE failed at {page_addr:#x}: {e}");
|
|
||||||
}
|
|
||||||
served_bitmap[range_idx].set_bit(page_idx as usize);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => {
|
|
||||||
// The kernel can report a transient EAGAIN while the fault
|
|
||||||
// is being resolved; yield and retry instead of aborting restore.
|
|
||||||
thread::yield_now();
|
thread::yield_now();
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
served = true;
|
served = true;
|
||||||
@@ -1328,46 +1277,23 @@ impl MemoryManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let range = &ranges[range_idx];
|
let range = &ranges[range_idx];
|
||||||
let file_pos = range.file_pos(page_idx);
|
|
||||||
let page_addr = range.page_addr(page_idx);
|
|
||||||
let len = range.page_size as usize;
|
|
||||||
|
|
||||||
let advance = match snapshot_file.read_exact_at(&mut page_buf[..len], file_pos) {
|
let advance = match source.resolve(uffd_fd.as_fd(), range, page_idx) {
|
||||||
Ok(()) => match uffd::copy(
|
Ok(FaultResolution::Served) => {
|
||||||
uffd_fd.as_fd(),
|
pages_prefaulted += 1;
|
||||||
page_addr,
|
served_bitmap[range_idx].set_bit(page_idx as usize);
|
||||||
page_buf.as_ptr(),
|
true
|
||||||
range.page_size,
|
}
|
||||||
) {
|
Ok(FaultResolution::Retry) => {
|
||||||
Ok(()) => {
|
// Unlike the on demand handler (which must retry to wake
|
||||||
pages_prefaulted += 1;
|
// the faulting thread), prefault can safely skip: any
|
||||||
true
|
// future guest access will simply page-fault and be
|
||||||
}
|
// served by the on demand path.
|
||||||
Err(e) if e.raw_os_error() == Some(libc::EEXIST) => {
|
true
|
||||||
// Should be unreachable: in single-thread mode with
|
}
|
||||||
// MISSING-only registration, the only installer is us,
|
|
||||||
// and the bitmap check above already filtered served
|
|
||||||
// pages. Treat as a bug signal but keep going.
|
|
||||||
warn!(
|
|
||||||
"UFFD prefault: unexpected EEXIST at {page_addr:#x} \
|
|
||||||
(bitmap/handler bug?)"
|
|
||||||
);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => {
|
|
||||||
// Unlike the on-demand handler (which must retry to
|
|
||||||
// wake the faulting thread), prefault can safely skip:
|
|
||||||
// any future guest access will simply page-fault and
|
|
||||||
// be served by the on-demand path.
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("UFFD prefault: UFFDIO_COPY error at {page_addr:#x}: {e}");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("UFFD prefault: read error at {file_pos:#x}: {e}");
|
let page_addr = range.page_addr(page_idx);
|
||||||
|
warn!("UFFD prefault: source error at {page_addr:#x}: {e}");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+97
-2
@@ -13,10 +13,11 @@
|
|||||||
//! original memory mapping, so it remains compatible with VFIO device
|
//! original memory mapping, so it remains compatible with VFIO device
|
||||||
//! passthrough and shared-memory-backed guest RAM.
|
//! passthrough and shared-memory-backed guest RAM.
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::Error;
|
use std::io::{self, Error};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||||
|
use std::os::unix::fs::FileExt;
|
||||||
|
|
||||||
use crate::userfaultfd;
|
use crate::userfaultfd;
|
||||||
|
|
||||||
@@ -179,6 +180,100 @@ struct UffdioRange {
|
|||||||
len: u64,
|
len: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A guest memory range registered with userfaultfd, plus where its bytes
|
||||||
|
/// live in the snapshot file.
|
||||||
|
pub(crate) struct UffdRange {
|
||||||
|
pub host_addr: u64,
|
||||||
|
pub length: u64,
|
||||||
|
pub source_offset: u64,
|
||||||
|
pub page_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UffdRange {
|
||||||
|
pub fn num_pages(&self) -> u64 {
|
||||||
|
self.length.div_ceil(self.page_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_addr(&self, page_idx: u64) -> u64 {
|
||||||
|
self.host_addr + page_idx * self.page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_source_offset(&self, page_idx: u64) -> u64 {
|
||||||
|
self.source_offset + page_idx * self.page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_index_of(&self, addr: u64) -> Option<u64> {
|
||||||
|
let page_addr = addr & !(self.page_size - 1);
|
||||||
|
(page_addr >= self.host_addr && page_addr < self.host_addr + self.length)
|
||||||
|
.then(|| (page_addr - self.host_addr) / self.page_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of a page fault being resolved.
|
||||||
|
pub(crate) enum FaultResolution {
|
||||||
|
/// Page installed.
|
||||||
|
Served,
|
||||||
|
/// Indicates the page couldn't be installed and it's worth retrying.
|
||||||
|
Retry,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider of guest-memory page contents for a UFFD handler.
|
||||||
|
pub(crate) trait UffdMemorySource: Send {
|
||||||
|
fn resolve(
|
||||||
|
&mut self,
|
||||||
|
uffd_fd: BorrowedFd<'_>,
|
||||||
|
range: &UffdRange,
|
||||||
|
page_idx: u64,
|
||||||
|
) -> Result<FaultResolution, io::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Source that reads pages from a local snapshot file.
|
||||||
|
pub(crate) struct FileUffdMemorySource {
|
||||||
|
file: File,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileUffdMemorySource {
|
||||||
|
pub fn new(file: File) -> Self {
|
||||||
|
Self {
|
||||||
|
file,
|
||||||
|
buf: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UffdMemorySource for FileUffdMemorySource {
|
||||||
|
fn resolve(
|
||||||
|
&mut self,
|
||||||
|
uffd_fd: BorrowedFd<'_>,
|
||||||
|
range: &UffdRange,
|
||||||
|
page_idx: u64,
|
||||||
|
) -> Result<FaultResolution, io::Error> {
|
||||||
|
let page_size = range.page_size as usize;
|
||||||
|
let page_addr = range.page_addr(page_idx);
|
||||||
|
let file_pos = range.page_source_offset(page_idx);
|
||||||
|
|
||||||
|
if self.buf.len() < page_size {
|
||||||
|
self.buf.resize(page_size, 0);
|
||||||
|
}
|
||||||
|
self.file
|
||||||
|
.read_exact_at(&mut self.buf[..page_size], file_pos)?;
|
||||||
|
|
||||||
|
match copy(uffd_fd, page_addr, self.buf.as_ptr(), range.page_size) {
|
||||||
|
Ok(()) => Ok(FaultResolution::Served),
|
||||||
|
Err(e) if e.raw_os_error() == Some(libc::EEXIST) => {
|
||||||
|
// Installed concurrently. Wake any blocked threads.
|
||||||
|
if let Err(e) = wake(uffd_fd, page_addr, range.page_size) {
|
||||||
|
log::warn!("UFFDIO_WAKE failed at {page_addr:#x}: {e}");
|
||||||
|
}
|
||||||
|
Ok(FaultResolution::Served)
|
||||||
|
}
|
||||||
|
Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => Ok(FaultResolution::Retry),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wake threads waiting on a fault in the given range without copying data.
|
/// Wake threads waiting on a fault in the given range without copying data.
|
||||||
///
|
///
|
||||||
/// Needed after UFFDIO_COPY returns EEXIST: the page was already resolved
|
/// Needed after UFFDIO_COPY returns EEXIST: the page was already resolved
|
||||||
|
|||||||
Reference in New Issue
Block a user