Merge pull request #57 from liqinggd/dev-pagecache

Add page cache manager as the pager for vmo
This commit is contained in:
Tate, Hongliang Tian 2023-01-17 12:30:15 +08:00 committed by GitHub
commit 6651a642d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1481 additions and 25 deletions

42
src/Cargo.lock generated
View File

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

View File

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

View File

@ -0,0 +1,380 @@
use crate::prelude::*;
use alloc::str;
use alloc::string::String;
use super::file::FileDescripter;
use super::inode_handle::InodeHandle;
use super::ramfs::RamFS;
use super::utils::{
AccessMode, CreationFlags, Dentry, FileSystem, InodeMode, InodeType, StatusFlags, PATH_MAX,
SYMLINKS_MAX,
};
use super::vfs_inode::VfsInode;
lazy_static! {
static ref ROOT_FS: Arc<dyn FileSystem> = RamFS::new();
}
pub struct FsResolver {
root: Arc<Dentry>,
cwd: Arc<Dentry>,
}
impl Clone for FsResolver {
fn clone(&self) -> Self {
Self {
root: self.root.clone(),
cwd: self.cwd.clone(),
}
}
}
impl FsResolver {
pub fn new() -> Result<Self> {
let root = {
let root_inode = VfsInode::new(ROOT_FS.root_inode())?;
Dentry::new_root(root_inode)
};
Ok(Self {
root: root.clone(),
cwd: root,
})
}
/// Get the root directory
pub fn root(&self) -> &Arc<Dentry> {
&self.root
}
/// Get the current working directory
pub fn cwd(&self) -> &Arc<Dentry> {
&self.cwd
}
/// Set the current working directory.
pub fn set_cwd(&mut self, dentry: Arc<Dentry>) {
self.cwd = dentry;
}
/// Open or create a file inode handler.
pub fn open(&self, path: &FsPath, flags: u32, mode: u16) -> Result<InodeHandle> {
let creation_flags = CreationFlags::from_bits_truncate(flags);
let status_flags = StatusFlags::from_bits_truncate(flags);
let access_mode = AccessMode::from_u32(flags)?;
let inode_mode = InodeMode::from_bits_truncate(mode);
let follow_tail_link = !creation_flags.contains(CreationFlags::O_NOFOLLOW);
let dentry = match self.lookup(path, follow_tail_link) {
Ok(dentry) => {
let inode = dentry.inode().raw_inode();
if inode.metadata().type_ == InodeType::SymLink
&& !status_flags.contains(StatusFlags::O_PATH)
{
return_errno_with_message!(Errno::ELOOP, "file is a symlink");
}
if creation_flags.contains(CreationFlags::O_CREAT)
&& creation_flags.contains(CreationFlags::O_EXCL)
{
return_errno_with_message!(Errno::EEXIST, "file exists");
}
if creation_flags.contains(CreationFlags::O_DIRECTORY)
&& inode.metadata().type_ != InodeType::Dir
{
return_errno_with_message!(
Errno::ENOTDIR,
"O_DIRECTORY is specified but file is not a directory"
);
}
dentry
}
Err(e)
if e.error() == Errno::ENOENT
&& creation_flags.contains(CreationFlags::O_CREAT) =>
{
if creation_flags.contains(CreationFlags::O_DIRECTORY) {
return_errno_with_message!(Errno::ENOTDIR, "cannot create directory");
}
let (dir_dentry, file_name) = self.lookup_dir_and_base_name(path)?;
if file_name.ends_with("/") {
return_errno_with_message!(Errno::EISDIR, "path refers to a directory");
}
if !dir_dentry.inode().raw_inode().metadata().mode.is_writable() {
return_errno_with_message!(Errno::EPERM, "file cannot be created");
}
let new_dentry =
dir_dentry.create_child(&file_name, InodeType::File, inode_mode)?;
new_dentry
}
Err(e) => return Err(e),
};
let inode_handle = InodeHandle::new(dentry, access_mode, status_flags)?;
Ok(inode_handle)
}
/// Lookup dentry according to FsPath
pub fn lookup(&self, path: &FsPath, follow_tail_link: bool) -> Result<Arc<Dentry>> {
let dentry = match path.inner {
FsPathInner::Absolute(path) => {
self.lookup_from_parent(&self.root, path.trim_start_matches('/'), follow_tail_link)?
}
FsPathInner::CwdRelative(path) => {
self.lookup_from_parent(&self.cwd, path, follow_tail_link)?
}
FsPathInner::Cwd => self.cwd.clone(),
FsPathInner::FdRelative(fd, path) => {
let parent = self.lookup_dentry_from_fd(fd)?;
self.lookup_from_parent(&parent, path, follow_tail_link)?
}
FsPathInner::Fd(fd) => self.lookup_dentry_from_fd(fd)?,
};
Ok(dentry)
}
/// Lookup the dir dentry and base file name of the giving path.
///
/// If encounters symlinks, should deference them.
fn lookup_dir_and_base_name(&self, path: &FsPath) -> Result<(Arc<Dentry>, String)> {
let (mut dir_dentry, mut base_name) = match path.inner {
FsPathInner::Absolute(path) => {
let (dir, file_name) = split_path(path);
(
self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?,
String::from(file_name),
)
}
FsPathInner::CwdRelative(path) => {
let (dir, file_name) = split_path(path);
(
self.lookup_from_parent(&self.cwd, path, true)?,
String::from(file_name),
)
}
FsPathInner::FdRelative(fd, path) => {
let (dir, file_name) = split_path(path);
let parent = self.lookup_dentry_from_fd(fd)?;
(
self.lookup_from_parent(&parent, path, true)?,
String::from(file_name),
)
}
_ => return_errno!(Errno::ENOENT),
};
loop {
match dir_dentry.get(&base_name.trim_end_matches('/')) {
Ok(dentry) if dentry.inode().raw_inode().metadata().type_ == InodeType::SymLink => {
let link = {
let mut link = dentry.inode().raw_inode().read_link()?;
if link.is_empty() {
return_errno_with_message!(Errno::ENOENT, "invalid symlink");
}
if base_name.ends_with("/") && !link.ends_with("/") {
link += "/";
}
link
};
let (dir, file_name) = split_path(&link);
if dir.starts_with("/") {
dir_dentry =
self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?;
base_name = String::from(file_name);
} else {
dir_dentry = self.lookup_from_parent(&dir_dentry, dir, true)?;
base_name = String::from(file_name);
}
}
_ => break,
}
}
Ok((dir_dentry, base_name))
}
/// Lookup dentry from parent
///
/// The length of `path` cannot exceed PATH_MAX.
/// If `path` ends with `/`, then the returned inode must be a directory inode.
///
/// While looking up the dentry, symbolic links will be followed for
/// at most `SYMLINKS_MAX` times.
///
/// If `follow_tail_link` is true and the trailing component is a symlink,
/// it will be followed.
/// Symlinks in earlier components of the path will always be followed.
fn lookup_from_parent(
&self,
parent: &Arc<Dentry>,
relative_path: &str,
follow_tail_link: bool,
) -> Result<Arc<Dentry>> {
debug_assert!(!relative_path.starts_with("/"));
if relative_path.len() > PATH_MAX {
return_errno_with_message!(Errno::ENAMETOOLONG, "path is too long");
}
// To handle symlinks
let mut link_path = String::new();
let mut follows = 0;
// Initialize the first dentry and the relative path
let (mut dentry, mut relative_path) = (parent.clone(), relative_path);
while !relative_path.is_empty() {
let (next_name, path_remain, must_be_dir) =
if let Some((prefix, suffix)) = relative_path.split_once('/') {
let suffix = suffix.trim_start_matches('/');
(prefix, suffix, true)
} else {
(relative_path, "", false)
};
// Iterate next dentry
let next_dentry = dentry.get(next_name)?;
let next_type = next_dentry.inode().raw_inode().metadata().type_;
let next_is_tail = path_remain.is_empty();
// If next inode is a symlink, follow symlinks at most `SYMLINKS_MAX` times.
if next_type == InodeType::SymLink && (follow_tail_link || !next_is_tail) {
if follows >= SYMLINKS_MAX {
return_errno_with_message!(Errno::ELOOP, "too many symlinks");
}
let link_path_remain = {
let mut tmp_link_path = next_dentry.inode().raw_inode().read_link()?;
if tmp_link_path.is_empty() {
return_errno_with_message!(Errno::ENOENT, "empty symlink");
}
if !path_remain.is_empty() {
tmp_link_path += "/";
tmp_link_path += path_remain;
} else if must_be_dir {
tmp_link_path += "/";
}
tmp_link_path
};
// Change the dentry and relative path according to symlink
if link_path_remain.starts_with("/") {
dentry = self.root.clone();
}
link_path.clear();
link_path.push_str(&link_path_remain.trim_start_matches('/'));
relative_path = &link_path;
follows += 1;
} else {
// If path ends with `/`, the inode must be a directory
if must_be_dir && next_type != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "inode is not dir");
}
dentry = next_dentry;
relative_path = path_remain;
}
}
Ok(dentry)
}
/// Lookup dentry from the giving fd
fn lookup_dentry_from_fd(&self, fd: FileDescripter) -> Result<Arc<Dentry>> {
let current = current!();
let file_table = current.file_table().lock();
let inode_handle = file_table
.get_file(fd)?
.as_inode_handle()
.ok_or(Error::with_message(Errno::EBADE, "not inode"))?;
Ok(inode_handle.dentry().clone())
}
}
pub const AT_FDCWD: FileDescripter = -100;
pub struct FsPath<'a> {
inner: FsPathInner<'a>,
}
#[derive(Debug)]
enum FsPathInner<'a> {
// absolute path
Absolute(&'a str),
// path is relative to Cwd
CwdRelative(&'a str),
// Cwd
Cwd,
// path is relative to DirFd
FdRelative(FileDescripter, &'a str),
// Fd
Fd(FileDescripter),
}
impl<'a> FsPath<'a> {
pub fn new(dirfd: FileDescripter, path: &'a str) -> Result<Self> {
if path.len() > PATH_MAX {
return_errno_with_message!(Errno::ENAMETOOLONG, "path name too long");
}
let fs_path_inner = if path.starts_with("/") {
FsPathInner::Absolute(path)
} else if dirfd >= 0 {
if path.is_empty() {
FsPathInner::Fd(dirfd)
} else {
FsPathInner::FdRelative(dirfd, path)
}
} else if dirfd == AT_FDCWD {
if path.is_empty() {
FsPathInner::Cwd
} else {
FsPathInner::CwdRelative(path)
}
} else {
return_errno_with_message!(Errno::EINVAL, "invalid dirfd number");
};
Ok(Self {
inner: fs_path_inner,
})
}
}
impl<'a> TryFrom<&'a str> for FsPath<'a> {
type Error = crate::error::Error;
fn try_from(path: &'a str) -> Result<FsPath> {
if path.is_empty() {
return_errno_with_message!(Errno::ENOENT, "path is an empty string");
}
FsPath::new(AT_FDCWD, path)
}
}
/// Split a `path` to (`dir_path`, `file_name`).
///
/// The `dir_path` must be a directory.
///
/// The `file_name` is the last component. It can be suffixed by "/".
///
/// Example:
///
/// The path "/dir/file/" will be split to ("/dir", "file/").
fn split_path(path: &str) -> (&str, &str) {
let file_name = path
.split_inclusive('/')
.filter(|&x| x != "/")
.last()
.unwrap_or(".");
let mut split = path.trim_end_matches('/').rsplitn(2, '/');
let dir_path = if split.next().unwrap().is_empty() {
"/"
} else {
let mut dir = split.next().unwrap_or(".").trim_end_matches('/');
if dir.is_empty() {
dir = "/";
}
dir
};
(dir_path, file_name)
}

View File

@ -6,11 +6,11 @@ use super::*;
impl InodeHandle<Rights> { impl InodeHandle<Rights> {
pub fn new( pub fn new(
inode: Arc<dyn Inode>, dentry: Arc<Dentry>,
access_mode: AccessMode, access_mode: AccessMode,
status_flags: StatusFlags, status_flags: StatusFlags,
) -> Result<Self> { ) -> Result<Self> {
let inode_info = inode.metadata(); let inode_info = dentry.inode().raw_inode().metadata();
if access_mode.is_readable() && !inode_info.mode.is_readable() { if access_mode.is_readable() && !inode_info.mode.is_readable() {
return_errno_with_message!(Errno::EACCES, "File is not readable"); return_errno_with_message!(Errno::EACCES, "File is not readable");
} }
@ -21,7 +21,7 @@ impl InodeHandle<Rights> {
return_errno_with_message!(Errno::EISDIR, "Directory cannot open to write"); return_errno_with_message!(Errno::EISDIR, "Directory cannot open to write");
} }
let inner = Arc::new(InodeHandle_ { let inner = Arc::new(InodeHandle_ {
inode, dentry,
offset: Mutex::new(0), offset: Mutex::new(0),
access_mode, access_mode,
status_flags: Mutex::new(status_flags), status_flags: Mutex::new(status_flags),

View File

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

View File

@ -3,9 +3,12 @@ pub mod fcntl;
pub mod file; pub mod file;
pub mod file_handle; pub mod file_handle;
pub mod file_table; pub mod file_table;
pub mod fs_resolver;
pub mod inode_handle; pub mod inode_handle;
pub mod ioctl; pub mod ioctl;
pub mod poll; pub mod poll;
pub mod ramfs;
pub mod stat; pub mod stat;
pub mod stdio; pub mod stdio;
pub mod utils; pub mod utils;
pub mod vfs_inode;

View File

@ -0,0 +1,568 @@
use crate::prelude::*;
use alloc::str;
use alloc::string::String;
use core::any::Any;
use core::sync::atomic::{AtomicUsize, Ordering};
use jinux_frame::vm::VmFrame;
use spin::{RwLock, RwLockWriteGuard};
use super::*;
use crate::fs::ioctl::IoctlCmd;
use crate::fs::utils::{
DirentWriterContext, FileSystem, Inode, InodeMode, InodeType, Metadata, SuperBlock,
};
pub struct RamFS {
metadata: RwLock<SuperBlock>,
root: Arc<RamInode>,
inode_allocator: AtomicUsize,
}
impl RamFS {
pub fn new() -> Arc<Self> {
let root = Arc::new(RamInode(RwLock::new(Inode_::new_dir(
ROOT_INO,
InodeMode::from_bits_truncate(0o755),
))));
let ramfs = Arc::new(Self {
metadata: RwLock::new(SuperBlock::new(RAMFS_MAGIC, BLOCK_SIZE, NAME_MAX)),
root,
inode_allocator: AtomicUsize::new(ROOT_INO + 1),
});
let mut root = ramfs.root.0.write();
root.inner
.as_direntry_mut()
.unwrap()
.init(Arc::downgrade(&ramfs.root), Arc::downgrade(&ramfs.root));
root.this = Arc::downgrade(&ramfs.root);
root.fs = Arc::downgrade(&ramfs);
drop(root);
ramfs
}
fn alloc_id(&self) -> usize {
let next_id = self.inode_allocator.fetch_add(1, Ordering::SeqCst);
self.metadata.write().files += 1;
next_id
}
}
impl FileSystem for RamFS {
fn sync(&self) -> Result<()> {
// do nothing
Ok(())
}
fn root_inode(&self) -> Arc<dyn Inode> {
self.root.clone()
}
fn sb(&self) -> SuperBlock {
self.metadata.read().clone()
}
}
struct RamInode(RwLock<Inode_>);
struct Inode_ {
inner: Inner,
metadata: Metadata,
this: Weak<RamInode>,
fs: Weak<RamFS>,
}
impl Inode_ {
pub fn new_dir(ino: usize, mode: InodeMode) -> Self {
Self {
inner: Inner::Dir(DirEntry::new()),
metadata: Metadata::new_dir(ino, mode),
this: Weak::default(),
fs: Weak::default(),
}
}
pub fn new_file(ino: usize, mode: InodeMode) -> Self {
Self {
inner: Inner::File,
metadata: Metadata::new_file(ino, mode),
this: Weak::default(),
fs: Weak::default(),
}
}
pub fn new_symlink(ino: usize, mode: InodeMode) -> Self {
Self {
inner: Inner::SymLink(Str256::from("")),
metadata: Metadata::new_synlink(ino, mode),
this: Weak::default(),
fs: Weak::default(),
}
}
}
enum Inner {
Dir(DirEntry),
File,
SymLink(Str256),
}
impl Inner {
fn as_direntry(&self) -> Option<&DirEntry> {
match self {
Inner::Dir(dir_entry) => Some(dir_entry),
_ => None,
}
}
fn as_direntry_mut(&mut self) -> Option<&mut DirEntry> {
match self {
Inner::Dir(dir_entry) => Some(dir_entry),
_ => None,
}
}
fn as_symlink(&self) -> Option<&str> {
match self {
Inner::SymLink(link) => Some(link.as_ref()),
_ => None,
}
}
fn as_symlink_mut(&mut self) -> Option<&mut Str256> {
match self {
Inner::SymLink(link) => Some(link),
_ => None,
}
}
}
struct DirEntry {
children: LinkedList<(Str256, Arc<RamInode>)>,
this: Weak<RamInode>,
parent: Weak<RamInode>,
}
macro_rules! write_inode_entry {
($ctx:expr, $name:expr, $inode:expr, $total_written:expr) => {
let ctx = $ctx;
let name = $name;
let inode = $inode;
let total_written = $total_written;
match ctx.write_entry(
name.as_ref(),
inode.metadata().ino as u64,
inode.metadata().type_,
) {
Ok(written_len) => {
*total_written += written_len;
}
Err(e) => {
if *total_written == 0 {
return Err(e);
} else {
return Ok(*total_written);
}
}
}
};
}
impl DirEntry {
fn new() -> Self {
Self {
children: LinkedList::new(),
this: Weak::default(),
parent: Weak::default(),
}
}
fn init(&mut self, this: Weak<RamInode>, parent: Weak<RamInode>) {
self.this = this;
self.set_parent(parent);
}
fn set_parent(&mut self, parent: Weak<RamInode>) {
self.parent = parent;
}
fn contains_entry(&self, name: &str) -> bool {
if name == "." || name == ".." {
true
} else {
self.children
.iter()
.find(|(child, _)| child == &Str256::from(name))
.is_some()
}
}
fn get_entry(&self, name: &str) -> Option<(usize, Arc<RamInode>)> {
if name == "." {
Some((0, self.this.upgrade().unwrap()))
} else if name == ".." {
Some((1, self.parent.upgrade().unwrap()))
} else {
self.children
.iter()
.enumerate()
.find(|(idx, (child, inode))| child == &Str256::from(name))
.map(|(idx, (_, inode))| (idx + 2, inode.clone()))
}
}
fn append_entry(&mut self, name: &str, inode: Arc<RamInode>) {
self.children.push_back((Str256::from(name), inode))
}
fn remove_entry(&mut self, idx: usize) -> (Str256, Arc<RamInode>) {
assert!(idx >= 2);
self.children.remove(idx - 2)
}
fn modify_entry(&mut self, idx: usize, new_name: &str) {
assert!(idx >= 2);
let (name, _) = self.children.iter_mut().nth(idx - 2).unwrap();
*name = Str256::from(new_name);
}
fn iterate_entries(&self, mut ctx: &mut DirentWriterContext) -> Result<usize> {
let mut total_written_len = 0;
let idx = ctx.pos();
// Write the two special entries
if idx == 0 {
let this_inode = self.this.upgrade().unwrap();
write_inode_entry!(&mut ctx, ".", &this_inode, &mut total_written_len);
}
if idx <= 1 {
let parent_inode = self.parent.upgrade().unwrap();
write_inode_entry!(&mut ctx, "..", &parent_inode, &mut total_written_len);
}
// Write the normal entries
let skipped_children = if idx < 2 { 0 } else { idx - 2 };
for (name, child) in self.children.iter().skip(skipped_children) {
write_inode_entry!(&mut ctx, name, child, &mut total_written_len);
}
Ok(total_written_len)
}
fn is_empty_children(&self) -> bool {
self.children.is_empty()
}
}
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct Str256([u8; 256]);
impl AsRef<str> for Str256 {
fn as_ref(&self) -> &str {
let len = self.0.iter().enumerate().find(|(_, &b)| b == 0).unwrap().0;
str::from_utf8(&self.0[0..len]).unwrap()
}
}
impl<'a> From<&'a str> for Str256 {
fn from(s: &'a str) -> Self {
let mut inner = [0u8; 256];
let len = if s.len() > NAME_MAX {
NAME_MAX
} else {
s.len()
};
inner[0..len].copy_from_slice(&s.as_bytes()[0..len]);
Str256(inner)
}
}
impl core::fmt::Debug for Str256 {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl Inode for RamInode {
fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
// do nothing
Ok(())
}
fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
// do nothing
Ok(())
}
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
return_errno_with_message!(Errno::EOPNOTSUPP, "direct read is not supported");
}
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
return_errno_with_message!(Errno::EOPNOTSUPP, "direct read is not supported");
}
fn resize(&self, new_size: usize) -> Result<()> {
if self.0.read().metadata.type_ != InodeType::File {
return_errno_with_message!(Errno::EISDIR, "self is not file");
}
self.0.write().metadata.size = new_size;
Ok(())
}
fn mknod(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result<Arc<dyn Inode>> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
let mut self_inode = self.0.write();
if self_inode.inner.as_direntry().unwrap().contains_entry(name) {
return_errno_with_message!(Errno::EEXIST, "entry exists");
}
let new_inode = match type_ {
InodeType::File => {
let file_inode = Arc::new(RamInode(RwLock::new(Inode_::new_file(
self_inode.fs.upgrade().unwrap().alloc_id(),
mode,
))));
file_inode.0.write().fs = self_inode.fs.clone();
file_inode
}
InodeType::Dir => {
let dir_inode = Arc::new(RamInode(RwLock::new(Inode_::new_dir(
self_inode.fs.upgrade().unwrap().alloc_id(),
mode,
))));
dir_inode.0.write().fs = self_inode.fs.clone();
dir_inode.0.write().inner.as_direntry_mut().unwrap().init(
Arc::downgrade(&dir_inode),
self_inode.inner.as_direntry().unwrap().this.clone(),
);
dir_inode
}
InodeType::SymLink => {
let sym_inode = Arc::new(RamInode(RwLock::new(Inode_::new_symlink(
self_inode.fs.upgrade().unwrap().alloc_id(),
mode,
))));
sym_inode.0.write().fs = self_inode.fs.clone();
sym_inode
}
_ => {
panic!("unsupported inode type");
}
};
new_inode.0.write().this = Arc::downgrade(&new_inode);
self_inode
.inner
.as_direntry_mut()
.unwrap()
.append_entry(name, new_inode.clone());
Ok(new_inode)
}
fn readdir(&self, ctx: &mut DirentWriterContext) -> Result<usize> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
let self_inode = self.0.read();
let total_written_len = self_inode
.inner
.as_direntry()
.unwrap()
.iterate_entries(ctx)?;
Ok(total_written_len)
}
fn link(&self, old: &Arc<dyn Inode>, name: &str) -> Result<()> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
let old = old
.downcast_ref::<RamInode>()
.ok_or(Error::new(Errno::EXDEV))?;
if old.0.read().metadata.type_ == InodeType::Dir {
return_errno_with_message!(Errno::EPERM, "old is a dir");
}
let mut self_inode = self.0.write();
if self_inode.inner.as_direntry().unwrap().contains_entry(name) {
return_errno_with_message!(Errno::EEXIST, "entry exist");
}
self_inode
.inner
.as_direntry_mut()
.unwrap()
.append_entry(name, old.0.read().this.upgrade().unwrap());
Ok(())
}
fn unlink(&self, name: &str) -> Result<()> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
if name == "." || name == ".." {
return_errno_with_message!(Errno::EISDIR, "unlink . or ..");
}
let mut self_inode = self.0.write();
let self_dir = self_inode.inner.as_direntry_mut().unwrap();
let (idx, other) = self_dir.get_entry(name).ok_or(Error::new(Errno::ENOENT))?;
let other_inode = other.0.read();
if other_inode.metadata.type_ == InodeType::Dir
&& !other_inode.inner.as_direntry().unwrap().is_empty_children()
{
return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty");
}
self_dir.remove_entry(idx);
Ok(())
}
fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
let (_, inode) = self
.0
.read()
.inner
.as_direntry()
.unwrap()
.get_entry(name)
.ok_or(Error::new(Errno::ENOENT))?;
Ok(inode as _)
}
fn rename(&self, old_name: &str, target: &Arc<dyn Inode>, new_name: &str) -> Result<()> {
if self.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
}
let target = target
.downcast_ref::<RamInode>()
.ok_or(Error::new(Errno::EXDEV))?;
if target.0.read().metadata.type_ != InodeType::Dir {
return_errno_with_message!(Errno::ENOTDIR, "target is not dir");
}
if old_name == "." || old_name == ".." {
return_errno_with_message!(Errno::EISDIR, "old_name is . or ..");
}
if new_name == "." || new_name == ".." {
return_errno_with_message!(Errno::EISDIR, "new_name is . or ..");
}
let src_inode = self.lookup(old_name)?;
if src_inode.metadata().ino == target.metadata().ino {
return_errno_with_message!(Errno::EINVAL, "target is a descendant of old");
}
if let Ok(dst_inode) = target.lookup(new_name) {
if src_inode.metadata().ino == dst_inode.metadata().ino {
return Ok(());
}
match (src_inode.metadata().type_, dst_inode.metadata().type_) {
(InodeType::Dir, InodeType::Dir) => {
let dst_inode = dst_inode.downcast_ref::<RamInode>().unwrap();
if !dst_inode
.0
.read()
.inner
.as_direntry()
.unwrap()
.is_empty_children()
{
return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty");
}
}
(InodeType::Dir, _) => {
return_errno_with_message!(Errno::ENOTDIR, "old is not dir");
}
(_, InodeType::Dir) => {
return_errno_with_message!(Errno::EISDIR, "new is dir");
}
_ => {}
}
}
if self.metadata().ino == target.metadata().ino {
let mut self_inode = self.0.write();
let self_dir = self_inode.inner.as_direntry_mut().unwrap();
let (idx, _) = self_dir
.get_entry(old_name)
.ok_or(Error::new(Errno::ENOENT))?;
self_dir.modify_entry(idx, new_name);
} else {
let (mut self_inode, mut target_inode) = write_lock_two_inodes(self, target);
let self_dir = self_inode.inner.as_direntry_mut().unwrap();
let (idx, src_inode) = self_dir
.get_entry(old_name)
.ok_or(Error::new(Errno::ENOENT))?;
self_dir.remove_entry(idx);
target_inode
.inner
.as_direntry_mut()
.unwrap()
.append_entry(new_name, src_inode.clone());
drop(self_inode);
drop(target_inode);
if src_inode.metadata().type_ == InodeType::Dir {
src_inode
.0
.write()
.inner
.as_direntry_mut()
.unwrap()
.set_parent(target.0.read().this.clone());
}
}
Ok(())
}
fn read_link(&self) -> Result<String> {
if self.0.read().metadata.type_ != InodeType::SymLink {
return_errno_with_message!(Errno::EINVAL, "self is not symlink");
}
let self_inode = self.0.read();
let link = self_inode.inner.as_symlink().unwrap();
Ok(String::from(link))
}
fn write_link(&self, target: &str) -> Result<()> {
if self.0.read().metadata.type_ != InodeType::SymLink {
return_errno_with_message!(Errno::EINVAL, "self is not symlink");
}
let mut self_inode = self.0.write();
let link = self_inode.inner.as_symlink_mut().unwrap();
*link = Str256::from(target);
Ok(())
}
fn metadata(&self) -> Metadata {
self.0.read().metadata.clone()
}
fn sync(&self) -> Result<()> {
// do nothing
Ok(())
}
fn fs(&self) -> Arc<dyn FileSystem> {
Weak::upgrade(&self.0.read().fs).unwrap()
}
fn ioctl(&self, cmd: &IoctlCmd) -> Result<()> {
return_errno!(Errno::ENOSYS);
}
fn as_any_ref(&self) -> &dyn Any {
self
}
}
fn write_lock_two_inodes<'a>(
this: &'a RamInode,
other: &'a RamInode,
) -> (RwLockWriteGuard<'a, Inode_>, RwLockWriteGuard<'a, Inode_>) {
if this.0.read().metadata.ino < other.0.read().metadata.ino {
let this = this.0.write();
let other = other.0.write();
(this, other)
} else {
let other = other.0.write();
let this = this.0.write();
(this, other)
}
}

View File

@ -0,0 +1,10 @@
//! Ramfs based on PageCache
pub use fs::RamFS;
mod fs;
const RAMFS_MAGIC: usize = 0x0102_1994;
const BLOCK_SIZE: usize = 4096;
const NAME_MAX: usize = 255;
const ROOT_INO: usize = 1;

View File

@ -1,3 +1,4 @@
use crate::prelude::*;
use crate::rights::Rights; use crate::rights::Rights;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -28,6 +29,21 @@ impl AccessMode {
} }
} }
impl AccessMode {
pub fn from_u32(flags: u32) -> Result<Self> {
let bits = (flags & 0b11) as u8;
if bits > Self::O_RDWR as u8 {
return_errno_with_message!(Errno::EINVAL, "invalid bits for access mode");
}
Ok(match bits {
0 => Self::O_RDONLY,
1 => Self::O_WRONLY,
2 => Self::O_RDWR,
_ => unreachable!(),
})
}
}
impl From<Rights> for AccessMode { impl From<Rights> for AccessMode {
fn from(rights: Rights) -> AccessMode { fn from(rights: Rights) -> AccessMode {
if rights.contains(Rights::READ) && rights.contains(Rights::WRITE) { if rights.contains(Rights::READ) && rights.contains(Rights::WRITE) {

View File

@ -0,0 +1,23 @@
use bitflags::bitflags;
bitflags! {
pub struct CreationFlags: u32 {
/// create file if it does not exist
const O_CREAT = 1 << 6;
/// error if CREATE and the file exists
const O_EXCL = 1 << 7;
/// not become the process's controlling terminal
const O_NOCTTY = 1 << 8;
/// truncate file upon open
const O_TRUNC = 1 << 9;
/// file is a directory
const O_DIRECTORY = 1 << 16;
/// pathname is not a symbolic link
const O_NOFOLLOW = 1 << 17;
/// close on exec
const O_CLOEXEC = 1 << 19;
/// create an unnamed temporary regular file
/// O_TMPFILE is (_O_TMPFILE | O_DIRECTORY)
const _O_TMPFILE = 1 << 22;
}
}

View File

@ -0,0 +1,127 @@
use crate::prelude::*;
use alloc::string::String;
use super::{InodeMode, InodeType, NAME_MAX};
use crate::fs::vfs_inode::VfsInode;
pub struct Dentry {
inner: RwLock<Dentry_>,
inode: VfsInode,
}
struct Dentry_ {
name: String,
this: Weak<Dentry>,
parent: Option<Weak<Dentry>>,
children: BTreeMap<String, Arc<Dentry>>,
}
impl Dentry_ {
pub fn new(name: &str, parent: Option<Weak<Dentry>>) -> Self {
Self {
name: String::from(name),
this: Weak::default(),
parent,
children: BTreeMap::new(),
}
}
}
impl Dentry {
/// Create a new dentry cache with root inode
pub fn new_root(inode: VfsInode) -> Arc<Self> {
let root = Self::new("/", inode, None);
root
}
/// Internal constructor
fn new(name: &str, inode: VfsInode, parent: Option<Weak<Dentry>>) -> Arc<Self> {
let dentry = {
let inner = RwLock::new(Dentry_::new(name, parent));
Arc::new(Self { inner, inode })
};
dentry.inner.write().this = Arc::downgrade(&dentry);
dentry
}
fn name(&self) -> String {
self.inner.read().name.clone()
}
pub fn inode(&self) -> &VfsInode {
&self.inode
}
pub fn create_child(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result<Arc<Self>> {
let mut inner = self.inner.write();
let child = {
let inode = VfsInode::new(self.inode().raw_inode().mknod(name, type_, mode)?)?;
Dentry::new(name, inode, Some(inner.this.clone()))
};
inner.children.insert(String::from(name), child.clone());
Ok(child)
}
fn this(&self) -> Arc<Dentry> {
self.inner.read().this.upgrade().unwrap()
}
fn parent(&self) -> Option<Arc<Dentry>> {
self.inner
.read()
.parent
.as_ref()
.map(|p| p.upgrade().unwrap())
}
pub fn get(&self, name: &str) -> Result<Arc<Dentry>> {
if self.inode.raw_inode().metadata().type_ != InodeType::Dir {
return_errno!(Errno::ENOTDIR);
}
if name.len() > NAME_MAX {
return_errno!(Errno::ENAMETOOLONG);
}
let dentry = match name {
"." => self.this(),
".." => self.parent().unwrap_or(self.this()),
name => {
let mut inner = self.inner.write();
if let Some(dentry) = inner.children.get(name) {
dentry.clone()
} else {
let inode = VfsInode::new(self.inode.raw_inode().lookup(name)?)?;
let dentry = Dentry::new(name, inode, Some(inner.this.clone()));
inner.children.insert(String::from(name), dentry.clone());
dentry
}
}
};
Ok(dentry)
}
pub fn abs_path(&self) -> String {
let mut path = self.name();
let mut dentry = self.this();
loop {
match dentry.parent() {
None => break,
Some(parent_dentry) => {
path = {
let parent_name = parent_dentry.name();
if parent_name != "/" {
parent_name + "/" + &path
} else {
parent_name + &path
}
};
dentry = parent_dentry;
}
}
}
debug_assert!(path.starts_with("/"));
path
}
}

View File

@ -18,6 +18,24 @@ pub struct SuperBlock {
pub flags: usize, pub flags: usize,
} }
impl SuperBlock {
pub fn new(magic: usize, block_size: usize, name_len: usize) -> Self {
Self {
magic,
bsize: block_size,
blocks: 0,
bfree: 0,
bavail: 0,
files: 0,
ffree: 0,
fsid: 0,
namelen: 255,
frsize: block_size,
flags: 0,
}
}
}
pub trait FileSystem: Sync + Send { pub trait FileSystem: Sync + Send {
fn sync(&self) -> Result<()>; fn sync(&self) -> Result<()>;

View File

@ -2,6 +2,7 @@ use alloc::string::String;
use alloc::sync::Arc; use alloc::sync::Arc;
use bitflags::bitflags; use bitflags::bitflags;
use core::any::Any; use core::any::Any;
use jinux_frame::vm::VmFrame;
use super::{DirentWriterContext, FileSystem}; use super::{DirentWriterContext, FileSystem};
use crate::fs::ioctl::IoctlCmd; use crate::fs::ioctl::IoctlCmd;
@ -90,6 +91,65 @@ pub struct Metadata {
pub rdev: usize, pub rdev: usize,
} }
impl Metadata {
pub fn new_dir(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 2,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::Dir,
mode,
nlinks: 2,
uid: 0,
gid: 0,
rdev: 0,
}
}
pub fn new_file(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 0,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::File,
mode,
nlinks: 1,
uid: 0,
gid: 0,
rdev: 0,
}
}
pub fn new_synlink(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 0,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::SymLink,
mode,
nlinks: 1,
uid: 0,
gid: 0,
rdev: 0,
}
}
}
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Timespec { pub struct Timespec {
pub sec: i64, pub sec: i64,
@ -101,6 +161,10 @@ pub trait Inode: Any + Sync + Send {
fn metadata(&self) -> Metadata; 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 read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize>;
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize>; fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize>;

View File

@ -1,15 +1,21 @@
//! VFS components //! VFS components
pub use access_mode::AccessMode; pub use access_mode::AccessMode;
pub use creation_flags::CreationFlags;
pub use dentry_cache::Dentry;
pub use dirent_writer::{DirentWriter, DirentWriterContext}; pub use dirent_writer::{DirentWriter, DirentWriterContext};
pub use fs::{FileSystem, SuperBlock}; pub use fs::{FileSystem, SuperBlock};
pub use inode::{Inode, InodeMode, InodeType, Metadata, Timespec}; pub use inode::{Inode, InodeMode, InodeType, Metadata, Timespec};
pub use page_cache::PageCacheManager;
pub use status_flags::StatusFlags; pub use status_flags::StatusFlags;
mod access_mode; mod access_mode;
mod creation_flags;
mod dentry_cache;
mod dirent_writer; mod dirent_writer;
mod fs; mod fs;
mod inode; mod inode;
mod page_cache;
mod status_flags; mod status_flags;
#[derive(Copy, PartialEq, Eq, Clone, Debug)] #[derive(Copy, PartialEq, Eq, Clone, Debug)]
@ -18,3 +24,12 @@ pub enum SeekFrom {
End(i64), End(i64),
Current(i64), Current(i64),
} }
/// Maximum bytes in a path
pub const PATH_MAX: usize = 4096;
/// Maximum bytes in a file name
pub const NAME_MAX: usize = 255;
/// The upper limit for resolving symbolic links
pub const SYMLINKS_MAX: usize = 40;

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,30 @@
use crate::prelude::*;
use super::utils::{Inode, PageCacheManager};
use crate::rights::Rights;
use crate::vm::vmo::{Vmo, VmoFlags, VmoOptions};
#[derive(Clone)]
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

@ -15,6 +15,7 @@
// We should find a proper method to replace this feature with min_specialization, which is a sound feature. // We should find a proper method to replace this feature with min_specialization, which is a sound feature.
#![feature(specialization)] #![feature(specialization)]
#![feature(fn_traits)] #![feature(fn_traits)]
#![feature(linked_list_remove)]
use crate::{ use crate::{
prelude::*, prelude::*,
@ -32,6 +33,7 @@ use crate::{
}; };
extern crate alloc; extern crate alloc;
extern crate lru;
pub mod driver; pub mod driver;
pub mod error; pub mod error;

View File

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