Skip to content

Commit 40bddef

Browse files
Sudan Landgewearyzen
authored andcommitted
refactor: use slab for user_data instead of box
Use slab crate instead of box to avoid `unsafe`. Move generic type from push/pop to IoUring struct. Signed-off-by: ihciah <[email protected]> Signed-off-by: Sudan Landge <[email protected]>
1 parent e7ddf27 commit 40bddef

File tree

10 files changed

+165
-191
lines changed

10 files changed

+165
-191
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/vmm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ bincode = "1.2.1"
3737
micro_http = { git = "https://github.com/firecracker-microvm/micro-http" }
3838
log-instrument = { path = "../log-instrument", optional = true }
3939
crc64 = "2.0.0"
40+
slab = "0.4.7"
4041

4142
seccompiler = { path = "../seccompiler" }
4243
utils = { path = "../utils" }

src/vmm/src/devices/virtio/virtio_block/io/async_io.rs

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub enum AsyncIoError {
3737
#[derive(Debug)]
3838
pub struct AsyncFileEngine<T> {
3939
file: File,
40-
ring: IoUring,
40+
ring: IoUring<WrappedUserData<T>>,
4141
completion_evt: EventFd,
4242
phantom: PhantomData<T>,
4343
}
@@ -73,7 +73,10 @@ impl<T: Debug> WrappedUserData<T> {
7373
}
7474

7575
impl<T: Debug> AsyncFileEngine<T> {
76-
fn new_ring(file: &File, completion_fd: RawFd) -> Result<IoUring, io_uring::IoUringError> {
76+
fn new_ring(
77+
file: &File,
78+
completion_fd: RawFd,
79+
) -> Result<IoUring<WrappedUserData<T>>, io_uring::IoUringError> {
7780
IoUring::new(
7881
u32::from(IO_URING_NUM_ENTRIES),
7982
vec![file],
@@ -142,21 +145,18 @@ impl<T: Debug> AsyncFileEngine<T> {
142145

143146
let wrapped_user_data = WrappedUserData::new_with_dirty_tracking(addr, user_data);
144147

145-
// SAFETY: Safe because we trust that the host kernel will pass us back a completed entry
146-
// with this same `user_data`, so that the value will not be leaked.
147-
unsafe {
148-
self.ring.push(Operation::read(
148+
self.ring
149+
.push(Operation::read(
149150
0,
150151
buf as usize,
151152
count,
152153
offset,
153154
wrapped_user_data,
154155
))
155-
}
156-
.map_err(|err_tuple| UserDataError {
157-
user_data: err_tuple.1.user_data,
158-
error: AsyncIoError::IoUring(err_tuple.0),
159-
})
156+
.map_err(|err_tuple| UserDataError {
157+
user_data: err_tuple.1.user_data,
158+
error: AsyncIoError::IoUring(err_tuple.0),
159+
})
160160
}
161161

162162
pub fn push_write(
@@ -179,34 +179,29 @@ impl<T: Debug> AsyncFileEngine<T> {
179179

180180
let wrapped_user_data = WrappedUserData::new(user_data);
181181

182-
// SAFETY: Safe because we trust that the host kernel will pass us back a completed entry
183-
// with this same `user_data`, so that the value will not be leaked.
184-
unsafe {
185-
self.ring.push(Operation::write(
182+
self.ring
183+
.push(Operation::write(
186184
0,
187185
buf as usize,
188186
count,
189187
offset,
190188
wrapped_user_data,
191189
))
192-
}
193-
.map_err(|err_tuple| UserDataError {
194-
user_data: err_tuple.1.user_data,
195-
error: AsyncIoError::IoUring(err_tuple.0),
196-
})
190+
.map_err(|err_tuple| UserDataError {
191+
user_data: err_tuple.1.user_data,
192+
error: AsyncIoError::IoUring(err_tuple.0),
193+
})
197194
}
198195

199196
pub fn push_flush(&mut self, user_data: T) -> Result<(), UserDataError<T, AsyncIoError>> {
200197
let wrapped_user_data = WrappedUserData::new(user_data);
201198

202-
// SAFETY: Safe because we trust that the host kernel will pass us back a completed entry
203-
// with this same `user_data`, so that the value will not be leaked.
204-
unsafe { self.ring.push(Operation::fsync(0, wrapped_user_data)) }.map_err(|err_tuple| {
205-
UserDataError {
199+
self.ring
200+
.push(Operation::fsync(0, wrapped_user_data))
201+
.map_err(|err_tuple| UserDataError {
206202
user_data: err_tuple.1.user_data,
207203
error: AsyncIoError::IoUring(err_tuple.0),
208-
}
209-
})
204+
})
210205
}
211206

212207
pub fn kick_submission_queue(&mut self) -> Result<(), AsyncIoError> {
@@ -242,11 +237,7 @@ impl<T: Debug> AsyncFileEngine<T> {
242237
}
243238

244239
fn do_pop(&mut self) -> Result<Option<Cqe<WrappedUserData<T>>>, AsyncIoError> {
245-
// SAFETY: We trust that the host kernel did not touch the operation's `user_data` field.
246-
// The `T` type is the same one used for `push`ing and since the kernel made this entry
247-
// available in the completion queue, we now have full ownership of it.
248-
249-
unsafe { self.ring.pop::<WrappedUserData<T>>() }.map_err(AsyncIoError::IoUring)
240+
self.ring.pop().map_err(AsyncIoError::IoUring)
250241
}
251242

252243
pub fn pop(&mut self, mem: &GuestMemoryMmap) -> Result<Option<Cqe<T>>, AsyncIoError> {

src/vmm/src/io_uring/mod.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl IoUringError {
7878

7979
/// Main object representing an io_uring instance.
8080
#[derive(Debug)]
81-
pub struct IoUring {
81+
pub struct IoUring<T> {
8282
registered_fds_count: u32,
8383
squeue: SubmissionQueue,
8484
cqueue: CompletionQueue,
@@ -91,9 +91,10 @@ pub struct IoUring {
9191
// The total number of ops. These includes the ops on the submission queue, the in-flight ops
9292
// and the ops that are in the CQ, but haven't been popped yet.
9393
num_ops: u32,
94+
slab: slab::Slab<T>,
9495
}
9596

96-
impl IoUring {
97+
impl<T: Debug> IoUring<T> {
9798
/// Create a new instance.
9899
///
99100
/// # Arguments
@@ -136,13 +137,16 @@ impl IoUring {
136137

137138
let squeue = SubmissionQueue::new(fd, &params).map_err(IoUringError::SQueue)?;
138139
let cqueue = CompletionQueue::new(fd, &params).map_err(IoUringError::CQueue)?;
140+
let slab =
141+
slab::Slab::with_capacity(params.sq_entries as usize + params.cq_entries as usize);
139142

140143
let mut instance = Self {
141144
squeue,
142145
cqueue,
143146
fd: file,
144147
registered_fds_count: 0,
145148
num_ops: 0,
149+
slab,
146150
};
147151

148152
instance.check_operations()?;
@@ -161,11 +165,7 @@ impl IoUring {
161165
}
162166

163167
/// Push an [`Operation`](operation/struct.Operation.html) onto the submission queue.
164-
///
165-
/// # Safety
166-
/// Unsafe because we pass a raw user_data pointer to the kernel.
167-
/// It's up to the caller to make sure that this value is ever freed (not leaked).
168-
pub unsafe fn push<T: Debug>(&mut self, op: Operation<T>) -> Result<(), (IoUringError, T)> {
168+
pub fn push(&mut self, op: Operation<T>) -> Result<(), (IoUringError, T)> {
169169
// validate that we actually did register fds
170170
let fd = op.fd();
171171
match self.registered_fds_count {
@@ -176,29 +176,38 @@ impl IoUring {
176176
return Err((IoUringError::FullCQueue, op.user_data()));
177177
}
178178
self.squeue
179-
.push(op.into_sqe())
179+
.push(op.into_sqe(&mut self.slab))
180180
.map(|res| {
181181
// This is safe since self.num_ops < IORING_MAX_CQ_ENTRIES (65536)
182182
self.num_ops += 1;
183183
res
184184
})
185-
.map_err(|err_tuple: (SQueueError, T)| -> (IoUringError, T) {
186-
(IoUringError::SQueue(err_tuple.0), err_tuple.1)
185+
.map_err(|(sqe_err, user_data_key)| -> (IoUringError, T) {
186+
(
187+
IoUringError::SQueue(sqe_err),
188+
// We don't use slab.try_remove here for 2 reasons:
189+
// 1. user_data was insertde in slab with step `op.into_sqe` just
190+
// before the push op so the user_data key should be valid and if
191+
// key is valid then `slab.remove()` will not fail.
192+
// 2. If we use `slab.try_remove()` we'll have to find a way to return
193+
// a default value for the generic type T which is difficult because
194+
// it expands to more crates which don't make it easy to define a
195+
// default/clone type for type T.
196+
// So beleiving that `slab.remove` won't fail we don't use
197+
// the `slab.try_remove` method.
198+
#[allow(clippy::cast_possible_truncation)]
199+
self.slab.remove(user_data_key as usize),
200+
)
187201
})
188202
}
189203
}
190204
}
191205

192206
/// Pop a completed entry off the completion queue. Returns `Ok(None)` if there are no entries.
193207
/// The type `T` must be the same as the `user_data` type used for `push`-ing the operation.
194-
///
195-
/// # Safety
196-
/// Unsafe because we reconstruct the `user_data` from a raw pointer passed by the kernel.
197-
/// It's up to the caller to make sure that `T` is the correct type of the `user_data`, that
198-
/// the raw pointer is valid and that we have full ownership of that address.
199-
pub unsafe fn pop<T: Debug>(&mut self) -> Result<Option<Cqe<T>>, IoUringError> {
208+
pub fn pop(&mut self) -> Result<Option<Cqe<T>>, IoUringError> {
200209
self.cqueue
201-
.pop()
210+
.pop(&mut self.slab)
202211
.map(|maybe_cqe| {
203212
maybe_cqe.map(|cqe| {
204213
// This is safe since the pop-ed CQEs have been previously pushed. However
@@ -391,8 +400,8 @@ mod tests {
391400
use super::*;
392401
use crate::vstate::memory::{Bytes, MmapRegion};
393402

394-
fn drain_cqueue(ring: &mut IoUring) {
395-
while let Some(entry) = unsafe { ring.pop::<u32>().unwrap() } {
403+
fn drain_cqueue(ring: &mut IoUring<u32>) {
404+
while let Some(entry) = ring.pop().unwrap() {
396405
entry.result().unwrap();
397406

398407
// Assert that there were no partial writes.
@@ -581,9 +590,7 @@ mod tests {
581590
ring.submit_and_wait_all().unwrap();
582591
drain_cqueue(&mut ring);
583592
}
584-
unsafe {
585-
ring.push(operation).unwrap();
586-
}
593+
ring.push(operation).unwrap();
587594
}
588595

589596
// Submit any left async ops and wait.

src/vmm/src/io_uring/operation/cqe.rs

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,13 @@ unsafe impl ByteValued for io_uring_cqe {}
1313
#[derive(Debug)]
1414
pub struct Cqe<T> {
1515
res: i32,
16-
user_data: Box<T>,
16+
user_data: T,
1717
}
1818

1919
impl<T: Debug> Cqe<T> {
20-
/// Construct a [`Cqe`] object from a raw `io_uring_cqe`.
21-
///
22-
/// # Safety
23-
/// Unsafe because we assume full ownership of the inner.user_data address.
24-
/// We assume that it points to a valid address created with a `Box<T>`,
25-
/// with the correct type `T`, and that ownership of that address is passed
26-
/// to this function.
27-
pub(crate) unsafe fn new(inner: io_uring_cqe) -> Self {
28-
Self {
29-
res: inner.res,
30-
user_data: Box::from_raw(inner.user_data as *mut T),
31-
}
20+
/// Construct a Cqe object from a raw `io_uring_cqe`.
21+
pub(crate) fn new(res: i32, user_data: T) -> Self {
22+
Self { res, user_data }
3223
}
3324

3425
/// Return the number of bytes successfully transferred by this operation.
@@ -51,13 +42,13 @@ impl<T: Debug> Cqe<T> {
5142
pub fn map_user_data<U: Debug, F: FnOnce(T) -> U>(self, op: F) -> Cqe<U> {
5243
Cqe {
5344
res: self.res,
54-
user_data: Box::new(op(self.user_data())),
45+
user_data: op(self.user_data()),
5546
}
5647
}
5748

5849
/// Consume the object and return the user_data.
5950
pub fn user_data(self) -> T {
60-
*self.user_data
51+
self.user_data
6152
}
6253
}
6354

@@ -69,15 +60,8 @@ mod tests {
6960
fn test_result() {
7061
// Check that `result()` returns an `Error` when `res` is negative.
7162
{
72-
let user_data = Box::new(10u8);
73-
74-
let cqe: Cqe<u8> = unsafe {
75-
Cqe::new(io_uring_cqe {
76-
user_data: Box::into_raw(user_data) as u64,
77-
res: -22,
78-
flags: 0,
79-
})
80-
};
63+
let user_data = 10_u8;
64+
let cqe: Cqe<u8> = Cqe::new(-22, user_data);
8165

8266
assert_eq!(
8367
cqe.result().unwrap_err().kind(),
@@ -87,32 +71,27 @@ mod tests {
8771

8872
// Check that `result()` returns Ok() when `res` is positive.
8973
{
90-
let user_data = Box::new(10u8);
91-
92-
let cqe: Cqe<u8> = unsafe {
93-
Cqe::new(io_uring_cqe {
94-
user_data: Box::into_raw(user_data) as u64,
95-
res: 128,
96-
flags: 0,
97-
})
98-
};
74+
let user_data = 10_u8;
75+
let cqe: Cqe<u8> = Cqe::new(128, user_data);
9976

10077
assert_eq!(cqe.result().unwrap(), 128);
10178
}
10279
}
10380

10481
#[test]
10582
fn test_user_data() {
106-
let user_data = Box::new(10u8);
107-
108-
let cqe: Cqe<u8> = unsafe {
109-
Cqe::new(io_uring_cqe {
110-
user_data: Box::into_raw(user_data) as u64,
111-
res: 0,
112-
flags: 0,
113-
})
114-
};
83+
let user_data = 10_u8;
84+
let cqe: Cqe<u8> = Cqe::new(0, user_data);
11585

11686
assert_eq!(cqe.user_data(), 10);
11787
}
88+
89+
#[test]
90+
fn test_map_user_data() {
91+
let user_data = 10_u8;
92+
let cqe: Cqe<u8> = Cqe::new(0, user_data);
93+
let cqe = cqe.map_user_data(|x| x + 1);
94+
95+
assert_eq!(cqe.user_data(), 11);
96+
}
11897
}

0 commit comments

Comments
 (0)