asterinas/kernel/comps/block/src/request_queue.rs

219 lines
6.5 KiB
Rust
Raw Normal View History

2024-01-03 03:22:36 +00:00
// SPDX-License-Identifier: MPL-2.0
2024-06-19 08:18:39 +00:00
use ostd::sync::{Mutex, WaitQueue};
2023-09-18 03:47:17 +00:00
use super::{
bio::{BioEnqueueError, BioType, SubmittedBio},
id::Sid,
};
use crate::prelude::*;
2024-01-25 07:00:19 +00:00
/// 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,
max_nr_segments_per_bio: usize,
2024-01-25 07:00:19 +00:00
}
impl BioRequestSingleQueue {
/// Creates an empty queue.
pub fn new() -> Self {
Self::with_max_nr_segments_per_bio(usize::MAX)
}
/// Creates an empty queue with the upper bound for the number of segments in a bio.
pub fn with_max_nr_segments_per_bio(max_nr_segments_per_bio: usize) -> Self {
2024-01-25 07:00:19 +00:00
Self {
queue: Mutex::new(VecDeque::new()),
num_requests: AtomicUsize::new(0),
wait_queue: WaitQueue::new(),
max_nr_segments_per_bio,
2024-01-25 07:00:19 +00:00
}
}
/// Returns the upper limit for the number of segments per bio.
pub fn max_nr_segments_per_bio(&self) -> usize {
self.max_nr_segments_per_bio
}
2024-01-25 07:00:19 +00:00
/// Returns the number of requests currently in this queue.
pub fn num_requests(&self) -> usize {
self.num_requests.load(Ordering::Relaxed)
}
2023-09-18 03:47:17 +00:00
/// Enqueues a `SubmittedBio` to this queue.
///
2024-01-25 07:00:19 +00:00
/// 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`.
2023-09-18 03:47:17 +00:00
///
/// This method will wake up the waiter if a new `BioRequest` is enqueued.
2024-01-25 07:00:19 +00:00
pub fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
if bio.segments().len() >= self.max_nr_segments_per_bio {
return Err(BioEnqueueError::TooBig);
}
2024-01-25 07:00:19 +00:00
let mut queue = self.queue.lock();
if let Some(request) = queue.front_mut() {
if request.can_merge(&bio)
&& request.num_segments() + bio.segments().len() <= self.max_nr_segments_per_bio
{
2024-01-25 07:00:19 +00:00
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(())
}
2023-09-18 03:47:17 +00:00
/// Dequeues a `BioRequest` from this queue.
///
/// This method will wait until one request can be retrieved.
2024-01-25 07:00:19 +00:00
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()
}
2023-09-18 03:47:17 +00:00
}
/// The block I/O request.
2024-01-25 07:00:19 +00:00
///
/// 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)]
2023-09-18 03:47:17 +00:00
pub struct BioRequest {
/// The type of the I/O
type_: BioType,
/// The range of target sectors on the device
sid_range: Range<Sid>,
/// The number of segments
num_segments: usize,
2023-09-18 03:47:17 +00:00
/// The submitted bios
bios: VecDeque<SubmittedBio>,
}
impl BioRequest {
/// Returns the type of the I/O.
pub fn type_(&self) -> BioType {
self.type_
}
/// Returns the range of sector id on device.
pub fn sid_range(&self) -> &Range<Sid> {
&self.sid_range
}
/// Returns an iterator to the `SubmittedBio`s.
pub fn bios(&self) -> impl Iterator<Item = &SubmittedBio> {
self.bios.iter()
}
/// Returns the number of segments.
pub fn num_segments(&self) -> usize {
self.num_segments
}
2023-09-18 03:47:17 +00:00
/// Returns `true` if can merge the `SubmittedBio`, `false` otherwise.
pub fn can_merge(&self, rq_bio: &SubmittedBio) -> bool {
if rq_bio.type_() != self.type_ {
return false;
}
rq_bio.sid_range().start == self.sid_range.end
|| rq_bio.sid_range().end == self.sid_range.start
}
/// Merges the `SubmittedBio` into this request.
///
/// The merged `SubmittedBio` can only be placed at the front or back.
///
2024-05-30 11:25:58 +00:00
/// # Panics
2023-09-18 03:47:17 +00:00
///
/// If the `SubmittedBio` can not be merged, this method will panic.
pub fn merge_bio(&mut self, rq_bio: SubmittedBio) {
assert!(self.can_merge(&rq_bio));
let rq_bio_nr_segments = rq_bio.segments().len();
2023-09-18 03:47:17 +00:00
if rq_bio.sid_range().start == self.sid_range.end {
self.sid_range.end = rq_bio.sid_range().end;
self.bios.push_back(rq_bio);
} else {
self.sid_range.start = rq_bio.sid_range().start;
self.bios.push_front(rq_bio);
}
self.num_segments += rq_bio_nr_segments;
2023-09-18 03:47:17 +00:00
}
}
impl From<SubmittedBio> for BioRequest {
fn from(bio: SubmittedBio) -> Self {
Self {
type_: bio.type_(),
sid_range: bio.sid_range().clone(),
num_segments: bio.segments().len(),
2023-09-18 03:47:17 +00:00
bios: {
let mut bios = VecDeque::with_capacity(1);
bios.push_front(bio);
bios
},
}
}
}