asterinas icon indicating copy to clipboard operation
asterinas copied to clipboard

[BUG] Resizing vmo of page cache hangs

Open skpupil opened this issue 10 months ago • 1 comments

When I run the test 437.leslie3d from SPEC CPU 2006 within the Asterinas kernel, the program fails to terminate. However, when I execute the same program within the Linux kernel, it completes in just 3 seconds. Using the perf tool to examine CPU function execution, I found that out of 1000 samples, over 600 were of the same function. Hence, this indicates a bug. The command: ./leslie3d_base.amd64-m64-gcc41-nn < leslie3d.in perf tool conclusion:

    659 at,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,,aster_nix::fs::utils::dentry::Dentry::resize,out>,
     70 at,core::ptr::drop_in_place<aster_frame::task::processor::DisablePreemptGuard>,at,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,
     69 at,at,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,,aster_nix::fs::utils::dentry::Dentry::resize
     60 dst=<optimized,at,at,alloc::alloc::Global>>,,at,at,__from_kernel,??,??,??
     58 at,aster_frame::sync::rwlock::RwLock<aster_nix::fs::ramfs::fs::Inode_>::try_read<aster_nix::fs::ramfs::fs::Inode_>,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,,aster_nix::fs::utils::dentry::Dentry::resize
     48 core::ptr::drop_in_place<core::option::Option<aster_frame::sync::rwlock::RwLockReadGuard<aster_nix::fs::ramfs::fs::Inode_>>>,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,,aster_nix::fs::utils::dentry::Dentry::resize,out>,
     13 out>),at,out>),at,core::ptr::drop_in_place<aster_frame::task::processor::DisablePreemptGuard>,at,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages
      8 out>),at,at,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,
      5 at,aster_frame::arch::x86::timer::timer_callback,alloc::boxed::{impl#49}::call<(&trapframe::arch::trap::TrapFrame),,out>,,at,__from_kernel,??,??,??,??
      4 out>),at,out>),at,at,aster_frame::sync::rwlock::RwLock<aster_nix::fs::ramfs::fs::Inode_>::try_read<aster_nix::fs::ramfs::fs::Inode_>,at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages
      4 at,at,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page,aster_nix::vm::vmo::Vmo_::decommit_pages,aster_nix::vm::vmo::Vmo_::resize,aster_nix::vm::vmo::Vmo<aster_rights::TRightSet<typeflags_util::set::Cons<aster_rights::Signal,,out>,,aster_nix::fs::utils::dentry::Dentry::resize,out>,,out>,
      2 vector33,core::core_arch::x86::sse2::_mm_pause,at,at,at,??,??,??,??,aster_nix::fs::utils::page_cache::{impl#5}::decommit_page

skpupil avatar Apr 18 '24 07:04 skpupil

I've tracked the call process where 437.leslie3d hangs, and it actually results in a deadlock, as shown below: Initially, a read lock is obtained in RamInode::resize() method and is then upgraded to a write lock in order to modify the inode's metadata.

// kernel/aster-nix/src/fs/ramfs/fs.rs
impl Inode for RamInode {
    fn resize(&self, new_size: usize) -> Result<()> {
        ...
        let self_inode = self.0.upread();
       ...
        let mut self_inode = self_inode.upgrade();
       ...
        let page_cache = self_inode.inner.as_file().unwrap();
        page_cache.pages().resize(new_size)?;
       ...
    }
}

This also involves calling the resize method of page_cache to synchronize the pages, ultimately leading to the Vmo_::resize method.

// kernel/aster-nix/src/vm/vmo/mod.rs
impl Vmo_{
    pub fn resize(&self, new_size: usize) -> Result<()> {
        ...
        if new_size < old_size {
            self.decommit_pages(&mut lock.0, new_size..old_size)?;
        }
        ...
    }
}

When the file size decreases, the PageCacheManager::decommit_pages() in is eventually called.

// kernel/aster-nix/src/fs/utils/page_cache.rs
impl Pager for PageCacheManager {
    fn decommit_page(&self, idx: usize) -> Result<()> {
        ...
        if idx < backend.npages() {
        ...
    }
}

Here, an attempt is made to obtain the npages of PageCacheBackend. In fact, this PageCacheBackend trait is still implemented by RamInode.

// kernel/aster-nix/src/fs/ramfs/fs.rs
impl Inode for RamInode {
    fn size(&self) -> usize {
        self.0.read().metadata.size
    }
}

At this point, the deadlock becomes quite clear. This deadlock call chain has taken a very long detour, so it's still necessary to use real-world benchmarks to test our system.

My solution is to release the write lock and regain the read lock after modifying the metadata of inode. However, 437.leslie3d runs very slowly compared with Linux. My test is about 25s.

LclclcdIsACat avatar Apr 18 '24 15:04 LclclcdIsACat

FIXED. thx

skpupil avatar May 16 '24 07:05 skpupil