Skip to content

Commit 274535d

Browse files
committed
GuestMemory: Add get_slices()
With virtual memory, seemingly consecutive I/O virtual memory regions may actually be fragmented across multiple pages in our userspace mapping. Existing `descriptor_utils::Reader::new()` (and `Writer`) implementations (e.g. in virtiofsd or vm-virtio/virtio-queue) use `GuestMemory::get_slice()` to turn guest memory address ranges into valid slices in our address space; but with this fragmentation, it is easily possible that a range no longer corresponds to a single slice. To fix this, add a `get_slices()` method that iterates over potentially multiple slices instead of a single one. We should probably also deprecate `get_slice()`, but I’m hesitant to do it in the same commit/PR. (We could also try to use `try_access()` as an existing internal iterator instead of this new external iterator, which would require adding lifetimes to `try_access()` so the region and thus slices derived from it could be moved outside of the closure. However, that will not work for virtual memory that we are going to introduce later: It will have a dirty bitmap that is independent of the one in guest memory regions, so its `try_access()` function will need to dirty it after the access. Therefore, the access must happen in that closure and the reference to the region must not be moved outside.) Signed-off-by: Hanna Czenczek <[email protected]>
1 parent fbd1426 commit 274535d

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
is now a type alias for `GuestRegionContainer<GuestRegionMmap>`).
1010
- \[[#338](https://github.com/rust-vmm/vm-memory/pull/338)\] Make `GuestMemoryAtomic` always implement `Clone`.
1111
- \[[#338](https://github.com/rust-vmm/vm-memory/pull/338)\] Make `GuestAddressSpace` a subtrait of `Clone`.
12+
- \[[#339](https://github.com/rust-vmm/vm-memory/pull/339)\] Add `GuestMemory::get_slices()`
1213

1314
### Changed
1415

src/bitmap/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ pub(crate) mod tests {
266266
let dirty_len = size_of_val(&val);
267267

268268
let (region, region_addr) = m.to_region_addr(dirty_addr).unwrap();
269-
let slice = m.get_slice(dirty_addr, dirty_len).unwrap();
269+
let mut slices = m.get_slices(dirty_addr, dirty_len);
270+
let slice = slices.next().unwrap().unwrap();
271+
assert!(slices.next().is_none());
270272

271273
assert!(range_is_clean(&region.bitmap(), 0, region.len() as usize));
272274
assert!(range_is_clean(slice.bitmap(), 0, dirty_len));

src/guest_memory.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,92 @@ pub trait GuestMemory {
455455
.ok_or(Error::InvalidGuestAddress(addr))
456456
.and_then(|(r, addr)| r.get_slice(addr, count))
457457
}
458+
459+
/// Returns an iterator over [`VolatileSlice`](struct.VolatileSlice.html)s, together covering
460+
/// `count` bytes starting at `addr`.
461+
///
462+
/// Iterating in this way is necessary because the given address range may be fragmented across
463+
/// multiple [`GuestMemoryRegion`]s.
464+
///
465+
/// The iterator’s items are wrapped in [`Result`], i.e. errors are reported on individual
466+
/// items. If there is no such error, the cumulative length of all items will be equal to
467+
/// `count`. If `count` is 0, an empty iterator will be returned.
468+
fn get_slices<'a>(
469+
&'a self,
470+
addr: GuestAddress,
471+
count: usize,
472+
) -> GuestMemorySliceIterator<'a, Self> {
473+
GuestMemorySliceIterator {
474+
mem: self,
475+
addr,
476+
count,
477+
}
478+
}
479+
}
480+
481+
/// Iterates over [`VolatileSlice`]s that together form a guest memory area.
482+
///
483+
/// Returned by [`GuestMemory::get_slices()`].
484+
#[derive(Debug)]
485+
pub struct GuestMemorySliceIterator<'a, M: GuestMemory + ?Sized> {
486+
/// Underlying memory
487+
mem: &'a M,
488+
/// Next address in the guest memory area
489+
addr: GuestAddress,
490+
/// Remaining bytes in the guest memory area
491+
count: usize,
492+
}
493+
494+
impl<'a, M: GuestMemory + ?Sized> GuestMemorySliceIterator<'a, M> {
495+
/// Helper function for [`<Self as Iterator>::next()`].
496+
///
497+
/// Get the next slice (i.e. the one starting from `self.addr` with a length up to
498+
/// `self.count`) and update the internal state.
499+
///
500+
/// # Safety
501+
///
502+
/// This function does not reset to `self.count` to 0 in case of error, i.e. will not stop
503+
/// iterating. Actual behavior after an error is ill-defined, so the caller must check the
504+
/// return value, and in case of an error, reset `self.count` to 0.
505+
///
506+
/// (This is why this function exists, so this resetting can be done in a single central
507+
/// location.)
508+
unsafe fn do_next(&mut self) -> Option<Result<VolatileSlice<'a, MS<'a, M>>>> {
509+
if self.count == 0 {
510+
return None;
511+
}
512+
513+
let Some((region, start)) = self.mem.to_region_addr(self.addr) else {
514+
return Some(Err(Error::InvalidGuestAddress(self.addr)));
515+
};
516+
517+
let cap = region.len() - start.raw_value();
518+
let len = std::cmp::min(cap, self.count as GuestUsize);
519+
520+
self.count -= len as usize;
521+
self.addr = match self.addr.overflowing_add(len as GuestUsize) {
522+
(x @ GuestAddress(0), _) | (x, false) => x,
523+
(_, true) => return Some(Err(Error::GuestAddressOverflow)),
524+
};
525+
526+
Some(region.get_slice(start, len as usize))
527+
}
528+
}
529+
530+
impl<'a, M: GuestMemory + ?Sized> Iterator for GuestMemorySliceIterator<'a, M> {
531+
type Item = Result<VolatileSlice<'a, MS<'a, M>>>;
532+
533+
fn next(&mut self) -> Option<Self::Item> {
534+
// Safe: We reset `self.count` to 0 on error
535+
match unsafe { self.do_next() } {
536+
Some(Ok(slice)) => Some(Ok(slice)),
537+
other => {
538+
// On error (or end), reset to 0 so iteration remains stopped
539+
self.count = 0;
540+
other
541+
}
542+
}
543+
}
458544
}
459545

460546
impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T {

src/mmap/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,56 @@ mod tests {
624624
assert!(guest_mem.get_slice(GuestAddress(0xc00), 0x100).is_err());
625625
}
626626

627+
#[test]
628+
fn test_guest_memory_get_slices() {
629+
let start_addr1 = GuestAddress(0);
630+
let start_addr2 = GuestAddress(0x800);
631+
let start_addr3 = GuestAddress(0xc00);
632+
let guest_mem = GuestMemoryMmap::from_ranges(&[
633+
(start_addr1, 0x400),
634+
(start_addr2, 0x400),
635+
(start_addr3, 0x400),
636+
])
637+
.unwrap();
638+
639+
// Same cases as `test_guest_memory_get_slice()`, just with `get_slices()`.
640+
let slice_size = 0x200;
641+
let mut slices = guest_mem.get_slices(GuestAddress(0x100), slice_size);
642+
let slice = slices.next().unwrap().unwrap();
643+
assert!(slices.next().is_none());
644+
assert_eq!(slice.len(), slice_size);
645+
646+
let slice_size = 0x400;
647+
let mut slices = guest_mem.get_slices(GuestAddress(0x800), slice_size);
648+
let slice = slices.next().unwrap().unwrap();
649+
assert!(slices.next().is_none());
650+
assert_eq!(slice.len(), slice_size);
651+
652+
// Empty iterator.
653+
assert!(guest_mem
654+
.get_slices(GuestAddress(0x900), 0)
655+
.next()
656+
.is_none());
657+
658+
// Error cases, wrong size or base address.
659+
let mut slices = guest_mem.get_slices(GuestAddress(0), 0x500);
660+
assert_eq!(slices.next().unwrap().unwrap().len(), 0x400);
661+
assert!(slices.next().unwrap().is_err());
662+
assert!(slices.next().is_none());
663+
let mut slices = guest_mem.get_slices(GuestAddress(0x600), 0x100);
664+
assert!(slices.next().unwrap().is_err());
665+
assert!(slices.next().is_none());
666+
let mut slices = guest_mem.get_slices(GuestAddress(0x1000), 0x100);
667+
assert!(slices.next().unwrap().is_err());
668+
assert!(slices.next().is_none());
669+
670+
// Test fragmented case
671+
let mut slices = guest_mem.get_slices(GuestAddress(0xa00), 0x400);
672+
assert_eq!(slices.next().unwrap().unwrap().len(), 0x200);
673+
assert_eq!(slices.next().unwrap().unwrap().len(), 0x200);
674+
assert!(slices.next().is_none());
675+
}
676+
627677
#[test]
628678
fn test_atomic_accesses() {
629679
let region = GuestRegionMmap::from_range(GuestAddress(0), 0x1000, None).unwrap();

0 commit comments

Comments
 (0)