diff --git a/libkernel/src/fs/blk/block_cache.rs b/libkernel/src/fs/blk/block_cache.rs new file mode 100644 index 00000000..091d7730 --- /dev/null +++ b/libkernel/src/fs/blk/block_cache.rs @@ -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, + 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, + /// Maximum number of blocks the cache can hold + capacity: usize, + /// Size of each block in bytes + block_size: usize, + pub dev: Box, +} + +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) -> 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 { + 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> { + 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) { + 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) { + 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(()) + } +} diff --git a/libkernel/src/fs/blk/buffer.rs b/libkernel/src/fs/blk/buffer.rs index f3810de2..bacdc874 100644 --- a/libkernel/src/fs/blk/buffer.rs +++ b/libkernel/src/fs/blk/buffer.rs @@ -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. @@ -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, +pub struct BlockBuffer { + // TODO: Change to rwlock when we have one. + // This would require a bit of rearchitecture to maximize read sharing. + cache: Mutex, block_size: usize, } -impl BlockBuffer { +impl BlockBuffer +where + CPU: CpuOps, +{ /// Creates a new `BlockBuffer` that wraps the given block device. pub fn new(dev: Box) -> 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. @@ -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; @@ -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 } } diff --git a/libkernel/src/fs/blk/mod.rs b/libkernel/src/fs/blk/mod.rs index abc1677b..9cf0d7a8 100644 --- a/libkernel/src/fs/blk/mod.rs +++ b/libkernel/src/fs/blk/mod.rs @@ -1,2 +1,3 @@ +mod block_cache; pub mod buffer; pub mod ramdisk; diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index a6ea3b44..2ef8c20c 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -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, @@ -29,7 +30,10 @@ use ext4_view::{ }; #[async_trait] -impl Ext4Read for BlockBuffer { +impl Ext4Read for BlockBuffer +where + CPU: crate::CpuOps + Send + Sync, +{ async fn read( &self, start_byte: u64, @@ -247,7 +251,10 @@ pub struct Ext4Filesystem { impl Ext4Filesystem { /// Construct a new EXT4 filesystem instance. - pub async fn new(dev: BlockBuffer, id: u64) -> Result> { + pub async fn new(dev: BlockBuffer, id: u64) -> Result> + where + CPU: CpuOps + Send + Sync + 'static, + { let inner = Ext4::load(Box::new(dev)).await?; Ok(Arc::new_cyclic(|weak| Self { inner, diff --git a/libkernel/src/fs/filesystems/fat32/bpb.rs b/libkernel/src/fs/filesystems/fat32/bpb.rs index b0420aee..1e83f04a 100644 --- a/libkernel/src/fs/filesystems/fat32/bpb.rs +++ b/libkernel/src/fs/filesystems/fat32/bpb.rs @@ -42,7 +42,10 @@ pub struct BiosParameterBlock { unsafe impl Pod for BiosParameterBlock {} impl BiosParameterBlock { - pub async fn new(dev: &BlockBuffer) -> Result { + pub async fn new(dev: &BlockBuffer) -> Result + where + CPU: crate::CpuOps, + { let bpb: Self = dev.read_obj(0).await?; if bpb._fat_size_16 != 0 || bpb._root_entry_count != 0 { diff --git a/libkernel/src/fs/filesystems/fat32/fat.rs b/libkernel/src/fs/filesystems/fat32/fat.rs index 03c670d6..3e7ce4a6 100644 --- a/libkernel/src/fs/filesystems/fat32/fat.rs +++ b/libkernel/src/fs/filesystems/fat32/fat.rs @@ -1,4 +1,5 @@ use crate::{ + CpuOps, error::{FsError, IoError, Result}, fs::blk::buffer::BlockBuffer, }; @@ -72,11 +73,14 @@ impl<'a> Iterator for ClusterChainIterator<'a> { } impl Fat { - pub async fn read_fat( - dev: &BlockBuffer, + pub async fn read_fat( + dev: &BlockBuffer, bpb: &BiosParameterBlock, fat_number: usize, - ) -> Result { + ) -> Result + where + CPU: CpuOps, + { let (start, end) = bpb.fat_region(fat_number).ok_or(FsError::InvalidFs)?; let mut fat: Vec = Vec::with_capacity( @@ -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; @@ -150,7 +155,7 @@ mod test { } } - fn setup_fat_test(fat_data: &[u32]) -> BlockBuffer { + fn setup_fat_test(fat_data: &[u32]) -> BlockBuffer { let mut data = Vec::new(); data.extend(fat_data.iter().flat_map(|x| x.to_le_bytes())); diff --git a/libkernel/src/fs/filesystems/fat32/mod.rs b/libkernel/src/fs/filesystems/fat32/mod.rs index defe4431..b58a9ff3 100644 --- a/libkernel/src/fs/filesystems/fat32/mod.rs +++ b/libkernel/src/fs/filesystems/fat32/mod.rs @@ -1,4 +1,5 @@ use crate::{ + CpuOps, error::{FsError, Result}, fs::{FileType, Filesystem, Inode, InodeId, attr::FileAttr, blk::buffer::BlockBuffer}, }; @@ -71,16 +72,22 @@ impl Display for Cluster { } } -pub struct Fat32Filesystem { - dev: BlockBuffer, +pub struct Fat32Filesystem +where + CPU: CpuOps, +{ + dev: BlockBuffer, bpb: BiosParameterBlock, fat: Fat, id: u64, this: Weak, } -impl Fat32Filesystem { - pub async fn new(dev: BlockBuffer, id: u64) -> Result> { +impl Fat32Filesystem +where + CPU: CpuOps, +{ + pub async fn new(dev: BlockBuffer, id: u64) -> Result> { let bpb = BiosParameterBlock::new(&dev).await?; let fat = Fat::read_fat(&dev, &bpb, 0).await?; @@ -123,7 +130,10 @@ trait Fat32Operations: Send + Sync + 'static { fn iter_clusters(&self, root: Cluster) -> impl Iterator> + Send; } -impl Fat32Operations for Fat32Filesystem { +impl Fat32Operations for Fat32Filesystem +where + CPU: CpuOps, +{ async fn read_sector(&self, sector: Sector, offset: usize, buf: &mut [u8]) -> Result { debug_assert!(offset < self.bpb.sector_size()); @@ -163,7 +173,10 @@ impl Fat32Operations for Fat32Filesystem { } #[async_trait] -impl Filesystem for Fat32Filesystem { +impl Filesystem for Fat32Filesystem +where + CPU: CpuOps, +{ fn id(&self) -> u64 { self.id } diff --git a/src/drivers/fs/ext4.rs b/src/drivers/fs/ext4.rs index 2bb4a877..e34cf34e 100644 --- a/src/drivers/fs/ext4.rs +++ b/src/drivers/fs/ext4.rs @@ -1,3 +1,4 @@ +use crate::arch::ArchImpl; use crate::{drivers::Driver, fs::FilesystemDriver}; use alloc::{boxed::Box, sync::Arc}; use async_trait::async_trait; @@ -33,7 +34,7 @@ impl FilesystemDriver for Ext4FsDriver { device: Option>, ) -> Result> { match device { - Some(dev) => Ok(Ext4Filesystem::new(BlockBuffer::new(dev), fs_id).await?), + Some(dev) => Ok(Ext4Filesystem::new(BlockBuffer::::new(dev), fs_id).await?), None => { warn!("Could not mount fat32 fs with no block device"); Err(KernelError::InvalidValue) diff --git a/src/drivers/fs/fat32.rs b/src/drivers/fs/fat32.rs index 66ff1133..00c02cec 100644 --- a/src/drivers/fs/fat32.rs +++ b/src/drivers/fs/fat32.rs @@ -1,3 +1,4 @@ +use crate::arch::ArchImpl; use crate::{drivers::Driver, fs::FilesystemDriver}; use alloc::{boxed::Box, sync::Arc}; use async_trait::async_trait; @@ -33,7 +34,7 @@ impl FilesystemDriver for Fat32FsDriver { device: Option>, ) -> Result> { match device { - Some(dev) => Ok(Fat32Filesystem::new(BlockBuffer::new(dev), fs_id).await?), + Some(dev) => Ok(Fat32Filesystem::new(BlockBuffer::::new(dev), fs_id).await?), None => { warn!("Could not mount fat32 fs with no block device"); Err(KernelError::InvalidValue)