-
Notifications
You must be signed in to change notification settings - Fork 110
Preliminary virtual memory work #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
is now a type alias for `GuestRegionContainer<GuestRegionMmap>`). | ||
- \[[#338](https://github.com/rust-vmm/vm-memory/pull/338)\] Make `GuestMemoryAtomic` always implement `Clone`. | ||
- \[[#338](https://github.com/rust-vmm/vm-memory/pull/338)\] Make `GuestAddressSpace` a subtrait of `Clone`. | ||
- \[[#339](https://github.com/rust-vmm/vm-memory/pull/339)\] Add `GuestMemory::get_slices()` | ||
|
||
### Changed | ||
|
||
|
@@ -22,6 +23,8 @@ | |
and `GuestRegionMmap::from_range` to be separate from the error type returned by `GuestRegionCollection` functions. | ||
Change return type of `GuestRegionMmap::new` from `Result` to `Option`. | ||
- \[#324](https:////github.com/rust-vmm/vm-memory/pull/324)\] `GuestMemoryRegion::bitmap()` now returns a `BitmapSlice`. Accessing the full bitmap is now possible only if the type of the memory region is know, for example with `MmapRegion::bitmap()`. | ||
- \[[#339](https://github.com/rust-vmm/vm-memory/pull/339)\] Fix `Bytes::read()` and `Bytes::write()` not to ignore `try_access()`'s `count` parameter | ||
- \[[#339](https://github.com/rust-vmm/vm-memory/pull/339)\] Implement `Bytes::load()` and `Bytes::store()` with `try_access()` instead of `to_region_addr()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also specify that it's only relevant for the blanket impl |
||
|
||
### Removed | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
use std::convert::From; | ||
use std::fs::File; | ||
use std::io; | ||
use std::mem::size_of; | ||
use std::ops::{BitAnd, BitOr, Deref}; | ||
use std::rc::Rc; | ||
use std::sync::atomic::Ordering; | ||
|
@@ -455,6 +456,93 @@ pub trait GuestMemory { | |
.ok_or(Error::InvalidGuestAddress(addr)) | ||
.and_then(|(r, addr)| r.get_slice(addr, count)) | ||
} | ||
|
||
/// Returns an iterator over [`VolatileSlice`](struct.VolatileSlice.html)s, together covering | ||
/// `count` bytes starting at `addr`. | ||
/// | ||
/// Iterating in this way is necessary because the given address range may be fragmented across | ||
/// multiple [`GuestMemoryRegion`]s. | ||
/// | ||
/// The iterator’s items are wrapped in [`Result`], i.e. errors are reported on individual | ||
/// items. If there is no such error, the cumulative length of all items will be equal to | ||
/// `count`. If `count` is 0, an empty iterator will be returned. | ||
fn get_slices<'a>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we reimplement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it can be deprecated, I don’t have a stake in that game. :)
So we can definitely try and see whether the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gave it a try for vm-memory, and turns out the problem is less operating on VolatileSlice instead of GuestRegion, but more than the iterator doesn't give us the diff --git a/src/bitmap/mod.rs b/src/bitmap/mod.rs
index 802ebef1ca72..c92b7e618cfd 100644
--- a/src/bitmap/mod.rs
+++ b/src/bitmap/mod.rs
@@ -288,16 +288,12 @@ pub(crate) mod tests {
// Finally, let's invoke the generic tests for `Bytes`.
let check_range_closure = |m: &M, start: usize, len: usize, clean: bool| -> bool {
- let mut check_result = true;
- m.try_access(len, GuestAddress(start as u64), |_, size, reg_addr, reg| {
- if !check_range(®.bitmap(), reg_addr.0 as usize, size, clean) {
- check_result = false;
- }
- Ok(size)
- })
- .unwrap();
-
- check_result
+ m.get_slices(GuestAddress(start as u64), len)
+ .try_fold(true, |check_result, slice| {
+ let slice = slice?;
+ Ok(check_result && check_range(slice.bitmap(), 0, slice.len(), clean))
+ })
+ .unwrap()
};
test_bytes(
diff --git a/src/guest_memory.rs b/src/guest_memory.rs
index b9d3b62e5227..09fd38d898c2 100644
--- a/src/guest_memory.rs
+++ b/src/guest_memory.rs
@@ -354,9 +354,12 @@ pub trait GuestMemory {
/// Check whether the range [base, base + len) is valid.
fn check_range(&self, base: GuestAddress, len: usize) -> bool {
- match self.try_access(len, base, |_, count, _, _| -> Result<usize> { Ok(count) }) {
+ match self
+ .get_slices(base, len)
+ .try_fold(0, |acc, r| r.map(|slice| acc + slice.len()))
+ {
Ok(count) => count == len,
- _ => false,
+ Err(_) => false,
}
}
@@ -549,23 +552,13 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T {
type E = Error;
fn write(&self, buf: &[u8], addr: GuestAddress) -> Result<usize> {
- self.try_access(
- buf.len(),
- addr,
- |offset, count, caddr, region| -> Result<usize> {
- region.write(&buf[offset..(offset + count)], caddr)
- },
- )
+ self.get_slices(addr, buf.len())
+ .try_fold(0, |acc, slice| Ok(acc + slice?.write(&buf[acc..], 0)?))
}
fn read(&self, buf: &mut [u8], addr: GuestAddress) -> Result<usize> {
- self.try_access(
- buf.len(),
- addr,
- |offset, count, caddr, region| -> Result<usize> {
- region.read(&mut buf[offset..(offset + count)], caddr)
- },
- )
+ self.get_slices(addr, buf.len())
+ .try_fold(0, |acc, slice| Ok(acc + slice?.read(&mut buf[acc..], 0)?))
}
/// # Examples
@@ -629,8 +622,9 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T {
where
F: ReadVolatile,
{
- self.try_access(count, addr, |_, len, caddr, region| -> Result<usize> {
- region.read_volatile_from(caddr, src, len)
+ self.get_slices(addr, count).try_fold(0, |acc, r| {
+ let slice = r?;
+ Ok(acc + slice.read_volatile_from(0, src, slice.len())?)
})
}
@@ -657,10 +651,12 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T {
where
F: WriteVolatile,
{
- self.try_access(count, addr, |_, len, caddr, region| -> Result<usize> {
+ self.get_slices(addr, count).try_fold(0, |acc, r| {
+ let slice = r?;
// For a non-RAM region, reading could have side effects, so we
// must use write_all().
- region.write_all_volatile_to(caddr, dst, len).map(|()| len)
+ slice.write_all_volatile_to(0, dst, slice.len())?;
+ Ok(acc + slice.len())
})
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If you think it helps, we could of course add that, e.g. as part of |
||
&'a self, | ||
addr: GuestAddress, | ||
count: usize, | ||
) -> GuestMemorySliceIterator<'a, Self> { | ||
GuestMemorySliceIterator { | ||
mem: self, | ||
addr, | ||
count, | ||
} | ||
} | ||
} | ||
|
||
/// Iterates over [`VolatileSlice`]s that together form a guest memory area. | ||
/// | ||
/// Returned by [`GuestMemory::get_slices()`]. | ||
#[derive(Debug)] | ||
pub struct GuestMemorySliceIterator<'a, M: GuestMemory + ?Sized> { | ||
roypat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Underlying memory | ||
mem: &'a M, | ||
/// Next address in the guest memory area | ||
addr: GuestAddress, | ||
/// Remaining bytes in the guest memory area | ||
count: usize, | ||
} | ||
|
||
impl<'a, M: GuestMemory + ?Sized> GuestMemorySliceIterator<'a, M> { | ||
/// Helper function for [`<Self as Iterator>::next()`]. | ||
/// | ||
/// Get the next slice (i.e. the one starting from `self.addr` with a length up to | ||
/// `self.count`) and update the internal state. | ||
/// | ||
/// # Safety | ||
/// | ||
/// This function does not reset to `self.count` to 0 in case of error, i.e. will not stop | ||
/// iterating. Actual behavior after an error is ill-defined, so the caller must check the | ||
/// return value, and in case of an error, reset `self.count` to 0. | ||
/// | ||
/// (This is why this function exists, so this resetting can be done in a single central | ||
/// location.) | ||
unsafe fn do_next(&mut self) -> Option<Result<VolatileSlice<'a, MS<'a, M>>>> { | ||
if self.count == 0 { | ||
return None; | ||
} | ||
|
||
let Some((region, start)) = self.mem.to_region_addr(self.addr) else { | ||
return Some(Err(Error::InvalidGuestAddress(self.addr))); | ||
}; | ||
|
||
let cap = region.len() - start.raw_value(); | ||
let len = std::cmp::min(cap, self.count as GuestUsize); | ||
|
||
self.count -= len as usize; | ||
self.addr = match self.addr.overflowing_add(len as GuestUsize) { | ||
(x @ GuestAddress(0), _) | (x, false) => x, | ||
(_, true) => return Some(Err(Error::GuestAddressOverflow)), | ||
}; | ||
|
||
Some(region.get_slice(start, len as usize)) | ||
} | ||
} | ||
|
||
impl<'a, M: GuestMemory + ?Sized> Iterator for GuestMemorySliceIterator<'a, M> { | ||
type Item = Result<VolatileSlice<'a, MS<'a, M>>>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
// SAFETY: | ||
// We reset `self.count` to 0 on error | ||
match unsafe { self.do_next() } { | ||
Some(Ok(slice)) => Some(Ok(slice)), | ||
other => { | ||
// On error (or end), reset to 0 so iteration remains stopped | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right! |
||
self.count = 0; | ||
other | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T { | ||
|
@@ -464,8 +552,8 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T { | |
self.try_access( | ||
buf.len(), | ||
addr, | ||
|offset, _count, caddr, region| -> Result<usize> { | ||
region.write(&buf[offset..], caddr) | ||
|offset, count, caddr, region| -> Result<usize> { | ||
region.write(&buf[offset..(offset + count)], caddr) | ||
}, | ||
) | ||
} | ||
|
@@ -474,8 +562,8 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T { | |
self.try_access( | ||
buf.len(), | ||
addr, | ||
|offset, _count, caddr, region| -> Result<usize> { | ||
region.read(&mut buf[offset..], caddr) | ||
|offset, count, caddr, region| -> Result<usize> { | ||
region.read(&mut buf[offset..(offset + count)], caddr) | ||
}, | ||
) | ||
} | ||
|
@@ -591,17 +679,62 @@ impl<T: GuestMemory + ?Sized> Bytes<GuestAddress> for T { | |
} | ||
|
||
fn store<O: AtomicAccess>(&self, val: O, addr: GuestAddress, order: Ordering) -> Result<()> { | ||
// `find_region` should really do what `to_region_addr` is doing right now, except | ||
// it should keep returning a `Result`. | ||
self.to_region_addr(addr) | ||
.ok_or(Error::InvalidGuestAddress(addr)) | ||
.and_then(|(region, region_addr)| region.store(val, region_addr, order)) | ||
let expected = size_of::<O>(); | ||
|
||
let completed = self.try_access( | ||
expected, | ||
addr, | ||
|offset, len, region_addr, region| -> Result<usize> { | ||
assert_eq!(offset, 0); | ||
if len < expected { | ||
return Err(Error::PartialBuffer { | ||
expected, | ||
completed: 0, | ||
}); | ||
} | ||
region.store(val, region_addr, order).map(|()| expected) | ||
}, | ||
)?; | ||
|
||
if completed < expected { | ||
Err(Error::PartialBuffer { | ||
expected, | ||
completed, | ||
}) | ||
} else { | ||
Ok(()) | ||
} | ||
Comment on lines
+682
to
+706
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this one (and the one below) would be a bit simpler in terms of let iter = self.get_slices(addr, size_of::<O>());
let vslice = iter.next()?;
if iter.next().is_some() {
return Err(PartialBuffer {0})
}
vslice.store(val) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. heh, or just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, OK. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does! In fact, GuestMemoryRegion::store() just defers to Btw, this function is exactly what made me go "mh, maybe there is a use for get_slice() after all". Because atomic loads/stores that cross region boundaries cannot work, and not something that is an implementation TODO that can be solved somehow (e.g. if the guest gives us a virt queue where an atomic goes across region boundaries, then best we can do is fail the device activation). |
||
} | ||
|
||
fn load<O: AtomicAccess>(&self, addr: GuestAddress, order: Ordering) -> Result<O> { | ||
self.to_region_addr(addr) | ||
.ok_or(Error::InvalidGuestAddress(addr)) | ||
.and_then(|(region, region_addr)| region.load(region_addr, order)) | ||
let expected = size_of::<O>(); | ||
let mut result = None::<O>; | ||
|
||
let completed = self.try_access( | ||
expected, | ||
addr, | ||
|offset, len, region_addr, region| -> Result<usize> { | ||
assert_eq!(offset, 0); | ||
if len < expected { | ||
return Err(Error::PartialBuffer { | ||
expected, | ||
completed: 0, | ||
}); | ||
} | ||
result = Some(region.load(region_addr, order)?); | ||
Ok(expected) | ||
}, | ||
)?; | ||
|
||
if completed < expected { | ||
Err(Error::PartialBuffer { | ||
expected, | ||
completed, | ||
}) | ||
} else { | ||
// Must be set because `completed == expected` | ||
Ok(result.unwrap()) | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably go into a
Fixed
section. Also, let's specify that this only applies to the blanket impl provided forT: GuestMemory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, right!