mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-28 03:43:23 +00:00
Use frame metadada for page cache
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
cdac59beda
commit
f332797084
@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use core::{iter, ops::Range};
|
use core::{
|
||||||
|
iter,
|
||||||
|
ops::Range,
|
||||||
|
sync::atomic::{AtomicU8, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
use align_ext::AlignExt;
|
use align_ext::AlignExt;
|
||||||
use aster_block::bio::{BioStatus, BioWaiter};
|
use aster_block::bio::{BioStatus, BioWaiter};
|
||||||
use aster_rights::Full;
|
use aster_rights::Full;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use ostd::mm::{DynUFrame, Frame, FrameAllocOptions, VmIo};
|
use ostd::{
|
||||||
|
impl_untyped_frame_meta_for,
|
||||||
|
mm::{DynUFrame, Frame, FrameAllocOptions, UntypedMem, VmIo},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -242,7 +249,7 @@ impl ReadaheadState {
|
|||||||
/// Waits for the previous readahead.
|
/// Waits for the previous readahead.
|
||||||
pub fn wait_for_prev_readahead(
|
pub fn wait_for_prev_readahead(
|
||||||
&mut self,
|
&mut self,
|
||||||
pages: &mut MutexGuard<LruCache<usize, Page>>,
|
pages: &mut MutexGuard<LruCache<usize, CachePage>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if matches!(self.waiter.wait(), Some(BioStatus::Complete)) {
|
if matches!(self.waiter.wait(), Some(BioStatus::Complete)) {
|
||||||
let Some(window) = &self.ra_window else {
|
let Some(window) = &self.ra_window else {
|
||||||
@ -250,7 +257,7 @@ impl ReadaheadState {
|
|||||||
};
|
};
|
||||||
for idx in window.readahead_range() {
|
for idx in window.readahead_range() {
|
||||||
if let Some(page) = pages.get_mut(&idx) {
|
if let Some(page) = pages.get_mut(&idx) {
|
||||||
page.set_state(PageState::UpToDate);
|
page.store_state(PageState::UpToDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.waiter.clear();
|
self.waiter.clear();
|
||||||
@ -297,20 +304,20 @@ impl ReadaheadState {
|
|||||||
/// Sends the relevant read request and sets the relevant page in the page cache to `Uninit`.
|
/// Sends the relevant read request and sets the relevant page in the page cache to `Uninit`.
|
||||||
pub fn conduct_readahead(
|
pub fn conduct_readahead(
|
||||||
&mut self,
|
&mut self,
|
||||||
pages: &mut MutexGuard<LruCache<usize, Page>>,
|
pages: &mut MutexGuard<LruCache<usize, CachePage>>,
|
||||||
backend: Arc<dyn PageCacheBackend>,
|
backend: Arc<dyn PageCacheBackend>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(window) = &self.ra_window else {
|
let Some(window) = &self.ra_window else {
|
||||||
return_errno!(Errno::EINVAL)
|
return_errno!(Errno::EINVAL)
|
||||||
};
|
};
|
||||||
for async_idx in window.readahead_range() {
|
for async_idx in window.readahead_range() {
|
||||||
let mut async_page = Page::alloc()?;
|
let mut async_page = CachePage::alloc()?;
|
||||||
let pg_waiter = backend.read_page_async(async_idx, async_page.frame().into())?;
|
let pg_waiter = backend.read_page_async(async_idx, (&async_page).into())?;
|
||||||
if pg_waiter.nreqs() > 0 {
|
if pg_waiter.nreqs() > 0 {
|
||||||
self.waiter.concat(pg_waiter);
|
self.waiter.concat(pg_waiter);
|
||||||
} else {
|
} else {
|
||||||
// Some backends (e.g. RamFS) do not issue requests, but fill the page directly.
|
// Some backends (e.g. RamFS) do not issue requests, but fill the page directly.
|
||||||
async_page.set_state(PageState::UpToDate);
|
async_page.store_state(PageState::UpToDate);
|
||||||
}
|
}
|
||||||
pages.put(async_idx, async_page);
|
pages.put(async_idx, async_page);
|
||||||
}
|
}
|
||||||
@ -324,7 +331,7 @@ impl ReadaheadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct PageCacheManager {
|
struct PageCacheManager {
|
||||||
pages: Mutex<LruCache<usize, Page>>,
|
pages: Mutex<LruCache<usize, CachePage>>,
|
||||||
backend: Weak<dyn PageCacheBackend>,
|
backend: Weak<dyn PageCacheBackend>,
|
||||||
ra_state: Mutex<ReadaheadState>,
|
ra_state: Mutex<ReadaheadState>,
|
||||||
}
|
}
|
||||||
@ -360,8 +367,8 @@ impl PageCacheManager {
|
|||||||
let backend_npages = backend.npages();
|
let backend_npages = backend.npages();
|
||||||
for idx in page_idx_range.start..page_idx_range.end {
|
for idx in page_idx_range.start..page_idx_range.end {
|
||||||
if let Some(page) = pages.peek(&idx) {
|
if let Some(page) = pages.peek(&idx) {
|
||||||
if *page.state() == PageState::Dirty && idx < backend_npages {
|
if page.load_state() == PageState::Dirty && idx < backend_npages {
|
||||||
let waiter = backend.write_page_async(idx, page.frame().into())?;
|
let waiter = backend.write_page_async(idx, page.into())?;
|
||||||
bio_waiter.concat(waiter);
|
bio_waiter.concat(waiter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,7 +383,7 @@ impl PageCacheManager {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|(idx, _)| page_idx_range.contains(*idx))
|
.filter(|(idx, _)| page_idx_range.contains(*idx))
|
||||||
{
|
{
|
||||||
page.set_state(PageState::UpToDate);
|
page.store_state(PageState::UpToDate);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -395,28 +402,28 @@ impl PageCacheManager {
|
|||||||
// 3. The requested page is on disk, need a sync read operation here.
|
// 3. The requested page is on disk, need a sync read operation here.
|
||||||
let frame = if let Some(page) = pages.get(&idx) {
|
let frame = if let Some(page) = pages.get(&idx) {
|
||||||
// Cond 1 & 2.
|
// Cond 1 & 2.
|
||||||
if let PageState::Uninit = page.state() {
|
if let PageState::Uninit = page.load_state() {
|
||||||
// Cond 2: We should wait for the previous readahead.
|
// Cond 2: We should wait for the previous readahead.
|
||||||
// If there is no previous readahead, an error must have occurred somewhere.
|
// If there is no previous readahead, an error must have occurred somewhere.
|
||||||
assert!(ra_state.request_number() != 0);
|
assert!(ra_state.request_number() != 0);
|
||||||
ra_state.wait_for_prev_readahead(&mut pages)?;
|
ra_state.wait_for_prev_readahead(&mut pages)?;
|
||||||
pages.get(&idx).unwrap().frame().clone()
|
pages.get(&idx).unwrap().clone()
|
||||||
} else {
|
} else {
|
||||||
// Cond 1.
|
// Cond 1.
|
||||||
page.frame().clone()
|
page.clone()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Cond 3.
|
// Cond 3.
|
||||||
// Conducts the sync read operation.
|
// Conducts the sync read operation.
|
||||||
let page = if idx < backend.npages() {
|
let page = if idx < backend.npages() {
|
||||||
let mut page = Page::alloc()?;
|
let mut page = CachePage::alloc()?;
|
||||||
backend.read_page(idx, page.frame().into())?;
|
backend.read_page(idx, (&page).into())?;
|
||||||
page.set_state(PageState::UpToDate);
|
page.store_state(PageState::UpToDate);
|
||||||
page
|
page
|
||||||
} else {
|
} else {
|
||||||
Page::alloc_zero()?
|
CachePage::alloc_zero()?
|
||||||
};
|
};
|
||||||
let frame = page.frame().clone();
|
let frame = page.clone();
|
||||||
pages.put(idx, page);
|
pages.put(idx, page);
|
||||||
frame
|
frame
|
||||||
};
|
};
|
||||||
@ -445,7 +452,7 @@ impl Pager for PageCacheManager {
|
|||||||
fn update_page(&self, idx: usize) -> Result<()> {
|
fn update_page(&self, idx: usize) -> Result<()> {
|
||||||
let mut pages = self.pages.lock();
|
let mut pages = self.pages.lock();
|
||||||
if let Some(page) = pages.get_mut(&idx) {
|
if let Some(page) = pages.get_mut(&idx) {
|
||||||
page.set_state(PageState::Dirty);
|
page.store_state(PageState::Dirty);
|
||||||
} else {
|
} else {
|
||||||
warn!("The page {} is not in page cache", idx);
|
warn!("The page {} is not in page cache", idx);
|
||||||
}
|
}
|
||||||
@ -456,12 +463,12 @@ impl Pager for PageCacheManager {
|
|||||||
fn decommit_page(&self, idx: usize) -> Result<()> {
|
fn decommit_page(&self, idx: usize) -> Result<()> {
|
||||||
let page_result = self.pages.lock().pop(&idx);
|
let page_result = self.pages.lock().pop(&idx);
|
||||||
if let Some(page) = page_result {
|
if let Some(page) = page_result {
|
||||||
if let PageState::Dirty = page.state() {
|
if let PageState::Dirty = page.load_state() {
|
||||||
let Some(backend) = self.backend.upgrade() else {
|
let Some(backend) = self.backend.upgrade() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if idx < backend.npages() {
|
if idx < backend.npages() {
|
||||||
backend.write_page(idx, page.frame().into())?;
|
backend.write_page(idx, (&page).into())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,67 +478,96 @@ impl Pager for PageCacheManager {
|
|||||||
|
|
||||||
fn commit_overwrite(&self, idx: usize) -> Result<DynUFrame> {
|
fn commit_overwrite(&self, idx: usize) -> Result<DynUFrame> {
|
||||||
if let Some(page) = self.pages.lock().get(&idx) {
|
if let Some(page) = self.pages.lock().get(&idx) {
|
||||||
return Ok(page.frame.clone().into());
|
return Ok(page.clone().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let page = Page::alloc_zero()?;
|
let page = CachePage::alloc_zero()?;
|
||||||
Ok(self
|
Ok(self.pages.lock().get_or_insert(idx, || page).clone().into())
|
||||||
.pages
|
|
||||||
.lock()
|
|
||||||
.get_or_insert(idx, || page)
|
|
||||||
.frame
|
|
||||||
.clone()
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A page in the page cache.
|
||||||
|
pub type CachePage = Frame<CachePageMeta>;
|
||||||
|
|
||||||
|
/// Metadata for a page in the page cache.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Page {
|
pub struct CachePageMeta {
|
||||||
frame: Frame<()>,
|
pub state: AtomicPageState,
|
||||||
state: PageState,
|
// TODO: Add a reverse mapping from the page to VMO for eviction.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl_untyped_frame_meta_for!(CachePageMeta);
|
||||||
pub fn alloc() -> Result<Self> {
|
|
||||||
let frame = FrameAllocOptions::new().zeroed(false).alloc_frame()?;
|
pub trait CachePageExt {
|
||||||
Ok(Self {
|
fn metadata(&self) -> &CachePageMeta;
|
||||||
frame,
|
|
||||||
state: PageState::Uninit,
|
fn alloc() -> Result<CachePage> {
|
||||||
})
|
let meta = CachePageMeta {
|
||||||
|
state: AtomicPageState {
|
||||||
|
state: AtomicU8::new(PageState::Uninit as u8),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let page = FrameAllocOptions::new()
|
||||||
|
.zeroed(false)
|
||||||
|
.alloc_frame_with(meta)?;
|
||||||
|
Ok(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_zero() -> Result<Self> {
|
fn alloc_zero() -> Result<CachePage> {
|
||||||
let frame = FrameAllocOptions::new().alloc_frame()?;
|
let page = Self::alloc()?;
|
||||||
Ok(Self {
|
page.writer().fill(0);
|
||||||
frame,
|
Ok(page)
|
||||||
state: PageState::Dirty,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame(&self) -> &Frame<()> {
|
fn load_state(&self) -> PageState {
|
||||||
&self.frame
|
self.metadata().state.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> &PageState {
|
fn store_state(&mut self, new_state: PageState) {
|
||||||
&self.state
|
self.metadata().state.store(new_state, Ordering::Relaxed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_state(&mut self, new_state: PageState) {
|
impl CachePageExt for CachePage {
|
||||||
self.state = new_state;
|
fn metadata(&self) -> &CachePageMeta {
|
||||||
|
self.meta()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum PageState {
|
#[repr(u8)]
|
||||||
|
pub enum PageState {
|
||||||
/// `Uninit` indicates a new allocated page which content has not been initialized.
|
/// `Uninit` indicates a new allocated page which content has not been initialized.
|
||||||
/// The page is available to write, not available to read.
|
/// The page is available to write, not available to read.
|
||||||
Uninit,
|
Uninit = 0,
|
||||||
/// `UpToDate` indicates a page which content is consistent with corresponding disk content.
|
/// `UpToDate` indicates a page which content is consistent with corresponding disk content.
|
||||||
/// The page is available to read and write.
|
/// The page is available to read and write.
|
||||||
UpToDate,
|
UpToDate = 1,
|
||||||
/// `Dirty` indicates a page which content has been updated and not written back to underlying disk.
|
/// `Dirty` indicates a page which content has been updated and not written back to underlying disk.
|
||||||
/// The page is available to read and write.
|
/// The page is available to read and write.
|
||||||
Dirty,
|
Dirty = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page state with atomic operations.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AtomicPageState {
|
||||||
|
state: AtomicU8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomicPageState {
|
||||||
|
pub fn load(&self, order: Ordering) -> PageState {
|
||||||
|
let val = self.state.load(order);
|
||||||
|
match val {
|
||||||
|
0 => PageState::Uninit,
|
||||||
|
1 => PageState::UpToDate,
|
||||||
|
2 => PageState::Dirty,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&self, val: PageState, order: Ordering) {
|
||||||
|
self.state.store(val as u8, order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait represents the backend for the page cache.
|
/// This trait represents the backend for the page cache.
|
||||||
|
Reference in New Issue
Block a user