ahrightos_fs/
block_cache.rs1use crate::block_dev::{BlockDevice, BlockDeviceHandle, BLOCK_SIZE};
2use crate::file::FileError;
3use alloc::{boxed::Box, collections::VecDeque};
4use spin::Mutex;
5
6const DEFAULT_CACHE_BLOCKS: usize = 64;
7
8struct CachedBlock {
9 block_no: usize,
10 data: Box<[u8; BLOCK_SIZE]>,
11 dirty: bool,
12}
13
14struct CacheInner {
15 entries: VecDeque<CachedBlock>,
16 capacity: usize,
17 hits: u64,
18 misses: u64,
19}
20
21impl CacheInner {
22 fn new(capacity: usize) -> Self {
23 Self {
24 entries: VecDeque::with_capacity(capacity.max(1)),
25 capacity: capacity.max(1),
26 hits: 0,
27 misses: 0,
28 }
29 }
30
31 fn find(&self, block_no: usize) -> Option<usize> {
32 self.entries
33 .iter()
34 .position(|entry| entry.block_no == block_no)
35 }
36
37 fn touch(&mut self, idx: usize) {
38 if idx + 1 == self.entries.len() {
39 return;
40 }
41 if let Some(entry) = self.entries.remove(idx) {
42 self.entries.push_back(entry);
43 }
44 }
45
46 fn evict_lru(&mut self, dev: &dyn BlockDevice) -> Result<(), FileError> {
47 if let Some(victim) = self.entries.pop_front() {
48 if victim.dirty {
49 dev.write_block(victim.block_no, victim.data.as_ref())?;
50 }
51 }
52 Ok(())
53 }
54
55 fn dirty_count(&self) -> usize {
56 self.entries.iter().filter(|entry| entry.dirty).count()
57 }
58
59 fn flush_block(&mut self, dev: &dyn BlockDevice, block_no: usize) -> Result<bool, FileError> {
60 let Some(idx) = self.find(block_no) else {
61 return Ok(false);
62 };
63 if self.entries[idx].dirty {
64 let victim = &self.entries[idx];
65 dev.write_block(victim.block_no, victim.data.as_ref())?;
66 self.entries[idx].dirty = false;
67 }
68 Ok(true)
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct BlockCacheStats {
74 pub hits: u64,
75 pub misses: u64,
76 pub cached_blocks: usize,
77 pub dirty_blocks: usize,
78 pub capacity: usize,
79}
80
81impl BlockCacheStats {
82 pub fn total_reads(self) -> u64 {
83 self.hits + self.misses
84 }
85
86 pub fn hit_rate_percent(self) -> u64 {
87 let total = self.total_reads();
88 if total == 0 {
89 0
90 } else {
91 self.hits * 100 / total
92 }
93 }
94}
95
96pub struct BlockCache {
97 dev: BlockDeviceHandle,
98 inner: Mutex<CacheInner>,
99}
100
101impl BlockCache {
102 pub fn new(dev: BlockDeviceHandle) -> Self {
103 Self {
104 dev,
105 inner: Mutex::new(CacheInner::new(DEFAULT_CACHE_BLOCKS)),
106 }
107 }
108
109 pub fn with_capacity(dev: BlockDeviceHandle, capacity: usize) -> Self {
110 Self {
111 dev,
112 inner: Mutex::new(CacheInner::new(capacity)),
113 }
114 }
115
116 pub fn flush(&self) -> Result<(), FileError> {
117 let mut inner = self.inner.lock();
118 for entry in &inner.entries {
119 if entry.dirty {
120 self.dev.write_block(entry.block_no, entry.data.as_ref())?;
121 }
122 }
123 for entry in &mut inner.entries {
124 entry.dirty = false;
125 }
126 Ok(())
127 }
128
129 pub fn stats(&self) -> (u64, u64) {
130 let inner = self.inner.lock();
131 (inner.hits, inner.misses)
132 }
133
134 pub fn stats_snapshot(&self) -> BlockCacheStats {
135 let inner = self.inner.lock();
136 BlockCacheStats {
137 hits: inner.hits,
138 misses: inner.misses,
139 cached_blocks: inner.entries.len(),
140 dirty_blocks: inner.dirty_count(),
141 capacity: inner.capacity,
142 }
143 }
144
145 pub fn reset_stats(&self) {
146 let mut inner = self.inner.lock();
147 inner.hits = 0;
148 inner.misses = 0;
149 }
150
151 pub fn flush_block(&self, block_no: usize) -> Result<bool, FileError> {
152 self.inner.lock().flush_block(&*self.dev, block_no)
153 }
154}
155
156impl BlockDevice for BlockCache {
157 fn read_block(&self, block_no: usize, buf: &mut [u8]) -> Result<(), FileError> {
158 if buf.len() != BLOCK_SIZE {
159 return Err(FileError::InvalidOperation);
160 }
161 let mut inner = self.inner.lock();
162
163 if let Some(idx) = inner.find(block_no) {
164 buf.copy_from_slice(inner.entries[idx].data.as_ref());
165 inner.touch(idx);
166 inner.hits += 1;
167 return Ok(());
168 }
169
170 inner.misses += 1;
171 if inner.entries.len() >= inner.capacity {
172 inner.evict_lru(&*self.dev)?;
173 }
174
175 let mut data = Box::new([0u8; BLOCK_SIZE]);
176 self.dev.read_block(block_no, data.as_mut())?;
177 buf.copy_from_slice(data.as_ref());
178 inner.entries.push_back(CachedBlock {
179 block_no,
180 data,
181 dirty: false,
182 });
183 Ok(())
184 }
185
186 fn write_block(&self, block_no: usize, buf: &[u8]) -> Result<(), FileError> {
187 if buf.len() != BLOCK_SIZE {
188 return Err(FileError::InvalidOperation);
189 }
190 let mut inner = self.inner.lock();
191
192 if let Some(idx) = inner.find(block_no) {
193 inner.entries[idx].data.copy_from_slice(buf);
194 inner.entries[idx].dirty = true;
195 inner.touch(idx);
196 return Ok(());
197 }
198
199 if inner.entries.len() >= inner.capacity {
200 inner.evict_lru(&*self.dev)?;
201 }
202
203 let mut data = Box::new([0u8; BLOCK_SIZE]);
204 data.copy_from_slice(buf);
205 inner.entries.push_back(CachedBlock {
206 block_no,
207 data,
208 dirty: true,
209 });
210 Ok(())
211 }
212
213 fn block_count(&self) -> usize {
214 self.dev.block_count()
215 }
216
217 fn flush(&self) -> Result<(), FileError> {
218 BlockCache::flush(self)
219 }
220}
221
222impl Drop for BlockCache {
223 fn drop(&mut self) {
224 let _ = self.flush();
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::DummyBlockDev;
232 use alloc::sync::Arc;
233
234 fn block(fill: u8) -> [u8; BLOCK_SIZE] {
235 [fill; BLOCK_SIZE]
236 }
237
238 #[test]
239 fn repeated_read_uses_cache_and_updates_stats() {
240 let raw = Arc::new(DummyBlockDev::new(4));
241 raw.write_block(1, &block(7)).unwrap();
242 let cache = BlockCache::with_capacity(raw, 2);
243
244 let mut buf = [0u8; BLOCK_SIZE];
245 cache.read_block(1, &mut buf).unwrap();
246 assert_eq!(buf, block(7));
247 cache.read_block(1, &mut buf).unwrap();
248
249 let stats = cache.stats_snapshot();
250 assert_eq!(stats.misses, 1);
251 assert_eq!(stats.hits, 1);
252 assert_eq!(stats.cached_blocks, 1);
253 assert_eq!(stats.hit_rate_percent(), 50);
254 }
255
256 #[test]
257 fn write_back_defers_device_write_until_flush() {
258 let raw = Arc::new(DummyBlockDev::new(4));
259 let cache = BlockCache::with_capacity(raw.clone(), 2);
260
261 cache.write_block(2, &block(9)).unwrap();
262 let mut raw_buf = [0u8; BLOCK_SIZE];
263 raw.read_block(2, &mut raw_buf).unwrap();
264 assert_eq!(raw_buf, block(0));
265
266 assert!(cache.flush_block(2).unwrap());
267 raw.read_block(2, &mut raw_buf).unwrap();
268 assert_eq!(raw_buf, block(9));
269 }
270}