io_uring: Pin iovecs in memory

Pointers to the iovec structs are shared with the kernel for the
duration of an I/O operation and so must be pinned in memory.  Use
Pin<Box<[iovec]>> instead of Vec<iovec> to express this and also to
prevent any changes made in the future from accidentally doing something
that causes the memory location of the iovecs to change.

BUG=none
TEST=unit tests

Change-Id: I317f3a6f68d457b0b0a6c86494d506b0e978b5fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2387823
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
This commit is contained in:
Chirantan Ekbote 2020-09-01 17:49:12 +09:00 committed by Commit Bot
parent b6c1eba580
commit 370ee0fb75

View file

@ -10,6 +10,7 @@ use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::pin::Pin;
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
@ -94,7 +95,7 @@ pub struct URingContext {
submit_ring: SubmitQueueState, submit_ring: SubmitQueueState,
submit_queue_entries: SubmitQueueEntries, submit_queue_entries: SubmitQueueEntries,
complete_ring: CompleteQueueState, complete_ring: CompleteQueueState,
io_vecs: Vec<libc::iovec>, io_vecs: Pin<Box<[libc::iovec]>>,
in_flight: usize, // The number of pending operations. in_flight: usize, // The number of pending operations.
added: usize, // The number of ops added since the last call to `io_uring_enter`. added: usize, // The number of ops added since the last call to `io_uring_enter`.
num_sqes: usize, // The total number of sqes allocated in shared memory. num_sqes: usize, // The total number of sqes allocated in shared memory.
@ -157,13 +158,16 @@ impl URingContext {
submit_ring, submit_ring,
submit_queue_entries, submit_queue_entries,
complete_ring, complete_ring,
io_vecs: vec![ io_vecs: Pin::from(
libc::iovec { vec![
iov_base: null_mut(), libc::iovec {
iov_len: 0 iov_base: null_mut(),
}; iov_len: 0
num_sqe };
], num_sqe
]
.into_boxed_slice(),
),
added: 0, added: 0,
num_sqes: ring_params.sq_entries as usize, num_sqes: ring_params.sq_entries as usize,
in_flight: 0, in_flight: 0,
@ -282,7 +286,12 @@ impl URingContext {
where where
I: Iterator<Item = libc::iovec>, I: Iterator<Item = libc::iovec>,
{ {
self.add_writev(iovecs.collect(), fd, offset, user_data) self.add_writev(
Pin::from(iovecs.collect::<Vec<_>>().into_boxed_slice()),
fd,
offset,
user_data,
)
} }
/// Asynchronously writes to `fd` from the addresses given in `iovecs`. /// Asynchronously writes to `fd` from the addresses given in `iovecs`.
@ -295,7 +304,7 @@ impl URingContext {
/// The iovecs reference must be kept alive until the op returns. /// The iovecs reference must be kept alive until the op returns.
pub unsafe fn add_writev( pub unsafe fn add_writev(
&mut self, &mut self,
iovecs: Vec<libc::iovec>, iovecs: Pin<Box<[libc::iovec]>>,
fd: RawFd, fd: RawFd,
offset: u64, offset: u64,
user_data: UserData, user_data: UserData,
@ -327,7 +336,12 @@ impl URingContext {
where where
I: Iterator<Item = libc::iovec>, I: Iterator<Item = libc::iovec>,
{ {
self.add_readv(iovecs.collect(), fd, offset, user_data) self.add_readv(
Pin::from(iovecs.collect::<Vec<_>>().into_boxed_slice()),
fd,
offset,
user_data,
)
} }
/// Asynchronously reads from `fd` to the addresses given in `iovecs`. /// Asynchronously reads from `fd` to the addresses given in `iovecs`.
@ -340,7 +354,7 @@ impl URingContext {
/// The iovecs reference must be kept alive until the op returns. /// The iovecs reference must be kept alive until the op returns.
pub unsafe fn add_readv( pub unsafe fn add_readv(
&mut self, &mut self,
iovecs: Vec<libc::iovec>, iovecs: Pin<Box<[libc::iovec]>>,
fd: RawFd, fd: RawFd,
offset: u64, offset: u64,
user_data: UserData, user_data: UserData,
@ -588,7 +602,7 @@ struct CompleteQueueState {
completed: usize, completed: usize,
//For ops that pass in arrays of iovecs, they need to be valid for the duration of the //For ops that pass in arrays of iovecs, they need to be valid for the duration of the
//operation because the kernel might read them at any time. //operation because the kernel might read them at any time.
pending_op_addrs: BTreeMap<UserData, Vec<libc::iovec>>, pending_op_addrs: BTreeMap<UserData, Pin<Box<[libc::iovec]>>>,
} }
impl CompleteQueueState { impl CompleteQueueState {
@ -610,7 +624,7 @@ impl CompleteQueueState {
} }
} }
fn add_op_data(&mut self, user_data: UserData, addrs: Vec<libc::iovec>) { fn add_op_data(&mut self, user_data: UserData, addrs: Pin<Box<[libc::iovec]>>) {
self.pending_op_addrs.insert(user_data, addrs); self.pending_op_addrs.insert(user_data, addrs);
} }
@ -756,12 +770,11 @@ mod tests {
vec![IoSliceMut::new(buf)] vec![IoSliceMut::new(buf)]
.into_iter() .into_iter()
.map(|slice| std::mem::transmute::<IoSliceMut, libc::iovec>(slice)) .map(|slice| std::mem::transmute::<IoSliceMut, libc::iovec>(slice))
.collect::<Vec<libc::iovec>>()
}; };
let (user_data_ret, res) = unsafe { let (user_data_ret, res) = unsafe {
// Safe because the `wait` call waits until the kernel is done with `buf`. // Safe because the `wait` call waits until the kernel is done with `buf`.
uring uring
.add_readv_iter(io_vecs.into_iter(), fd, offset, user_data) .add_readv_iter(io_vecs, fd, offset, user_data)
.unwrap(); .unwrap();
uring.wait().unwrap().next().unwrap() uring.wait().unwrap().next().unwrap()
}; };