Add page cache manager as the pager for vmo

This commit is contained in:
LI Qing 2023-01-04 17:26:49 +08:00
parent 4629b8a15e
commit 4a3b0576b2
11 changed files with 250 additions and 24 deletions

42
src/Cargo.lock generated
View File

@ -13,6 +13,17 @@ dependencies = [
"rsdp",
]
[[package]]
name = "ahash"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.32"
@ -91,6 +102,15 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63201c624b8c8883921b1a1accc8916c4fa9dbfb15d122b26e4dde945b86bbf"
[[package]]
name = "hashbrown"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
dependencies = [
"ahash",
]
[[package]]
name = "itertools"
version = "0.10.5"
@ -172,6 +192,7 @@ dependencies = [
"jinux-util",
"jinux-virtio",
"lazy_static",
"lru",
"pod",
"pod-derive",
"ringbuffer",
@ -263,6 +284,21 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "lru"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17"
dependencies = [
"hashbrown",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "pod"
version = "0.1.0"
@ -429,6 +465,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "virtio-input-decoder"
version = "0.1.4"

View File

@ -27,6 +27,7 @@ ringbuffer = "0.10.0"
spin = "0.9.4"
vte = "0.10"
lru = "0.9.0"
[dependencies.lazy_static]
version = "1.0"

View File

@ -6,11 +6,11 @@ use super::*;
impl InodeHandle<Rights> {
pub fn new(
inode: Arc<dyn Inode>,
inode: VfsInode,
access_mode: AccessMode,
status_flags: StatusFlags,
) -> Result<Self> {
let inode_info = inode.metadata();
let inode_info = inode.raw_inode().metadata();
if access_mode.is_readable() && !inode_info.mode.is_readable() {
return_errno_with_message!(Errno::EACCES, "File is not readable");
}

View File

@ -4,16 +4,18 @@ mod dyn_cap;
mod static_cap;
use super::utils::{
AccessMode, DirentWriter, DirentWriterContext, Inode, InodeType, SeekFrom, StatusFlags,
AccessMode, DirentWriter, DirentWriterContext, InodeType, SeekFrom, StatusFlags,
};
use super::vfs_inode::VfsInode;
use crate::prelude::*;
use crate::rights::Rights;
use alloc::sync::Arc;
use jinux_frame::vm::VmIo;
pub struct InodeHandle<R = Rights>(Arc<InodeHandle_>, R);
struct InodeHandle_ {
inode: Arc<dyn Inode>,
inode: VfsInode,
offset: Mutex<usize>,
access_mode: AccessMode,
status_flags: Mutex<StatusFlags>,
@ -22,15 +24,18 @@ struct InodeHandle_ {
impl InodeHandle_ {
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
let mut offset = self.offset.lock();
let file_size = self.inode.metadata().size;
let file_size = self.inode.raw_inode().metadata().size;
let start = file_size.min(*offset);
let end = file_size.min(*offset + buf.len());
let len = if self.status_flags.lock().contains(StatusFlags::O_DIRECT) {
self.inode.read_at(start, &mut buf[0..start - end])?
self.inode
.raw_inode()
.read_at(start, &mut buf[0..end - start])?
} else {
self.inode.read_at(start, &mut buf[0..start - end])?
// TODO: use page cache
// self.inode.pages().read_at(start, buf[0..start - end])?
self.inode
.pages()
.read_bytes(start, &mut buf[0..end - start])?;
end - start
};
*offset += len;
@ -39,20 +44,23 @@ impl InodeHandle_ {
pub fn write(&self, buf: &[u8]) -> Result<usize> {
let mut offset = self.offset.lock();
let file_size = self.inode.metadata().size;
let file_size = self.inode.raw_inode().metadata().size;
if self.status_flags.lock().contains(StatusFlags::O_APPEND) {
*offset = file_size;
}
let len = if self.status_flags.lock().contains(StatusFlags::O_DIRECT) {
self.inode.write_at(*offset, buf)?
self.inode.raw_inode().write_at(*offset, buf)?
} else {
self.inode.write_at(*offset, buf)?
// TODO: use page cache
// let len = self.inode.pages().write_at(*offset, buf)?;
// if offset + len > file_size {
// self.inode.resize(offset + len)?;
// }
// len
let pages = self.inode.pages();
let should_expand_size = *offset + buf.len() > file_size;
if should_expand_size {
pages.resize(*offset + buf.len())?;
}
pages.write_bytes(*offset, buf)?;
if should_expand_size {
self.inode.raw_inode().resize(*offset + buf.len())?;
}
buf.len()
};
*offset += len;
@ -69,7 +77,7 @@ impl InodeHandle_ {
off as i64
}
SeekFrom::End(off /* as i64 */) => {
let file_size = self.inode.metadata().size as i64;
let file_size = self.inode.raw_inode().metadata().size as i64;
assert!(file_size >= 0);
file_size
.checked_add(off)
@ -116,7 +124,7 @@ impl InodeHandle_ {
pub fn readdir(&self, writer: &mut dyn DirentWriter) -> Result<usize> {
let mut offset = self.offset.lock();
let mut dir_writer_ctx = DirentWriterContext::new(*offset, writer);
let written_size = self.inode.readdir(&mut dir_writer_ctx)?;
let written_size = self.inode.raw_inode().readdir(&mut dir_writer_ctx)?;
*offset = dir_writer_ctx.pos();
Ok(written_size)
}

View File

@ -9,3 +9,4 @@ pub mod poll;
pub mod stat;
pub mod stdio;
pub mod utils;
pub mod vfs_inode;

View File

@ -2,6 +2,7 @@ use alloc::string::String;
use alloc::sync::Arc;
use bitflags::bitflags;
use core::any::Any;
use jinux_frame::vm::VmFrame;
use super::{DirentWriterContext, FileSystem};
use crate::fs::ioctl::IoctlCmd;
@ -101,6 +102,10 @@ pub trait Inode: Any + Sync + Send {
fn metadata(&self) -> Metadata;
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()>;
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()>;
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize>;
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize>;

View File

@ -4,12 +4,14 @@ pub use access_mode::AccessMode;
pub use dirent_writer::{DirentWriter, DirentWriterContext};
pub use fs::{FileSystem, SuperBlock};
pub use inode::{Inode, InodeMode, InodeType, Metadata, Timespec};
pub use page_cache::PageCacheManager;
pub use status_flags::StatusFlags;
mod access_mode;
mod dirent_writer;
mod fs;
mod inode;
mod page_cache;
mod status_flags;
#[derive(Copy, PartialEq, Eq, Clone, Debug)]

View File

@ -0,0 +1,131 @@
use super::Inode;
use crate::prelude::*;
use crate::vm::vmo::Pager;
use jinux_frame::vm::{VmAllocOptions, VmFrame, VmFrameVec};
use lru::LruCache;
pub struct PageCacheManager {
pages: Mutex<LruCache<usize, Page>>,
backed_inode: Weak<dyn Inode>,
}
impl PageCacheManager {
pub fn new(inode: &Weak<dyn Inode>) -> Self {
Self {
pages: Mutex::new(LruCache::unbounded()),
backed_inode: inode.clone(),
}
}
}
impl Pager for PageCacheManager {
fn commit_page(&self, offset: usize) -> Result<VmFrame> {
let page_idx = offset / PAGE_SIZE;
let mut pages = self.pages.lock();
let frame = if let Some(page) = pages.get(&page_idx) {
page.frame()
} else {
let page = if offset < self.backed_inode.upgrade().unwrap().metadata().size {
let mut page = Page::alloc()?;
self.backed_inode
.upgrade()
.unwrap()
.read_page(page_idx, &page.frame())?;
page.set_state(PageState::UpToDate);
page
} else {
Page::alloc_zero()?
};
let frame = page.frame();
pages.put(page_idx, page);
frame
};
Ok(frame)
}
fn update_page(&self, offset: usize) -> Result<()> {
let page_idx = offset / PAGE_SIZE;
let mut pages = self.pages.lock();
if let Some(page) = pages.get_mut(&page_idx) {
page.set_state(PageState::Dirty);
} else {
error!("page {} is not in page cache", page_idx);
panic!();
}
Ok(())
}
fn decommit_page(&self, offset: usize) -> Result<()> {
let page_idx = offset / PAGE_SIZE;
let mut pages = self.pages.lock();
if let Some(page) = pages.pop(&page_idx) {
match page.state() {
PageState::Dirty => self
.backed_inode
.upgrade()
.unwrap()
.write_page(page_idx, &page.frame())?,
_ => (),
}
} else {
warn!("page {} is not in page cache, do nothing", page_idx);
}
Ok(())
}
}
struct Page {
frame: VmFrame,
state: PageState,
}
impl Page {
pub fn alloc() -> Result<Self> {
let frame = {
let vm_alloc_option = VmAllocOptions::new(1);
let mut frames = VmFrameVec::allocate(&vm_alloc_option)?;
frames.pop().unwrap()
};
Ok(Self {
frame,
state: PageState::Uninit,
})
}
pub fn alloc_zero() -> Result<Self> {
let frame = {
let vm_alloc_option = VmAllocOptions::new(1);
let mut frames = VmFrameVec::allocate(&vm_alloc_option)?;
frames.zero();
frames.pop().unwrap()
};
Ok(Self {
frame,
state: PageState::Dirty,
})
}
pub fn frame(&self) -> VmFrame {
self.frame.clone()
}
pub fn state(&self) -> &PageState {
&self.state
}
pub fn set_state(&mut self, new_state: PageState) {
self.state = new_state;
}
}
enum PageState {
/// `Uninit` indicates a new allocated page which content has not been initialized.
/// The page is available to write, not available to read.
Uninit,
/// `UpToDate` indicates a page which content is consistent with corresponding disk content.
/// The page is available to read and write.
UpToDate,
/// `Dirty` indicates a page which content has been updated and not written back to underlying disk.
/// The page is available to read and write.
Dirty,
}

View File

@ -0,0 +1,29 @@
use crate::prelude::*;
use super::utils::{Inode, PageCacheManager};
use crate::rights::Rights;
use crate::vm::vmo::{Vmo, VmoFlags, VmoOptions};
pub struct VfsInode {
raw_inode: Arc<dyn Inode>,
pages: Vmo,
}
impl VfsInode {
pub fn new(raw_inode: Arc<dyn Inode>) -> Result<Self> {
let page_cache_manager = Arc::new(PageCacheManager::new(&Arc::downgrade(&raw_inode)));
let pages = VmoOptions::<Rights>::new(raw_inode.metadata().size)
.flags(VmoFlags::RESIZABLE)
.pager(page_cache_manager)
.alloc()?;
Ok(Self { raw_inode, pages })
}
pub fn pages(&self) -> &Vmo {
&self.pages
}
pub fn raw_inode(&self) -> &Arc<dyn Inode> {
&self.raw_inode
}
}

View File

@ -32,6 +32,7 @@ use crate::{
};
extern crate alloc;
extern crate lru;
pub mod driver;
pub mod error;

View File

@ -265,14 +265,14 @@ impl Vmo_ {
return_errno_with_message!(Errno::EINVAL, "read range exceeds vmo size");
}
let read_range = offset..(offset + read_len);
let frames = self.ensure_all_pages_exist(read_range, false)?;
let frames = self.ensure_all_pages_exist(&read_range, false)?;
let read_offset = offset % PAGE_SIZE;
Ok(frames.read_bytes(read_offset, buf)?)
}
/// Ensure all pages inside range are backed up vm frames, returns the frames.
fn ensure_all_pages_exist(&self, range: Range<usize>, write_page: bool) -> Result<VmFrameVec> {
let page_idx_range = get_page_idx_range(&range);
fn ensure_all_pages_exist(&self, range: &Range<usize>, write_page: bool) -> Result<VmFrameVec> {
let page_idx_range = get_page_idx_range(range);
let mut frames = VmFrameVec::empty();
for page_idx in page_idx_range {
let mut page_frame = self.get_backup_frame(page_idx, write_page, true)?;
@ -382,9 +382,15 @@ impl Vmo_ {
}
let write_range = offset..(offset + write_len);
let frames = self.ensure_all_pages_exist(write_range, true)?;
let frames = self.ensure_all_pages_exist(&write_range, true)?;
let write_offset = offset % PAGE_SIZE;
frames.write_bytes(write_offset, buf)?;
if let Some(pager) = &self.inner.lock().pager {
let page_idx_range = get_page_idx_range(&write_range);
for page_idx in page_idx_range {
pager.update_page(page_idx * PAGE_SIZE)?;
}
}
Ok(())
}