Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions libkernel/src/fs/blk/block_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use crate::fs::BlockDevice;
use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;

struct CacheEntry {
block_number: u64,
data: Vec<u8>,
dirty: bool,
}

/// LRU block cache
pub(crate) struct BlockCache {
/// The cache entries, ordered from most recently used (front) to least recently used (back)
/// This never exceeds `capacity` in length.
cache: VecDeque<CacheEntry>,
/// Maximum number of blocks the cache can hold
capacity: usize,
/// Size of each block in bytes
block_size: usize,
pub dev: Box<dyn BlockDevice>,
}

impl BlockCache {
/// Creates a new BlockCache with the given capacity (in number of blocks)
/// and block size (in bytes).
pub fn new(capacity: usize, block_size: usize, dev: Box<dyn BlockDevice>) -> Self {
Self {
cache: VecDeque::with_capacity(capacity),
capacity,
block_size,
dev,
}
}

/// Finds the position of a block in the cache
fn find_position(&self, block_number: u64) -> Option<usize> {
for (i, entry) in self.cache.iter().enumerate() {
if entry.block_number == block_number {
return Some(i);
}
}
None
}

/// Retrieves a block from the cache, loading it from the device if necessary.
pub async fn get_or_load(&mut self, block_number: u64) -> crate::error::Result<&[u8]> {
let block_size = self.block_size;

if let Some(pos) = self.find_position(block_number) {
let entry = self.cache.remove(pos).unwrap();
self.cache.push_front(entry);
return Ok(&self.cache.front().unwrap().data);
}

// Not in cache, read from device
let mut data = vec![0; block_size];
self.dev.read(block_number, &mut data).await?;

self.insert(block_number, data);

// Return a reference to the newly inserted block at the front
Ok(&self
.cache
.front()
.expect("cache should have an entry after insert")
.data)
}

/// Retrieves a mutable block from the cache, loading it from the device if necessary.
/// Marks the block as dirty.
pub async fn get_or_load_mut(
&mut self,
block_number: u64,
) -> crate::error::Result<&mut Vec<u8>> {
let block_size = self.block_size;

if let Some(pos) = self.find_position(block_number) {
let entry = self.cache.remove(pos).unwrap();
self.cache.push_front(entry);
// Mark as dirty since we are returning a mutable reference
if let Some(entry) = self.cache.front_mut() {
entry.dirty = true;
} else {
panic!("cache should have an entry after re-insert");
}
return Ok(&mut self.cache.front_mut().unwrap().data);
}

// Not in cache, read from device
let mut data = vec![0; block_size];
self.dev.read(block_number, &mut data).await?;

self.insert(block_number, data);

// Mark as dirty since we are returning a mutable reference
if let Some(entry) = self.cache.front_mut() {
entry.dirty = true;
} else {
panic!("cache should have an entry after insert");
}

// Return a mutable reference to the newly inserted block at the front
Ok(&mut self
.cache
.front_mut()
.expect("cache should have an entry after insert")
.data)
}

/// Inserts a block into the cache.
pub fn insert(&mut self, block_number: u64, data: Vec<u8>) {
if self.cache.len() == self.capacity {
self.cache.pop_back();
}
self.cache.push_front(CacheEntry {
block_number,
data,
dirty: false,
});
}

#[expect(dead_code)]
pub fn write_back(&mut self, block_number: u64, data: Vec<u8>) {
if let Some(entry) = self
.cache
.iter_mut()
.find(|entry| entry.block_number == block_number)
{
entry.data = data;
entry.dirty = true;
} else {
self.insert(block_number, data);
if let Some(entry) = self
.cache
.iter_mut()
.find(|entry| entry.block_number == block_number)
{
entry.dirty = true;
}
}
}

/// Writes all dirty blocks back to the device.
pub async fn write_dirty(&mut self) -> crate::error::Result<()> {
for entry in self.cache.iter_mut() {
if entry.dirty {
self.dev.write(entry.block_number, &entry.data).await?;
entry.dirty = false;
}
}
Ok(())
}
}
66 changes: 46 additions & 20 deletions libkernel/src/fs/blk/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::{mem, slice};

use crate::{error::Result, fs::BlockDevice, pod::Pod};
use crate::{CpuOps, error::Result, fs::BlockDevice, pod::Pod};

use crate::fs::blk::block_cache::BlockCache;
use crate::sync::mutex::Mutex;
use alloc::{boxed::Box, vec};

/// A buffer that provides byte-level access to an underlying BlockDevice.
Expand All @@ -11,17 +13,25 @@ use alloc::{boxed::Box, vec};
/// blocks or are not aligned to block boundaries.
///
/// TODO: Cache blocks.
pub struct BlockBuffer {
dev: Box<dyn BlockDevice>,
pub struct BlockBuffer<CPU: CpuOps> {
// TODO: Change to rwlock when we have one.
// This would require a bit of rearchitecture to maximize read sharing.
cache: Mutex<BlockCache, CPU>,
block_size: usize,
}

impl BlockBuffer {
impl<CPU> BlockBuffer<CPU>
where
CPU: CpuOps,
{
/// Creates a new `BlockBuffer` that wraps the given block device.
pub fn new(dev: Box<dyn BlockDevice>) -> Self {
let block_size = dev.block_size();

Self { dev, block_size }
Self {
cache: Mutex::new(BlockCache::new(64, block_size, dev)),
block_size,
}
}

/// Reads a sequence of bytes starting at a specific offset.
Expand All @@ -41,7 +51,14 @@ impl BlockBuffer {

let mut temp_buf = vec![0; num_blocks_to_read as usize * self.block_size];

self.dev.read(start_block, &mut temp_buf).await?;
let mut cache = self.cache.lock().await;
for block_index in 0..num_blocks_to_read {
let block_number = start_block + block_index;
let block_data = cache.get_or_load(block_number).await?;
let start = (block_index as usize) * self.block_size;
let end = start + self.block_size;
temp_buf[start..end].copy_from_slice(block_data);
}

let start_in_temp_buf = (offset % self.block_size as u64) as usize;
let end_in_temp_buf = start_in_temp_buf + len;
Expand Down Expand Up @@ -89,25 +106,34 @@ impl BlockBuffer {
let num_blocks_to_rw = end_block - start_block + 1;
let mut temp_buf = vec![0; num_blocks_to_rw as usize * self.block_size];

// Read all affected blocks from the device into our temporary buffer.
// This preserves the data in the blocks that we are not modifying.
self.dev.read(start_block, &mut temp_buf).await?;

// Copy the user's data into the correct position in our temporary
// buffer.
let start_in_temp_buf = (offset % self.block_size as u64) as usize;
let end_in_temp_buf = start_in_temp_buf + len;

temp_buf[start_in_temp_buf..end_in_temp_buf].copy_from_slice(buf);

// Write the entire modified buffer back to the device.
self.dev.write(start_block, &temp_buf).await?;
let mut cache = self.cache.lock().await;
for block_index in 0..num_blocks_to_rw {
let block_number = start_block + block_index;
let block_data = cache.get_or_load_mut(block_number).await?;
let start = (block_index as usize) * self.block_size;
let end = start + self.block_size;
temp_buf[start..end].copy_from_slice(block_data);
}
// // Read all affected blocks from the device into our temporary buffer.
// // This preserves the data in the blocks that we are not modifying.
// self.dev.read(start_block, &mut temp_buf).await?;
//
// // Copy the user's data into the correct position in our temporary
// // buffer.
// let start_in_temp_buf = (offset % self.block_size as u64) as usize;
// let end_in_temp_buf = start_in_temp_buf + len;
//
// temp_buf[start_in_temp_buf..end_in_temp_buf].copy_from_slice(buf);
//
// // Write the entire modified buffer back to the device.
// self.dev.write(start_block, &temp_buf).await?;

Ok(())
}

/// Forwards a sync call to the underlying device.
pub async fn sync(&self) -> Result<()> {
self.dev.sync().await
self.cache.lock().await.write_dirty().await?;
self.cache.lock().await.dev.sync().await
}
}
1 change: 1 addition & 0 deletions libkernel/src/fs/blk/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod block_cache;
pub mod buffer;
pub mod ramdisk;
11 changes: 9 additions & 2 deletions libkernel/src/fs/filesystems/ext4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::fs::pathbuf::PathBuf;
use crate::fs::{DirStream, Dirent};
use crate::proc::ids::{Gid, Uid};
use crate::{
CpuOps,
error::{KernelError, Result},
fs::{
FileType, Filesystem, Inode, InodeId,
Expand All @@ -29,7 +30,10 @@ use ext4_view::{
};

#[async_trait]
impl Ext4Read for BlockBuffer {
impl<CPU> Ext4Read for BlockBuffer<CPU>
where
CPU: crate::CpuOps + Send + Sync,
{
async fn read(
&self,
start_byte: u64,
Expand Down Expand Up @@ -247,7 +251,10 @@ pub struct Ext4Filesystem {

impl Ext4Filesystem {
/// Construct a new EXT4 filesystem instance.
pub async fn new(dev: BlockBuffer, id: u64) -> Result<Arc<Self>> {
pub async fn new<CPU>(dev: BlockBuffer<CPU>, id: u64) -> Result<Arc<Self>>
where
CPU: CpuOps + Send + Sync + 'static,
{
let inner = Ext4::load(Box::new(dev)).await?;
Ok(Arc::new_cyclic(|weak| Self {
inner,
Expand Down
5 changes: 4 additions & 1 deletion libkernel/src/fs/filesystems/fat32/bpb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ pub struct BiosParameterBlock {
unsafe impl Pod for BiosParameterBlock {}

impl BiosParameterBlock {
pub async fn new(dev: &BlockBuffer) -> Result<Self> {
pub async fn new<CPU>(dev: &BlockBuffer<CPU>) -> Result<Self>
where
CPU: crate::CpuOps,
{
let bpb: Self = dev.read_obj(0).await?;

if bpb._fat_size_16 != 0 || bpb._root_entry_count != 0 {
Expand Down
13 changes: 9 additions & 4 deletions libkernel/src/fs/filesystems/fat32/fat.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
CpuOps,
error::{FsError, IoError, Result},
fs::blk::buffer::BlockBuffer,
};
Expand Down Expand Up @@ -72,11 +73,14 @@ impl<'a> Iterator for ClusterChainIterator<'a> {
}

impl Fat {
pub async fn read_fat(
dev: &BlockBuffer,
pub async fn read_fat<CPU>(
dev: &BlockBuffer<CPU>,
bpb: &BiosParameterBlock,
fat_number: usize,
) -> Result<Self> {
) -> Result<Self>
where
CPU: CpuOps,
{
let (start, end) = bpb.fat_region(fat_number).ok_or(FsError::InvalidFs)?;

let mut fat: Vec<FatEntry> = Vec::with_capacity(
Expand Down Expand Up @@ -113,6 +117,7 @@ mod test {
use crate::fs::filesystems::fat32::bpb::test::create_test_bpb;
use crate::fs::filesystems::fat32::fat::{Fat, FatEntry};
use crate::fs::{BlockDevice, blk::buffer::BlockBuffer};
use crate::test::MockCpuOps;
use async_trait::async_trait;

const EOC: u32 = 0xFFFFFFFF;
Expand Down Expand Up @@ -150,7 +155,7 @@ mod test {
}
}

fn setup_fat_test(fat_data: &[u32]) -> BlockBuffer {
fn setup_fat_test(fat_data: &[u32]) -> BlockBuffer<MockCpuOps> {
let mut data = Vec::new();
data.extend(fat_data.iter().flat_map(|x| x.to_le_bytes()));

Expand Down
Loading