Skip to content

Commit f996e89

Browse files
committed
feat(IovDeque): configurable queue len
Add a const generic parameter to specify the length of the IovDeque. This way different devices can use different sizes for the queue. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 2689dd6 commit f996e89

File tree

4 files changed

+69
-60
lines changed

4 files changed

+69
-60
lines changed

src/vmm/src/devices/virtio/iov_deque.rs

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::os::fd::AsRawFd;
66
use libc::{c_int, c_void, iovec, off_t, size_t};
77
use memfd;
88

9-
use super::queue::FIRECRACKER_MAX_QUEUE_SIZE;
109
use crate::arch::PAGE_SIZE;
1110

1211
#[derive(Debug, thiserror::Error, displaydoc::Display)]
@@ -69,26 +68,42 @@ pub enum IovDequeError {
6968
//
7069
// Like that, the elements stored in the buffer are always laid out in contiguous virtual memory,
7170
// so making a slice out of them does not require any copies.
71+
//
72+
// The `L` const generic determines the maximum number of `iovec` elements the queue should hold.
73+
//
74+
// ```Rust
75+
// pub struct iovec {
76+
// pub iov_base: *mut ::c_void,
77+
// pub iov_len: ::size_t,
78+
// }
79+
// ```
80+
//
81+
// This value must be a multiple of 256 because this is the maximum number of `iovec` can fit into
82+
// 1 memory page: 256 * sizeof(iovec) == 4096 == PAGE_SIZE. IovDeque only operates with `PAGE_SIZE`
83+
// granularity.
7284
#[derive(Debug)]
73-
pub struct IovDeque {
85+
pub struct IovDeque<const L: u16> {
7486
pub iov: *mut libc::iovec,
7587
pub start: u16,
7688
pub len: u16,
7789
}
7890

7991
// SAFETY: This is `Send`. We hold sole ownership of the underlying buffer.
80-
unsafe impl Send for IovDeque {}
92+
unsafe impl<const L: u16> Send for IovDeque<L> {}
93+
94+
impl<const L: u16> IovDeque<L> {
95+
const BYTES: usize = L as usize * std::mem::size_of::<iovec>();
96+
const _ASSERT: () = assert!(Self::BYTES % PAGE_SIZE == 0);
8197

82-
impl IovDeque {
8398
/// Create a [`memfd`] object that represents a single physical page
8499
fn create_memfd() -> Result<memfd::Memfd, IovDequeError> {
85100
// Create a sealable memfd.
86101
let opts = memfd::MemfdOptions::default().allow_sealing(true);
87-
let mfd = opts.create("sized-1K")?;
102+
let mfd = opts.create("iov_deque")?;
88103

89104
// Resize to system page size.
90105
mfd.as_file()
91-
.set_len(PAGE_SIZE.try_into().unwrap())
106+
.set_len(Self::BYTES.try_into().unwrap())
92107
.map_err(IovDequeError::MemfdResize)?;
93108

94109
// Add seals to prevent further resizing.
@@ -121,35 +136,13 @@ impl IovDeque {
121136

122137
/// Allocate memory for our ring buffer
123138
///
124-
/// This will allocate exactly two pages of virtual memory. In order to implement the
125-
/// optimization that allows us to always have elements in contiguous memory we need
126-
/// allocations at the granularity of `PAGE_SIZE`. Now, our queues are at maximum 256
127-
/// descriptors long and `struct iovec` looks like this:
128-
///
129-
/// ```Rust
130-
/// pub struct iovec {
131-
/// pub iov_base: *mut ::c_void,
132-
/// pub iov_len: ::size_t,
133-
/// }
134-
/// ```
135-
///
136-
/// so, it's 16 bytes long. As a result, we need a single page for holding the actual data of
137-
/// our buffer.
139+
/// This will allocate 2 * `Self::BYTES` bytes of virtual memory.
138140
fn allocate_ring_buffer_memory() -> Result<*mut c_void, IovDequeError> {
139-
// The fact that we allocate two pages is due to the size of `struct iovec` times our queue
140-
// size equals the page size. Add here a debug assertion to reflect that and ensure that we
141-
// will adapt our logic if the assumption changes in the future.
142-
const {
143-
assert!(
144-
std::mem::size_of::<iovec>() * FIRECRACKER_MAX_QUEUE_SIZE as usize == PAGE_SIZE
145-
);
146-
}
147-
148141
// SAFETY: We are calling the system call with valid arguments
149142
unsafe {
150143
Self::mmap(
151144
std::ptr::null_mut(),
152-
PAGE_SIZE * 2,
145+
Self::BYTES * 2,
153146
libc::PROT_NONE,
154147
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
155148
-1,
@@ -169,7 +162,7 @@ impl IovDeque {
169162
let _ = unsafe {
170163
Self::mmap(
171164
buffer,
172-
PAGE_SIZE,
165+
Self::BYTES,
173166
libc::PROT_READ | libc::PROT_WRITE,
174167
libc::MAP_SHARED | libc::MAP_FIXED,
175168
raw_memfd,
@@ -180,19 +173,17 @@ impl IovDeque {
180173
// Map the second page of virtual memory to the physical page described by the memfd object
181174
//
182175
// SAFETY: This is safe because:
183-
// * Both `buffer` and the result of `buffer.add(PAGE_SIZE)` are within bounds of the
176+
// * Both `buffer` and the result of `buffer.add(Self::BYTES)` are within bounds of the
184177
// allocation we got from `Self::allocate_ring_buffer_memory`.
185-
// * The computed offset is `PAGE_SIZE * size_of::<c_void>() == PAGE_SIZE bytes` which fits
186-
// in `isize`
187178
// * The resulting pointer is the beginning of the second page of our allocation, so it
188179
// doesn't wrap around the address space.
189-
let next_page = unsafe { buffer.add(PAGE_SIZE) };
180+
let next_page = unsafe { buffer.add(Self::BYTES) };
190181

191182
// SAFETY: We are calling the system call with valid arguments
192183
let _ = unsafe {
193184
Self::mmap(
194185
next_page,
195-
PAGE_SIZE,
186+
Self::BYTES,
196187
libc::PROT_READ | libc::PROT_WRITE,
197188
libc::MAP_SHARED | libc::MAP_FIXED,
198189
raw_memfd,
@@ -216,7 +207,7 @@ impl IovDeque {
216207
/// Returns `true` if the [`IovDeque`] is full, `false` otherwise
217208
#[inline(always)]
218209
pub fn is_full(&self) -> bool {
219-
self.len() == FIRECRACKER_MAX_QUEUE_SIZE
210+
self.len() == L
220211
}
221212

222213
/// Resets the queue, dropping all its elements.
@@ -261,8 +252,8 @@ impl IovDeque {
261252

262253
self.start += nr_iovecs;
263254
self.len -= nr_iovecs;
264-
if self.start >= FIRECRACKER_MAX_QUEUE_SIZE {
265-
self.start -= FIRECRACKER_MAX_QUEUE_SIZE;
255+
if self.start >= L {
256+
self.start -= L;
266257
}
267258
}
268259

@@ -318,19 +309,21 @@ impl IovDeque {
318309
}
319310
}
320311

321-
impl Drop for IovDeque {
312+
impl<const L: u16> Drop for IovDeque<L> {
322313
fn drop(&mut self) {
323314
// SAFETY: We are passing an address that we got from a previous allocation of `2 *
324-
// PAGE_SIZE` bytes by calling mmap
325-
let _ = unsafe { libc::munmap(self.iov.cast(), PAGE_SIZE * 2) };
315+
// Self::BYTES` bytes by calling mmap
316+
let _ = unsafe { libc::munmap(self.iov.cast(), Self::BYTES * 2) };
326317
}
327318
}
328319

329320
#[cfg(test)]
330321
mod tests {
331322
use libc::iovec;
332323

333-
use super::IovDeque;
324+
// Redefine `IovDeque` with specific length. Otherwise
325+
// Rust will not know what to do.
326+
type IovDeque = super::IovDeque<256>;
334327

335328
#[test]
336329
fn test_new() {

src/vmm/src/devices/virtio/iovec.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use vm_memory::{
1111
};
1212

1313
use super::iov_deque::{IovDeque, IovDequeError};
14+
use super::queue::FIRECRACKER_MAX_QUEUE_SIZE;
1415
use crate::devices::virtio::queue::DescriptorChain;
1516
use crate::vstate::memory::GuestMemoryMmap;
1617

@@ -223,10 +224,11 @@ pub struct ParsedDescriptorChain {
223224
/// It describes a write-only buffer passed to us by the guest that is scattered across multiple
224225
/// memory regions. Additionally, this wrapper provides methods that allow reading arbitrary ranges
225226
/// of data from that buffer.
227+
/// `L` const generic value must be a multiple of 256 as required by the `IovDeque` requirements.
226228
#[derive(Debug)]
227-
pub struct IoVecBufferMut {
229+
pub struct IoVecBufferMut<const L: u16 = FIRECRACKER_MAX_QUEUE_SIZE> {
228230
// container of the memory regions included in this IO vector
229-
pub vecs: IovDeque,
231+
pub vecs: IovDeque<L>,
230232
// Total length of the IoVecBufferMut
231233
// We use `u32` here because we use this type in devices which
232234
// should not give us huge buffers. In any case this
@@ -236,9 +238,9 @@ pub struct IoVecBufferMut {
236238

237239
// SAFETY: `IoVecBufferMut` doesn't allow for interior mutability and no shared ownership is
238240
// possible as it doesn't implement clone
239-
unsafe impl Send for IoVecBufferMut {}
241+
unsafe impl<const L: u16> Send for IoVecBufferMut<L> {}
240242

241-
impl IoVecBufferMut {
243+
impl<const L: u16> IoVecBufferMut<L> {
242244
/// Append a `DescriptorChain` in this `IoVecBufferMut`
243245
///
244246
/// # Safety
@@ -477,7 +479,11 @@ mod tests {
477479
use libc::{c_void, iovec};
478480
use vm_memory::VolatileMemoryError;
479481

480-
use super::{IoVecBuffer, IoVecBufferMut};
482+
use super::IoVecBuffer;
483+
// Redefine `IoVecBufferMut` with specific length. Otherwise
484+
// Rust will not know what to do.
485+
type IoVecBufferMut = super::IoVecBufferMut<256>;
486+
481487
use crate::devices::virtio::iov_deque::IovDeque;
482488
use crate::devices::virtio::queue::{Queue, VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
483489
use crate::devices::virtio::test_utils::VirtQueue;
@@ -514,7 +520,7 @@ mod tests {
514520
}
515521
}
516522

517-
impl From<&mut [u8]> for IoVecBufferMut {
523+
impl<const L: u16> From<&mut [u8]> for super::IoVecBufferMut<L> {
518524
fn from(buf: &mut [u8]) -> Self {
519525
let mut vecs = IovDeque::new().unwrap();
520526
vecs.push_back(iovec {
@@ -529,7 +535,7 @@ mod tests {
529535
}
530536
}
531537

532-
impl From<Vec<&mut [u8]>> for IoVecBufferMut {
538+
impl<const L: u16> From<Vec<&mut [u8]>> for super::IoVecBufferMut<L> {
533539
fn from(buffer: Vec<&mut [u8]>) -> Self {
534540
let mut len = 0;
535541
let mut vecs = IovDeque::new().unwrap();
@@ -804,9 +810,14 @@ mod verification {
804810
use vm_memory::bitmap::BitmapSlice;
805811
use vm_memory::VolatileSlice;
806812

807-
use super::{IoVecBuffer, IoVecBufferMut};
808-
use crate::arch::PAGE_SIZE;
813+
use super::IoVecBuffer;
809814
use crate::devices::virtio::iov_deque::IovDeque;
815+
// Redefine `IoVecBufferMut` and `IovDeque` with specific length. Otherwise
816+
// Rust will not know what to do.
817+
type IoVecBufferMut256 = super::IoVecBufferMut<256>;
818+
type IovDeque256 = IovDeque<256>;
819+
820+
use crate::arch::PAGE_SIZE;
810821
use crate::devices::virtio::queue::FIRECRACKER_MAX_QUEUE_SIZE;
811822

812823
// Maximum memory size to use for our buffers. For the time being 1KB.
@@ -837,7 +848,7 @@ mod verification {
837848
///
838849
/// This stub helps imitate the effect of mirroring without all the elaborate memory
839850
/// allocation trick.
840-
pub fn push_back(deque: &mut IovDeque, iov: iovec) {
851+
pub fn push_back<const L: u16>(deque: &mut IovDeque<L>, iov: iovec) {
841852
// This should NEVER happen, since our ring buffer is as big as the maximum queue size.
842853
// We also check for the sanity of the VirtIO queues, in queue.rs, which means that if
843854
// we ever try to add something in a full ring buffer, there is an internal
@@ -893,22 +904,22 @@ mod verification {
893904
}
894905
}
895906

896-
fn create_iov_deque() -> IovDeque {
907+
fn create_iov_deque() -> IovDeque256 {
897908
// SAFETY: safe because the layout has non-zero size
898909
let mem = unsafe {
899910
std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(
900911
2 * PAGE_SIZE,
901912
PAGE_SIZE,
902913
))
903914
};
904-
IovDeque {
915+
IovDeque256 {
905916
iov: mem.cast(),
906917
start: kani::any_where(|&start| start < FIRECRACKER_MAX_QUEUE_SIZE),
907918
len: 0,
908919
}
909920
}
910921

911-
fn create_iovecs_mut(mem: *mut u8, size: usize, nr_descs: usize) -> (IovDeque, u32) {
922+
fn create_iovecs_mut(mem: *mut u8, size: usize, nr_descs: usize) -> (IovDeque256, u32) {
912923
let mut vecs = create_iov_deque();
913924
let mut len = 0u32;
914925
for _ in 0..nr_descs {
@@ -928,7 +939,7 @@ mod verification {
928939
(vecs, len)
929940
}
930941

931-
impl IoVecBufferMut {
942+
impl IoVecBufferMut256 {
932943
fn any_of_length(nr_descs: usize) -> Self {
933944
// We only write into `IoVecBufferMut` objects, so we can simply create a guest memory
934945
// object initialized to zeroes, trying to be nice to Kani.
@@ -1018,10 +1029,12 @@ mod verification {
10181029
#[kani::proof]
10191030
#[kani::unwind(5)]
10201031
#[kani::solver(cadical)]
1032+
// The `IovDeque` is defined as type alias in the kani module. Because of this
1033+
// we need to specify original type here for stub to work.
10211034
#[kani::stub(IovDeque::push_back, stubs::push_back)]
10221035
fn verify_write_to_iovec() {
10231036
for nr_descs in 0..MAX_DESC_LENGTH {
1024-
let mut iov_mut = IoVecBufferMut::any_of_length(nr_descs);
1037+
let mut iov_mut = IoVecBufferMut256::any_of_length(nr_descs);
10251038

10261039
let mut buf = kani::vec::any_vec::<u8, GUEST_MEMORY_SIZE>();
10271040
let offset: u32 = kani::any();

src/vmm/src/devices/virtio/net/tap.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,13 @@ pub mod tests {
219219
use std::os::unix::ffi::OsStrExt;
220220

221221
use super::*;
222-
use crate::devices::virtio::iovec::IoVecBufferMut;
223222
use crate::devices::virtio::net::gen;
224223
use crate::devices::virtio::net::test_utils::{enable, if_index, TapTrafficSimulator};
225224

225+
// Redefine `IoVecBufferMut` with specific length. Otherwise
226+
// Rust will not know what to do.
227+
type IoVecBufferMut = crate::devices::virtio::iovec::IoVecBufferMut<256>;
228+
226229
// The size of the virtio net header
227230
const VNET_HDR_SIZE: usize = 10;
228231

src/vmm/src/devices/virtio/rng/device.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ mod tests {
442442
let desc = entropy_dev.queues_mut()[RNG_QUEUE].pop().unwrap();
443443
assert!(matches!(
444444
// SAFETY: This descriptor chain is only loaded into one buffer
445-
unsafe { IoVecBufferMut::from_descriptor_chain(&mem, desc) },
445+
unsafe { IoVecBufferMut::<256>::from_descriptor_chain(&mem, desc) },
446446
Err(crate::devices::virtio::iovec::IoVecError::ReadOnlyDescriptor)
447447
));
448448

0 commit comments

Comments
 (0)