mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: Add owned async I/O operations
Add `AsyncIoOperation` and `AsyncIoCompletion` as the owned request and completion types that will be used to ensure buffers for async io outlive the operations that use them. Later commits will update the `AsyncIo` trait to expose apis using only these instead of raw iovecs. Signed-off-by: Dylan Reid <dgreid@fb.com>
This commit is contained in:
@@ -4,10 +4,14 @@
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
mod completion;
|
||||
mod guest_memory_target;
|
||||
mod operation;
|
||||
mod owned_io_buffer;
|
||||
|
||||
pub use completion::AsyncIoCompletion;
|
||||
pub use guest_memory_target::GuestMemoryTarget;
|
||||
pub use operation::AsyncIoOperation;
|
||||
pub use owned_io_buffer::OwnedIoBuffer;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
42
block/src/async_io/completion.rs
Normal file
42
block/src/async_io/completion.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::{AsyncIoOperation, OwnedIoBuffer};
|
||||
|
||||
/// Completion returned by an owned async I/O backend.
|
||||
///
|
||||
/// The completion carries the caller provided `user_data`, the result,
|
||||
/// and any owned buffer that can now be dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncIoCompletion {
|
||||
/// Caller provided identifier associated with the submitted operation.
|
||||
pub user_data: u64,
|
||||
/// I/O result reported by the backend.
|
||||
///
|
||||
/// Successful operations report a non-negative byte count. Failed
|
||||
/// operations report a negative errno value.
|
||||
pub result: i32,
|
||||
/// The backing buffer that can now be dropped or re-used.
|
||||
pub buffer: Option<OwnedIoBuffer>,
|
||||
}
|
||||
|
||||
impl AsyncIoCompletion {
|
||||
/// Creates a completion from its parts.
|
||||
pub fn new(user_data: u64, result: i32, buffer: Option<OwnedIoBuffer>) -> Self {
|
||||
Self {
|
||||
user_data,
|
||||
result,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a completion by consuming the operation that just completed.
|
||||
///
|
||||
/// This returns ownership of any completion buffer carried by the
|
||||
/// operation.
|
||||
pub fn from_operation(op: AsyncIoOperation, result: i32) -> Self {
|
||||
let user_data = op.user_data();
|
||||
Self::new(user_data, result, op.into_completion_buffer())
|
||||
}
|
||||
}
|
||||
239
block/src/async_io/operation.rs
Normal file
239
block/src/async_io/operation.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::io;
|
||||
use std::ops::Range;
|
||||
|
||||
use super::{GuestMemoryTarget, OwnedIoBuffer};
|
||||
|
||||
/// A single async IO operation.
|
||||
///
|
||||
/// Each operation owns or retains the memory target for the duration of the
|
||||
/// operation so backends can submit it to the kernel or copy through safe helper
|
||||
/// methods.
|
||||
#[derive(Debug)]
|
||||
pub enum AsyncIoOperation {
|
||||
/// Read from disk into guest memory.
|
||||
ReadToMemory {
|
||||
/// Disk offset for the operation.
|
||||
offset: libc::off_t,
|
||||
/// Guest-memory destination.
|
||||
target: GuestMemoryTarget,
|
||||
/// Caller-provided completion identifier.
|
||||
user_data: u64,
|
||||
},
|
||||
/// Write from guest memory to disk.
|
||||
WriteFromMemory {
|
||||
/// Disk offset for the operation.
|
||||
offset: libc::off_t,
|
||||
/// Guest-memory source.
|
||||
target: GuestMemoryTarget,
|
||||
/// Caller-provided completion identifier.
|
||||
user_data: u64,
|
||||
},
|
||||
/// Read from disk into an owned host-memory buffer.
|
||||
ReadToVec {
|
||||
/// Disk offset for the operation.
|
||||
offset: libc::off_t,
|
||||
/// Owned destination buffer.
|
||||
buffer: OwnedIoBuffer,
|
||||
/// Caller-provided completion identifier.
|
||||
user_data: u64,
|
||||
},
|
||||
/// Write from an owned host-memory buffer to disk.
|
||||
WriteFromVec {
|
||||
/// Disk offset for the operation.
|
||||
offset: libc::off_t,
|
||||
/// Owned source buffer.
|
||||
buffer: OwnedIoBuffer,
|
||||
/// Caller-provided completion identifier.
|
||||
user_data: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl AsyncIoOperation {
|
||||
/// Creates an operation that reads from disk into guest memory.
|
||||
pub fn read_to_memory(offset: libc::off_t, target: GuestMemoryTarget, user_data: u64) -> Self {
|
||||
Self::ReadToMemory {
|
||||
offset,
|
||||
target,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an operation that writes from guest memory to disk.
|
||||
pub fn write_from_memory(
|
||||
offset: libc::off_t,
|
||||
target: GuestMemoryTarget,
|
||||
user_data: u64,
|
||||
) -> Self {
|
||||
Self::WriteFromMemory {
|
||||
offset,
|
||||
target,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an operation that reads from disk into an owned buffer.
|
||||
pub fn read_to_vec(offset: libc::off_t, buffer: OwnedIoBuffer, user_data: u64) -> Self {
|
||||
Self::ReadToVec {
|
||||
offset,
|
||||
buffer,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an operation that writes from an owned buffer to disk.
|
||||
pub fn write_from_vec(offset: libc::off_t, buffer: OwnedIoBuffer, user_data: u64) -> Self {
|
||||
Self::WriteFromVec {
|
||||
offset,
|
||||
buffer,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value provided at construction.
|
||||
pub fn user_data(&self) -> u64 {
|
||||
match self {
|
||||
Self::ReadToMemory { user_data, .. }
|
||||
| Self::WriteFromMemory { user_data, .. }
|
||||
| Self::ReadToVec { user_data, .. }
|
||||
| Self::WriteFromVec { user_data, .. } => *user_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the disk offset for this operation.
|
||||
pub fn offset(&self) -> libc::off_t {
|
||||
match self {
|
||||
Self::ReadToMemory { offset, .. }
|
||||
| Self::WriteFromMemory { offset, .. }
|
||||
| Self::ReadToVec { offset, .. }
|
||||
| Self::WriteFromVec { offset, .. } => *offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the disk offset for this operation.
|
||||
pub fn set_offset(&mut self, new_offset: libc::off_t) {
|
||||
match self {
|
||||
Self::ReadToMemory { offset, .. }
|
||||
| Self::WriteFromMemory { offset, .. }
|
||||
| Self::ReadToVec { offset, .. }
|
||||
| Self::WriteFromVec { offset, .. } => *offset = new_offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this operation reads from disk.
|
||||
pub fn is_read(&self) -> bool {
|
||||
matches!(self, Self::ReadToMemory { .. } | Self::ReadToVec { .. })
|
||||
}
|
||||
|
||||
/// Returns the retained iovec array for kernel submission.
|
||||
///
|
||||
/// The iovec pointers are valid while this operation is alive.
|
||||
pub fn iovecs(&self) -> &[libc::iovec] {
|
||||
match self {
|
||||
Self::ReadToMemory { target, .. } | Self::WriteFromMemory { target, .. } => {
|
||||
target.iovecs()
|
||||
}
|
||||
Self::ReadToVec { buffer, .. } | Self::WriteFromVec { buffer, .. } => buffer.iovecs(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the total number of bytes described by the operation iovecs.
|
||||
pub fn total_len(&self) -> usize {
|
||||
match self {
|
||||
Self::ReadToMemory { target, .. } | Self::WriteFromMemory { target, .. } => {
|
||||
target.total_len()
|
||||
}
|
||||
Self::ReadToVec { buffer, .. } | Self::WriteFromVec { buffer, .. } => {
|
||||
buffer.total_len()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn checked_range(total_len: usize, start: usize, len: usize) -> io::Result<Range<usize>> {
|
||||
if start <= total_len
|
||||
&& let Some(end) = start.checked_add(len)
|
||||
&& end <= total_len
|
||||
{
|
||||
return Ok(start..end);
|
||||
}
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"async I/O buffer range out of bounds",
|
||||
))
|
||||
}
|
||||
|
||||
/// Copies bytes into a read operation at `start`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn write_bytes_at(&mut self, start: usize, data: &[u8]) -> io::Result<()> {
|
||||
match self {
|
||||
Self::ReadToMemory { target, .. } => {
|
||||
target.write_bytes_at(start, data).map_err(io::Error::other)
|
||||
}
|
||||
Self::ReadToVec { buffer, .. } => {
|
||||
let range = Self::checked_range(buffer.total_len(), start, data.len())?;
|
||||
buffer.as_mut_slice()[range].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
Self::WriteFromMemory { .. } | Self::WriteFromVec { .. } => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"cannot write into a write operation",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills a read operation with zeroes at `start`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn fill_zeroes_at(&mut self, start: usize, len: usize) -> io::Result<()> {
|
||||
match self {
|
||||
Self::ReadToMemory { target, .. } => {
|
||||
target.fill_zeroes_at(start, len).map_err(io::Error::other)
|
||||
}
|
||||
Self::ReadToVec { buffer, .. } => {
|
||||
let range = Self::checked_range(buffer.total_len(), start, len)?;
|
||||
buffer.as_mut_slice()[range].fill(0);
|
||||
Ok(())
|
||||
}
|
||||
Self::WriteFromMemory { .. } | Self::WriteFromVec { .. } => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"cannot write into a write operation",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies bytes out of a write operation at `start`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn read_bytes_at(&self, start: usize, data: &mut [u8]) -> io::Result<()> {
|
||||
match self {
|
||||
Self::WriteFromMemory { target, .. } => {
|
||||
target.read_bytes_at(start, data).map_err(io::Error::other)
|
||||
}
|
||||
Self::WriteFromVec { buffer, .. } => {
|
||||
let range = Self::checked_range(buffer.total_len(), start, data.len())?;
|
||||
data.copy_from_slice(&buffer.as_slice()[range]);
|
||||
Ok(())
|
||||
}
|
||||
Self::ReadToMemory { .. } | Self::ReadToVec { .. } => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"cannot read from a read operation",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the operation and returns the buffer needed by its completion.
|
||||
///
|
||||
/// Only `ReadToVec` operations return a buffer because callers need the
|
||||
/// data they read.
|
||||
pub fn into_completion_buffer(self) -> Option<OwnedIoBuffer> {
|
||||
match self {
|
||||
Self::ReadToVec { buffer, .. } => Some(buffer),
|
||||
Self::ReadToMemory { .. }
|
||||
| Self::WriteFromMemory { .. }
|
||||
| Self::WriteFromVec { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user