Refine the API of BlockDevice trait

This commit is contained in:
LI Qing
2024-01-25 16:00:19 +09:00
committed by Tate, Hongliang Tian
parent 45a328b087
commit 31998d1cd4
5 changed files with 129 additions and 124 deletions

View File

@ -17,6 +17,7 @@ use int_to_c_enum::TryFromInt;
/// (2) The target sectors on the device for doing I/O, /// (2) The target sectors on the device for doing I/O,
/// (3) The memory locations (`BioSegment`) from/to which data are read/written, /// (3) The memory locations (`BioSegment`) from/to which data are read/written,
/// (4) The optional callback function that will be invoked when the I/O is completed. /// (4) The optional callback function that will be invoked when the I/O is completed.
#[derive(Debug)]
pub struct Bio(Arc<BioInner>); pub struct Bio(Arc<BioInner>);
impl Bio { impl Bio {
@ -85,10 +86,7 @@ impl Bio {
); );
assert!(result.is_ok()); assert!(result.is_ok());
if let Err(e) = block_device if let Err(e) = block_device.enqueue(SubmittedBio(self.0.clone())) {
.request_queue()
.enqueue(SubmittedBio(self.0.clone()))
{
// Fail to submit, revert the status. // Fail to submit, revert the status.
let result = self.0.status.compare_exchange( let result = self.0.status.compare_exchange(
BioStatus::Submit as u32, BioStatus::Submit as u32,
@ -151,6 +149,7 @@ impl From<BioEnqueueError> for aster_frame::Error {
/// This structure holds a list of `Bio` requests and provides functionality to /// This structure holds a list of `Bio` requests and provides functionality to
/// wait for their completion and retrieve their statuses. /// wait for their completion and retrieve their statuses.
#[must_use] #[must_use]
#[derive(Debug)]
pub struct BioWaiter { pub struct BioWaiter {
bios: Vec<Arc<BioInner>>, bios: Vec<Arc<BioInner>>,
} }
@ -230,6 +229,7 @@ impl Default for BioWaiter {
/// A submitted `Bio` object. /// A submitted `Bio` object.
/// ///
/// The request queue of block device only accepts a `SubmittedBio` into the queue. /// The request queue of block device only accepts a `SubmittedBio` into the queue.
#[derive(Debug)]
pub struct SubmittedBio(Arc<BioInner>); pub struct SubmittedBio(Arc<BioInner>);
impl SubmittedBio { impl SubmittedBio {
@ -309,6 +309,18 @@ impl BioInner {
} }
} }
impl Debug for BioInner {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("BioInner")
.field("type", &self.type_())
.field("sid_range", &self.sid_range())
.field("status", &self.status())
.field("segments", &self.segments())
.field("complete_fn", &self.complete_fn)
.finish()
}
}
/// The type of `Bio`. /// The type of `Bio`.
#[derive(Clone, Copy, Debug, PartialEq, TryFromInt)] #[derive(Clone, Copy, Debug, PartialEq, TryFromInt)]
#[repr(u8)] #[repr(u8)]

View File

@ -40,7 +40,8 @@ mod impl_block_device;
mod prelude; mod prelude;
pub mod request_queue; pub mod request_queue;
use self::{prelude::*, request_queue::BioRequestQueue}; use self::bio::{BioEnqueueError, SubmittedBio};
use self::prelude::*;
use aster_frame::sync::SpinLock; use aster_frame::sync::SpinLock;
use component::init_component; use component::init_component;
@ -52,8 +53,8 @@ pub const BLOCK_SIZE: usize = aster_frame::config::PAGE_SIZE;
pub const SECTOR_SIZE: usize = 512; pub const SECTOR_SIZE: usize = 512;
pub trait BlockDevice: Send + Sync + Any + Debug { pub trait BlockDevice: Send + Sync + Any + Debug {
/// Returns this block device's request queue, to which block I/O requests may be submitted. /// Enqueues a new `SubmittedBio` to the block device.
fn request_queue(&self) -> &dyn BioRequestQueue; fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError>;
fn handle_irq(&self); fn handle_irq(&self);
} }

View File

@ -8,4 +8,4 @@ pub(crate) use alloc::vec::Vec;
pub(crate) use core::any::Any; pub(crate) use core::any::Any;
pub(crate) use core::fmt::Debug; pub(crate) use core::fmt::Debug;
pub(crate) use core::ops::Range; pub(crate) use core::ops::Range;
pub(crate) use core::sync::atomic::{AtomicU32, Ordering}; pub(crate) use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};

View File

@ -7,24 +7,118 @@ use super::{
id::Sid, id::Sid,
}; };
/// Represents the software staging queue for the `BioRequest` objects. use aster_frame::sync::{Mutex, WaitQueue};
pub trait BioRequestQueue {
/// A simple block I/O request queue backed by one internal FIFO queue.
///
/// It is a FIFO producer-consumer queue, where the producer (e.g., filesystem)
/// submits requests to the queue, and the consumer (e.g., block device driver)
/// continuously consumes and processes these requests from the queue.
///
/// It supports merging the new request with the front request if if the type
/// is same and the sector range is contiguous.
pub struct BioRequestSingleQueue {
queue: Mutex<VecDeque<BioRequest>>,
num_requests: AtomicUsize,
wait_queue: WaitQueue,
}
impl BioRequestSingleQueue {
/// Creates an empty queue.
pub fn new() -> Self {
Self {
queue: Mutex::new(VecDeque::new()),
num_requests: AtomicUsize::new(0),
wait_queue: WaitQueue::new(),
}
}
/// Returns the number of requests currently in this queue.
pub fn num_requests(&self) -> usize {
self.num_requests.load(Ordering::Relaxed)
}
/// Enqueues a `SubmittedBio` to this queue. /// Enqueues a `SubmittedBio` to this queue.
/// ///
/// This `SubmittedBio` will be merged into an existing `BioRequest`, or a new /// When enqueueing the `SubmittedBio`, try to insert it into the last request if the
/// `BioRequest` will be created from the `SubmittedBio` before being placed /// type is same and the sector range is contiguous.
/// into the queue. /// Otherwise, creates and inserts a new request for the `SubmittedBio`.
/// ///
/// This method will wake up the waiter if a new `BioRequest` is enqueued. /// This method will wake up the waiter if a new `BioRequest` is enqueued.
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError>; pub fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
let mut queue = self.queue.lock();
if let Some(request) = queue.front_mut() {
if request.can_merge(&bio) {
request.merge_bio(bio);
return Ok(());
}
}
let new_request = BioRequest::from(bio);
queue.push_front(new_request);
self.inc_num_requests();
drop(queue);
self.wait_queue.wake_all();
Ok(())
}
/// Dequeues a `BioRequest` from this queue. /// Dequeues a `BioRequest` from this queue.
/// ///
/// This method will wait until one request can be retrieved. /// This method will wait until one request can be retrieved.
fn dequeue(&self) -> BioRequest; pub fn dequeue(&self) -> BioRequest {
let mut num_requests = self.num_requests();
loop {
if num_requests > 0 {
let mut queue = self.queue.lock();
if let Some(request) = queue.pop_back() {
self.dec_num_requests();
return request;
}
}
num_requests = self.wait_queue.wait_until(|| {
let num_requests = self.num_requests();
if num_requests > 0 {
Some(num_requests)
} else {
None
}
});
}
}
fn dec_num_requests(&self) {
self.num_requests.fetch_sub(1, Ordering::Relaxed);
}
fn inc_num_requests(&self) {
self.num_requests.fetch_add(1, Ordering::Relaxed);
}
}
impl Default for BioRequestSingleQueue {
fn default() -> Self {
Self::new()
}
}
impl Debug for BioRequestSingleQueue {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("BioRequestSingleQueue")
.field("num_requests", &self.num_requests())
.field("queue", &self.queue.lock())
.finish()
}
} }
/// The block I/O request. /// The block I/O request.
///
/// The advantage of this data structure is to merge several `SubmittedBio`s that are
/// contiguous on the target device's sector address, allowing them to be collectively
/// processed in a queue.
#[derive(Debug)]
pub struct BioRequest { pub struct BioRequest {
/// The type of the I/O /// The type of the I/O
type_: BioType, type_: BioType,

View File

@ -1,22 +1,16 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use core::{ use core::{fmt::Debug, hint::spin_loop, mem::size_of};
fmt::Debug,
hint::spin_loop,
mem::size_of,
sync::atomic::{AtomicUsize, Ordering},
};
use alloc::{boxed::Box, collections::VecDeque, string::ToString, sync::Arc, vec::Vec}; use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
use aster_block::{ use aster_block::{
bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio}, bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio},
id::Sid, id::Sid,
request_queue::{BioRequest, BioRequestQueue}, request_queue::{BioRequest, BioRequestSingleQueue},
}; };
use aster_frame::{ use aster_frame::{
io_mem::IoMem, io_mem::IoMem,
sync::SpinLock, sync::SpinLock,
sync::{Mutex, WaitQueue},
trap::TrapFrame, trap::TrapFrame,
vm::{VmAllocOptions, VmFrame, VmIo, VmReader, VmWriter}, vm::{VmAllocOptions, VmFrame, VmIo, VmReader, VmWriter},
}; };
@ -37,7 +31,7 @@ use super::{BlockFeatures, VirtioBlockConfig};
pub struct BlockDevice { pub struct BlockDevice {
device: DeviceInner, device: DeviceInner,
/// The software staging queue. /// The software staging queue.
queue: BioSingleQueue, queue: BioRequestSingleQueue,
} }
impl BlockDevice { impl BlockDevice {
@ -47,7 +41,7 @@ impl BlockDevice {
let device = DeviceInner::init(transport)?; let device = DeviceInner::init(transport)?;
Self { Self {
device, device,
queue: BioSingleQueue::new(), queue: BioRequestSingleQueue::new(),
} }
}; };
aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(block_device)); aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(block_device));
@ -117,8 +111,8 @@ impl BlockDevice {
} }
impl aster_block::BlockDevice for BlockDevice { impl aster_block::BlockDevice for BlockDevice {
fn request_queue(&self) -> &dyn BioRequestQueue { fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
&self.queue self.queue.enqueue(bio)
} }
fn handle_irq(&self) { fn handle_irq(&self) {
@ -303,99 +297,3 @@ impl Default for BlockResp {
} }
} }
} }
/// A simple block I/O request queue with a single queue.
///
/// It is a FIFO producer-consumer queue, where the producer (e.g., filesystem)
/// submits requests to the queue, and the consumer (e.g., block device driver)
/// continuously consumes and processes these requests from the queue.
pub struct BioSingleQueue {
queue: Mutex<VecDeque<BioRequest>>,
num_requests: AtomicUsize,
wait_queue: WaitQueue,
}
impl BioSingleQueue {
/// Creates an empty queue.
pub fn new() -> Self {
Self {
queue: Mutex::new(VecDeque::new()),
num_requests: AtomicUsize::new(0),
wait_queue: WaitQueue::new(),
}
}
/// Returns the number of requests currently in this queue.
pub fn num_requests(&self) -> usize {
self.num_requests.load(Ordering::Relaxed)
}
fn dec_num_requests(&self) {
self.num_requests.fetch_sub(1, Ordering::Relaxed);
}
fn inc_num_requests(&self) {
self.num_requests.fetch_add(1, Ordering::Relaxed);
}
}
impl Default for BioSingleQueue {
fn default() -> Self {
Self::new()
}
}
impl Debug for BioSingleQueue {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("BioSingleQueue")
.field("num_requests", &self.num_requests())
.finish()
}
}
impl BioRequestQueue for BioSingleQueue {
/// Enqueues a `SubmittedBio` to this queue.
///
/// When enqueueing the `SubmittedBio`, try to insert it into the last request if the
/// type is same and the sector range is contiguous.
/// Otherwise, creates and inserts a new request for the `SubmittedBio`.
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
let mut queue = self.queue.lock();
if let Some(request) = queue.front_mut() {
if request.can_merge(&bio) {
request.merge_bio(bio);
return Ok(());
}
}
let new_request = BioRequest::from(bio);
queue.push_front(new_request);
drop(queue);
self.inc_num_requests();
self.wait_queue.wake_all();
Ok(())
}
/// Dequeues a `BioRequest` from this queue.
fn dequeue(&self) -> BioRequest {
let mut num_requests = self.num_requests();
loop {
if num_requests > 0 {
if let Some(request) = self.queue.lock().pop_back() {
self.dec_num_requests();
return request;
}
}
num_requests = self.wait_queue.wait_until(|| {
let num_requests = self.num_requests();
if num_requests > 0 {
Some(num_requests)
} else {
None
}
});
}
}
}