diff --git a/kernel-rs/src/fs/lfs/cleaner.rs b/kernel-rs/src/fs/lfs/cleaner.rs index 8d51026f..e7465eda 100644 --- a/kernel-rs/src/fs/lfs/cleaner.rs +++ b/kernel-rs/src/fs/lfs/cleaner.rs @@ -259,7 +259,7 @@ impl Lfs { inode.free((tx, ctx)); } BlockType::Imap => { - let mut imap = self.imap(ctx); + let mut imap = tx.imap(ctx); imap.update(entry.block_no, seg, ctx).unwrap().free(ctx); if seg.is_full() { seg.commit(true, ctx); diff --git a/kernel-rs/src/fs/lfs/imap.rs b/kernel-rs/src/fs/lfs/imap.rs index 963ec1a8..907ad711 100644 --- a/kernel-rs/src/fs/lfs/imap.rs +++ b/kernel-rs/src/fs/lfs/imap.rs @@ -23,7 +23,7 @@ struct DImapBlock { impl<'s> From<&'s BufData> for &'s DImapBlock { fn from(b: &'s BufData) -> Self { - const_assert!(mem::size_of::() <= BSIZE); + const_assert!(mem::size_of::() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::() == 0); unsafe { &*(b.as_ptr() as *const DImapBlock) } } @@ -31,7 +31,7 @@ impl<'s> From<&'s BufData> for &'s DImapBlock { impl<'s> From<&'s mut BufData> for &'s mut DImapBlock { fn from(b: &'s mut BufData) -> Self { - const_assert!(mem::size_of::() <= BSIZE); + const_assert!(mem::size_of::() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::() == 0); unsafe { &mut *(b.as_mut_ptr() as *mut DImapBlock) } } @@ -40,12 +40,12 @@ impl<'s> From<&'s mut BufData> for &'s mut DImapBlock { /// Stores the address of each imap block. pub struct Imap { dev_no: u32, - ninodes: usize, + ninodes: u32, addr: [u32; IMAPSIZE], } impl Imap { - pub fn new(dev_no: u32, ninodes: usize, addr: [u32; IMAPSIZE]) -> Self { + pub fn new(dev_no: u32, ninodes: u32, addr: [u32; IMAPSIZE]) -> Self { Self { dev_no, ninodes, @@ -91,11 +91,11 @@ impl Imap { let buf = self.get_imap_block(i, ctx); let imap_block: &DImapBlock = buf.data().into(); for j in 0..NENTRY { - let inum = i * NENTRY + j; + let inum = (i * NENTRY + j) as u32; // inum: (0, ninodes) if inum != 0 && inum < self.ninodes && imap_block.entry[j] == 0 { buf.free(ctx); - return Some(inum as u32); + return Some(inum); } } buf.free(ctx); @@ -153,15 +153,10 @@ impl Imap { seg: &mut SegManager, ctx: &KernelCtx<'_, '_>, ) -> bool { - assert!( - 0 < inum && inum < ctx.kernel().fs().superblock().ninodes(), - "invalid inum" - ); + assert!(0 < inum && inum < self.ninodes, "invalid inum"); let (block_no, offset) = self.get_imap_block_no(inum); if let Some(mut buf) = self.update(block_no as u32, seg, ctx) { // Update entry. - const_assert!(mem::size_of::() <= mem::size_of::()); - const_assert!(mem::align_of::() % mem::align_of::() == 0); let imap_block: &mut DImapBlock = buf.data_mut().into(); imap_block.entry[offset] = disk_block_no; buf.free(ctx); diff --git a/kernel-rs/src/fs/lfs/inode.rs b/kernel-rs/src/fs/lfs/inode.rs index 4645655a..0f9c2d27 100644 --- a/kernel-rs/src/fs/lfs/inode.rs +++ b/kernel-rs/src/fs/lfs/inode.rs @@ -10,7 +10,7 @@ use crate::{ fs::{DInodeType, Inode, InodeGuard, InodeType, Itable, RcInode, Tx}, hal::hal, lock::SleepLock, - param::{BSIZE, NINODE, ROOTDEV}, + param::{NINODE, ROOTDEV}, proc::KernelCtx, util::{memset, strong_pin::StrongPin}, }; @@ -70,7 +70,7 @@ impl<'s> TryFrom<&'s BufData> for &'s Dinode { type Error = &'static str; fn try_from(b: &'s BufData) -> Result<&Dinode, &'static str> { - const_assert!(mem::size_of::() <= BSIZE); + const_assert!(mem::size_of::() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::() == 0); // Disk content uses intel byte order. @@ -86,7 +86,7 @@ impl<'s> TryFrom<&'s BufData> for &'s Dinode { impl<'s> From<&'s BufData> for &'s [u32; NINDIRECT] { fn from(b: &'s BufData) -> Self { - const_assert!(mem::size_of::<[u32; NINDIRECT]>() <= BSIZE); + const_assert!(mem::size_of::<[u32; NINDIRECT]>() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::<[u32; NINDIRECT]>() == 0); unsafe { &*(b.as_ptr() as *const [u32; NINDIRECT]) } } @@ -94,7 +94,7 @@ impl<'s> From<&'s BufData> for &'s [u32; NINDIRECT] { impl<'s> From<&'s mut BufData> for &'s mut [u32; NINDIRECT] { fn from(b: &'s mut BufData) -> Self { - const_assert!(mem::size_of::<[u32; NINDIRECT]>() <= BSIZE); + const_assert!(mem::size_of::<[u32; NINDIRECT]>() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::<[u32; NINDIRECT]>() == 0); unsafe { &mut *(b.as_mut_ptr() as *mut [u32; NINDIRECT]) } } @@ -228,7 +228,7 @@ impl InodeGuard<'_, Lfs> { /// Copy a modified in-memory inode to disk. pub fn update(&self, tx: &Tx<'_, Lfs>, ctx: &KernelCtx<'_, '_>) { // 1. Write the inode to segment. - let mut seg = tx.fs.segmanager(ctx); + let mut seg = tx.segmanager(ctx); let (mut bp, disk_block_no) = seg.get_or_add_updated_inode_block(self.inum, ctx).unwrap(); const_assert!(mem::size_of::() <= mem::size_of::()); @@ -278,7 +278,7 @@ impl InodeGuard<'_, Lfs> { } // 2. Write the imap to segment. - let mut imap = tx.fs.imap(ctx); + let mut imap = tx.imap(ctx); assert!(imap.set(self.inum, disk_block_no, &mut seg, ctx)); if seg.is_full() { seg.commit(true, ctx); @@ -513,8 +513,8 @@ impl Itable { tx: &Tx<'_, Lfs>, ctx: &KernelCtx<'_, '_>, ) -> RcInode { - let mut seg = tx.fs.segmanager(ctx); - let mut imap = tx.fs.imap(ctx); + let mut seg = tx.segmanager(ctx); + let mut imap = tx.imap(ctx); // 1. Write the inode. let inum = imap.get_empty_inum(ctx).unwrap(); diff --git a/kernel-rs/src/fs/lfs/lfs.rs b/kernel-rs/src/fs/lfs/lfs.rs new file mode 100644 index 00000000..c31dd3fa --- /dev/null +++ b/kernel-rs/src/fs/lfs/lfs.rs @@ -0,0 +1,260 @@ +//! The `Lfs` struct. Also includes the `Checkpoint`, `SegManagerReadOnlyGuard`, and `ImapReadOnlyGuard` type. +#![allow(clippy::module_inception)] + +use core::mem; +use core::ops::Deref; + +use pin_project::pin_project; +use spin::Once; +use static_assertions::const_assert; + +use super::{Imap, Itable, SegManager, SegTable, Superblock, Tx, TxManager}; +use crate::{ + bio::BufData, + hal::hal, + lock::{SleepLock, SleepLockGuard, SleepableLock}, + param::IMAPSIZE, + proc::KernelCtx, + util::strong_pin::StrongPin, +}; + +#[pin_project] +pub struct Lfs { + /// Initializing superblock should run only once because forkret() calls FileSystem::init(). + /// There should be one superblock per disk device, but we run with only one device. + superblock: Once, + + /// In-memory inodes. + #[pin] + itable: Itable, + + /// The segment manager. + segmanager: Once>, + + /// Imap. + imap: Once>, + + tx_manager: Once>, +} + +/// On-disk checkpoint structure. +#[repr(C)] +pub struct Checkpoint { + imap: [u32; IMAPSIZE], + segtable: SegTable, + timestamp: u32, +} + +impl<'s> From<&'s BufData> for &'s Checkpoint { + fn from(b: &'s BufData) -> Self { + const_assert!(mem::size_of::() <= mem::size_of::()); + const_assert!(mem::align_of::() % mem::align_of::() == 0); + unsafe { &*(b.as_ptr() as *const Checkpoint) } + } +} + +/// A read-only guard of a `SegManager`. +/// Must be `free`d when done using it. +pub struct SegManagerReadOnlyGuard<'s>(SleepLockGuard<'s, SegManager>); + +impl SegManagerReadOnlyGuard<'_> { + pub fn free(self, ctx: &KernelCtx<'_, '_>) { + self.0.free(ctx); + } +} + +impl Deref for SegManagerReadOnlyGuard<'_> { + type Target = SegManager; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A read-only guard of a `SegManager`. +/// Must be `free`d when done using it. +pub struct ImapReadOnlyGuard<'s>(SleepLockGuard<'s, Imap>); + +impl ImapReadOnlyGuard<'_> { + pub fn free(self, ctx: &KernelCtx<'_, '_>) { + self.0.free(ctx); + } +} + +impl Deref for ImapReadOnlyGuard<'_> { + type Target = Imap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Tx<'_, Lfs> { + /// Acquires the lock on the `SegManager` and returns the lock guard. + /// Use this to write blocks to the segment. + /// Note that you must `free` the guard when done using it. + pub fn segmanager(&self, ctx: &KernelCtx<'_, '_>) -> SleepLockGuard<'_, SegManager> { + self.fs.segmanager.get().expect("segmanager").lock(ctx) + } + + /// Acquires the lock on the `Imap` and returns the lock guard. + /// Use this to mutate the `Imap`. + /// Note that you must `free` the guard when done using it. + pub fn imap(&self, ctx: &KernelCtx<'_, '_>) -> SleepLockGuard<'_, Imap> { + self.fs.imap.get().expect("imap").lock(ctx) + } +} + +impl Lfs { + pub const fn new() -> Self { + Self { + superblock: Once::new(), + itable: Itable::::new_itable(), + segmanager: Once::new(), + imap: Once::new(), + tx_manager: Once::new(), + } + } + + /// Returns a reference to the `Superblock`. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn superblock(&self) -> &Superblock { + self.superblock.get().expect("superblock") + } + + #[allow(clippy::needless_lifetimes)] + /// Returns a reference to the `Itable`. + pub fn itable<'s>(self: StrongPin<'s, Self>) -> StrongPin<'s, Itable> { + unsafe { StrongPin::new_unchecked(&self.as_pin().get_ref().itable) } + } + + /// Acquires the lock on the `SegManager` and returns a read-only guard. + /// Note that you must `free` the guard when done using it. + /// + /// # Note + /// + /// If you need a writable lock guard, use `Tx::segmanager` instead. + /// Note that this means you must have started a transaction. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn segmanager(&self, ctx: &KernelCtx<'_, '_>) -> SegManagerReadOnlyGuard<'_> { + SegManagerReadOnlyGuard(self.segmanager.get().expect("segmanager").lock(ctx)) + } + + /// Returns a raw pointer to the `SegManager`. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn segmanager_raw(&self) -> *mut SegManager { + self.segmanager.get().expect("segmanager").get_mut_raw() + } + + /// Acquires the lock on the `Imap` and returns a read-only guard. + /// Note that you must `free` the guard when done using it. + /// + /// # Note + /// + /// If you need a writable lock guard, use `Tx::imap` instead. + /// Note that this means you must have started a transaction. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn imap(&self, ctx: &KernelCtx<'_, '_>) -> ImapReadOnlyGuard<'_> { + ImapReadOnlyGuard(self.imap.get().expect("imap").lock(ctx)) + } + + /// Returns a raw pointer to the `Imap`. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn imap_raw(&self) -> *mut Imap { + self.imap.get().expect("imap").get_mut_raw() + } + + /// Acquires the lock on the `TxManager` and returns a lock guard. + /// + /// # Panic + /// + /// Panics if `self` is not initialized. + pub fn tx_manager(&self) -> &SleepableLock { + self.tx_manager.get().expect("tx_manager") + } + + /// Initializes `self`. + /// Does nothing if already initialized. + pub fn initialize(&self, dev: u32, ctx: &KernelCtx<'_, '_>) { + if !self.superblock.is_completed() { + // Load the superblock. + let buf = hal().disk().read(dev, 1, ctx); + let superblock = self.superblock.call_once(|| Superblock::new(&buf)); + buf.free(ctx); + + // Load the checkpoint. + let (bno1, bno2) = superblock.get_chkpt_block_no(); + let buf1 = hal().disk().read(dev, bno1, ctx); + let chkpt1: &Checkpoint = buf1.data().into(); + let buf2 = hal().disk().read(dev, bno2, ctx); + let chkpt2: &Checkpoint = buf2.data().into(); + + let (chkpt, timestamp, stored_at_first) = if chkpt1.timestamp > chkpt2.timestamp { + (chkpt1, chkpt1.timestamp, true) + } else { + (chkpt2, chkpt2.timestamp, false) + }; + + let segtable = chkpt.segtable; + let imap = chkpt.imap; + // let timestamp = chkpt.timestamp; + buf1.free(ctx); + buf2.free(ctx); + + // Load other components using the checkpoint content. + let _ = self.segmanager.call_once(|| { + SleepLock::new( + "segment", + SegManager::new(dev, segtable, superblock.nsegments()), + ) + }); + let _ = self + .imap + .call_once(|| SleepLock::new("imap", Imap::new(dev, superblock.ninodes(), imap))); + let _ = self.tx_manager.call_once(|| { + SleepableLock::new( + "tx_manager", + TxManager::new(dev, stored_at_first, timestamp), + ) + }); + } + } + + /// Commits the checkpoint at the checkpoint region. + /// If `first` is `true`, writes it at the first checkpoint region. Otherwise, writes at the second region. + pub fn commit_checkpoint( + &self, + first: bool, + timestamp: u32, + seg: &SegManager, + imap: &Imap, + dev: u32, + ctx: &KernelCtx<'_, '_>, + ) { + let (bno1, bno2) = self.superblock().get_chkpt_block_no(); + let block_no = if first { bno1 } else { bno2 }; + + let mut buf = ctx.kernel().bcache().get_buf_and_clear(dev, block_no, ctx); + let chkpt = unsafe { &mut *(buf.data_mut().as_ptr() as *mut Checkpoint) }; + chkpt.segtable = seg.dsegtable(); + chkpt.imap = imap.dimap(); + chkpt.timestamp = timestamp; + hal().disk().write(&mut buf, ctx); + buf.free(ctx); + } +} diff --git a/kernel-rs/src/fs/lfs/mod.rs b/kernel-rs/src/fs/lfs/mod.rs index bf7d7a8b..72ebd16a 100644 --- a/kernel-rs/src/fs/lfs/mod.rs +++ b/kernel-rs/src/fs/lfs/mod.rs @@ -2,33 +2,29 @@ use core::cell::UnsafeCell; use core::mem; use core::ops::Deref; -use pin_project::pin_project; -use spin::Once; -use static_assertions::const_assert; - use super::{ DInodeType, FcntlFlags, FileName, FileSystem, Inode, InodeGuard, InodeType, Itable, Path, RcInode, Stat, Tx, }; -use crate::util::strong_pin::StrongPin; use crate::{ - bio::BufData, file::{FileType, InodeFileType}, hal::hal, - lock::{SleepLock, SleepLockGuard, SleepableLock}, - param::{BSIZE, IMAPSIZE}, + param::BSIZE, proc::KernelCtx, + util::strong_pin::StrongPin, }; mod cleaner; mod imap; mod inode; +mod lfs; mod segment; mod superblock; mod tx; use imap::Imap; use inode::{Dinode, Dirent, InodeInner}; +pub use lfs::Lfs; use segment::{SegManager, SegTable}; use superblock::Superblock; use tx::TxManager; @@ -40,152 +36,12 @@ const NDIRECT: usize = 12; const NINDIRECT: usize = BSIZE.wrapping_div(mem::size_of::()); const MAXFILE: usize = NDIRECT.wrapping_add(NINDIRECT); -#[pin_project] -pub struct Lfs { - /// Initializing superblock should run only once because forkret() calls FileSystem::init(). - /// There should be one superblock per disk device, but we run with only one device. - superblock: Once, - - /// In-memory inodes. - #[pin] - itable: Itable, - - /// The segment manager. - segmanager: Once>, - - /// Imap. - imap: Once>, - - tx_manager: Once>, -} - -/// On-disk checkpoint structure. -#[repr(C)] -pub struct Checkpoint { - imap: [u32; IMAPSIZE], - segtable: SegTable, - timestamp: u32, -} - -impl<'s> From<&'s BufData> for &'s Checkpoint { - fn from(b: &'s BufData) -> Self { - const_assert!(mem::size_of::() <= BSIZE); - const_assert!(mem::align_of::() % mem::align_of::() == 0); - unsafe { &*(b.as_ptr() as *const Checkpoint) } - } -} - -impl Lfs { - pub const fn new() -> Self { - Self { - superblock: Once::new(), - itable: Itable::::new_itable(), - segmanager: Once::new(), - imap: Once::new(), - tx_manager: Once::new(), - } - } - - fn superblock(&self) -> &Superblock { - self.superblock.get().expect("superblock") - } - - #[allow(clippy::needless_lifetimes)] - fn itable<'s>(self: StrongPin<'s, Self>) -> StrongPin<'s, Itable> { - unsafe { StrongPin::new_unchecked(&self.as_pin().get_ref().itable) } - } - - pub fn segmanager(&self, ctx: &KernelCtx<'_, '_>) -> SleepLockGuard<'_, SegManager> { - self.segmanager.get().expect("segmanager").lock(ctx) - } - - pub fn segmanager_raw(&self) -> *mut SegManager { - self.segmanager.get().expect("segmanager").get_mut_raw() - } - - pub fn imap(&self, ctx: &KernelCtx<'_, '_>) -> SleepLockGuard<'_, Imap> { - self.imap.get().expect("imap").lock(ctx) - } - - pub fn imap_raw(&self) -> *mut Imap { - self.imap.get().expect("imap").get_mut_raw() - } - - fn tx_manager(&self) -> &SleepableLock { - self.tx_manager.get().expect("tx_manager") - } - - /// Commits the checkpoint at the checkpoint region. - /// If `first` is `true`, writes it at the first checkpoint region. Otherwise, writes at the second region. - pub fn commit_checkpoint( - &self, - first: bool, - timestamp: u32, - seg: &SegManager, - imap: &Imap, - dev: u32, - ctx: &KernelCtx<'_, '_>, - ) { - let (bno1, bno2) = self.superblock().get_chkpt_block_no(); - let block_no = if first { bno1 } else { bno2 }; - - let mut buf = ctx.kernel().bcache().get_buf_and_clear(dev, block_no, ctx); - let chkpt = unsafe { &mut *(buf.data_mut().as_ptr() as *mut Checkpoint) }; - chkpt.segtable = seg.dsegtable(); - chkpt.imap = imap.dimap(); - chkpt.timestamp = timestamp; - hal().disk().write(&mut buf, ctx); - buf.free(ctx); - } -} - impl FileSystem for Lfs { type Dirent = Dirent; type InodeInner = InodeInner; fn init(&self, dev: u32, ctx: &KernelCtx<'_, '_>) { - if !self.superblock.is_completed() { - // Load the superblock. - let buf = hal().disk().read(dev, 1, ctx); - let superblock = self.superblock.call_once(|| Superblock::new(&buf)); - buf.free(ctx); - - // Load the checkpoint. - let (bno1, bno2) = superblock.get_chkpt_block_no(); - let buf1 = hal().disk().read(dev, bno1, ctx); - let chkpt1: &Checkpoint = buf1.data().into(); - let buf2 = hal().disk().read(dev, bno2, ctx); - let chkpt2: &Checkpoint = buf2.data().into(); - - let (chkpt, timestamp, stored_at_first) = if chkpt1.timestamp > chkpt2.timestamp { - (chkpt1, chkpt1.timestamp, true) - } else { - (chkpt2, chkpt2.timestamp, false) - }; - - let segtable = chkpt.segtable; - let imap = chkpt.imap; - // let timestamp = chkpt.timestamp; - buf1.free(ctx); - buf2.free(ctx); - - // Load other components using the checkpoint content. - let _ = self.segmanager.call_once(|| { - SleepLock::new( - "segment", - SegManager::new(dev, segtable, superblock.nsegments()), - ) - }); - let _ = self.imap.call_once(|| { - SleepLock::new("imap", Imap::new(dev, superblock.ninodes() as usize, imap)) - }); - let _ = self.tx_manager.call_once(|| { - SleepableLock::new( - "tx_manager", - TxManager::new(dev, stored_at_first, timestamp), - ) - }); - } + self.initialize(dev, ctx); } fn root(self: StrongPin<'_, Self>) -> RcInode { @@ -477,7 +333,7 @@ impl FileSystem for Lfs { } let mut tot: u32 = 0; while tot < n { - let mut seg = tx.fs.segmanager(&k); + let mut seg = tx.segmanager(&k); let mut bp = guard.writable_data_block(off as usize / BSIZE, &mut seg, tx, &k); let m = core::cmp::min(n - tot, BSIZE as u32 - off % BSIZE as u32); let begin = (off % BSIZE as u32) as usize; @@ -559,8 +415,8 @@ impl FileSystem for Lfs { // so this acquiresleep() won't block (or deadlock). let mut ip = inode.lock(ctx); - let mut seg = tx.fs.segmanager(ctx); - let mut imap = tx.fs.imap(ctx); + let mut seg = tx.segmanager(ctx); + let mut imap = tx.imap(ctx); assert!(imap.set(ip.inum, 0, &mut seg, ctx)); if seg.is_full() { seg.commit(true, ctx); diff --git a/kernel-rs/src/fs/lfs/segment.rs b/kernel-rs/src/fs/lfs/segment.rs index 7cd7f951..26049231 100644 --- a/kernel-rs/src/fs/lfs/segment.rs +++ b/kernel-rs/src/fs/lfs/segment.rs @@ -152,7 +152,7 @@ impl<'s> TryFrom<&'s BufData> for &'s DSegSum { type Error = &'static str; fn try_from(b: &'s BufData) -> Result { - const_assert!(mem::size_of::() <= BSIZE); + const_assert!(mem::size_of::() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::() == 0); // Disk content uses intel byte order. diff --git a/kernel-rs/src/fs/lfs/superblock.rs b/kernel-rs/src/fs/lfs/superblock.rs index 22195fa7..165fe16d 100644 --- a/kernel-rs/src/fs/lfs/superblock.rs +++ b/kernel-rs/src/fs/lfs/superblock.rs @@ -4,7 +4,7 @@ use static_assertions::const_assert; use crate::{ bio::{Buf, BufData}, - param::{BSIZE, SEGSIZE}, + param::SEGSIZE, }; const FSMAGIC: u32 = 0x10203040; @@ -47,7 +47,7 @@ impl<'s> TryFrom<&'s BufData> for &'s Superblock { type Error = &'static str; fn try_from(b: &'s BufData) -> Result { - const_assert!(mem::size_of::() <= BSIZE); + const_assert!(mem::size_of::() <= mem::size_of::()); const_assert!(mem::align_of::() % mem::align_of::() == 0); // Disk content uses intel byte order. diff --git a/kernel-rs/src/fs/lfs/tx.rs b/kernel-rs/src/fs/lfs/tx.rs index 7e992853..ff2cf1b9 100644 --- a/kernel-rs/src/fs/lfs/tx.rs +++ b/kernel-rs/src/fs/lfs/tx.rs @@ -80,7 +80,7 @@ impl SleepableLock { seg.free(ctx); if guard.committing || - // This op might exhause the `Bcache`; wait for the outstanding sys calls to be done. + // This op might exhaust the `Bcache`; wait for the outstanding sys calls to be done. (guard.outstanding + 1) * MAXOPBLOCKS as i32 > NBUF as i32 || // This op might exhaust segments; wait for cleaner. (guard.outstanding as u32 + 1) * MAXOPBLOCKS as u32 + MIN_REQUIRED_BLOCKS as u32 > remaining + nfree * (SEGSIZE as u32 - 1)