From 1a2de4ac6c96cf33bd7fda29af9fc53590acb99b Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 10:44:08 +0800 Subject: [PATCH 01/28] fs: fix inline ext4 extent lookup --- third_party/ext4_rs/src/ext4_defs/extents.rs | 25 +++++++++++-------- third_party/ext4_rs/src/ext4_impls/extents.rs | 7 +++++- third_party/ext4_rs/src/ext4_impls/inode.rs | 16 +++++++++--- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/third_party/ext4_rs/src/ext4_defs/extents.rs b/third_party/ext4_rs/src/ext4_defs/extents.rs index b8c9662f..8def1ddb 100644 --- a/third_party/ext4_rs/src/ext4_defs/extents.rs +++ b/third_party/ext4_rs/src/ext4_defs/extents.rs @@ -281,18 +281,8 @@ impl ExtentNode { impl ExtentNode { /// Binary search for the extent that contains the given block. pub fn binsearch_extent(&mut self, lblock: Ext4Lblk) -> Option<(Ext4Extent, usize)> { - // empty node if self.header.entries_count == 0 { - match &self.data { - NodeData::Root(root_data) => { - let extent = Ext4Extent::load_from_u32(&root_data[3..]); - return Some((extent, 0)); - } - NodeData::Internal(internal_data) => { - let extent = Ext4Extent::load_from_u8(&internal_data[12..]); - return Some((extent, 0)); - } - } + return None; } match &mut self.data { @@ -474,6 +464,19 @@ impl Ext4Extent { lo | hi } + /// Returns true if this extent covers the given logical block. + pub fn contains_lblock(&self, lblock: Ext4Lblk) -> bool { + let len = self.get_actual_len() as u64; + if len == 0 { + return false; + } + + let start = self.first_block as u64; + let end = start + len; + let lblock = lblock as u64; + start <= lblock && lblock < end + } + /// Stores the physical block number to which this extent points. pub fn store_pblock(&mut self, pblock: u64) { self.start_lo = (pblock & 0xffffffff) as u32; diff --git a/third_party/ext4_rs/src/ext4_impls/extents.rs b/third_party/ext4_rs/src/ext4_impls/extents.rs index d2f61828..eb5a4588 100644 --- a/third_party/ext4_rs/src/ext4_impls/extents.rs +++ b/third_party/ext4_rs/src/ext4_impls/extents.rs @@ -59,12 +59,17 @@ impl Ext4 { // Handle the case where depth is 0 if let Some((extent, pos)) = node.binsearch_extent(lblock) { + let pblock = if extent.contains_lblock(lblock) { + lblock as u64 - extent.get_first_block() as u64 + extent.get_pblock() + } else { + 0 + }; search_path.path.push(ExtentPathNode { header: node.header, index: None, extent: Some(extent), position: pos, - pblock: lblock as u64 - extent.get_first_block() as u64 + extent.get_pblock(), + pblock, pblock_of_node, }); search_path.maxdepth = node.header.depth; diff --git a/third_party/ext4_rs/src/ext4_impls/inode.rs b/third_party/ext4_rs/src/ext4_impls/inode.rs index c25231c3..6f086b04 100644 --- a/third_party/ext4_rs/src/ext4_impls/inode.rs +++ b/third_party/ext4_rs/src/ext4_impls/inode.rs @@ -77,6 +77,12 @@ impl Ext4 { if let Ok(path) = search_path { // get the last path let path = path.path.last().unwrap(); + let Some(extent) = path.extent else { + return_errno_with_message!(Errno::ENOENT, "extent not found"); + }; + if !extent.contains_lblock(lblock) { + return_errno_with_message!(Errno::ENOENT, "logical block is not mapped"); + } // get physical block id let fblock = path.pblock; @@ -423,9 +429,14 @@ impl Ext4 { return return_errno_with_message!(Errno::ENOENT, "No extents found"); } - let mut current_header = root_header; - let mut current_block = inode_ref.inode.root_extent_block(); let mut depth = root_header.depth; + if depth == 0 { + let last_pos = root_header.entries_count as usize - 1; + let mut inode = inode_ref.inode; + return Ok(inode.root_extent_at(last_pos)); + } + + let mut current_block = inode_ref.inode.root_extent_block(); while depth > 0 { let index_block = Block::load( @@ -443,7 +454,6 @@ impl Ext4 { + (index_header.entries_count - 1) as usize * EXT4_EXTENT_INDEX_SIZE..], ); current_block = last_idx.leaf_lo as u64 | ((last_idx.leaf_hi as u64) << 32); - current_header = index_header; depth -= 1; } From 5dee549d4f30c5292b7850c69debea1cadb682d4 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 10:52:35 +0800 Subject: [PATCH 02/28] fs: harden ext4 write allocation --- third_party/ext4_rs/src/ext4_impls/file.rs | 76 ++++++++++----------- third_party/ext4_rs/src/ext4_impls/inode.rs | 14 ++-- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/third_party/ext4_rs/src/ext4_impls/file.rs b/third_party/ext4_rs/src/ext4_impls/file.rs index 74f4b4d2..c7015f91 100644 --- a/third_party/ext4_rs/src/ext4_impls/file.rs +++ b/third_party/ext4_rs/src/ext4_impls/file.rs @@ -2,7 +2,6 @@ use crate::ext4_defs::*; use crate::prelude::*; use crate::return_errno_with_message; use crate::utils::path_check; -use core::cmp::max; // use std::time::{Duration, Instant}; impl Ext4 { @@ -206,21 +205,13 @@ impl Ext4 { let adjust_read_size = min(BLOCK_SIZE - unaligned_start_offset, read_buf_len); // get iblock physical block id - let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) { - Ok(idx) => idx, - Err(e) => { - return_errno_with_message!( - Errno::EIO, - "Failed to get physical block for logical block" - ); - } + let data = match self.get_pblock_idx(&inode_ref, iblock as u32) { + Ok(pblock_idx) => self + .block_device + .read_offset(pblock_idx as usize * BLOCK_SIZE), + Err(_) => vec![0u8; BLOCK_SIZE], }; - // read data - let data = self - .block_device - .read_offset(pblock_idx as usize * BLOCK_SIZE); - // copy data to read buffer read_buf[cursor..cursor + adjust_read_size].copy_from_slice( &data[unaligned_start_offset..unaligned_start_offset + adjust_read_size], @@ -247,20 +238,12 @@ impl Ext4 { } // get iblock physical block id - let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) { - Ok(idx) => idx, - Err(e) => { - return_errno_with_message!( - Errno::EIO, - "Failed to get physical block for logical block" - ); - } + let data = match self.get_pblock_idx(&inode_ref, iblock as u32) { + Ok(pblock_idx) => self + .block_device + .read_offset(pblock_idx as usize * BLOCK_SIZE), + Err(_) => vec![0u8; BLOCK_SIZE], }; - - // read data - let data = self - .block_device - .read_offset(pblock_idx as usize * BLOCK_SIZE); // log::trace!("[Read] Read block data - physical_block: {}, data_len: {}", pblock_idx, data.len()); // copy data to read buffer @@ -307,7 +290,11 @@ impl Ext4 { // Calculate the start and end block index let iblock_start = offset / BLOCK_SIZE; - let iblock_last = (offset + write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; + let write_end = match offset.checked_add(write_buf_len) { + Some(v) => v, + None => return return_errno_with_message!(Errno::EINVAL, "Write offset overflow"), + }; + let iblock_last = (write_end + BLOCK_SIZE - 1) / BLOCK_SIZE; let total_blocks_needed = iblock_last - iblock_start; // start block index @@ -328,19 +315,29 @@ impl Ext4 { // Start bgid for block allocation let mut start_bgid = 1; - // Pre-allocate blocks if needed - let blocks_to_allocate = if iblk_idx >= ifile_blocks as usize { - total_blocks_needed - } else { - max(0, total_blocks_needed - (ifile_blocks as usize - iblk_idx)) - }; + // Pre-allocate blocks up to the write end. This keeps extended regions + // zero-backed and avoids sparse holes that the current extent code + // cannot otherwise distinguish from missing mappings. + let existing_file_blocks = ifile_blocks as usize; + let blocks_to_allocate = iblock_last.saturating_sub(existing_file_blocks); if blocks_to_allocate > 0 { log::trace!("[Pre-allocation] Allocating {} blocks", blocks_to_allocate); // 使用append_inode_pblk_batch进行批量块分配 let allocated_blocks = - self.append_inode_pblk_batch(&mut inode_ref, &mut start_bgid, blocks_to_allocate)?; + self.append_inode_pblk_batch( + &mut inode_ref, + &mut start_bgid, + existing_file_blocks as u32, + blocks_to_allocate, + )?; + + let zero_block = vec![0u8; BLOCK_SIZE]; + for block in &allocated_blocks { + self.block_device + .write_offset(*block as usize * BLOCK_SIZE, &zero_block); + } // If we couldn't allocate all blocks, adjust the write size if allocated_blocks.len() < blocks_to_allocate { @@ -387,10 +384,10 @@ impl Ext4 { // Verify we have enough blocks for the write let required_blocks = (write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; - let available_blocks = if iblk_idx >= ifile_blocks as usize { + let available_blocks = if iblk_idx >= existing_file_blocks { new_blocks } else { - (ifile_blocks as usize - iblk_idx) + new_blocks + existing_file_blocks.saturating_sub(iblk_idx) + new_blocks }; if available_blocks < required_blocks { @@ -517,7 +514,10 @@ impl Ext4 { } // Update file size if necessary - let new_size = offset + written; + let new_size = match offset.checked_add(written) { + Some(v) => v, + None => return return_errno_with_message!(Errno::EINVAL, "File size overflow"), + }; if new_size > file_size as usize { log::trace!( "[Write] Updating file size from {} to {}", diff --git a/third_party/ext4_rs/src/ext4_impls/inode.rs b/third_party/ext4_rs/src/ext4_impls/inode.rs index 6f086b04..1968ed3b 100644 --- a/third_party/ext4_rs/src/ext4_impls/inode.rs +++ b/third_party/ext4_rs/src/ext4_impls/inode.rs @@ -250,10 +250,11 @@ impl Ext4 { &self, inode_ref: &mut Ext4InodeRef, start_bgid: &mut u32, + start_lblock: u32, block_count: usize, ) -> Result> { let inode_size = inode_ref.inode.size(); - let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + let iblock = start_lblock; // Use new optimized block allocation function let allocated_blocks = self.balloc_alloc_block_batch(inode_ref, start_bgid, block_count)?; @@ -412,12 +413,17 @@ impl Ext4 { } // Update inode size, ensuring it doesn't overflow - let new_size = match inode_size.checked_add((allocated_blocks.len() * BLOCK_SIZE) as u64) { + let new_size = match (iblock as u64) + .checked_add(allocated_blocks.len() as u64) + .and_then(|blocks| blocks.checked_mul(BLOCK_SIZE as u64)) + { Some(v) => v, None => return return_errno_with_message!(Errno::EINVAL, "File size overflow"), }; - inode_ref.inode.set_size(new_size); - self.write_back_inode(inode_ref); + if new_size > inode_size { + inode_ref.inode.set_size(new_size); + self.write_back_inode(inode_ref); + } Ok(allocated_blocks) } From 66a60e3e032e4a710b9070ae558a2645264ffe71 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 10:59:33 +0800 Subject: [PATCH 03/28] tests: cover ext4 extended writes --- os/src/fs/tests/ext4/ext4_io.rs | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/os/src/fs/tests/ext4/ext4_io.rs b/os/src/fs/tests/ext4/ext4_io.rs index fae39233..cb13d267 100644 --- a/os/src/fs/tests/ext4/ext4_io.rs +++ b/os/src/fs/tests/ext4/ext4_io.rs @@ -96,6 +96,78 @@ test_case!(test_ext4_multiple_writes, { kassert!(&buf[..] == b"AAABBBCCC"); }); +test_case!(test_ext4_chunked_write_across_blocks, { + const BLOCK_SIZE: usize = 4096; + const CHUNK_SIZE: usize = 1024; + const TOTAL_SIZE: usize = BLOCK_SIZE * 2 + CHUNK_SIZE; + + let fs = create_test_ext4(); + let inode = create_test_file(&fs, "chunked.bin").unwrap(); + let mut expected = vec![0u8; TOTAL_SIZE]; + + for i in 0..TOTAL_SIZE { + expected[i] = (i % 251) as u8; + } + + for offset in (0..TOTAL_SIZE).step_by(CHUNK_SIZE) { + let end = offset + CHUNK_SIZE; + let written = inode.write_at(offset, &expected[offset..end]).unwrap(); + kassert!(written == CHUNK_SIZE); + } + + let metadata = inode.metadata().unwrap(); + kassert!(metadata.size == TOTAL_SIZE); + + let mut actual = vec![0u8; TOTAL_SIZE]; + let read = inode.read_at(0, &mut actual).unwrap(); + kassert!(read == TOTAL_SIZE); + kassert!(actual == expected); +}); + +test_case!(test_ext4_write_beyond_eof_zero_fills_gap, { + const TAIL_OFFSET: usize = 8192; + let fs = create_test_ext4(); + let inode = create_test_file(&fs, "gap.bin").unwrap(); + + let written = inode.write_at(TAIL_OFFSET, b"tail").unwrap(); + kassert!(written == 4); + + let metadata = inode.metadata().unwrap(); + kassert!(metadata.size == TAIL_OFFSET + 4); + + let mut buf = vec![0xffu8; TAIL_OFFSET + 4]; + let read = inode.read_at(0, &mut buf).unwrap(); + kassert!(read == TAIL_OFFSET + 4); + + for byte in &buf[..TAIL_OFFSET] { + kassert!(*byte == 0); + } + kassert!(&buf[TAIL_OFFSET..] == b"tail"); +}); + +test_case!(test_ext4_overwrite_existing_multiblock_file, { + const TOTAL_SIZE: usize = 8192; + const OVERWRITE_OFFSET: usize = 3072; + let fs = create_test_ext4(); + let initial = vec![b'A'; TOTAL_SIZE]; + let inode = create_test_file_with_content(&fs, "overwrite.bin", &initial).unwrap(); + + let written = inode.write_at(OVERWRITE_OFFSET, b"BBBBBBBB").unwrap(); + kassert!(written == 8); + + let mut actual = vec![0u8; TOTAL_SIZE]; + let read = inode.read_at(0, &mut actual).unwrap(); + kassert!(read == TOTAL_SIZE); + + for byte in &actual[..OVERWRITE_OFFSET] { + kassert!(*byte == b'A'); + } + kassert!(&actual[OVERWRITE_OFFSET..OVERWRITE_OFFSET + 8] == b"BBBBBBBB"); + for byte in &actual[OVERWRITE_OFFSET + 8..] { + kassert!(*byte == b'A'); + } +}); + test_case!(test_ext4_sync, { // 创建文件并写入 let fs = create_test_ext4(); From e53f0ce0d4d4d88b80dfddb15602443c346d6b82 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 11:34:51 +0800 Subject: [PATCH 04/28] syscall: avoid futex panic after address-space teardown --- os/src/kernel/syscall/task/futex_ops.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/os/src/kernel/syscall/task/futex_ops.rs b/os/src/kernel/syscall/task/futex_ops.rs index fe14f8d2..5368e4c1 100644 --- a/os/src/kernel/syscall/task/futex_ops.rs +++ b/os/src/kernel/syscall/task/futex_ops.rs @@ -28,12 +28,9 @@ pub fn futex( FUTEX_WAIT => { // 必须保证获取锁 → 定位等待队列 → 读取用户数据 → 比较 → 入队/释放锁 的序列是原子的 let task = current_task(); - let memory_space = task - .lock() - .memory_space - .as_ref() - .expect("futex: current task has no memory space.") - .clone(); + let Some(memory_space) = task.lock().memory_space.clone() else { + return -EFAULT; + }; let paddr = if let Some(paddr) = memory_space .lock() .translate(VA::from_usize(uaddr as usize)) @@ -106,12 +103,9 @@ pub fn futex( FUTEX_WAKE => { let mut wake_count = 0; let paddr = { - let memory_space = current_task() - .lock() - .memory_space - .as_ref() - .expect("futex: current task has no memory space.") - .clone(); + let Some(memory_space) = current_task().lock().memory_space.clone() else { + return -EFAULT; + }; if let Some(paddr) = memory_space .lock() .translate(VA::from_usize(uaddr as usize)) From 92bd179b3865ac91b6f0253f9321e1818927fedc Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 11:48:28 +0800 Subject: [PATCH 05/28] syscall: fix riscv clone tls argument order --- os/src/kernel/syscall/mod.rs | 4 ++-- os/src/kernel/syscall/task/clone_ops.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/os/src/kernel/syscall/mod.rs b/os/src/kernel/syscall/mod.rs index a7737c0f..26da85dc 100644 --- a/os/src/kernel/syscall/mod.rs +++ b/os/src/kernel/syscall/mod.rs @@ -261,8 +261,8 @@ impl_syscall!( c_ulong, // flags (a0) c_ulong, // stack (a1) *mut c_int, // parent_tid (a2) - *mut c_int, // child_tid (a3) - *mut c_void // tls (a4) + *mut c_void, // tls (a3) + *mut c_int // child_tid (a4) ) ); impl_syscall!( diff --git a/os/src/kernel/syscall/task/clone_ops.rs b/os/src/kernel/syscall/task/clone_ops.rs index 5098a94f..2db787d7 100644 --- a/os/src/kernel/syscall/task/clone_ops.rs +++ b/os/src/kernel/syscall/task/clone_ops.rs @@ -12,8 +12,8 @@ pub fn clone( flags: c_ulong, // a0: clone flags stack: c_ulong, // a1: child stack pointer ptid: *mut c_int, // a2: parent_tid pointer - ctid: *mut c_int, // a3: child_tid pointer - tls: *mut c_void, // a4: TLS pointer + tls: *mut c_void, // a3: TLS pointer + ctid: *mut c_int, // a4: child_tid pointer ) -> c_int { let requested_flags = if let Some(requested_flags) = CloneFlags::from_bits(flags as usize) { requested_flags From 795fb53aa45c119e53e2449feb4ee6f6e2efbca5 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 12:34:10 +0800 Subject: [PATCH 06/28] syscall: quiet non-tty terminal ioctls --- os/src/kernel/syscall/ioctl.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/os/src/kernel/syscall/ioctl.rs b/os/src/kernel/syscall/ioctl.rs index 1a8a4a04..0081f800 100644 --- a/os/src/kernel/syscall/ioctl.rs +++ b/os/src/kernel/syscall/ioctl.rs @@ -81,15 +81,7 @@ pub fn ioctl(fd: i32, request: u32, arg: usize) -> isize { TIOCGWINSZ | TIOCSWINSZ | TCGETS | TCSETS | TCSETSW | TCSETSF => { match file.ioctl(request, arg) { Ok(ret) => ret, - Err(FsError::NotSupported | FsError::NotTty) => { - pr_warn!( - "ioctl: fd={}, terminal request {:#x} ({}) not supported by file type", - fd, - request, - request - ); - -ENOTTY as isize - } + Err(FsError::NotSupported | FsError::NotTty) => -ENOTTY as isize, Err(e) => e.to_errno(), } } From e6e1bd853811ba67462f22f71a3af5146d900f87 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 12:39:52 +0800 Subject: [PATCH 07/28] syscall: ignore deprecated clone detached flag --- os/src/uapi/sched.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/os/src/uapi/sched.rs b/os/src/uapi/sched.rs index 116ef424..33108f3d 100644 --- a/os/src/uapi/sched.rs +++ b/os/src/uapi/sched.rs @@ -88,7 +88,8 @@ const CURRENTLY_SUPPORTED_FLAGS: CloneFlags = CloneFlags::from_bits_truncate( | CloneFlags::THREAD.bits() | CloneFlags::PARENT_SETTID.bits() | CloneFlags::CHILD_CLEARTID.bits() - | CloneFlags::CHILD_SETTID.bits(), + | CloneFlags::CHILD_SETTID.bits() + | CloneFlags::DETACHED.bits(), ); impl CloneFlags { From 548a33a60e690e0dcebd8bb9a337515da4d1ef3d Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 20:44:10 +0800 Subject: [PATCH 08/28] fs: cache ext4 regular file reads --- os/src/fs/ext4/inode.rs | 143 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 6 deletions(-) diff --git a/os/src/fs/ext4/inode.rs b/os/src/fs/ext4/inode.rs index 7b2881e8..68b145e0 100644 --- a/os/src/fs/ext4/inode.rs +++ b/os/src/fs/ext4/inode.rs @@ -8,6 +8,7 @@ use crate::sync::{Mutex, SpinLock}; use crate::uapi::time::TimeSpec; +use alloc::collections::BTreeMap; use alloc::string::String; use alloc::sync::{Arc, Weak}; use alloc::vec::Vec; @@ -19,6 +20,70 @@ use crate::vfs::dev::{ }; use crate::vfs::{Dentry, DirEntry, FileMode, FsError, Inode, InodeMetadata, InodeType}; +const READ_CACHE_PAGE_SIZE: usize = 4096; +const READ_CACHE_MAX_PAGES: usize = 128; + +struct CachedReadPage { + data: Vec, + age: u64, +} + +struct ReadCache { + pages: BTreeMap, + clock: u64, + generation: u64, +} + +impl ReadCache { + const fn new() -> Self { + Self { + pages: BTreeMap::new(), + clock: 0, + generation: 0, + } + } + + fn clear(&mut self) { + self.pages.clear(); + self.generation = self.generation.wrapping_add(1); + } + + fn generation(&self) -> u64 { + self.generation + } + + fn read(&mut self, page_index: usize, page_offset: usize, buf: &mut [u8]) -> Option { + self.clock = self.clock.wrapping_add(1); + let page = self.pages.get_mut(&page_index)?; + if page_offset >= page.data.len() { + return Some(0); + } + + page.age = self.clock; + let n = (page.data.len() - page_offset).min(buf.len()); + buf[..n].copy_from_slice(&page.data[page_offset..page_offset + n]); + Some(n) + } + + fn insert(&mut self, generation: u64, page_index: usize, data: Vec) { + if self.generation != generation || data.is_empty() { + return; + } + + self.clock = self.clock.wrapping_add(1); + if !self.pages.contains_key(&page_index) && self.pages.len() >= READ_CACHE_MAX_PAGES { + if let Some((&oldest_key, _)) = self.pages.iter().min_by_key(|(_, page)| page.age) { + self.pages.remove(&oldest_key); + } + } + + self.pages.insert(page_index, CachedReadPage { + data, + age: self.clock, + }); + } +} + /// Ext4 Inode 包装 pub struct Ext4Inode { /// ext4_rs 文件系统对象 @@ -30,6 +95,9 @@ pub struct Ext4Inode { /// 关联的 Dentry(弱引用,避免循环引用) /// 用于获取完整路径,而不是在 Inode 中重复存储 dentry: SpinLock>, + + /// Small per-inode page cache for repeated ELF/script/libc reads. + read_cache: SpinLock, } impl Ext4Inode { @@ -41,9 +109,14 @@ impl Ext4Inode { fs, ino, dentry: SpinLock::new(Weak::new()), + read_cache: SpinLock::new(ReadCache::new()), } } + fn invalidate_read_cache(&self) { + self.read_cache.lock().clear(); + } + #[cfg(test)] pub(crate) fn set_blocks_count_for_test(&self, blocks: u64) { let fs = self.fs.lock(); @@ -220,17 +293,71 @@ impl Inode for Ext4Inode { } fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + // Check if this is a directory let metadata = self.metadata()?; if metadata.inode_type == InodeType::Directory { return Err(FsError::IsDirectory); } - let fs = self.fs.lock(); + if offset >= metadata.size { + return Ok(0); + } - // ext4_rs 的 read_at 签名: pub fn read_at(&self, inode: u32, offset: usize, read_buf: &mut [u8]) - fs.read_at(self.ino, offset, buf) - .map_err(|_| FsError::IoError) + let target_len = buf.len().min(metadata.size - offset); + let mut copied = 0; + while copied < target_len { + let current_offset = offset + copied; + let page_index = current_offset / READ_CACHE_PAGE_SIZE; + let page_offset = current_offset % READ_CACHE_PAGE_SIZE; + let chunk_len = (READ_CACHE_PAGE_SIZE - page_offset).min(target_len - copied); + + if let Some(n) = self.read_cache.lock().read( + page_index, + page_offset, + &mut buf[copied..copied + chunk_len], + ) { + copied += n; + if n == 0 { + break; + } + continue; + } + + let generation = self.read_cache.lock().generation(); + let page_start = page_index * READ_CACHE_PAGE_SIZE; + let page_len = READ_CACHE_PAGE_SIZE.min(metadata.size - page_start); + let mut page_buf = alloc::vec![0u8; page_len]; + let nread = { + let fs = self.fs.lock(); + fs.read_at(self.ino, page_start, &mut page_buf) + .map_err(|_| FsError::IoError)? + }; + page_buf.truncate(nread); + + if page_offset >= page_buf.len() { + self.read_cache + .lock() + .insert(generation, page_index, page_buf); + break; + } + + let n = (page_buf.len() - page_offset).min(chunk_len); + buf[copied..copied + n].copy_from_slice(&page_buf[page_offset..page_offset + n]); + copied += n; + self.read_cache + .lock() + .insert(generation, page_index, page_buf); + + if n == 0 { + break; + } + } + + Ok(copied) } fn write_at(&self, offset: usize, buf: &[u8]) -> Result { @@ -243,8 +370,11 @@ impl Inode for Ext4Inode { let fs = self.fs.lock(); // ext4_rs 的 write_at 签名: pub fn write_at(&self, inode: u32, offset: usize, write_buf: &[u8]) - fs.write_at(self.ino, offset, buf) - .map_err(|_| FsError::IoError) + let written = fs + .write_at(self.ino, offset, buf) + .map_err(|_| FsError::IoError)?; + self.invalidate_read_cache(); + Ok(written) } fn lookup(&self, name: &str) -> Result, FsError> { @@ -745,6 +875,7 @@ impl Inode for Ext4Inode { } } + self.invalidate_read_cache(); Ok(()) } From d55bdaafd57d53511d20cc686f62c02c4d64d43c Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 20:57:33 +0800 Subject: [PATCH 09/28] fs: cache ext4 directory lookups --- os/src/fs/ext4/inode.rs | 150 ++++++++++++++++++++++++++------ os/src/fs/ext4/mod.rs | 6 +- os/src/kernel/syscall/fs/mod.rs | 11 ++- os/src/kernel/syscall/util.rs | 8 +- os/src/vfs/dentry.rs | 22 ++++- os/src/vfs/path.rs | 19 +++- 6 files changed, 175 insertions(+), 41 deletions(-) diff --git a/os/src/fs/ext4/inode.rs b/os/src/fs/ext4/inode.rs index 68b145e0..6d9f5b6c 100644 --- a/os/src/fs/ext4/inode.rs +++ b/os/src/fs/ext4/inode.rs @@ -21,7 +21,7 @@ use crate::vfs::dev::{ use crate::vfs::{Dentry, DirEntry, FileMode, FsError, Inode, InodeMetadata, InodeType}; const READ_CACHE_PAGE_SIZE: usize = 4096; -const READ_CACHE_MAX_PAGES: usize = 128; +const READ_CACHE_MAX_PAGES: usize = 512; struct CachedReadPage { data: Vec, @@ -29,9 +29,9 @@ struct CachedReadPage { } struct ReadCache { - pages: BTreeMap, + pages: BTreeMap<(u32, usize), CachedReadPage>, clock: u64, - generation: u64, + generations: BTreeMap, } impl ReadCache { @@ -39,22 +39,29 @@ impl ReadCache { Self { pages: BTreeMap::new(), clock: 0, - generation: 0, + generations: BTreeMap::new(), } } - fn clear(&mut self) { - self.pages.clear(); - self.generation = self.generation.wrapping_add(1); + fn clear_inode(&mut self, ino: u32) { + self.pages.retain(|(page_ino, _), _| *page_ino != ino); + let generation = self.generations.entry(ino).or_insert(0); + *generation = generation.wrapping_add(1); } - fn generation(&self) -> u64 { - self.generation + fn generation(&self, ino: u32) -> u64 { + self.generations.get(&ino).copied().unwrap_or(0) } - fn read(&mut self, page_index: usize, page_offset: usize, buf: &mut [u8]) -> Option { + fn read( + &mut self, + ino: u32, + page_index: usize, + page_offset: usize, + buf: &mut [u8], + ) -> Option { self.clock = self.clock.wrapping_add(1); - let page = self.pages.get_mut(&page_index)?; + let page = self.pages.get_mut(&(ino, page_index))?; if page_offset >= page.data.len() { return Some(0); } @@ -65,25 +72,73 @@ impl ReadCache { Some(n) } - fn insert(&mut self, generation: u64, page_index: usize, data: Vec) { - if self.generation != generation || data.is_empty() { + fn insert(&mut self, ino: u32, generation: u64, page_index: usize, data: Vec) { + if self.generation(ino) != generation || data.is_empty() { return; } self.clock = self.clock.wrapping_add(1); - if !self.pages.contains_key(&page_index) && self.pages.len() >= READ_CACHE_MAX_PAGES { + let key = (ino, page_index); + if !self.pages.contains_key(&key) && self.pages.len() >= READ_CACHE_MAX_PAGES { if let Some((&oldest_key, _)) = self.pages.iter().min_by_key(|(_, page)| page.age) { self.pages.remove(&oldest_key); } } - self.pages.insert(page_index, CachedReadPage { + self.pages.insert(key, CachedReadPage { data, age: self.clock, }); } } +struct LookupCache { + entries: BTreeMap>, +} + +impl LookupCache { + const fn new() -> Self { + Self { + entries: BTreeMap::new(), + } + } + + fn get(&self, parent_ino: u32, name: &str) -> Option { + self.entries.get(&parent_ino)?.get(name).copied() + } + + fn insert(&mut self, parent_ino: u32, name: &str, ino: u32) { + self.entries + .entry(parent_ino) + .or_default() + .insert(String::from(name), ino); + } + + fn remove(&mut self, parent_ino: u32, name: &str) { + let Some(entries) = self.entries.get_mut(&parent_ino) else { + return; + }; + entries.remove(name); + if entries.is_empty() { + self.entries.remove(&parent_ino); + } + } +} + +pub struct Ext4InodeCaches { + read: SpinLock, + lookup: SpinLock, +} + +impl Ext4InodeCaches { + pub const fn new() -> Self { + Self { + read: SpinLock::new(ReadCache::new()), + lookup: SpinLock::new(LookupCache::new()), + } + } +} + /// Ext4 Inode 包装 pub struct Ext4Inode { /// ext4_rs 文件系统对象 @@ -96,25 +151,29 @@ pub struct Ext4Inode { /// 用于获取完整路径,而不是在 Inode 中重复存储 dentry: SpinLock>, - /// Small per-inode page cache for repeated ELF/script/libc reads. - read_cache: SpinLock, + /// Shared filesystem-level caches for regular reads and directory lookup. + caches: Arc, } impl Ext4Inode { /// 创建新的 Ext4Inode /// /// 注意:初始创建时 dentry 为空,VFS 会在创建 Dentry 后调用 set_dentry() - pub fn new(fs: Arc>, ino: u32) -> Self { + pub fn new(fs: Arc>, caches: Arc, ino: u32) -> Self { Self { fs, ino, dentry: SpinLock::new(Weak::new()), - read_cache: SpinLock::new(ReadCache::new()), + caches, } } fn invalidate_read_cache(&self) { - self.read_cache.lock().clear(); + self.caches.read.lock().clear_inode(self.ino); + } + + fn drop_lookup_cache_entry(&self, name: &str) { + self.caches.lookup.lock().remove(self.ino, name); } #[cfg(test)] @@ -315,7 +374,8 @@ impl Inode for Ext4Inode { let page_offset = current_offset % READ_CACHE_PAGE_SIZE; let chunk_len = (READ_CACHE_PAGE_SIZE - page_offset).min(target_len - copied); - if let Some(n) = self.read_cache.lock().read( + if let Some(n) = self.caches.read.lock().read( + self.ino, page_index, page_offset, &mut buf[copied..copied + chunk_len], @@ -327,7 +387,7 @@ impl Inode for Ext4Inode { continue; } - let generation = self.read_cache.lock().generation(); + let generation = self.caches.read.lock().generation(self.ino); let page_start = page_index * READ_CACHE_PAGE_SIZE; let page_len = READ_CACHE_PAGE_SIZE.min(metadata.size - page_start); let mut page_buf = alloc::vec![0u8; page_len]; @@ -339,18 +399,20 @@ impl Inode for Ext4Inode { page_buf.truncate(nread); if page_offset >= page_buf.len() { - self.read_cache + self.caches + .read .lock() - .insert(generation, page_index, page_buf); + .insert(self.ino, generation, page_index, page_buf); break; } let n = (page_buf.len() - page_offset).min(chunk_len); buf[copied..copied + n].copy_from_slice(&page_buf[page_offset..page_offset + n]); copied += n; - self.read_cache + self.caches + .read .lock() - .insert(generation, page_index, page_buf); + .insert(self.ino, generation, page_index, page_buf); if n == 0 { break; @@ -384,6 +446,14 @@ impl Inode for Ext4Inode { return Err(FsError::NotDirectory); } + if let Some(child_ino) = self.caches.lookup.lock().get(self.ino, name) { + return Ok(Arc::new(Ext4Inode::new( + self.fs.clone(), + self.caches.clone(), + child_ino, + ))); + } + // 类似 create,lookup 也应该使用相对路径 // 直接在当前目录下查找指定名称的文件 let fs = self.fs.lock(); @@ -394,9 +464,14 @@ impl Inode for Ext4Inode { let child_ino = fs .generic_open(name, &mut parent, false, 0, &mut name_off) .map_err(|_| FsError::NotFound)?; + self.caches.lookup.lock().insert(self.ino, name, child_ino); // 创建子 Inode(暂时没有 dentry,VFS 会调用 set_dentry) - Ok(Arc::new(Ext4Inode::new(self.fs.clone(), child_ino))) + Ok(Arc::new(Ext4Inode::new( + self.fs.clone(), + self.caches.clone(), + child_ino, + ))) } fn create(&self, name: &str, mode: FileMode) -> Result, FsError> { @@ -420,8 +495,10 @@ impl Inode for Ext4Inode { child_inode.inode.set_mode(file_mode); fs.write_back_inode(&mut child_inode); + self.drop_lookup_cache_entry(name); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), + self.caches.clone(), child_inode.inode_num, ))) } @@ -454,7 +531,12 @@ impl Inode for Ext4Inode { inode_ref.inode.set_mode(dir_mode); fs.write_back_inode(&mut inode_ref); - Ok(Arc::new(Ext4Inode::new(self.fs.clone(), inode_id))) + self.drop_lookup_cache_entry(name); + Ok(Arc::new(Ext4Inode::new( + self.fs.clone(), + self.caches.clone(), + inode_id, + ))) } fn symlink(&self, name: &str, target: &str) -> Result, FsError> { @@ -492,8 +574,10 @@ impl Inode for Ext4Inode { .map_err(|_| FsError::IoError)?; } + self.drop_lookup_cache_entry(name); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), + self.caches.clone(), new_inode.inode_num, ))) } @@ -520,6 +604,7 @@ impl Inode for Ext4Inode { fs.link(&mut self_ref, &mut target_ref, name) .map_err(|_| FsError::NoSpace)?; + self.drop_lookup_cache_entry(name); Ok(()) } @@ -565,6 +650,7 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut parent_ref); } + self.drop_lookup_cache_entry(name); Ok(()) } @@ -579,7 +665,9 @@ impl Inode for Ext4Inode { let parent = self.ino; fs.dir_remove(parent, name) - .map(|_| ()) + .map(|_| { + self.drop_lookup_cache_entry(name); + }) .map_err(|_| FsError::NotFound) } @@ -802,6 +890,8 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut old_parent_ref); fs.write_back_inode(&mut new_parent_ref); + self.drop_lookup_cache_entry(old_name); + new_parent_ext4.drop_lookup_cache_entry(new_name); Ok(()) } @@ -1039,8 +1129,10 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut new_inode); + self.drop_lookup_cache_entry(name); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), + self.caches.clone(), new_inode.inode_num, ))) } diff --git a/os/src/fs/ext4/mod.rs b/os/src/fs/ext4/mod.rs index ea48a0d8..33c458c8 100644 --- a/os/src/fs/ext4/mod.rs +++ b/os/src/fs/ext4/mod.rs @@ -57,7 +57,7 @@ pub mod adpaters; pub mod inode; pub use adpaters::BlockDeviceAdapter; -pub use inode::Ext4Inode; +pub use inode::{Ext4Inode, Ext4InodeCaches}; use crate::device::block::BlockDriver; use crate::pr_info; @@ -125,8 +125,10 @@ impl Ext4FileSystem { let ext4 = Arc::new(Mutex::new(ext4)); + let inode_caches = Arc::new(Ext4InodeCaches::new()); + // 创建根 inode (inode 号 2 是 Ext4 的根目录) - let root = Arc::new(Ext4Inode::new(ext4.clone(), 2)); + let root = Arc::new(Ext4Inode::new(ext4.clone(), inode_caches.clone(), 2)); let fs = Arc::new(Ext4FileSystem { device, diff --git a/os/src/kernel/syscall/fs/mod.rs b/os/src/kernel/syscall/fs/mod.rs index fabb31f5..323264e9 100644 --- a/os/src/kernel/syscall/fs/mod.rs +++ b/os/src/kernel/syscall/fs/mod.rs @@ -31,9 +31,14 @@ pub const AT_REMOVEDIR: u32 = 0x200; pub const O_CLOEXEC: u32 = 0o2000000; fn drop_cached_child(parent: &Dentry, name: &str) { - if let Some(child) = parent.remove_child(name) { - DENTRY_CACHE.remove(&child.full_path()); - } + let parent_path = parent.full_path(); + let child_path = if parent_path == "/" { + alloc::format!("/{}", name) + } else { + alloc::format!("{}/{}", parent_path, name) + }; + parent.remove_child(name); + DENTRY_CACHE.remove_tree(&child_path); } mod fd_ops; diff --git a/os/src/kernel/syscall/util.rs b/os/src/kernel/syscall/util.rs index 5f633e3e..19151f2d 100644 --- a/os/src/kernel/syscall/util.rs +++ b/os/src/kernel/syscall/util.rs @@ -14,7 +14,7 @@ use crate::{ kernel::current_task, uapi::{errno::EINVAL, log::SyslogAction}, vfs::{ - DENTRY_CACHE, Dentry, File, FileMode, FsError, InodeType, OpenFlags, get_root_dentry, + DENTRY_CACHE, Dentry, File, FileMode, FsError, InodeType, OpenFlags, impls::{BlockDeviceFile, CharDeviceFile, PipeFile, RegFile}, normalize_path, vfs_lookup_from, }, @@ -113,7 +113,11 @@ pub fn get_args_safe(ptr_array: usize, _name: &str) -> Result, FsErr /// 这是系统调用层的辅助函数,处理 AT_FDCWD 和相对路径逻辑 pub fn resolve_at_path(dirfd: i32, path: &str) -> Result>, FsError> { let base_dentry = if path.starts_with('/') { - get_root_dentry()? + return match crate::vfs::vfs_lookup(path) { + Ok(dentry) => Ok(Some(dentry)), + Err(FsError::NotFound) => Ok(None), + Err(e) => Err(e), + }; } else if dirfd == super::fs::AT_FDCWD { current_task() .lock() diff --git a/os/src/vfs/dentry.rs b/os/src/vfs/dentry.rs index d4e1db5a..67d49449 100644 --- a/os/src/vfs/dentry.rs +++ b/os/src/vfs/dentry.rs @@ -104,7 +104,7 @@ //! } //! //! // 删除缓存 -//! DENTRY_CACHE.remove("/etc/passwd"); +//! DENTRY_CACHE.remove_tree("/etc/passwd"); //! ``` use crate::sync::SpinLock; @@ -275,9 +275,23 @@ impl DentryCache { self.cache.lock().insert(path, Arc::downgrade(dentry)); } - /// 从缓存中移除 - pub fn remove(&self, path: &str) { - self.cache.lock().remove(path); + /// 移除路径自身和它下面的所有子路径缓存。 + pub fn remove_tree(&self, path: &str) { + let mut cache = self.cache.lock(); + if path == "/" { + cache.clear(); + return; + } + + cache.retain(|cached_path, _| { + if cached_path == path { + return false; + } + match cached_path.strip_prefix(path) { + Some(rest) => !rest.starts_with('/'), + None => true, + } + }); } /// 清空缓存 diff --git a/os/src/vfs/path.rs b/os/src/vfs/path.rs index 070e713f..fca7b2ff 100644 --- a/os/src/vfs/path.rs +++ b/os/src/vfs/path.rs @@ -168,7 +168,7 @@ //! ``` use crate::kernel::current_task; -use crate::vfs::{Dentry, FsError, InodeType, get_root_dentry}; +use crate::vfs::{DENTRY_CACHE, Dentry, FsError, InodeType, get_root_dentry}; use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; @@ -312,6 +312,16 @@ pub fn split_path(path: &str) -> Result<(String, String), FsError> { /// /// 返回:`Ok(Arc)` 路径对应的目录项;`Err(FsError::NotFound)` 路径不存在;`Err(FsError::NotDirectory)` 中间组件不是目录 pub fn vfs_lookup(path: &str) -> Result, FsError> { + if path.starts_with('/') { + let normalized = normalize_path(path); + if let Some(dentry) = DENTRY_CACHE.lookup(&normalized) { + let dentry = check_mount_point(dentry)?; + if dentry.inode.metadata()?.inode_type != InodeType::Symlink { + return Ok(dentry); + } + } + } + let components = parse_path(path); // 确定起始 dentry @@ -483,6 +493,13 @@ fn get_cur_dir() -> Result, FsError> { /// 不会跟随它,而是直接返回链接文件的 dentry。 /// 路径中间的符号链接仍然会被跟随。 pub fn vfs_lookup_no_follow(path: &str) -> Result, FsError> { + if path.starts_with('/') { + let normalized = normalize_path(path); + if let Some(dentry) = DENTRY_CACHE.lookup(&normalized) { + return check_mount_point(dentry); + } + } + let components = parse_path(path); if components.is_empty() { From e58dc5570d4c43865ab1c80b3fcd73addac89571 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 21:17:26 +0800 Subject: [PATCH 10/28] signal: support musl pthread cancellation --- os/src/ipc/signal.rs | 3 +- os/src/kernel/syscall/signal.rs | 10 ++--- os/src/kernel/syscall/task/futex_ops.rs | 15 +++++-- os/src/uapi/signal.rs | 58 +++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/os/src/ipc/signal.rs b/os/src/ipc/signal.rs index fbe5cdcc..116220db 100644 --- a/os/src/ipc/signal.rs +++ b/os/src/ipc/signal.rs @@ -90,7 +90,8 @@ fn handle_one_signal(sig_flag: SignalFlags, action: SignalAction, task: &SharedT NUM_SIGSTOP | NUM_SIGTSTP | NUM_SIGTTIN | NUM_SIGTTOU => sig_stop(sig_num), NUM_SIGCONT => sig_continue(sig_num), NUM_SIGCHLD | NUM_SIGURG | NUM_SIGWINCH | NUM_SIGIO => sig_ignore(sig_num), - _ => panic!("Unhandled signal"), + NUM_SIGRTMIN..=NUM_SIGRTMAX => sig_terminate(sig_num), + _ => sig_terminate(sig_num), }, SIG_IGN => sig_ignore(sig_num), handler_addr => { diff --git a/os/src/kernel/syscall/signal.rs b/os/src/kernel/syscall/signal.rs index 173db2ec..1db4e36c 100644 --- a/os/src/kernel/syscall/signal.rs +++ b/os/src/kernel/syscall/signal.rs @@ -62,7 +62,6 @@ pub fn rt_sigprocmask( } else { return -EINVAL; }; - match how { SIG_BLOCK => { t.blocked |= new_flags; @@ -288,7 +287,7 @@ pub fn signal_stack(uss: *const StackT, uoss: *mut StackT) -> c_int { /// * 成功时返回 0 /// * 失败时返回负的错误码 pub fn kill(pid: c_int, sig: c_int) -> c_int { - if sig < 0 || sig as usize >= NSIG { + if sig < 0 || sig as usize > NSIG { return -EINVAL; } let task_manager = TASK_MANAGER.lock(); @@ -339,16 +338,17 @@ pub fn kill(pid: c_int, sig: c_int) -> c_int { /// * 成功时返回 0 /// * 失败时返回负的错误码 pub fn tkill(tid: c_int, sig: c_int) -> c_int { - if sig < 0 || sig as usize >= NSIG { + if tid <= 0 || sig < 0 || sig as usize > NSIG { return -EINVAL; } + let current_pid = current_task().lock().pid; let task_manager = TASK_MANAGER.lock(); let task = if let Some(task) = task_manager.get_task(tid as u32) { task } else { return -ESRCH; }; - if task.lock().pid != current_task().lock().pid { + if task.lock().pid != current_pid { return -EINVAL; } task_manager.send_signal(task, sig as usize); @@ -364,7 +364,7 @@ pub fn tkill(tid: c_int, sig: c_int) -> c_int { /// * 成功时返回 0 /// * 失败时返回负的错误码 pub fn tgkill(tgid: c_int, tid: c_int, sig: c_int) -> c_int { - if sig < 0 || sig as usize >= NSIG { + if tgid <= 0 || tid <= 0 || sig < 0 || sig as usize > NSIG { return -EINVAL; } let task_manager = TASK_MANAGER.lock(); diff --git a/os/src/kernel/syscall/task/futex_ops.rs b/os/src/kernel/syscall/task/futex_ops.rs index 5368e4c1..cbea482f 100644 --- a/os/src/kernel/syscall/task/futex_ops.rs +++ b/os/src/kernel/syscall/task/futex_ops.rs @@ -22,8 +22,6 @@ pub fn futex( let _private = (op & FUTEX_PRIVATE as c_int) != 0; // TODO: 目前不区分 PRIVATE 和 SHARED let realtime = (op & FUTEX_CLOCK_REALTIME as c_int) != 0; let op = op & !(FUTEX_PRIVATE as c_int) & !(FUTEX_CLOCK_REALTIME as c_int); - // HACK: 其实只需要锁定与 uaddr 对应的 Futex 等待队列 - let mut fm = FUTEX_MANAGER.lock(); match op as u32 { FUTEX_WAIT => { // 必须保证获取锁 → 定位等待队列 → 读取用户数据 → 比较 → 入队/释放锁 的序列是原子的 @@ -57,9 +55,18 @@ pub fn futex( return -EAGAIN; } + // HACK: 其实只需要锁定与 uaddr 对应的 Futex 等待队列 + let mut fm = FUTEX_MANAGER.lock(); let waitq = fm.get_wait_queue(paddr); - waitq.sleep(task.clone()); - sleep_task(task.clone(), true); + waitq.add_task(task.clone()); + let slept = sleep_task_prepare(task.clone(), true, |t| { + t.pending.has_deliverable_signal(t.blocked) + || t.shared_pending.lock().has_deliverable_signal(t.blocked) + }); + if !slept { + waitq.remove_task(&task); + return -EINTR; + } if !timeout.is_null() { let ts = unsafe { read_from_user(timeout) }; diff --git a/os/src/uapi/signal.rs b/os/src/uapi/signal.rs index 60843e85..b43373c5 100644 --- a/os/src/uapi/signal.rs +++ b/os/src/uapi/signal.rs @@ -104,7 +104,11 @@ impl Default for SignalAction { } /* 信号定义 */ -pub const NSIG: usize = 31; +/// Highest signal number accepted by Linux-style signal syscalls. +/// +/// musl uses signal 33 internally for pthread cancellation. Keeping the range at +/// the old non-realtime boundary made pthread_cancel() fail with EINVAL. +pub const NSIG: usize = 64; pub const NUM_SIGHUP: usize = 1; pub const NUM_SIGINT: usize = 2; @@ -137,6 +141,8 @@ pub const NUM_SIGWINCH: usize = 28; pub const NUM_SIGIO: usize = 29; pub const NUM_SIGPWR: usize = 30; pub const NUM_SIGSYS: usize = 31; +pub const NUM_SIGRTMIN: usize = 32; +pub const NUM_SIGRTMAX: usize = 64; bitflags! { #[derive(Clone, Debug, Copy)] @@ -174,6 +180,39 @@ bitflags! { const SIGIO = 1 << (NUM_SIGIO - 1); const SIGPWR = 1 << (NUM_SIGPWR - 1); const SIGSYS = 1 << (NUM_SIGSYS - 1); + const SIGRT32 = 1usize << 31; + const SIGRT33 = 1usize << 32; + const SIGRT34 = 1usize << 33; + const SIGRT35 = 1usize << 34; + const SIGRT36 = 1usize << 35; + const SIGRT37 = 1usize << 36; + const SIGRT38 = 1usize << 37; + const SIGRT39 = 1usize << 38; + const SIGRT40 = 1usize << 39; + const SIGRT41 = 1usize << 40; + const SIGRT42 = 1usize << 41; + const SIGRT43 = 1usize << 42; + const SIGRT44 = 1usize << 43; + const SIGRT45 = 1usize << 44; + const SIGRT46 = 1usize << 45; + const SIGRT47 = 1usize << 46; + const SIGRT48 = 1usize << 47; + const SIGRT49 = 1usize << 48; + const SIGRT50 = 1usize << 49; + const SIGRT51 = 1usize << 50; + const SIGRT52 = 1usize << 51; + const SIGRT53 = 1usize << 52; + const SIGRT54 = 1usize << 53; + const SIGRT55 = 1usize << 54; + const SIGRT56 = 1usize << 55; + const SIGRT57 = 1usize << 56; + const SIGRT58 = 1usize << 57; + const SIGRT59 = 1usize << 58; + const SIGRT60 = 1usize << 59; + const SIGRT61 = 1usize << 60; + const SIGRT62 = 1usize << 61; + const SIGRT63 = 1usize << 62; + const SIGRT64 = 1usize << 63; } } @@ -182,7 +221,7 @@ impl SignalFlags { if sig_num == 0 || sig_num > NSIG { return None; } - Some(SignalFlags::from_bits(1 << (sig_num - 1)).unwrap()) + Some(SignalFlags::from_bits(1usize << (sig_num - 1)).unwrap()) } /// 将 SignalFlags 转换为对应的信号编号 (1-NSIG)。 @@ -190,7 +229,7 @@ impl SignalFlags { /// 如果不包含任何信号,则返回 0 表示无效。 pub fn to_signal_number(&self) -> usize { for sig_num in 1..=NSIG { - if self.contains(SignalFlags::from_bits(1 << (sig_num - 1)).unwrap()) { + if self.contains(SignalFlags::from_bits(1usize << (sig_num - 1)).unwrap()) { return sig_num; } } @@ -456,6 +495,13 @@ pub struct UContextT { pub uc_stack: SignalStack, /// 信号掩码 pub uc_sigmask: SigSetT, + /// RISC-V Linux ABI padding before `uc_mcontext`. + /// + /// The kernel sigset_t used by rt signal syscalls is 8 bytes on riscv64, + /// but the ucontext ABI still reserves a 128-byte signal-mask area before + /// the machine context. musl reads `uc_mcontext.__gregs[REG_PC]` at this + /// ABI offset in its pthread cancellation handler. + pub __reserved: [u8; UCONTEXT_SIGMASK_PADDING_SIZE], /// 机器上下文 pub uc_mcontext: MContextT, } @@ -477,6 +523,7 @@ impl UContextT { uc_link: core::ptr::null_mut(), uc_stack: SignalStack::default(), uc_sigmask: 0, + __reserved: [0; UCONTEXT_SIGMASK_PADDING_SIZE], uc_mcontext: MContextT::new(), } } @@ -501,6 +548,7 @@ impl UContextT { uc_link: link, uc_stack: stack, uc_sigmask: sigmask, + __reserved: [0; UCONTEXT_SIGMASK_PADDING_SIZE], uc_mcontext: mcontext, } } @@ -533,7 +581,9 @@ pub const SS_AUTODISARM: usize = 1 << 31; /// 信号栈标志位掩码 pub const SS_FLAG_BITS: usize = SS_AUTODISARM; -#[repr(C)] +const UCONTEXT_SIGMASK_PADDING_SIZE: usize = 1024 / 8 - core::mem::size_of::(); + +#[repr(C, align(16))] #[derive(Clone, Copy, Debug)] /// 机器上下文结构体 pub struct MContextT { From ec569a7ceac6b3baf7074531e744d9cb1ece9632 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 21:18:14 +0800 Subject: [PATCH 11/28] docs: stop tracking local bug record --- docs/os-myself/big-bug-record.md | 76 -------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 docs/os-myself/big-bug-record.md diff --git a/docs/os-myself/big-bug-record.md b/docs/os-myself/big-bug-record.md deleted file mode 100644 index 11dd6bfc..00000000 --- a/docs/os-myself/big-bug-record.md +++ /dev/null @@ -1,76 +0,0 @@ -# 大问题记录 - -## 2026-06-23 评测打包把 BusyBox symlink 当成真实文件重复计数 - -### 现象 - -项目仓库里原来直接跟踪了 `data/risc-v_musl/bin` 和 `data/loongarch_musl/bin` 下的大量 BusyBox applet 符号链接。正常的 `git archive` 或普通 zip 不会把这些链接当成完整文件复制,但部分评测侧打包/扫描逻辑可能会跟随 symlink,把每个链接都当成一份 `busybox` 二进制内容重新计数。 - -同时,构建脚本原来会生成 4GiB 级别的 rootfs/ext4 镜像。评测机拉取仓库后执行 `make all`,如果它的中间打包或磁盘统计对 symlink/大镜像处理不佳,就会出现压缩体积和磁盘写入量远超实际需要的问题。 - -### 原因 - -BusyBox applet 的链接森林只是在运行时需要,不应该作为 Git 仓库里的真实追踪对象大量存在。评测环境实际只要求仓库提供源码和构建逻辑,运行时 rootfs 可以由 `make all` 临时生成。 - -`disk.img` 是 QEMU 运行时挂载给系统用的辅助磁盘;`kernel-rv`/`kernel-la` 是裸核 ELF。它们不是同一种东西。评测机会执行 `make all`,然后拿 `kernel-rv`/`kernel-la` 启动 QEMU,必要时再挂载我们生成的 `disk.img`/`disk-la.img`。 - -### 处理 - -已经把 BusyBox applet symlink 从 Git 追踪内容里移除,改成保存 `symlinks.manifest`。构建时 `build.rs` 会把 `data/{risc-v_musl,loongarch_musl}` 复制到临时 rootfs,再按 manifest 重建 symlink,保证运行时 rootfs 仍然有完整 BusyBox applet。 - -同时把 rootfs 镜像大小从 4096MiB 降到 256MiB。当前 `make all` 仍然会生成带分区表的 `disk.img` 和 `disk-la.img`,其中 Linux rootfs 分区是 256MiB,VFAT 分区是 64MiB,整体约 322MiB。这个大小足够当前 rootfs 使用,也显著降低评测机写盘和打包压力。 - -### 验证 - -使用本地 Docker 镜像 `zhouzhouyi/os-contest:20260510` 执行过 `make all`,确认 RISC-V/LoongArch 内核和磁盘镜像都能生成。对应变更已在提交 `18c8795 Reduce rootfs image size and rebuild busybox links` 中记录。 - -## 2026-06-23 官方测试盘 ext4 读写太慢,basic-musl 卡在前半段 - -### 现象 - -`make run-rv` 挂载官方测试盘 `/dev/vdb` 到 `/tests` 后,系统能进入 `basic-musl`,但执行非常慢。早期日志经常停在: - -- `Testing chdir` -- `Testing getpid` -- `Testing mkdir_` -- `Testing mount` -- `Testing unlink` - -这不是单纯的 syscall 不支持问题。许多子项已经能打印成功结果,但从官方 ext4 测试盘加载 ELF、动态链接器、脚本、目录项,以及在测试目录里写入/删除文件,会消耗大量 QEMU 时间。 - -### 原因 - -主要有三层: - -1. ext4 层以 4096B block 为单位读数据,但底层 VirtIO block sector 是 512B。原来的适配路径会把一次 4KiB 读取拆成多次 512B 请求,动态加载器和重复 exec 会放大这个成本。 -2. 官方测试盘是 4GiB raw ext4 镜像,测试目录在 `/tests/musl`。直接在这个盘上运行写入型测试时,目录创建、unlink、mount/umount 测试路径都会落到慢速 ext4 设备上。 -3. 一次性把 basic/busybox/lua/iperf 全部复制到 tmpfs 虽然能减少后续读写,但预复制本身会吃掉大量评测时间窗口。 - -### 处理 - -这次做了几类优化: - -- 给 `BlockDriver` 增加连续块批量读写接口,默认实现仍然循环单块读写,VirtIO MMIO/PCI 驱动覆盖为真正的 `read_blocks`/`write_blocks`。 -- 分区块设备把批量读写转发到底层设备,并自动加上分区起始 offset。 -- ext4 adapter 增加小型 4KiB 读缓存。对 aligned ext4 block read,直接用一次连续 sector 读替代 8 次 512B 单扇区读;写入时会让重叠缓存失效。 -- `mount -t tmpfs` 支持普通目录挂载,后续可以更灵活地把测试工作目录放进 tmpfs。 -- `rcS` 改成按测试组懒 staging:运行 `basic_testcode.sh` 前只复制 `basic`、`basic_testcode.sh`、`busybox` 到 `/tmp/musl`;如果 basic 能跑完,再继续复制 busybox/lua/iperf 对应依赖。这样不会在 basic 之前先复制所有组。 -- 给 tmpfs 补了 `chmod`/`chown` 元数据更新,避免 BusyBox `cp -R` 保留权限时报 `Not supported`,也让复制到 tmpfs 后的文件权限更接近原测试盘。 - -### 验证 - -所有验证都用 Docker 镜像 `zhouzhouyi/os-contest:20260510`,没有使用本机环境直接验证。 - -- `cargo fmt --manifest-path os/Cargo.toml --check` 通过。 -- `make all` 通过,重新生成 `kernel-rv`、`kernel-la`、`disk.img`、`disk-la.img`。 -- `timeout 240s make run-rv` 能挂载 `/dev/vdb`,把 basic 组 staging 到 `/tmp/musl`,进入 `#### OS COMP TEST GROUP START basic-musl ####`,且不再出现 `./busybox: not found`。 -- 最终 240 秒窗口内跑到 `Testing umount` 开始处。之前一次性 staging 全部白名单只能到 `getpid` 附近;直接从官方 ext4 测试盘运行在写入/删除类测试处明显更慢。 - -### 残余问题 - -当前优化还不是最终形态。basic 仍然无法在 240 秒本地窗口内完整跑完,最后停在 `umount` 附近;后续如果继续提速,优先看: - -1. ELF/动态链接器文件页缓存,而不是只缓存 ext4 block。 -2. 减少 `mount`/`umount` 测试里的 VFAT 初始化成本。 -3. 对目录项查找和路径解析加缓存,减少 exec 高频路径的重复 ext4 访问。 -4. 如果评测机总时间更长,可以保留当前按组懒 staging;如果只追 basic 分数,可以进一步只运行 basic 组,避免后续组影响关机和输出。 From 9bc91f2878e283804379b29a4bd8ecceee92b748 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 21:24:50 +0800 Subject: [PATCH 12/28] fix(task): exit group from thread leader --- os/src/kernel/syscall/task/exit_ops.rs | 6 ++++-- os/src/kernel/task/mod.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/os/src/kernel/syscall/task/exit_ops.rs b/os/src/kernel/syscall/task/exit_ops.rs index 9ac0b114..513f9a52 100644 --- a/os/src/kernel/syscall/task/exit_ops.rs +++ b/os/src/kernel/syscall/task/exit_ops.rs @@ -38,8 +38,10 @@ pub fn exit(code: c_int) -> c_int { pub fn exit_group(code: c_int) -> ! { // TODO: 处理 tid_addr 和 robust_list clear_child_tid_and_wake(); - crate::kernel::task::cleanup_current_process_resources_on_exit(); - exit_process(current_task(), code & 0xFF); + let task = current_task(); + let leader = crate::kernel::task::task_group_leader(&task).unwrap_or(task); + crate::kernel::task::cleanup_process_resources_on_exit(leader.clone()); + exit_process(leader, code & 0xFF); schedule(); unreachable!("exit: exit_task should not return."); } diff --git a/os/src/kernel/task/mod.rs b/os/src/kernel/task/mod.rs index c9cc19fb..82fa9ce0 100644 --- a/os/src/kernel/task/mod.rs +++ b/os/src/kernel/task/mod.rs @@ -101,6 +101,12 @@ pub(crate) fn terminate_task(code: usize) -> ! { unreachable!("terminate_task: should not return after scheduled out terminated task"); } +/// 获取任务所属线程组的 leader。 +pub fn task_group_leader(task: &SharedTask) -> Option { + let pid = task.lock().pid; + TASK_MANAGER.lock().get_task(pid) +} + /// 进程退出时的资源清理(Linux 语义子集): /// - 释放用户地址空间(页表 + 用户映射) /// - 关闭打开文件描述符(包括 socket fd) @@ -110,6 +116,11 @@ pub(crate) fn terminate_task(code: usize) -> ! { /// - 必须先切换到内核页表,再释放当前进程页表资源。 pub fn cleanup_current_process_resources_on_exit() { let task = current_task(); + cleanup_process_resources_on_exit(task); +} + +/// 清理指定进程/线程组 leader 持有的进程级资源。 +pub fn cleanup_process_resources_on_exit(task: SharedTask) { if !task.lock().is_process() { return; } From c760cad0e9442c16f8fc8a86a977e96a96d51371 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 21:26:26 +0800 Subject: [PATCH 13/28] fix(signal): terminate thread group via leader --- os/src/ipc/signal.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/os/src/ipc/signal.rs b/os/src/ipc/signal.rs index 116220db..a12048b1 100644 --- a/os/src/ipc/signal.rs +++ b/os/src/ipc/signal.rs @@ -14,8 +14,9 @@ use bitflags::bitflags; use crate::{ arch::{HwTrapFrame, TrapFrame}, kernel::{ - SharedTask, TASK_MANAGER, TaskManagerTrait, TaskState, current_cpu, current_task, - exit_process, exit_task, sleep_task, wake_up_task, yield_task, + SharedTask, TASK_MANAGER, TaskManagerTrait, TaskState, cleanup_process_resources_on_exit, + current_cpu, current_task, exit_process, exit_task, sleep_task, task_group_leader, + wake_up_task, yield_task, }, pr_err, uapi::signal::*, @@ -235,10 +236,10 @@ fn signal_from_flag(flag: SignalFlags) -> Option { /* 默认信号处理函数 */ /// 默认行为:进程中止 fn sig_terminate(sig_num: usize) { - let tasks = TASK_MANAGER.lock().get_process_threads(current_task()); - for task in tasks { - exit_process(task, (128 + sig_num) as i32); - } + let task = current_task(); + let leader = task_group_leader(&task).unwrap_or(task); + cleanup_process_resources_on_exit(leader.clone()); + exit_process(leader, (128 + sig_num) as i32); } /// 默认行为:终止并 Core Dump From 4649eb9e5d75b5b679430263d4ed0fcfee70ab28 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 21:29:06 +0800 Subject: [PATCH 14/28] fix(signal): schedule after default termination --- os/src/ipc/signal.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/os/src/ipc/signal.rs b/os/src/ipc/signal.rs index a12048b1..51dfa46d 100644 --- a/os/src/ipc/signal.rs +++ b/os/src/ipc/signal.rs @@ -15,8 +15,8 @@ use crate::{ arch::{HwTrapFrame, TrapFrame}, kernel::{ SharedTask, TASK_MANAGER, TaskManagerTrait, TaskState, cleanup_process_resources_on_exit, - current_cpu, current_task, exit_process, exit_task, sleep_task, task_group_leader, - wake_up_task, yield_task, + current_cpu, current_task, exit_process, exit_task, schedule, sleep_task, + task_group_leader, wake_up_task, yield_task, }, pr_err, uapi::signal::*, @@ -235,16 +235,18 @@ fn signal_from_flag(flag: SignalFlags) -> Option { /* 默认信号处理函数 */ /// 默认行为:进程中止 -fn sig_terminate(sig_num: usize) { +fn sig_terminate(sig_num: usize) -> ! { let task = current_task(); let leader = task_group_leader(&task).unwrap_or(task); cleanup_process_resources_on_exit(leader.clone()); exit_process(leader, (128 + sig_num) as i32); + schedule(); + unreachable!("sig_terminate: exited task should not return"); } /// 默认行为:终止并 Core Dump /// TODO: 实现生成 core dump 的功能 -fn sig_dump(sig_num: usize) { +fn sig_dump(sig_num: usize) -> ! { pr_err!("signal {}: generating core (stub)", sig_num); sig_terminate(sig_num); } From 041508e2a3ea041628fe0b87394718c124ec753f Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 22:35:04 +0800 Subject: [PATCH 15/28] test: stage non-ltp musl tests on tmpfs --- .gitignore | 4 +- data/loongarch_musl/etc/init.d/rcS | 48 +++++++++++++++++++- data/loongarch_musl/var/tmp/.gitkeep | 1 + data/risc-v_musl/etc/init.d/rcS | 68 ++++++++++++++++++++++++++-- data/risc-v_musl/var/tmp/.gitkeep | 1 + rust-skills | 1 + 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 data/loongarch_musl/var/tmp/.gitkeep create mode 100644 data/risc-v_musl/var/tmp/.gitkeep create mode 160000 rust-skills diff --git a/.gitignore b/.gitignore index 131d46f0..518f19e5 100644 --- a/.gitignore +++ b/.gitignore @@ -44,12 +44,12 @@ testsuits-for-oskernel/ # Logs os/loongarch.log - +*.log # Local notes os/可能的改进.md # Local agent guidance **/AGENTS.md - +**/CLAUDE.md # Local planning/refactor notes docs/ diff --git a/data/loongarch_musl/etc/init.d/rcS b/data/loongarch_musl/etc/init.d/rcS index 0711fe71..67731c3a 100755 --- a/data/loongarch_musl/etc/init.d/rcS +++ b/data/loongarch_musl/etc/init.d/rcS @@ -12,6 +12,7 @@ echo "--- Starting Minimal BusyBox System ---" /bin/mount -t sysfs none /sys # tmpfs/devtmpfs: 临时文件系统和设备文件 /bin/mount -t tmpfs none /tmp +/bin/mount -t tmpfs none /var/tmp # /dev: 设备节点 # 说明:即使内核已经提前挂载 /dev,这里再次 mount 也不会影响后续流程; # 且内核对 mount("/dev") 做了特殊处理:会在挂载 tmpfs 后自动 init_dev() 重建设备节点。 @@ -95,15 +96,57 @@ mount_official_test_image_if_present() { return 1 } +stage_musl_tests_to_tmpfs() { + src="/tests/musl" + dst="/tmp/tests/musl" + + [ -d "$src" ] || return 1 + + /bin/rm -rf "$dst" + /bin/mkdir -p "$dst" || return 1 + + echo "[Tests] staging non-LTP musl tests to $dst" + for entry in "$src"/*; do + [ -e "$entry" ] || continue + + base="${entry##*/}" + case "$base" in + ltp|ltp_testcode.sh) + continue + ;; + esac + + if ! /bin/cp -R "$entry" "$dst/"; then + echo "[Tests] failed to stage $entry" + /bin/rm -rf "$dst" + return 1 + fi + done + + return 0 +} + run_musl_tests_if_present() { # 官方测试镜像挂载到 /tests 后,当前自动入口只跑 musl 分组; # glibc 分组保留在测试镜像中,后续需要时直接调整本脚本。 mount_official_test_image_if_present || true [ -d /tests/musl ] || return 1 + staged_musl_dir="" + if stage_musl_tests_to_tmpfs; then + staged_musl_dir="/tmp/tests/musl" + else + echo "[Tests] staging failed; falling back to /tests/musl" + fi + ran=0 echo "[Tests] detected whitelisted musl test scripts; running in rcS" - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" + if [ -n "$staged_musl_dir" ]; then + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$staged_musl_dir:/tests/musl" + else + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" + fi + export TMPDIR="/tmp" export HOME="/" for name in \ @@ -113,6 +156,9 @@ run_musl_tests_if_present() { iperf_testcode.sh do f="/tests/musl/$name" + if [ -n "$staged_musl_dir" ] && [ "$name" != "ltp_testcode.sh" ] && [ -f "$staged_musl_dir/$name" ]; then + f="$staged_musl_dir/$name" + fi [ -f "$f" ] || continue dir="${f%/*}" diff --git a/data/loongarch_musl/var/tmp/.gitkeep b/data/loongarch_musl/var/tmp/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/data/loongarch_musl/var/tmp/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/risc-v_musl/etc/init.d/rcS b/data/risc-v_musl/etc/init.d/rcS index bda9fbd4..eaf64cbd 100755 --- a/data/risc-v_musl/etc/init.d/rcS +++ b/data/risc-v_musl/etc/init.d/rcS @@ -12,6 +12,7 @@ echo "--- Starting Minimal BusyBox System ---" /bin/mount -t sysfs none /sys # tmpfs/devtmpfs: 临时文件系统和设备文件 /bin/mount -t tmpfs none /tmp +/bin/mount -t tmpfs none /var/tmp # /dev: 设备节点 # 说明:即使内核已经提前挂载 /dev,这里再次 mount 也不会影响后续流程; # 且内核对 mount("/dev") 做了特殊处理:会在挂载 tmpfs 后自动 init_dev() 重建设备节点。 @@ -95,23 +96,76 @@ mount_official_test_image_if_present() { return 1 } +stage_musl_tests_to_tmpfs() { + src="/tests/musl" + dst="/tmp/tests/musl" + + [ -d "$src" ] || return 1 + + /bin/rm -rf "$dst" + /bin/mkdir -p "$dst" || return 1 + + echo "[Tests] staging non-LTP musl tests to $dst" + for entry in "$src"/*; do + [ -e "$entry" ] || continue + + base="${entry##*/}" + case "$base" in + ltp|ltp_testcode.sh) + continue + ;; + esac + + if ! /bin/cp -R "$entry" "$dst/"; then + echo "[Tests] failed to stage $entry" + /bin/rm -rf "$dst" + return 1 + fi + done + + return 0 +} + run_musl_tests_if_present() { # 官方测试镜像挂载到 /tests 后,自动扫描并运行 musl 下的全部测试脚本; # glibc 分组保留在测试镜像中,后续需要时直接调整本脚本。 mount_official_test_image_if_present || true [ -d /tests/musl ] || return 1 + staged_musl_dir="" + if stage_musl_tests_to_tmpfs; then + staged_musl_dir="/tmp/tests/musl" + else + echo "[Tests] staging failed; falling back to /tests/musl" + fi + ran=0 - echo "[Tests] detected musl test scripts; running directly from /tests in rcS" + echo "[Tests] detected musl test scripts; running in rcS" - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" + if [ -n "$staged_musl_dir" ]; then + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$staged_musl_dir:/tests/musl" + else + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" + fi + export TMPDIR="/tmp" export HOME="/" - for f in /tests/musl/*_testcode.sh; do - [ -f "$f" ] || continue + for src_f in /tests/musl/*_testcode.sh; do + [ -f "$src_f" ] || continue + + base="${src_f##*/}" + case "$base" in + basic_testcode.sh|busybox_testcode.sh|iperf_testcode.sh|lua_testcode.sh|iozone_testcode.sh|cyclictest_testcode.sh) + echo "[Tests] skipping already-validated $src_f" + continue + ;; + esac + f="$src_f" + if [ -n "$staged_musl_dir" ] && [ "$base" != "ltp_testcode.sh" ] && [ -f "$staged_musl_dir/$base" ]; then + f="$staged_musl_dir/$base" + fi dir="${f%/*}" - base="${f##*/}" echo "[Tests] running $f" ( cd "$dir" || exit 1 @@ -120,6 +174,10 @@ run_musl_tests_if_present() { ) rc=$? echo "[Tests] finished $f (rc=$rc)" + case "$rc" in + 0) ;; + *) echo "[Tests] ignoring failure from $f and continuing" ;; + esac ran=1 done [ "$ran" -eq 1 ] || return 1 diff --git a/data/risc-v_musl/var/tmp/.gitkeep b/data/risc-v_musl/var/tmp/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/data/risc-v_musl/var/tmp/.gitkeep @@ -0,0 +1 @@ + diff --git a/rust-skills b/rust-skills new file mode 160000 index 00000000..fa60f793 --- /dev/null +++ b/rust-skills @@ -0,0 +1 @@ +Subproject commit fa60f7931223646fb71c4586b4a6c8545016076a From e0da6b5e7e0f139eb81b6af783f66dba10a0d437 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 22:55:21 +0800 Subject: [PATCH 16/28] fix(sched): implement scheduler syscalls --- os/src/kernel/scheduler/mod.rs | 13 ++ os/src/kernel/syscall/dispatch.rs | 6 + os/src/kernel/syscall/mod.rs | 23 +++ os/src/kernel/syscall/numbers.rs | 6 + os/src/kernel/syscall/task/clone_ops.rs | 18 ++ os/src/kernel/syscall/task/mod.rs | 2 + os/src/kernel/syscall/task/sched_ops.rs | 226 ++++++++++++++++++++++++ os/src/kernel/task/task_struct.rs | 15 +- os/src/uapi/sched.rs | 24 +++ 9 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 os/src/kernel/syscall/task/sched_ops.rs diff --git a/os/src/kernel/scheduler/mod.rs b/os/src/kernel/scheduler/mod.rs index 482ef8d8..827721a8 100644 --- a/os/src/kernel/scheduler/mod.rs +++ b/os/src/kernel/scheduler/mod.rs @@ -98,6 +98,19 @@ pub fn pick_cpu() -> usize { NEXT_CPU.fetch_add(1, Ordering::Relaxed) % num_cpu } +/// 当前在线 CPU 掩码。 +/// +/// 该内核的 RISC-V 配置上限是 `MAX_CPU_COUNT`,因此一个 `usize` 足够表达 +/// sched affinity ABI 需要返回的在线 CPU 集。 +pub fn online_cpu_mask() -> usize { + let num_cpu = crate::kernel::num_cpu().min(usize::BITS as usize); + if num_cpu == usize::BITS as usize { + usize::MAX + } else { + (1usize << num_cpu) - 1 + } +} + /// 执行一次调度操作,切换到下一个任务 pub fn schedule() { // 读取并禁用中断,保护整个调度过程,并在返回时恢复原状态 diff --git a/os/src/kernel/syscall/dispatch.rs b/os/src/kernel/syscall/dispatch.rs index 34bed929..d9382c19 100644 --- a/os/src/kernel/syscall/dispatch.rs +++ b/os/src/kernel/syscall/dispatch.rs @@ -101,6 +101,12 @@ pub fn dispatch_syscall(frame: &mut impl SyscallFrame) { crate::kernel::syscall::numbers::SYS_SYSLOG => sys_syslog(frame), // 调度 + crate::kernel::syscall::numbers::SYS_SCHED_SETPARAM => sys_sched_setparam(frame), + crate::kernel::syscall::numbers::SYS_SCHED_SETSCHEDULER => sys_sched_setscheduler(frame), + crate::kernel::syscall::numbers::SYS_SCHED_GETSCHEDULER => sys_sched_getscheduler(frame), + crate::kernel::syscall::numbers::SYS_SCHED_GETPARAM => sys_sched_getparam(frame), + crate::kernel::syscall::numbers::SYS_SCHED_SETAFFINITY => sys_sched_setaffinity(frame), + crate::kernel::syscall::numbers::SYS_SCHED_GETAFFINITY => sys_sched_getaffinity(frame), crate::kernel::syscall::numbers::SYS_SCHED_YIELD => sys_sched_yield(frame), // 信号 diff --git a/os/src/kernel/syscall/mod.rs b/os/src/kernel/syscall/mod.rs index 26da85dc..3e914700 100644 --- a/os/src/kernel/syscall/mod.rs +++ b/os/src/kernel/syscall/mod.rs @@ -28,6 +28,7 @@ use crate::{ futex::RobustListHead, iovec::IoVec, resource::{Rlimit, Rusage}, + sched::SchedParam, signal::{SigInfoT, SignalAction}, sysinfo::SysInfo, time::{Itimerval, TimeSpec, Tms, timeval, timezone}, @@ -167,6 +168,28 @@ impl_syscall!( impl_syscall!(sys_clock_settime, clock_settime, (c_int, *const TimeSpec)); impl_syscall!(sys_clock_gettime, clock_gettime, (c_int, *mut TimeSpec)); impl_syscall!(sys_clock_getres, clock_getres, (c_int, *mut TimeSpec)); +impl_syscall!( + sys_sched_setparam, + sched_setparam, + (c_int, *const SchedParam) +); +impl_syscall!( + sys_sched_setscheduler, + sched_setscheduler, + (c_int, c_int, *const SchedParam) +); +impl_syscall!(sys_sched_getscheduler, sched_getscheduler, (c_int)); +impl_syscall!(sys_sched_getparam, sched_getparam, (c_int, *mut SchedParam)); +impl_syscall!( + sys_sched_setaffinity, + sched_setaffinity, + (c_int, usize, *const u8) +); +impl_syscall!( + sys_sched_getaffinity, + sched_getaffinity, + (c_int, usize, *mut u8) +); impl_syscall!(sys_sched_yield, sched_yield, ()); impl_syscall!(sys_syslog, syslog, (i32, *mut u8, i32)); diff --git a/os/src/kernel/syscall/numbers.rs b/os/src/kernel/syscall/numbers.rs index 3f213afb..7a78edc0 100644 --- a/os/src/kernel/syscall/numbers.rs +++ b/os/src/kernel/syscall/numbers.rs @@ -76,6 +76,12 @@ pub const SYS_CLOCK_GETRES: usize = 114; pub const SYS_SYSLOG: usize = 116; // ---- 调度 ---- +pub const SYS_SCHED_SETPARAM: usize = 118; +pub const SYS_SCHED_SETSCHEDULER: usize = 119; +pub const SYS_SCHED_GETSCHEDULER: usize = 120; +pub const SYS_SCHED_GETPARAM: usize = 121; +pub const SYS_SCHED_SETAFFINITY: usize = 122; +pub const SYS_SCHED_GETAFFINITY: usize = 123; pub const SYS_SCHED_YIELD: usize = 124; // ---- 信号 ---- diff --git a/os/src/kernel/syscall/task/clone_ops.rs b/os/src/kernel/syscall/task/clone_ops.rs index 2db787d7..9efda847 100644 --- a/os/src/kernel/syscall/task/clone_ops.rs +++ b/os/src/kernel/syscall/task/clone_ops.rs @@ -47,6 +47,10 @@ pub fn clone( uts, rlimit, exe_path, + sched_policy, + sched_priority, + sched_reset_on_fork, + cpu_affinity, ) = { let _guard = crate::sync::PreemptGuard::new(); let cpu = current_cpu(); @@ -68,6 +72,10 @@ pub fn clone( task.uts_namespace.clone(), task.rlimit.clone(), task.exe_path.clone(), + task.sched_policy, + task.sched_priority, + task.sched_reset_on_fork, + task.cpu_affinity, ) }; let exit_signal = requested_flags.get_exit_signal(); @@ -135,6 +143,16 @@ pub fn clone( fs, ); child_task.exe_path = exe_path; + if sched_reset_on_fork { + child_task.sched_policy = crate::uapi::sched::SCHED_NORMAL; + child_task.sched_priority = 0; + child_task.sched_reset_on_fork = false; + } else { + child_task.sched_policy = sched_policy; + child_task.sched_priority = sched_priority; + child_task.sched_reset_on_fork = false; + } + child_task.cpu_affinity = cpu_affinity & crate::kernel::online_cpu_mask(); if requested_flags.contains(CloneFlags::CHILD_SETTID) { unsafe { diff --git a/os/src/kernel/syscall/task/mod.rs b/os/src/kernel/syscall/task/mod.rs index 2a830f13..8d2d00f7 100644 --- a/os/src/kernel/syscall/task/mod.rs +++ b/os/src/kernel/syscall/task/mod.rs @@ -58,6 +58,7 @@ mod exec_ops; mod exit_ops; mod futex_ops; mod process_ops; +mod sched_ops; mod session_ops; mod time_ops; mod wait_ops; @@ -67,6 +68,7 @@ pub use exec_ops::*; pub use exit_ops::*; pub use futex_ops::*; pub use process_ops::*; +pub use sched_ops::*; pub use session_ops::*; pub use time_ops::*; pub use wait_ops::*; diff --git a/os/src/kernel/syscall/task/sched_ops.rs b/os/src/kernel/syscall/task/sched_ops.rs new file mode 100644 index 00000000..efd8d932 --- /dev/null +++ b/os/src/kernel/syscall/task/sched_ops.rs @@ -0,0 +1,226 @@ +use super::*; + +use crate::{ + arch::{Arch, ArchImpl, address::UA}, + kernel::task::Capabilities, + uapi::{ + resource::ResourceId, + sched::{ + SCHED_BATCH, SCHED_DEADLINE, SCHED_EXT, SCHED_FIFO, SCHED_IDLE, SCHED_NORMAL, + SCHED_RESET_ON_FORK, SCHED_RR, SCHED_RT_PRIORITY_MAX, SCHED_RT_PRIORITY_MIN, + SchedParam, + }, + }, + util::user_buffer::{validate_user_ptr, validate_user_ptr_mut}, +}; + +const CPU_SET_BYTES: usize = core::mem::size_of::(); + +fn get_target_task(pid: c_int) -> Result { + if pid < 0 { + return Err(ESRCH); + } + if pid == 0 { + return Ok(current_task()); + } + TASK_MANAGER.lock().get_task(pid as u32).ok_or(ESRCH) +} + +fn normalize_policy(policy: c_int) -> Result<(c_int, bool), c_int> { + let reset = policy & SCHED_RESET_ON_FORK != 0; + let base = policy & !SCHED_RESET_ON_FORK; + match base { + SCHED_NORMAL | SCHED_FIFO | SCHED_RR | SCHED_BATCH | SCHED_IDLE => Ok((base, reset)), + SCHED_DEADLINE | SCHED_EXT => Err(EINVAL), + _ => Err(EINVAL), + } +} + +fn validate_sched_param(policy: c_int, priority: c_int) -> Result<(), c_int> { + match policy { + SCHED_NORMAL | SCHED_BATCH | SCHED_IDLE => { + if priority == 0 { + Ok(()) + } else { + Err(EINVAL) + } + } + SCHED_FIFO | SCHED_RR => { + if (SCHED_RT_PRIORITY_MIN..=SCHED_RT_PRIORITY_MAX).contains(&priority) { + Ok(()) + } else { + Err(EINVAL) + } + } + _ => Err(EINVAL), + } +} + +fn check_sched_permission(target: &SharedTask, new_policy: c_int, new_priority: c_int) -> bool { + let current = current_task(); + let (current_cred, current_rlimit) = { + let cur = current.lock(); + ( + cur.credential, + cur.rlimit.lock().limits[ResourceId::Rtprio as usize].rlim_cur as c_int, + ) + }; + if current_cred.capabilities.has(Capabilities::SYS_NICE) { + return true; + } + + let (same_user, old_policy, old_priority, rtprio_limit) = { + let target = target.lock(); + let same_user = current_cred.euid == target.credential.euid + || current_cred.euid == target.credential.uid; + ( + same_user, + target.sched_policy, + target.sched_priority, + current_rlimit, + ) + }; + + if !same_user { + return false; + } + + match new_policy { + SCHED_NORMAL | SCHED_BATCH | SCHED_IDLE => true, + SCHED_FIFO | SCHED_RR => { + rtprio_limit > 0 + && new_priority <= rtprio_limit + && (old_policy == new_policy || new_priority <= old_priority || old_priority == 0) + } + _ => false, + } +} + +fn set_scheduler_common(pid: c_int, policy: Option, param: *const SchedParam) -> c_int { + if param.is_null() || !validate_user_ptr(param) { + return -EFAULT; + } + let param = read_from_user(param); + let task = match get_target_task(pid) { + Ok(task) => task, + Err(errno) => return -errno, + }; + + let (new_policy, reset_on_fork) = match policy { + Some(raw_policy) => match normalize_policy(raw_policy) { + Ok(v) => v, + Err(errno) => return -errno, + }, + None => { + let t = task.lock(); + (t.sched_policy, t.sched_reset_on_fork) + } + }; + + if let Err(errno) = validate_sched_param(new_policy, param.sched_priority) { + return -errno; + } + if !check_sched_permission(&task, new_policy, param.sched_priority) { + return -EPERM; + } + + let mut t = task.lock(); + t.sched_policy = new_policy; + t.sched_priority = param.sched_priority; + t.priority = if param.sched_priority > 0 { + (SCHED_RT_PRIORITY_MAX - param.sched_priority) as u8 + } else { + 0 + }; + if policy.is_some() { + t.sched_reset_on_fork = reset_on_fork; + } + 0 +} + +pub fn sched_setparam(pid: c_int, param: *const SchedParam) -> c_int { + set_scheduler_common(pid, None, param) +} + +pub fn sched_setscheduler(pid: c_int, policy: c_int, param: *const SchedParam) -> c_int { + set_scheduler_common(pid, Some(policy), param) +} + +pub fn sched_getscheduler(pid: c_int) -> c_int { + let task = match get_target_task(pid) { + Ok(task) => task, + Err(errno) => return -errno, + }; + let t = task.lock(); + let mut policy = t.sched_policy; + if t.sched_reset_on_fork { + policy |= SCHED_RESET_ON_FORK; + } + policy +} + +pub fn sched_getparam(pid: c_int, param: *mut SchedParam) -> c_int { + if param.is_null() || !validate_user_ptr_mut(param) { + return -EFAULT; + } + let task = match get_target_task(pid) { + Ok(task) => task, + Err(errno) => return -errno, + }; + let priority = task.lock().sched_priority; + write_to_user(param, SchedParam { + sched_priority: priority, + }); + 0 +} + +pub fn sched_setaffinity(pid: c_int, cpusetsize: usize, mask: *const u8) -> c_int { + if cpusetsize == 0 || mask.is_null() { + return -EINVAL; + } + let copy_len = core::cmp::min(cpusetsize, CPU_SET_BYTES); + let mut raw = [0u8; CPU_SET_BYTES]; + // SAFETY: copy_from_user validates the user mapping while copying. `raw` is a + // kernel stack buffer with at least `copy_len <= CPU_SET_BYTES` bytes. + if unsafe { + ArchImpl::copy_from_user(UA::from_usize(mask as usize), raw.as_mut_ptr(), copy_len) + } + .is_err() + { + return -EFAULT; + } + + let requested = usize::from_ne_bytes(raw); + let available = crate::kernel::online_cpu_mask(); + let normalized = requested & available; + if normalized == 0 { + return -EINVAL; + } + + let task = match get_target_task(pid) { + Ok(task) => task, + Err(errno) => return -errno, + }; + task.lock().cpu_affinity = normalized; + 0 +} + +pub fn sched_getaffinity(pid: c_int, cpusetsize: usize, mask: *mut u8) -> c_int { + if cpusetsize < CPU_SET_BYTES || mask.is_null() { + return -EINVAL; + } + let task = match get_target_task(pid) { + Ok(task) => task, + Err(errno) => return -errno, + }; + let affinity = task.lock().cpu_affinity & crate::kernel::online_cpu_mask(); + let raw = affinity.to_ne_bytes(); + // SAFETY: `raw` is a live kernel buffer and copy_to_user validates the user + // destination while copying `raw.len()` bytes. + if unsafe { ArchImpl::copy_to_user(raw.as_ptr(), UA::from_usize(mask as usize), raw.len()) } + .is_err() + { + return -EFAULT; + } + CPU_SET_BYTES as c_int +} diff --git a/os/src/kernel/task/task_struct.rs b/os/src/kernel/task/task_struct.rs index 978f93d6..7185082f 100644 --- a/os/src/kernel/task/task_struct.rs +++ b/os/src/kernel/task/task_struct.rs @@ -25,6 +25,7 @@ use crate::{ sync::SpinLock, uapi::{ resource::RlimitStruct, + sched::SCHED_NORMAL, signal::{SignalFlags, SignalStack}, uts_namespace::UtsNamespace, }, @@ -67,8 +68,13 @@ pub struct Task { /// None 表示任务未运行在任何 CPU 上 pub on_cpu: Option, /// CPU 亲和性掩码 - /// -1 表示可以在任何 CPU 上运行 - pub cpu_affinity: i32, + pub cpu_affinity: usize, + /// Linux 调度策略(SCHED_*,不含 SCHED_RESET_ON_FORK 标志位) + pub sched_policy: i32, + /// Linux realtime 调度优先级。普通策略固定为 0。 + pub sched_priority: i32, + /// fork/clone 时是否将子任务调度属性重置为普通策略。 + pub sched_reset_on_fork: bool, /// 任务当前的状态 pub state: TaskState, /// 任务的id @@ -404,7 +410,10 @@ impl Task { priority: 0, processor_id: 0, on_cpu: None, - cpu_affinity: -1, + cpu_affinity: crate::kernel::online_cpu_mask(), + sched_policy: SCHED_NORMAL, + sched_priority: 0, + sched_reset_on_fork: false, state: TaskState::Running, tid, pid, diff --git a/os/src/uapi/sched.rs b/os/src/uapi/sched.rs index 33108f3d..4cd94743 100644 --- a/os/src/uapi/sched.rs +++ b/os/src/uapi/sched.rs @@ -153,6 +153,30 @@ pub enum SchedulingPolicy { Ext = 7, // SCHED_EXT (保留用于外部调度器) } +impl SchedulingPolicy { + pub const fn as_raw(self) -> c_int { + self as c_int + } +} + +/// Linux `struct sched_param`. +#[repr(C)] +#[derive(Debug, Clone, Copy, Default)] +pub struct SchedParam { + pub sched_priority: c_int, +} + +pub const SCHED_NORMAL: c_int = SchedulingPolicy::Normal.as_raw(); +pub const SCHED_FIFO: c_int = SchedulingPolicy::Fifo.as_raw(); +pub const SCHED_RR: c_int = SchedulingPolicy::Rr.as_raw(); +pub const SCHED_BATCH: c_int = SchedulingPolicy::Batch.as_raw(); +pub const SCHED_IDLE: c_int = SchedulingPolicy::Idle.as_raw(); +pub const SCHED_DEADLINE: c_int = SchedulingPolicy::Deadline.as_raw(); +pub const SCHED_EXT: c_int = SchedulingPolicy::Ext.as_raw(); + +pub const SCHED_RT_PRIORITY_MIN: c_int = 1; +pub const SCHED_RT_PRIORITY_MAX: c_int = 99; + /// 调度标志:在 fork 时重置为 SCHED_NORMAL。 pub const SCHED_RESET_ON_FORK: c_int = 0x40000000; From 49a868c699ea425ca12abe69b0c3d36dfd63dbca Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 22:57:08 +0800 Subject: [PATCH 17/28] fix(sched): honor affinity and realtime priority --- os/src/kernel/scheduler/mod.rs | 27 +++++++++++++++++++++++-- os/src/kernel/scheduler/rr_scheduler.rs | 2 +- os/src/kernel/scheduler/task_queue.rs | 16 +++++++++++++++ os/src/kernel/syscall/task/clone_ops.rs | 5 ++++- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/os/src/kernel/scheduler/mod.rs b/os/src/kernel/scheduler/mod.rs index 827721a8..9b70423a 100644 --- a/os/src/kernel/scheduler/mod.rs +++ b/os/src/kernel/scheduler/mod.rs @@ -94,8 +94,27 @@ pub fn scheduler_of(cpu_id: usize) -> &'static SpinLock { /// 通过轮询方式为新任务选择一个目标 CPU。 pub fn pick_cpu() -> usize { + pick_cpu_from_mask(online_cpu_mask()) +} + +/// 在给定 CPU mask 内轮询选择目标 CPU。 +pub fn pick_cpu_from_mask(mask: usize) -> usize { + let online = online_cpu_mask(); + let mask = mask & online; + if mask == 0 { + return crate::arch::cpu_id().min(crate::kernel::num_cpu().saturating_sub(1)); + } + + let start = NEXT_CPU.fetch_add(1, Ordering::Relaxed); let num_cpu = crate::kernel::num_cpu(); - NEXT_CPU.fetch_add(1, Ordering::Relaxed) % num_cpu + for offset in 0..num_cpu { + let cpu = (start + offset) % num_cpu; + if mask & (1usize << cpu) != 0 { + return cpu; + } + } + + crate::arch::cpu_id().min(num_cpu.saturating_sub(1)) } /// 当前在线 CPU 掩码。 @@ -176,7 +195,11 @@ pub fn sleep_task(task: SharedTask, receive_signal: bool) { /// 参数: /// * `task`: 需要唤醒的任务 pub fn wake_up_task(task: SharedTask) { - let target_cpu = pick_cpu(); + let affinity = { + let t = task.lock(); + t.cpu_affinity + }; + let target_cpu = pick_cpu_from_mask(affinity); let current_cpu = crate::arch::cpu_id(); let task_tid = { task.lock().tid }; diff --git a/os/src/kernel/scheduler/rr_scheduler.rs b/os/src/kernel/scheduler/rr_scheduler.rs index 96448c35..fce36166 100644 --- a/os/src/kernel/scheduler/rr_scheduler.rs +++ b/os/src/kernel/scheduler/rr_scheduler.rs @@ -84,7 +84,7 @@ impl Scheduler for RRScheduler { ); // 选择下一个可运行任务 - let next_task = match self.run_queue.pop_task() { + let next_task = match self.run_queue.pop_highest_priority_task() { Some(t) => t, None => { // 没有可运行任务: diff --git a/os/src/kernel/scheduler/task_queue.rs b/os/src/kernel/scheduler/task_queue.rs index 49a63841..6bd20712 100644 --- a/os/src/kernel/scheduler/task_queue.rs +++ b/os/src/kernel/scheduler/task_queue.rs @@ -48,6 +48,22 @@ impl TaskQueue { } } + /// 弹出最高 realtime 优先级任务;同优先级保持 FIFO。 + pub fn pop_highest_priority_task(&mut self) -> Option { + let mut best_idx = None; + let mut best_priority = i32::MIN; + + for (idx, task) in self.queue.iter().enumerate() { + let priority = task.lock().sched_priority; + if priority > best_priority { + best_priority = priority; + best_idx = Some(idx); + } + } + + best_idx.map(|idx| self.queue.remove(idx)) + } + /// 检查任务是否在队列中 pub fn contains(&self, task: &SharedTask) -> bool { for t in &self.queue { diff --git a/os/src/kernel/syscall/task/clone_ops.rs b/os/src/kernel/syscall/task/clone_ops.rs index 9efda847..502d5447 100644 --- a/os/src/kernel/syscall/task/clone_ops.rs +++ b/os/src/kernel/syscall/task/clone_ops.rs @@ -153,6 +153,9 @@ pub fn clone( child_task.sched_reset_on_fork = false; } child_task.cpu_affinity = cpu_affinity & crate::kernel::online_cpu_mask(); + if child_task.cpu_affinity == 0 { + child_task.cpu_affinity = crate::kernel::online_cpu_mask(); + } if requested_flags.contains(CloneFlags::CHILD_SETTID) { unsafe { @@ -191,7 +194,7 @@ pub fn clone( .push(child_task.clone()); // 选择目标 CPU(负载均衡) - let target_cpu = crate::kernel::pick_cpu(); + let target_cpu = crate::kernel::pick_cpu_from_mask(child_task.lock().cpu_affinity); child_task.lock().on_cpu = Some(target_cpu); let child_tid = child_task.lock().tid; From a76240bc8790f01e6c69e2a5f2a49ae4e041e3e9 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 23:25:56 +0800 Subject: [PATCH 18/28] fix(ipc): implement sysv shared memory --- os/src/fs/proc/generators/process/maps.rs | 1 + os/src/ipc/shared_memory.rs | 306 ++++++++++++++---- os/src/kernel/syscall/dispatch.rs | 6 + os/src/kernel/syscall/ipc.rs | 154 ++++++++- os/src/kernel/syscall/mod.rs | 7 + os/src/kernel/syscall/numbers.rs | 6 + os/src/kernel/syscall/task/clone_ops.rs | 12 + os/src/kernel/syscall/task/exec_ops.rs | 1 + os/src/kernel/task/mod.rs | 51 ++- os/src/kernel/task/task_struct.rs | 16 +- .../mm/memory_space/mapping_area/map_ops.rs | 31 ++ os/src/mm/memory_space/mapping_area/mod.rs | 11 +- .../mm/memory_space/mapping_area/split_ops.rs | 33 ++ os/src/mm/memory_space/space/address_space.rs | 17 + os/src/mm/memory_space/space/mmap_ops.rs | 13 +- os/src/uapi/ipc.rs | 56 ++++ os/src/uapi/mod.rs | 1 + 17 files changed, 648 insertions(+), 74 deletions(-) create mode 100644 os/src/uapi/ipc.rs diff --git a/os/src/fs/proc/generators/process/maps.rs b/os/src/fs/proc/generators/process/maps.rs index 94baa5eb..5eae9762 100644 --- a/os/src/fs/proc/generators/process/maps.rs +++ b/os/src/fs/proc/generators/process/maps.rs @@ -94,6 +94,7 @@ impl ContentGenerator for MapsGenerator { MapType::Direct => "direct", MapType::Framed => "framed", MapType::Reserved => "reserved", + MapType::Shared => "shared", }; let pages = a.vpn_range().len(); let rss_pages = a.mapped_pages(); diff --git a/os/src/ipc/shared_memory.rs b/os/src/ipc/shared_memory.rs index a34c7084..690b72c8 100644 --- a/os/src/ipc/shared_memory.rs +++ b/os/src/ipc/shared_memory.rs @@ -1,98 +1,278 @@ -//! 共享内存模块 -//! -//! 提供共享物理页的分配与映射到当前进程用户空间的能力。 +//! System V shared memory registry. -use alloc::{sync::Arc, vec::Vec}; +use alloc::{collections::btree_map::BTreeMap, sync::Arc, vec::Vec}; +use core::ffi::{c_int, c_ulong}; + +use lazy_static::lazy_static; use crate::{ config::PAGE_SIZE, - kernel::{current_cpu, current_task}, + kernel::current_task, mm::{ + address::{PageNum, Ppn}, frame_allocator::{FrameTracker, alloc_frames}, - page_table::{PagingError, UniversalPTEFlag}, + }, + sync::SpinLock, + uapi::{ + errno::{EACCES, EEXIST, EINVAL, ENOENT, ENOMEM}, + ipc::{IPC_CREAT, IPC_EXCL, IPC_PRIVATE, IpcPerm, KeyT, SHM_HUGETLB, ShmIdDs}, + time::TimeSpec, }, }; -/// 共享内存表:简单管理若干共享段 -pub struct SharedMemoryTable { - memory: Vec>, +#[derive(Debug)] +pub struct ShmSegment { + pub id: c_int, + pub key: KeyT, + pub size: usize, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub cuid: u32, + pub cgid: u32, + pub cpid: c_int, + frames: Vec, + inner: SpinLock, } -impl SharedMemoryTable { - /// 创建共享内存表 - pub fn new() -> Self { - Self { memory: Vec::new() } +#[derive(Debug)] +struct ShmSegmentState { + marked_removed: bool, + attach_count: usize, + atime: i64, + dtime: i64, + ctime: i64, + lpid: c_int, +} + +impl ShmSegment { + fn new(id: c_int, key: KeyT, size: usize, shmflg: c_int) -> Result { + let pages = size.div_ceil(PAGE_SIZE); + let frames = alloc_frames(pages).ok_or(ENOMEM)?; + let cred = current_task().lock().credential; + let now = unix_time(); + Ok(Self { + id, + key, + size, + mode: (shmflg as u32) & 0o777, + uid: cred.euid, + gid: cred.egid, + cuid: cred.euid, + cgid: cred.egid, + cpid: current_task().lock().pid as c_int, + frames, + inner: SpinLock::new(ShmSegmentState { + marked_removed: false, + attach_count: 0, + atime: 0, + dtime: 0, + ctime: now, + lpid: 0, + }), + }) + } + + pub fn len(&self) -> usize { + self.size } - /// 新建共享段并登记,返回 Arc 句柄 - pub fn create(&mut self, pages: usize) -> Arc { - let shm = Arc::new(SharedMemory::new(pages)); - self.memory.push(shm.clone()); - shm + pub fn pages(&self) -> usize { + self.frames.len() } - /// 简单移除(若还被其他地方持有 Arc,不会真正释放) - pub fn remove(&mut self, shm: &Arc) -> bool { - if let Some(i) = self.memory.iter().position(|x| Arc::ptr_eq(x, shm)) { - self.memory.swap_remove(i); - // XXX: 是不是还应取消在当前进程用户空间上的映射 - true - } else { - false - } + pub fn ppn_at(&self, page_idx: usize) -> Option { + self.frames.get(page_idx).map(FrameTracker::ppn) } - /// 当前已登记的共享段数量 - pub fn len(&self) -> usize { - self.memory.len() + pub fn mark_attached(&self, pid: c_int) { + let mut inner = self.inner.lock(); + inner.attach_count += 1; + inner.atime = unix_time(); + inner.lpid = pid; + } + + pub fn mark_detached(&self, pid: c_int) -> bool { + let mut inner = self.inner.lock(); + inner.attach_count = inner.attach_count.saturating_sub(1); + inner.dtime = unix_time(); + inner.lpid = pid; + inner.marked_removed && inner.attach_count == 0 + } + + pub fn mark_removed(&self) -> bool { + let mut inner = self.inner.lock(); + inner.marked_removed = true; + inner.ctime = unix_time(); + inner.attach_count == 0 + } + + pub fn is_removed(&self) -> bool { + self.inner.lock().marked_removed } - pub fn is_empty(&self) -> bool { - self.memory.is_empty() + pub fn stat(&self) -> ShmIdDs { + let inner = self.inner.lock(); + ShmIdDs { + shm_perm: IpcPerm { + key: self.key, + uid: self.uid, + gid: self.gid, + cuid: self.cuid, + cgid: self.cgid, + mode: self.mode, + seq: 0, + ..IpcPerm::default() + }, + shm_segsz: self.size, + shm_atime: inner.atime, + shm_dtime: inner.dtime, + shm_ctime: inner.ctime, + shm_cpid: self.cpid, + shm_lpid: inner.lpid, + shm_nattch: inner.attach_count as c_ulong, + ..ShmIdDs::default() + } } } -/// 共享内存:持有一组物理页(FrameTracker) -pub struct SharedMemory { - frames: Vec, - len: usize, +#[derive(Debug)] +struct ShmRegistry { + next_id: c_int, + by_id: BTreeMap>, + by_key: BTreeMap, } -impl SharedMemory { - /// 分配 pages 个物理页作为共享段 - pub fn new(pages: usize) -> Self { - let frames = alloc_frames(pages).expect("unable to alloc shared memory"); - SharedMemory { - frames, - len: pages * PAGE_SIZE, +impl ShmRegistry { + fn new() -> Self { + Self { + next_id: 1, + by_id: BTreeMap::new(), + by_key: BTreeMap::new(), } } - /// 共享段字节数 - pub fn len(&self) -> usize { - self.len + fn allocate_id(&mut self) -> c_int { + let id = self.next_id; + self.next_id = self.next_id.saturating_add(1).max(1); + id + } + + fn get_or_create(&mut self, key: KeyT, size: usize, shmflg: c_int) -> Result { + if size == 0 { + return Err(EINVAL); + } + if shmflg & SHM_HUGETLB != 0 { + return Err(EINVAL); + } + + if key != IPC_PRIVATE + && let Some(id) = self.by_key.get(&key).copied() + { + let segment = self.by_id.get(&id).ok_or(ENOENT)?; + if segment.is_removed() { + return Err(ENOENT); + } + if shmflg & IPC_CREAT != 0 && shmflg & IPC_EXCL != 0 { + return Err(EEXIST); + } + if size > segment.size { + return Err(EINVAL); + } + return Ok(id); + } + + if key != IPC_PRIVATE && shmflg & IPC_CREAT == 0 { + return Err(ENOENT); + } + + let id = self.allocate_id(); + let segment = Arc::new(ShmSegment::new(id, key, size, shmflg)?); + if key != IPC_PRIVATE { + self.by_key.insert(key, id); + } + self.by_id.insert(id, segment); + Ok(id) + } + + fn get(&self, shmid: c_int) -> Result, c_int> { + let segment = self.by_id.get(&shmid).cloned().ok_or(EINVAL)?; + if segment.is_removed() { + return Err(EINVAL); + } + Ok(segment) + } + + fn mark_removed(&mut self, shmid: c_int) -> Result<(), c_int> { + let segment = self.by_id.get(&shmid).cloned().ok_or(EINVAL)?; + if segment.mark_removed() { + self.remove_segment(shmid); + } + Ok(()) + } + + fn remove_after_detach(&mut self, shmid: c_int) { + if let Some(segment) = self.by_id.get(&shmid) + && segment.is_removed() + && segment.inner.lock().attach_count == 0 + { + self.remove_segment(shmid); + } + } + + fn remove_segment(&mut self, shmid: c_int) { + if let Some(segment) = self.by_id.remove(&shmid) + && segment.key != IPC_PRIVATE + { + self.by_key.remove(&segment.key); + } } +} + +lazy_static! { + static ref SHM_REGISTRY: SpinLock = SpinLock::new(ShmRegistry::new()); +} + +pub fn shmget_segment(key: KeyT, size: usize, shmflg: c_int) -> Result { + SHM_REGISTRY.lock().get_or_create(key, size, shmflg) +} + +pub fn shm_segment(shmid: c_int) -> Result, c_int> { + SHM_REGISTRY.lock().get(shmid) +} + +pub fn shm_mark_removed(shmid: c_int) -> Result<(), c_int> { + SHM_REGISTRY.lock().mark_removed(shmid) +} - pub fn is_empty(&self) -> bool { - self.len == 0 +pub fn shm_detach_segment(segment: &Arc, pid: c_int) { + let should_remove = segment.mark_detached(pid); + if should_remove { + SHM_REGISTRY.lock().remove_after_detach(segment.id); } +} - /// 将共享段映射到当前进程用户空间 - /// 返回 - /// - Ok(usize) 成功;Err(PagingError) 失败 - pub fn map_to_user(self) -> Result { - let current = current_task(); - let mut task = current.lock(); - let space = task - .memory_space - .as_mut() - .expect("map_to_user_at: task has no user memory space"); +pub fn shm_check_access(segment: &ShmSegment, readonly: bool) -> Result<(), c_int> { + let cred = current_task().lock().credential; + if cred.is_root() { + return Ok(()); + } - let flags = UniversalPTEFlag::READABLE - | UniversalPTEFlag::WRITEABLE - | UniversalPTEFlag::USER_ACCESSIBLE - | UniversalPTEFlag::VALID; + let requested = if readonly { 0o4 } else { 0o6 }; + let available = if cred.euid == segment.uid || cred.euid == segment.cuid { + (segment.mode >> 6) & 0o7 + } else if cred.egid == segment.gid || cred.egid == segment.cgid { + (segment.mode >> 3) & 0o7 + } else { + segment.mode & 0o7 + }; - space.lock().mmap(0, self.len, flags) + if available & requested == requested { + return Ok(()); } + Err(EACCES) +} + +fn unix_time() -> i64 { + TimeSpec::now().tv_sec } diff --git a/os/src/kernel/syscall/dispatch.rs b/os/src/kernel/syscall/dispatch.rs index d9382c19..8b6f3a34 100644 --- a/os/src/kernel/syscall/dispatch.rs +++ b/os/src/kernel/syscall/dispatch.rs @@ -150,6 +150,12 @@ pub fn dispatch_syscall(frame: &mut impl SyscallFrame) { crate::kernel::syscall::numbers::SYS_GETTID => sys_gettid(frame), crate::kernel::syscall::numbers::SYS_SYSINFO => sys_sysinfo(frame), + // System V IPC + crate::kernel::syscall::numbers::SYS_SHMGET => sys_shmget(frame), + crate::kernel::syscall::numbers::SYS_SHMCTL => sys_shmctl(frame), + crate::kernel::syscall::numbers::SYS_SHMAT => sys_shmat(frame), + crate::kernel::syscall::numbers::SYS_SHMDT => sys_shmdt(frame), + // 网络 crate::kernel::syscall::numbers::SYS_SOCKET => sys_socket(frame), crate::kernel::syscall::numbers::SYS_SOCKETPAIR => sys_socketpair(frame), diff --git a/os/src/kernel/syscall/ipc.rs b/os/src/kernel/syscall/ipc.rs index 3681387c..802d4625 100644 --- a/os/src/kernel/syscall/ipc.rs +++ b/os/src/kernel/syscall/ipc.rs @@ -3,7 +3,19 @@ use alloc::sync::Arc; use crate::{ - kernel::current_task, + config::PAGE_SIZE, + ipc::{shm_check_access, shm_detach_segment, shm_mark_removed, shm_segment, shmget_segment}, + kernel::{ShmAttachment, current_memory_space, current_task}, + mm::{ + address::{PageNum, VA, Vpn, VpnRange}, + page_table::UniversalPTEFlag, + }, + uapi::{ + errno::{EFAULT, EINVAL}, + ipc::{ + IPC_RMID, IPC_STAT, KeyT, SHM_EXEC, SHM_RDONLY, SHM_REMAP, SHM_RND, SHMLBA, ShmIdDs, + }, + }, util::user_buffer::write_to_user, vfs::{FdFlags, File, FsError, OpenFlags, PipeFile}, }; @@ -76,3 +88,143 @@ pub fn pipe2(pipefd: *mut i32, flags: u32) -> isize { 0 } + +pub fn shmget(key: KeyT, size: usize, shmflg: i32) -> isize { + match shmget_segment(key, size, shmflg) { + Ok(id) => id as isize, + Err(errno) => -errno as isize, + } +} + +pub fn shmctl(shmid: i32, cmd: i32, buf: *mut ShmIdDs) -> isize { + match cmd { + IPC_STAT => { + if buf.is_null() { + return -EFAULT as isize; + } + let segment = match shm_segment(shmid) { + Ok(segment) => segment, + Err(errno) => return -errno as isize, + }; + write_to_user(buf, segment.stat()); + 0 + } + IPC_RMID => match shm_mark_removed(shmid) { + Ok(()) => 0, + Err(errno) => -errno as isize, + }, + _ => -EINVAL as isize, + } +} + +pub fn shmat(shmid: i32, shmaddr: *const u8, shmflg: i32) -> isize { + let unsupported = shmflg & !(SHM_RDONLY | SHM_RND | SHM_REMAP | SHM_EXEC); + if unsupported != 0 { + return -EINVAL as isize; + } + if shmflg & SHM_REMAP != 0 && shmaddr.is_null() { + return -EINVAL as isize; + } + + let readonly = shmflg & SHM_RDONLY != 0; + let segment = match shm_segment(shmid) { + Ok(segment) => segment, + Err(errno) => return -errno as isize, + }; + if let Err(errno) = shm_check_access(&segment, readonly) { + return -errno as isize; + } + + let len = segment.pages() * PAGE_SIZE; + let hint = shmaddr as usize; + let start = if hint == 0 { + let space = current_memory_space(); + match space.lock().find_free_region(len, PAGE_SIZE) { + Some(addr) => addr.as_usize(), + None => return -crate::uapi::errno::ENOMEM as isize, + } + } else if shmflg & SHM_RND != 0 { + hint & !(SHMLBA - 1) + } else if !hint.is_multiple_of(PAGE_SIZE) { + return -EINVAL as isize; + } else { + hint + }; + let end = match start.checked_add(len) { + Some(end) => end, + None => return -EINVAL as isize, + }; + + let start_vpn = Vpn::from_addr_floor(VA::from_usize(start)); + let end_vpn = Vpn::from_addr_ceil(VA::from_usize(end)); + let range = VpnRange::new(start_vpn, end_vpn); + + let mut flags = + UniversalPTEFlag::VALID | UniversalPTEFlag::READABLE | UniversalPTEFlag::USER_ACCESSIBLE; + if !readonly { + flags |= UniversalPTEFlag::WRITEABLE; + } + if shmflg & SHM_EXEC != 0 { + flags |= UniversalPTEFlag::EXECUTABLE; + } + + let memory_space = current_memory_space(); + let mut space = memory_space.lock(); + if shmflg & SHM_REMAP != 0 { + if let Err(_) = space.munmap(VA::from_usize(start), len) { + return -EINVAL as isize; + } + } + if let Err(_) = space.insert_shared_area(range, flags, segment.clone()) { + return -EINVAL as isize; + } + drop(space); + + let task = current_task(); + let (pid, table) = { + let t = task.lock(); + (t.pid as i32, t.shm_attachments.clone()) + }; + if let Some(old) = table.lock().remove(&start) { + shm_detach_segment(&old.segment, pid); + } + segment.mark_attached(pid); + table.lock().insert(start, ShmAttachment { + addr: start, + len, + segment, + }); + + start as isize +} + +pub fn shmdt(shmaddr: *const u8) -> isize { + let addr = shmaddr as usize; + if !addr.is_multiple_of(PAGE_SIZE) { + return -EINVAL as isize; + } + + let task = current_task(); + let (pid, table, attachment) = { + let t = task.lock(); + let pid = t.pid as i32; + let table = t.shm_attachments.clone(); + let attachment = match table.lock().remove(&addr) { + Some(attachment) => attachment, + None => return -EINVAL as isize, + }; + (pid, table, attachment) + }; + + let memory_space = current_memory_space(); + if let Err(_) = memory_space + .lock() + .munmap(VA::from_usize(attachment.addr), attachment.len) + { + table.lock().insert(addr, attachment); + return -EINVAL as isize; + } + + shm_detach_segment(&attachment.segment, pid); + 0 +} diff --git a/os/src/kernel/syscall/mod.rs b/os/src/kernel/syscall/mod.rs index 3e914700..04c0e55e 100644 --- a/os/src/kernel/syscall/mod.rs +++ b/os/src/kernel/syscall/mod.rs @@ -27,6 +27,7 @@ use crate::{ fs::LinuxStatFs, futex::RobustListHead, iovec::IoVec, + ipc::{KeyT, ShmIdDs}, resource::{Rlimit, Rusage}, sched::SchedParam, signal::{SigInfoT, SignalAction}, @@ -250,6 +251,12 @@ impl_syscall!(sys_getegid, getegid, ()); impl_syscall!(sys_gettid, gettid, ()); impl_syscall!(sys_sysinfo, sysinfo, (*mut SysInfo)); +// System V IPC +impl_syscall!(sys_shmget, shmget, (KeyT, usize, i32)); +impl_syscall!(sys_shmctl, shmctl, (i32, i32, *mut ShmIdDs)); +impl_syscall!(sys_shmat, shmat, (i32, *const u8, i32)); +impl_syscall!(sys_shmdt, shmdt, (*const u8)); + // 网络 (Networking/Sockets) impl_syscall!(sys_socket, socket, (i32, i32, i32)); impl_syscall!(sys_socketpair, socketpair, (i32, i32, i32, *mut i32)); diff --git a/os/src/kernel/syscall/numbers.rs b/os/src/kernel/syscall/numbers.rs index 7a78edc0..47a57f97 100644 --- a/os/src/kernel/syscall/numbers.rs +++ b/os/src/kernel/syscall/numbers.rs @@ -123,6 +123,12 @@ pub const SYS_GETEGID: usize = 177; pub const SYS_GETTID: usize = 178; pub const SYS_SYSINFO: usize = 179; +// ---- System V IPC ---- +pub const SYS_SHMGET: usize = 194; +pub const SYS_SHMCTL: usize = 195; +pub const SYS_SHMAT: usize = 196; +pub const SYS_SHMDT: usize = 197; + // ---- 网络/Socket ---- pub const SYS_SOCKET: usize = 198; pub const SYS_SOCKETPAIR: usize = 199; diff --git a/os/src/kernel/syscall/task/clone_ops.rs b/os/src/kernel/syscall/task/clone_ops.rs index 502d5447..8c8ba2c4 100644 --- a/os/src/kernel/syscall/task/clone_ops.rs +++ b/os/src/kernel/syscall/task/clone_ops.rs @@ -51,6 +51,7 @@ pub fn clone( sched_priority, sched_reset_on_fork, cpu_affinity, + shm_attachments, ) = { let _guard = crate::sync::PreemptGuard::new(); let cpu = current_cpu(); @@ -76,6 +77,11 @@ pub fn clone( task.sched_priority, task.sched_reset_on_fork, task.cpu_affinity, + if requested_flags.contains(CloneFlags::THREAD) { + task.shm_attachments.clone() + } else { + Arc::new(SpinLock::new(task.shm_attachments.lock().clone())) + }, ) }; let exit_signal = requested_flags.get_exit_signal(); @@ -156,6 +162,12 @@ pub fn clone( if child_task.cpu_affinity == 0 { child_task.cpu_affinity = crate::kernel::online_cpu_mask(); } + child_task.shm_attachments = shm_attachments; + if !requested_flags.contains(CloneFlags::THREAD) { + for attachment in child_task.shm_attachments.lock().values() { + attachment.segment.mark_attached(pid as c_int); + } + } if requested_flags.contains(CloneFlags::CHILD_SETTID) { unsafe { diff --git a/os/src/kernel/syscall/task/exec_ops.rs b/os/src/kernel/syscall/task/exec_ops.rs index 80a02aed..e0b68695 100644 --- a/os/src/kernel/syscall/task/exec_ops.rs +++ b/os/src/kernel/syscall/task/exec_ops.rs @@ -229,6 +229,7 @@ fn do_execve_switch( let task = current_task(); task.lock().fd_table.close_exec(); + crate::kernel::task::detach_all_shm(task.clone()); // 换掉当前任务的地址空间,e.g. 切换 satp { diff --git a/os/src/kernel/task/mod.rs b/os/src/kernel/task/mod.rs index 82fa9ce0..827e468c 100644 --- a/os/src/kernel/task/mod.rs +++ b/os/src/kernel/task/mod.rs @@ -2,7 +2,7 @@ //! //! 包含任务的创建、调度、终止等功能 //! 并由任务管理器维护所有任务的信息 -use core::sync::atomic::Ordering; +use core::{ffi::c_int, sync::atomic::Ordering}; mod cap; mod cred; @@ -30,12 +30,14 @@ pub use task_manager::{TASK_MANAGER, TaskManagerTrait}; pub use task_state::TaskState; pub use task_struct::FsStruct; pub use task_struct::SharedTask; +pub use task_struct::ShmAttachment; pub use task_struct::Task as TaskStruct; pub use work_queue::*; use alloc::sync::Arc; -use crate::mm::memory_space::MemorySpace; +use crate::ipc::shm_detach_segment; +use crate::mm::{address::VA, memory_space::MemorySpace}; use crate::sync::SpinLock; use crate::uapi::signal::NUM_SIGCHLD; use crate::{ @@ -89,6 +91,7 @@ pub(crate) fn terminate_task(code: usize) -> ! { let leader = TASK_MANAGER.lock().get_task(pid); if let Some(leader) = leader { if leader.lock().is_process() { + cleanup_process_resources_on_exit(leader.clone()); exit_process(leader, exit_code); } else { TASK_MANAGER.lock().exit_task(task, exit_code); @@ -146,10 +149,52 @@ pub fn cleanup_process_resources_on_exit(task: SharedTask) { drop(file); } - // 3) 释放用户地址空间。 + // 3) 分离 SysV shared memory 映射,更新全局 registry 的 attach 计数。 + detach_all_shm(task.clone()); + + // 4) 释放用户地址空间。 task.lock().memory_space = None; } +/// 分离一个进程持有的所有 SysV shared memory 映射。 +/// +/// 调用方可以在 exit/execve 清理路径中使用。该函数会先从 Task 中取走 +/// attachment 元数据,再释放 task 锁后执行 munmap 和 registry 更新,避免 +/// task -> address_space -> shm registry 的嵌套锁长期持有。 +pub fn detach_all_shm(task: SharedTask) { + let (pid, memory_space, attachment_table) = { + let t = task.lock(); + ( + t.pid as c_int, + t.memory_space.clone(), + t.shm_attachments.clone(), + ) + }; + + let attachments = core::mem::take(&mut *attachment_table.lock()); + if attachments.is_empty() { + return; + } + + if let Some(memory_space) = memory_space { + let mut space = memory_space.lock(); + for attachment in attachments.values() { + if let Err(err) = space.munmap(VA::from_usize(attachment.addr), attachment.len) { + crate::pr_warn!( + "detach_all_shm: failed to unmap shmid {} at 0x{:x}: {:?}", + attachment.segment.id, + attachment.addr, + err + ); + } + } + } + + for attachment in attachments.values() { + shm_detach_segment(&attachment.segment, pid); + } +} + /// 尝试获取当前task /// # 返回值:当前任务的SharedTask,如果没有则返回None pub fn try_current_task() -> Option { diff --git a/os/src/kernel/task/task_struct.rs b/os/src/kernel/task/task_struct.rs index 7185082f..dc277a77 100644 --- a/os/src/kernel/task/task_struct.rs +++ b/os/src/kernel/task/task_struct.rs @@ -4,14 +4,14 @@ #![allow(dead_code)] use core::sync::atomic::{AtomicPtr, Ordering}; -use alloc::{string::String, sync::Arc, vec::Vec}; +use alloc::{collections::btree_map::BTreeMap, string::String, sync::Arc, vec::Vec}; use crate::{ arch::{ HwTrapFrame, TrapFrame, kernel::{context::Context, task::setup_exec_stack_layout}, }, - ipc::{SignalHandlerTable, SignalPending}, + ipc::{ShmSegment, SignalHandlerTable, SignalPending}, kernel::{ WaitQueue, task::{forkret, task_state::TaskState}, @@ -36,6 +36,15 @@ use crate::{ /// 用于在多个地方引用同一个任务实例 pub type SharedTask = Arc>; +#[derive(Debug, Clone)] +pub struct ShmAttachment { + pub addr: usize, + pub len: usize, + pub segment: Arc, +} + +pub type ShmAttachmentTable = Arc>>; + /// 任务 /// 存放任务的核心信息 /// 其中的信息可以分为几大类: @@ -144,6 +153,8 @@ pub struct Task { pub fd_table: Arc, /// 文件系统信息 pub fs: Arc>, + /// 当前进程附加的 SysV shared memory 段,按 attach 地址索引。 + pub shm_attachments: ShmAttachmentTable, } /// 文件系统信息相关结构体 @@ -443,6 +454,7 @@ impl Task { umask: 0o022, fd_table, fs, + shm_attachments: Arc::new(SpinLock::new(BTreeMap::new())), } } diff --git a/os/src/mm/memory_space/mapping_area/map_ops.rs b/os/src/mm/memory_space/mapping_area/map_ops.rs index 97e2e4b7..18ef827a 100644 --- a/os/src/mm/memory_space/mapping_area/map_ops.rs +++ b/os/src/mm/memory_space/mapping_area/map_ops.rs @@ -34,6 +34,7 @@ impl MappingArea { TrackedFrames::Multiple(v) => v.len(), }) .sum(), + MapType::Shared => self.vpn_range.len(), _ => 0, } } @@ -60,6 +61,25 @@ impl MappingArea { permission, frames: BTreeMap::new(), file, + shared: None, + shared_page_offset: 0, + } + } + + pub fn new_shared( + vpn_range: VpnRange, + permission: UniversalPTEFlag, + segment: Arc, + ) -> Self { + MappingArea { + vpn_range, + area_type: AreaType::UserMmap, + map_type: MapType::Shared, + permission, + frames: BTreeMap::new(), + file: None, + shared: Some(segment), + shared_page_offset: 0, } } @@ -97,6 +117,17 @@ impl MappingArea { // PROT_NONE:不建立页表映射 return Ok(()); } + MapType::Shared => { + let segment = self + .shared + .as_ref() + .ok_or(page_table::PagingError::InvalidAddress)?; + let page_idx = + self.shared_page_offset + vpn.as_usize() - self.vpn_range.start().as_usize(); + segment + .ppn_at(page_idx) + .ok_or(page_table::PagingError::InvalidAddress)? + } }; page_table.map_with_batch(vpn, ppn, PageSize::Size4K, self.permission, batch)?; diff --git a/os/src/mm/memory_space/mapping_area/mod.rs b/os/src/mm/memory_space/mapping_area/mod.rs index 1c423b99..ef830012 100644 --- a/os/src/mm/memory_space/mapping_area/mod.rs +++ b/os/src/mm/memory_space/mapping_area/mod.rs @@ -1,8 +1,9 @@ -use alloc::collections::btree_map::BTreeMap; +use alloc::{collections::btree_map::BTreeMap, sync::Arc}; use core::cmp::min; use crate::arch::mm::TlbBatchContext; use crate::config::PAGE_SIZE; +use crate::ipc::ShmSegment; use crate::mm::address::{PA, PageNum, Ppn, UsizeConvert, Vpn, VpnRange}; use crate::mm::frame_allocator::{TrackedFrames, alloc_frame}; use crate::mm::memory_space::MmapFile; @@ -25,6 +26,8 @@ pub enum MapType { /// - mmap(PROT_NONE) 需要“成功占位”但不应该映射可访问页表项 /// - mprotect(PROT_NONE) 会把原有页表映射解除并转为 Reserved Reserved, + /// SysV shared memory segment mapping. + Shared, } /// 内存区域的类型 @@ -66,6 +69,12 @@ pub struct MappingArea { /// 文件映射信息(如果是文件映射) file: Option, + + /// SysV 共享内存段(如果是共享内存映射) + shared: Option>, + + /// `vpn_range.start()` 对应共享段内的页偏移。 + shared_page_offset: usize, } mod file_ops; diff --git a/os/src/mm/memory_space/mapping_area/split_ops.rs b/os/src/mm/memory_space/mapping_area/split_ops.rs index 2742d5d6..5b5f2547 100644 --- a/os/src/mm/memory_space/mapping_area/split_ops.rs +++ b/os/src/mm/memory_space/mapping_area/split_ops.rs @@ -18,6 +18,8 @@ impl MappingArea { prot: f.prot, flags: f.flags, }), + shared: self.shared.clone(), + shared_page_offset: self.shared_page_offset, } } @@ -180,6 +182,8 @@ impl MappingArea { self.permission, left_file, ); + left_area.shared = self.shared.clone(); + left_area.shared_page_offset = self.shared_page_offset; let mut right_area = MappingArea::new( right_range, @@ -188,6 +192,8 @@ impl MappingArea { self.permission, right_file, ); + right_area.shared = self.shared.clone(); + right_area.shared_page_offset = self.shared_page_offset + left_pages; // 分配帧:遍历原区域的 frames,根据 VPN 分配到左右区域 - 手动迭代并清空 let vpns: alloc::vec::Vec = self.frames.keys().copied().collect(); @@ -291,6 +297,10 @@ impl MappingArea { } else { None }; + if let Some(ref mut l) = left_area { + l.shared = self.shared.clone(); + l.shared_page_offset = self.shared_page_offset; + } let mut middle_area = MappingArea::new( middle_range, @@ -303,6 +313,8 @@ impl MappingArea { new_perm, middle_file, ); + middle_area.shared = self.shared.clone(); + middle_area.shared_page_offset = self.shared_page_offset + left_pages; let mut right_area = if change_end < area_end { Some(MappingArea::new( @@ -315,6 +327,10 @@ impl MappingArea { } else { None }; + if let Some(ref mut r) = right_area { + r.shared = self.shared.clone(); + r.shared_page_offset = self.shared_page_offset + left_pages + middle_pages; + } match self.map_type { MapType::Direct => return Err(page_table::PagingError::UnsupportedMapType), @@ -385,6 +401,18 @@ impl MappingArea { // Reserved + PROT_NONE:无需页表操作 } } + MapType::Shared => { + if !wants_mapping { + return Err(page_table::PagingError::UnsupportedMapType); + } + TlbBatchContext::execute(|batch| { + for vpn in VpnRange::new(change_start, change_end) { + page_table.update_flags_with_batch(vpn, new_perm, Some(batch))?; + } + Ok::<(), page_table::PagingError>(()) + })?; + middle_area.map_type = MapType::Shared; + } } let mut out = alloc::vec::Vec::new(); @@ -448,6 +476,7 @@ impl MappingArea { } else if unmap_start == area_start { // 情况 2: 解除映射了前半部分,保留 [unmap_end, area_end) self.vpn_range = VpnRange::new(unmap_end, area_end); + self.shared_page_offset += unmap_end.as_usize() - area_start.as_usize(); Ok(Some((self, None))) } else if unmap_end == area_end { // 情况 3: 解除映射了后半部分,保留 [area_start, unmap_start) @@ -495,6 +524,10 @@ impl MappingArea { self.permission, right_file, ); + left_area.shared = self.shared.clone(); + left_area.shared_page_offset = self.shared_page_offset; + right_area.shared = self.shared.clone(); + right_area.shared_page_offset = self.shared_page_offset + left_pages + middle_pages; // 分配 frames - 手动迭代并清空 let vpns: alloc::vec::Vec = self.frames.keys().copied().collect(); diff --git a/os/src/mm/memory_space/space/address_space.rs b/os/src/mm/memory_space/space/address_space.rs index 9e7337fb..7f7662b3 100644 --- a/os/src/mm/memory_space/space/address_space.rs +++ b/os/src/mm/memory_space/space/address_space.rs @@ -265,6 +265,18 @@ impl MemorySpace { Ok(()) } + /// 插入 SysV shared memory 映射区域。 + pub fn insert_shared_area( + &mut self, + vpn_range: VpnRange, + flags: UniversalPTEFlag, + segment: alloc::sync::Arc, + ) -> Result<(), PagingError> { + let area = MappingArea::new_shared(vpn_range, flags, segment); + self.insert_area(area)?; + Ok(()) + } + /// 插入一个帧映射区域,并可选择复制数据(带偏移量) pub fn insert_framed_area_with_offset( &mut self, @@ -348,6 +360,11 @@ impl MemorySpace { let new_area = area.clone_metadata(); new_space.areas.push(new_area); } + MapType::Shared => { + let mut new_area = area.clone_metadata(); + new_area.map(&mut new_space.page_table)?; + new_space.areas.push(new_area); + } } } diff --git a/os/src/mm/memory_space/space/mmap_ops.rs b/os/src/mm/memory_space/space/mmap_ops.rs index b0729d31..556a8e9c 100644 --- a/os/src/mm/memory_space/space/mmap_ops.rs +++ b/os/src/mm/memory_space/space/mmap_ops.rs @@ -399,9 +399,11 @@ impl MemorySpace { for (idx, area) in self.areas.iter().enumerate() { if area.vpn_range().overlaps(&change_range) { - // 只处理 Framed / Reserved,Direct 映射不允许修改权限 + // 只处理 Framed / Reserved / Shared,Direct 映射不允许修改权限 match area.map_type() { - MapType::Framed | MapType::Reserved => affected_indices.push(idx), + MapType::Framed | MapType::Reserved | MapType::Shared => { + affected_indices.push(idx) + } MapType::Direct => return Err(PagingError::UnsupportedMapType), } } @@ -412,12 +414,15 @@ impl MemorySpace { return Err(PagingError::InvalidAddress); } - // 验证所有需要修改的 VPN 都在某个 Framed 区域中 + // 验证所有需要修改的 VPN 都在某个可修改的用户区域中 for vpn in start_vpn.as_usize()..end_vpn.as_usize() { let vpn = Vpn::from_usize(vpn); let found = self.areas.iter().any(|area| { area.vpn_range().contains(vpn) - && matches!(area.map_type(), MapType::Framed | MapType::Reserved) + && matches!( + area.map_type(), + MapType::Framed | MapType::Reserved | MapType::Shared + ) }); if !found { return Err(PagingError::InvalidAddress); diff --git a/os/src/uapi/ipc.rs b/os/src/uapi/ipc.rs new file mode 100644 index 00000000..c5dbb701 --- /dev/null +++ b/os/src/uapi/ipc.rs @@ -0,0 +1,56 @@ +//! System V IPC UAPI subset. + +use core::ffi::{c_int, c_long, c_uint, c_ulong, c_ushort}; + +pub type KeyT = c_int; + +pub const IPC_PRIVATE: KeyT = 0; + +pub const IPC_CREAT: c_int = 0o1000; +pub const IPC_EXCL: c_int = 0o2000; +pub const IPC_NOWAIT: c_int = 0o4000; + +pub const IPC_RMID: c_int = 0; +pub const IPC_SET: c_int = 1; +pub const IPC_STAT: c_int = 2; +pub const IPC_INFO: c_int = 3; + +pub const SHM_HUGETLB: c_int = 0o4000; +pub const SHM_NORESERVE: c_int = 0o10000; + +pub const SHM_RDONLY: c_int = 0o10000; +pub const SHM_RND: c_int = 0o20000; +pub const SHM_REMAP: c_int = 0o40000; +pub const SHM_EXEC: c_int = 0o100000; + +pub const SHMLBA: usize = crate::config::PAGE_SIZE; + +#[repr(C)] +#[derive(Debug, Clone, Copy, Default)] +pub struct IpcPerm { + pub key: KeyT, + pub uid: c_uint, + pub gid: c_uint, + pub cuid: c_uint, + pub cgid: c_uint, + pub mode: c_uint, + pub seq: c_ushort, + pub __pad2: c_ushort, + pub __unused1: c_ulong, + pub __unused2: c_ulong, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Default)] +pub struct ShmIdDs { + pub shm_perm: IpcPerm, + pub shm_segsz: usize, + pub shm_atime: c_long, + pub shm_dtime: c_long, + pub shm_ctime: c_long, + pub shm_cpid: c_int, + pub shm_lpid: c_int, + pub shm_nattch: c_ulong, + pub __unused4: c_ulong, + pub __unused5: c_ulong, +} diff --git a/os/src/uapi/mod.rs b/os/src/uapi/mod.rs index 0bd1d49b..cec2ea18 100644 --- a/os/src/uapi/mod.rs +++ b/os/src/uapi/mod.rs @@ -10,6 +10,7 @@ pub mod fs; pub mod futex; pub mod ioctl; pub mod iovec; +pub mod ipc; pub mod log; pub mod mm; pub mod reboot; From d13d572c4b2aeecfffdce51421ba943aba51fcf4 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 23:41:06 +0800 Subject: [PATCH 19/28] fix(fs): treat mkdir root as existing --- os/src/kernel/syscall/fs/path_ops.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/os/src/kernel/syscall/fs/path_ops.rs b/os/src/kernel/syscall/fs/path_ops.rs index 5b4af508..86279637 100644 --- a/os/src/kernel/syscall/fs/path_ops.rs +++ b/os/src/kernel/syscall/fs/path_ops.rs @@ -100,11 +100,10 @@ pub fn mkdirat(dirfd: i32, pathname: *const c_char, mode: u32) -> isize { Ok(s) => s, Err(e) => return e.to_errno(), }; - let path_str = if path_str.bytes().all(|b| b == b'/') { - path_str - } else { - path_str.trim_end_matches('/').into() - }; + if !path_str.is_empty() && path_str.bytes().all(|b| b == b'/') { + return FsError::AlreadyExists.to_errno(); + } + let path_str = alloc::string::String::from(path_str.trim_end_matches('/')); // 分割路径为目录和文件名 let (dir_path, dirname) = match split_parent_preserving_basename(&path_str) { From db3e24f69bb43000ba74623c60abf0ea9f311114 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 23:49:07 +0800 Subject: [PATCH 20/28] fix(vfs): preserve mounted root paths --- os/src/vfs/dentry.rs | 25 +++++++++++++++++++++++-- os/src/vfs/mount.rs | 14 ++++++++++++-- os/src/vfs/tests/dentry.rs | 17 +++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/os/src/vfs/dentry.rs b/os/src/vfs/dentry.rs index 67d49449..63dad6d7 100644 --- a/os/src/vfs/dentry.rs +++ b/os/src/vfs/dentry.rs @@ -132,6 +132,9 @@ pub struct Dentry { /// 如果此 dentry 是挂载点,指向挂载的根 dentry mount_point: SpinLock>>, + + /// 如果此 dentry 是某个挂载文件系统的根,指向外层挂载点 dentry + mounted_on: SpinLock>>, } impl fmt::Debug for Dentry { @@ -159,6 +162,7 @@ impl Dentry { parent: SpinLock::new(Weak::new()), children: SpinLock::new(BTreeMap::new()), mount_point: SpinLock::new(None), + mounted_on: SpinLock::new(None), }); dentry.inode.set_dentry(Arc::downgrade(&dentry)); @@ -197,11 +201,22 @@ impl Dentry { let mut components = alloc::vec::Vec::new(); let mut current_name = self.name.clone(); let mut current_parent = self.parent(); + let mut current_mounted_on = self.mounted_on.lock().as_ref().and_then(Weak::upgrade); - // 向上遍历到根目录 + // 向上遍历到全局根目录。挂载文件系统的 root dentry 名字也是 "/", + // 但它的全局路径应继续通过外层挂载点回溯。 loop { - // 根目录的名字是 "/" if current_name == "/" { + if let Some(mount_parent) = current_mounted_on { + current_name = mount_parent.name.clone(); + current_parent = mount_parent.parent(); + current_mounted_on = mount_parent + .mounted_on + .lock() + .as_ref() + .and_then(Weak::upgrade); + continue; + } break; } @@ -212,6 +227,7 @@ impl Dentry { Some(parent) => { current_name = parent.name.clone(); current_parent = parent.parent(); + current_mounted_on = parent.mounted_on.lock().as_ref().and_then(Weak::upgrade); } None => break, // 到达根或孤立节点 } @@ -241,6 +257,11 @@ impl Dentry { pub fn get_mount(&self) -> Option> { self.mount_point.lock().as_ref()?.upgrade() } + + /// 标记此 dentry 是挂载文件系统根,挂载在外层的 `mount_parent` 上。 + pub fn set_mounted_on(&self, mount_parent: &Arc) { + *self.mounted_on.lock() = Some(Arc::downgrade(mount_parent)); + } } // 全局 dentry 缓存实例 diff --git a/os/src/vfs/mount.rs b/os/src/vfs/mount.rs index b8ea86a8..cf38f62c 100644 --- a/os/src/vfs/mount.rs +++ b/os/src/vfs/mount.rs @@ -313,12 +313,20 @@ impl MountTable { flags: MountFlags, device: Option, ) -> Result<(), FsError> { - use crate::vfs::normalize_path; + use crate::vfs::{normalize_path, vfs_lookup}; let normalized_path = normalize_path(path); + let mount_parent = if normalized_path == "/" { + None + } else { + vfs_lookup(&normalized_path).ok() + }; // 创建挂载点 let mount_point = MountPoint::new(fs, normalized_path.clone(), flags, device); + if let Some(parent) = mount_parent.as_ref() { + mount_point.root.set_mounted_on(parent); + } // 添加到挂载栈 let mut mounts = self.mounts.lock(); @@ -328,7 +336,9 @@ impl MountTable { .push(mount_point.clone()); // 如果挂载点的 dentry 已经存在于缓存中,更新其挂载信息 - if let Some(dentry) = crate::vfs::DENTRY_CACHE.lookup(&normalized_path) { + if let Some(dentry) = + mount_parent.or_else(|| crate::vfs::DENTRY_CACHE.lookup(&normalized_path)) + { dentry.set_mount(&mount_point.root); } diff --git a/os/src/vfs/tests/dentry.rs b/os/src/vfs/tests/dentry.rs index d583272c..596c5cd0 100644 --- a/os/src/vfs/tests/dentry.rs +++ b/os/src/vfs/tests/dentry.rs @@ -83,6 +83,23 @@ test_case!(test_dentry_full_path, { kassert!(path == "/dir1/dir2/file.txt"); }); +test_case!(test_mounted_root_full_path, { + let fs = SimpleFs::new(); + let root_inode = fs.root_inode(); + + let root = Dentry::new("/".to_string(), root_inode.clone()); + let tmp = Dentry::new("tmp".to_string(), root_inode.clone()); + root.add_child(tmp.clone()); + + let mounted_root = Dentry::new("/".to_string(), root_inode.clone()); + mounted_root.set_mounted_on(&tmp); + let tests = Dentry::new("tests".to_string(), root_inode.clone()); + mounted_root.add_child(tests.clone()); + + kassert!(mounted_root.full_path() == "/tmp"); + kassert!(tests.full_path() == "/tmp/tests"); +}); + test_case!(test_dentry_multiple_children, { let fs = SimpleFs::new(); let root_inode = fs.root_inode(); From e9890ac7e6336a2f69a612bea5d091606b26e2f8 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 00:07:07 +0800 Subject: [PATCH 21/28] fix(ipc): refine sysv shm removal semantics --- os/src/ipc/shared_memory.rs | 83 ++++++++++++++++++++++++------------ os/src/kernel/syscall/ipc.rs | 3 ++ os/src/uapi/ipc.rs | 1 + 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/os/src/ipc/shared_memory.rs b/os/src/ipc/shared_memory.rs index 690b72c8..a21e00dd 100644 --- a/os/src/ipc/shared_memory.rs +++ b/os/src/ipc/shared_memory.rs @@ -7,15 +7,15 @@ use lazy_static::lazy_static; use crate::{ config::PAGE_SIZE, - kernel::current_task, + kernel::{Capabilities, current_task}, mm::{ address::{PageNum, Ppn}, frame_allocator::{FrameTracker, alloc_frames}, }, sync::SpinLock, uapi::{ - errno::{EACCES, EEXIST, EINVAL, ENOENT, ENOMEM}, - ipc::{IPC_CREAT, IPC_EXCL, IPC_PRIVATE, IpcPerm, KeyT, SHM_HUGETLB, ShmIdDs}, + errno::{EACCES, EEXIST, EINVAL, ENOENT, ENOMEM, EPERM}, + ipc::{IPC_CREAT, IPC_EXCL, IPC_PRIVATE, IpcPerm, KeyT, SHM_DEST, SHM_HUGETLB, ShmIdDs}, time::TimeSpec, }, }; @@ -113,6 +113,11 @@ impl ShmSegment { pub fn stat(&self) -> ShmIdDs { let inner = self.inner.lock(); + let mode = if inner.marked_removed { + self.mode | SHM_DEST as u32 + } else { + self.mode + }; ShmIdDs { shm_perm: IpcPerm { key: self.key, @@ -120,7 +125,7 @@ impl ShmSegment { gid: self.gid, cuid: self.cuid, cgid: self.cgid, - mode: self.mode, + mode, seq: 0, ..IpcPerm::default() }, @@ -159,32 +164,36 @@ impl ShmRegistry { } fn get_or_create(&mut self, key: KeyT, size: usize, shmflg: c_int) -> Result { - if size == 0 { - return Err(EINVAL); - } if shmflg & SHM_HUGETLB != 0 { return Err(EINVAL); } - if key != IPC_PRIVATE - && let Some(id) = self.by_key.get(&key).copied() - { - let segment = self.by_id.get(&id).ok_or(ENOENT)?; - if segment.is_removed() { - return Err(ENOENT); - } - if shmflg & IPC_CREAT != 0 && shmflg & IPC_EXCL != 0 { - return Err(EEXIST); - } - if size > segment.size { - return Err(EINVAL); + if key != IPC_PRIVATE { + if let Some(id) = self.by_key.get(&key).copied() { + let segment = self.by_id.get(&id).cloned(); + if let Some(segment) = segment + && !segment.is_removed() + { + if shmflg & IPC_CREAT != 0 && shmflg & IPC_EXCL != 0 { + return Err(EEXIST); + } + if size > segment.size { + return Err(EINVAL); + } + let requested = (shmflg as u32) & 0o666; + shm_check_mode_access(&segment, requested)?; + return Ok(id); + } + self.by_key.remove(&key); } - return Ok(id); } if key != IPC_PRIVATE && shmflg & IPC_CREAT == 0 { return Err(ENOENT); } + if size == 0 { + return Err(EINVAL); + } let id = self.allocate_id(); let segment = Arc::new(ShmSegment::new(id, key, size, shmflg)?); @@ -196,15 +205,15 @@ impl ShmRegistry { } fn get(&self, shmid: c_int) -> Result, c_int> { - let segment = self.by_id.get(&shmid).cloned().ok_or(EINVAL)?; - if segment.is_removed() { - return Err(EINVAL); - } - Ok(segment) + self.by_id.get(&shmid).cloned().ok_or(EINVAL) } fn mark_removed(&mut self, shmid: c_int) -> Result<(), c_int> { let segment = self.by_id.get(&shmid).cloned().ok_or(EINVAL)?; + shm_check_control(&segment)?; + if segment.key != IPC_PRIVATE && self.by_key.get(&segment.key).copied() == Some(shmid) { + self.by_key.remove(&segment.key); + } if segment.mark_removed() { self.remove_segment(shmid); } @@ -223,6 +232,7 @@ impl ShmRegistry { fn remove_segment(&mut self, shmid: c_int) { if let Some(segment) = self.by_id.remove(&shmid) && segment.key != IPC_PRIVATE + && self.by_key.get(&segment.key).copied() == Some(shmid) { self.by_key.remove(&segment.key); } @@ -253,12 +263,20 @@ pub fn shm_detach_segment(segment: &Arc, pid: c_int) { } pub fn shm_check_access(segment: &ShmSegment, readonly: bool) -> Result<(), c_int> { + let requested = if readonly { 0o4 } else { 0o6 }; + shm_check_mode_access(segment, requested) +} + +fn shm_check_mode_access(segment: &ShmSegment, requested: u32) -> Result<(), c_int> { + if requested == 0 { + return Ok(()); + } + let cred = current_task().lock().credential; - if cred.is_root() { + if cred.capabilities.has(Capabilities::IPC_OWNER) { return Ok(()); } - let requested = if readonly { 0o4 } else { 0o6 }; let available = if cred.euid == segment.uid || cred.euid == segment.cuid { (segment.mode >> 6) & 0o7 } else if cred.egid == segment.gid || cred.egid == segment.cgid { @@ -273,6 +291,17 @@ pub fn shm_check_access(segment: &ShmSegment, readonly: bool) -> Result<(), c_in Err(EACCES) } +fn shm_check_control(segment: &ShmSegment) -> Result<(), c_int> { + let cred = current_task().lock().credential; + if cred.euid == segment.uid + || cred.euid == segment.cuid + || cred.capabilities.has(Capabilities::IPC_OWNER) + { + return Ok(()); + } + Err(EPERM) +} + fn unix_time() -> i64 { TimeSpec::now().tv_sec } diff --git a/os/src/kernel/syscall/ipc.rs b/os/src/kernel/syscall/ipc.rs index 802d4625..fd321252 100644 --- a/os/src/kernel/syscall/ipc.rs +++ b/os/src/kernel/syscall/ipc.rs @@ -106,6 +106,9 @@ pub fn shmctl(shmid: i32, cmd: i32, buf: *mut ShmIdDs) -> isize { Ok(segment) => segment, Err(errno) => return -errno as isize, }; + if let Err(errno) = shm_check_access(&segment, true) { + return -errno as isize; + } write_to_user(buf, segment.stat()); 0 } diff --git a/os/src/uapi/ipc.rs b/os/src/uapi/ipc.rs index c5dbb701..ca84fdba 100644 --- a/os/src/uapi/ipc.rs +++ b/os/src/uapi/ipc.rs @@ -18,6 +18,7 @@ pub const IPC_INFO: c_int = 3; pub const SHM_HUGETLB: c_int = 0o4000; pub const SHM_NORESERVE: c_int = 0o10000; +pub const SHM_DEST: c_int = 0o1000; pub const SHM_RDONLY: c_int = 0o10000; pub const SHM_RND: c_int = 0o20000; pub const SHM_REMAP: c_int = 0o40000; From 9a7f7f03825bcafae33902bde5f02fc0625ac2ac Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 00:34:26 +0800 Subject: [PATCH 22/28] fix(time): wire clock_nanosleep syscall --- os/src/kernel/syscall/dispatch.rs | 1 + os/src/kernel/syscall/mod.rs | 5 +++++ os/src/kernel/syscall/numbers.rs | 1 + 3 files changed, 7 insertions(+) diff --git a/os/src/kernel/syscall/dispatch.rs b/os/src/kernel/syscall/dispatch.rs index 8b6f3a34..beb0e96f 100644 --- a/os/src/kernel/syscall/dispatch.rs +++ b/os/src/kernel/syscall/dispatch.rs @@ -98,6 +98,7 @@ pub fn dispatch_syscall(frame: &mut impl SyscallFrame) { crate::kernel::syscall::numbers::SYS_CLOCK_SETTIME => sys_clock_settime(frame), crate::kernel::syscall::numbers::SYS_CLOCK_GETTIME => sys_clock_gettime(frame), crate::kernel::syscall::numbers::SYS_CLOCK_GETRES => sys_clock_getres(frame), + crate::kernel::syscall::numbers::SYS_CLOCK_NANOSLEEP => sys_clock_nanosleep(frame), crate::kernel::syscall::numbers::SYS_SYSLOG => sys_syslog(frame), // 调度 diff --git a/os/src/kernel/syscall/mod.rs b/os/src/kernel/syscall/mod.rs index 04c0e55e..f6c87e1b 100644 --- a/os/src/kernel/syscall/mod.rs +++ b/os/src/kernel/syscall/mod.rs @@ -169,6 +169,11 @@ impl_syscall!( impl_syscall!(sys_clock_settime, clock_settime, (c_int, *const TimeSpec)); impl_syscall!(sys_clock_gettime, clock_gettime, (c_int, *mut TimeSpec)); impl_syscall!(sys_clock_getres, clock_getres, (c_int, *mut TimeSpec)); +impl_syscall!( + sys_clock_nanosleep, + clock_nanosleep, + (c_int, c_int, *const TimeSpec, *mut TimeSpec) +); impl_syscall!( sys_sched_setparam, sched_setparam, diff --git a/os/src/kernel/syscall/numbers.rs b/os/src/kernel/syscall/numbers.rs index 47a57f97..11cefe0e 100644 --- a/os/src/kernel/syscall/numbers.rs +++ b/os/src/kernel/syscall/numbers.rs @@ -73,6 +73,7 @@ pub const SYS_SETITIMER: usize = 103; pub const SYS_CLOCK_SETTIME: usize = 112; pub const SYS_CLOCK_GETTIME: usize = 113; pub const SYS_CLOCK_GETRES: usize = 114; +pub const SYS_CLOCK_NANOSLEEP: usize = 115; pub const SYS_SYSLOG: usize = 116; // ---- 调度 ---- From 972f0cea8f5e0e39381f5336c772f660ec39d364 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 00:37:44 +0800 Subject: [PATCH 23/28] fix(fs): create /dev/shm for posix shm --- os/src/fs/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/os/src/fs/mod.rs b/os/src/fs/mod.rs index 5a3bd9c5..2623dee6 100644 --- a/os/src/fs/mod.rs +++ b/os/src/fs/mod.rs @@ -414,6 +414,13 @@ fn create_devices() -> Result<(), FsError> { let dir_mode = FileMode::S_IFDIR | FileMode::from_bits_truncate(0o755); dev_inode.mkdir("misc", dir_mode)?; + // POSIX shm_open() in musl resolves objects under /dev/shm. + let shm_dir_mode = FileMode::S_IFDIR | FileMode::from_bits_truncate(0o1777); + match dev_inode.mkdir("shm", shm_dir_mode) { + Ok(_) | Err(FsError::AlreadyExists) => {} + Err(err) => return Err(err), + } + // /dev/misc/rtc (10, 135) let misc_dentry = vfs_lookup("/dev/misc")?; misc_dentry From 2b4b3a4aaac3d5ccba7795a4ccaf330b163cc1f3 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 00:54:19 +0800 Subject: [PATCH 24/28] fix select timeout fdset semantics --- os/src/kernel/syscall/io.rs | 90 +++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/os/src/kernel/syscall/io.rs b/os/src/kernel/syscall/io.rs index 42bda890..81a0f926 100644 --- a/os/src/kernel/syscall/io.rs +++ b/os/src/kernel/syscall/io.rs @@ -6,6 +6,7 @@ use crate::kernel::current_task; use crate::uapi::errno::EFAULT; use crate::uapi::errno::EINVAL; use crate::uapi::iovec::IoVec; +use crate::uapi::select::FdSet; use crate::util::user_buffer::{ read_from_user, validate_user_ptr, validate_user_ptr_mut, write_to_user, }; @@ -833,6 +834,38 @@ pub fn select( select_common(nfds, readfds, writefds, exceptfds, timeout_trigger) } +fn write_select_fd_sets( + readfds: usize, + writefds: usize, + exceptfds: usize, + read_set: Option<&FdSet>, + write_set: Option<&FdSet>, + except_set: Option<&FdSet>, +) { + if let Some(set) = read_set { + write_to_user(readfds as *mut FdSet, *set); + } + if let Some(set) = write_set { + write_to_user(writefds as *mut FdSet, *set); + } + if let Some(set) = except_set { + write_to_user(exceptfds as *mut FdSet, *set); + } +} + +fn clear_select_fd_sets(readfds: usize, writefds: usize, exceptfds: usize) { + let empty = FdSet::new(); + if readfds != 0 { + write_to_user(readfds as *mut FdSet, empty); + } + if writefds != 0 { + write_to_user(writefds as *mut FdSet, empty); + } + if exceptfds != 0 { + write_to_user(exceptfds as *mut FdSet, empty); + } +} + fn select_common( nfds: usize, readfds: usize, @@ -842,7 +875,6 @@ fn select_common( ) -> isize { use crate::kernel::current_task; use crate::uapi::errno::{EBADF, EINTR, EINVAL}; - use crate::uapi::select::FdSet; if nfds > crate::uapi::select::FD_SETSIZE { return -(EINVAL as isize); @@ -934,15 +966,14 @@ fn select_common( } // EBADF if ready_count > 0 { - if let Some(ref set) = read_set { - write_to_user(readfds as *mut FdSet, *set); - } - if let Some(ref set) = write_set { - write_to_user(writefds as *mut FdSet, *set); - } - if let Some(ref set) = except_set { - write_to_user(exceptfds as *mut FdSet, *set); - } + write_select_fd_sets( + readfds, + writefds, + exceptfds, + read_set.as_ref(), + write_set.as_ref(), + except_set.as_ref(), + ); return ready_count; } @@ -952,22 +983,34 @@ fn select_common( return -(EINTR as isize); } - if let Some(0) = timeout_trigger { - return 0; - } - if let Some(trigger) = timeout_trigger { - use crate::kernel::timer::TIMER_QUEUE; - TIMER_QUEUE.lock().push(trigger, task.clone()); + if trigger == 0 || crate::arch::get_time() >= trigger { + clear_select_fd_sets(readfds, writefds, exceptfds); + return 0; + } } - // Atomic check-and-sleep to prevent lost wakeup - let slept = { - let mut wq = POLL_WAIT_QUEUE.lock(); - wq.sleep_if(task.clone(), || { - let (ready, _, _, _) = check_fds(); - ready > 0 - }) + let should_not_sleep = || { + let (ready, _, _, _) = check_fds(); + ready > 0 || timeout_trigger.is_some_and(|trigger| crate::arch::get_time() >= trigger) + }; + + // Keep the timer from firing before sleep_if has actually moved this task out of Running. + let slept = if let Some(trigger) = timeout_trigger { + use crate::kernel::timer::TIMER_QUEUE; + let mut timer_q = TIMER_QUEUE.lock(); + timer_q.push(trigger, task.clone()); + let slept = POLL_WAIT_QUEUE + .lock() + .sleep_if(task.clone(), should_not_sleep); + if !slept { + timer_q.remove_task(&task); + } + slept + } else { + POLL_WAIT_QUEUE + .lock() + .sleep_if(task.clone(), should_not_sleep) }; if slept { @@ -988,6 +1031,7 @@ fn select_common( if let Some(trigger) = timeout_trigger && crate::arch::get_time() >= trigger { + clear_select_fd_sets(readfds, writefds, exceptfds); return 0; } } From 3bc88734dc9408b4ab4b8c967a30d5afbdb7d47e Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 01:00:23 +0800 Subject: [PATCH 25/28] add musl loader symlinks for ltp --- data/loongarch_musl/symlinks.manifest | 2 ++ data/risc-v_musl/symlinks.manifest | 1 + 2 files changed, 3 insertions(+) diff --git a/data/loongarch_musl/symlinks.manifest b/data/loongarch_musl/symlinks.manifest index 527a68db..a8ebe1ea 100644 --- a/data/loongarch_musl/symlinks.manifest +++ b/data/loongarch_musl/symlinks.manifest @@ -90,6 +90,8 @@ bin/usleep busybox bin/vi busybox bin/watch busybox bin/zcat busybox +lib/libc.so /tests/musl/lib/libc.so +lib64/ld-musl-loongarch-lp64d.so.1 ../lib/libc.so linuxrc bin/busybox sbin/acpid ../bin/busybox sbin/adjtimex ../bin/busybox diff --git a/data/risc-v_musl/symlinks.manifest b/data/risc-v_musl/symlinks.manifest index 1e30011f..922d67b0 100644 --- a/data/risc-v_musl/symlinks.manifest +++ b/data/risc-v_musl/symlinks.manifest @@ -91,6 +91,7 @@ bin/vi busybox bin/watch busybox bin/zcat busybox lib/ld-musl-riscv64-sf.so.1 libc.so +lib/ld-musl-riscv64.so.1 libc.so linuxrc bin/busybox sbin/acpid ../bin/busybox sbin/adjtimex ../bin/busybox From 9bf2b76d6e66cf3be81557ed60b4bd836a46fe7f Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 13:27:45 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BD=9C=E7=94=A8?= =?UTF-8?q?=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/risc-v_musl/etc/init.d/rcS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/risc-v_musl/etc/init.d/rcS b/data/risc-v_musl/etc/init.d/rcS index eaf64cbd..848150f4 100755 --- a/data/risc-v_musl/etc/init.d/rcS +++ b/data/risc-v_musl/etc/init.d/rcS @@ -116,7 +116,7 @@ stage_musl_tests_to_tmpfs() { ;; esac - if ! /bin/cp -R "$entry" "$dst/"; then + if ! /bin/cp -a "$entry" "$dst/"; then echo "[Tests] failed to stage $entry" /bin/rm -rf "$dst" return 1 @@ -155,7 +155,7 @@ run_musl_tests_if_present() { base="${src_f##*/}" case "$base" in - basic_testcode.sh|busybox_testcode.sh|iperf_testcode.sh|lua_testcode.sh|iozone_testcode.sh|cyclictest_testcode.sh) + unixbench_testcode.sh|lmbench_testcode.sh|libcbench_testcode.sh|libctest_testcode.sh) echo "[Tests] skipping already-validated $src_f" continue ;; From 4546a173eeec292e192a7d6a7fe7e2617c5365f1 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 15:01:31 +0800 Subject: [PATCH 27/28] fix loongarch sigreturn context restore --- data/loongarch_musl/etc/init.d/rcS | 2 +- os/src/arch/loongarch/trap/sigreturn.S | 1 + os/src/arch/loongarch/trap/trap_frame.rs | 6 ++++-- os/src/arch/loongarch/trap/trap_handler.rs | 7 +++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data/loongarch_musl/etc/init.d/rcS b/data/loongarch_musl/etc/init.d/rcS index 67731c3a..fb65aba0 100755 --- a/data/loongarch_musl/etc/init.d/rcS +++ b/data/loongarch_musl/etc/init.d/rcS @@ -116,7 +116,7 @@ stage_musl_tests_to_tmpfs() { ;; esac - if ! /bin/cp -R "$entry" "$dst/"; then + if ! /bin/cp -a "$entry" "$dst/"; then echo "[Tests] failed to stage $entry" /bin/rm -rf "$dst" return 1 diff --git a/os/src/arch/loongarch/trap/sigreturn.S b/os/src/arch/loongarch/trap/sigreturn.S index 370b9bef..95ff50c1 100644 --- a/os/src/arch/loongarch/trap/sigreturn.S +++ b/os/src/arch/loongarch/trap/sigreturn.S @@ -7,6 +7,7 @@ __sigreturn_trampoline: li.w $a7, 139 # __NR_rt_sigreturn syscall 0 + break 0 # rt_sigreturn must not return .globl __sigreturn_trampoline_end __sigreturn_trampoline_end: diff --git a/os/src/arch/loongarch/trap/trap_frame.rs b/os/src/arch/loongarch/trap/trap_frame.rs index 140f2b29..432effd8 100644 --- a/os/src/arch/loongarch/trap/trap_frame.rs +++ b/os/src/arch/loongarch/trap/trap_frame.rs @@ -238,7 +238,8 @@ impl TrapFrame { /// 将 TrapFrame 转换为 MContextT pub fn to_mcontext(&self) -> MContextT { let mut gregs = [0u64; 32]; - for i in 0..32 { + gregs[0] = self.era as u64; + for i in 1..32 { gregs[i] = self.regs[i] as u64; } MContextT { @@ -255,7 +256,8 @@ impl TrapFrame { /// 从 MContextT 恢复 TrapFrame pub fn restore_from_mcontext(&mut self, mcontext: &MContextT) { - for i in 0..32 { + self.era = mcontext.gregs[0] as usize; + for i in 1..32 { self.regs[i] = mcontext.gregs[i] as usize; } self.fregs.copy_from_slice(&mcontext.fpregs[..32]); diff --git a/os/src/arch/loongarch/trap/trap_handler.rs b/os/src/arch/loongarch/trap/trap_handler.rs index 7d224232..ccfd1da5 100644 --- a/os/src/arch/loongarch/trap/trap_handler.rs +++ b/os/src/arch/loongarch/trap/trap_handler.rs @@ -258,6 +258,7 @@ fn handle_interrupt(estat: usize) { } fn user_panic(estat: usize, era: usize, trap_frame: &TrapFrame) { + let ecode = (estat >> 16) & 0x3f; let badv: usize; let badi: usize; unsafe { @@ -268,9 +269,15 @@ fn user_panic(estat: usize, era: usize, trap_frame: &TrapFrame) { emergency_println!(" UNEXPECTED TRAP IN USER MODE (PLV>0)"); emergency_println!("==============================================="); emergency_println!("estat: {:#x}", estat); + emergency_println!("ecode: {:#x}", ecode); emergency_println!("era : {:#x}", era); emergency_println!("badv : {:#x}", badv); emergency_println!("badi : {:#x}", badi); + emergency_println!( + "a7/sp: {:#x}/{:#x}", + trap_frame.regs[11], + trap_frame.regs[3] + ); emergency_println!("regs : {:#x?}", trap_frame.regs); panic!( "Unexpected trap in user mode: estat={:#x}, era={:#x}, badv={:#x}, badi={:#x}", From cbf44ee72a9ebfce4be53d5ad8abd64616de1e31 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Thu, 25 Jun 2026 15:45:09 +0800 Subject: [PATCH 28/28] fix pr review edge cases --- os/src/fs/ext4/inode.rs | 34 ++++++++++++++++- os/src/fs/ext4/mod.rs | 2 +- os/src/kernel/scheduler/task_queue.rs | 13 ++++--- os/src/kernel/syscall/io.rs | 16 ++++++-- os/src/kernel/syscall/ipc.rs | 44 ++++++++++++++++------ os/src/kernel/syscall/task/sched_ops.rs | 17 +++++++++ third_party/ext4_rs/src/ext4_impls/file.rs | 13 +++---- 7 files changed, 107 insertions(+), 32 deletions(-) diff --git a/os/src/fs/ext4/inode.rs b/os/src/fs/ext4/inode.rs index 6d9f5b6c..db0a9249 100644 --- a/os/src/fs/ext4/inode.rs +++ b/os/src/fs/ext4/inode.rs @@ -22,6 +22,7 @@ use crate::vfs::{Dentry, DirEntry, FileMode, FsError, Inode, InodeMetadata, Inod const READ_CACHE_PAGE_SIZE: usize = 4096; const READ_CACHE_MAX_PAGES: usize = 512; +const LOOKUP_CACHE_MAX_ENTRIES: usize = 4096; struct CachedReadPage { data: Vec, @@ -94,12 +95,14 @@ impl ReadCache { struct LookupCache { entries: BTreeMap>, + len: usize, } impl LookupCache { const fn new() -> Self { Self { entries: BTreeMap::new(), + len: 0, } } @@ -108,21 +111,48 @@ impl LookupCache { } fn insert(&mut self, parent_ino: u32, name: &str, ino: u32) { - self.entries + let old = self + .entries .entry(parent_ino) .or_default() .insert(String::from(name), ino); + if old.is_none() { + self.len += 1; + } + self.evict_if_needed(); } fn remove(&mut self, parent_ino: u32, name: &str) { let Some(entries) = self.entries.get_mut(&parent_ino) else { return; }; - entries.remove(name); + if entries.remove(name).is_some() { + self.len = self.len.saturating_sub(1); + } if entries.is_empty() { self.entries.remove(&parent_ino); } } + + fn evict_if_needed(&mut self) { + while self.len > LOOKUP_CACHE_MAX_ENTRIES { + let Some(parent_ino) = self.entries.keys().next().copied() else { + self.len = 0; + return; + }; + let remove_parent = { + let entries = self.entries.get_mut(&parent_ino).unwrap(); + if let Some(name) = entries.keys().next().cloned() { + entries.remove(&name); + self.len -= 1; + } + entries.is_empty() + }; + if remove_parent { + self.entries.remove(&parent_ino); + } + } + } } pub struct Ext4InodeCaches { diff --git a/os/src/fs/ext4/mod.rs b/os/src/fs/ext4/mod.rs index 33c458c8..ef160f8a 100644 --- a/os/src/fs/ext4/mod.rs +++ b/os/src/fs/ext4/mod.rs @@ -128,7 +128,7 @@ impl Ext4FileSystem { let inode_caches = Arc::new(Ext4InodeCaches::new()); // 创建根 inode (inode 号 2 是 Ext4 的根目录) - let root = Arc::new(Ext4Inode::new(ext4.clone(), inode_caches.clone(), 2)); + let root = Arc::new(Ext4Inode::new(ext4.clone(), inode_caches, 2)); let fs = Arc::new(Ext4FileSystem { device, diff --git a/os/src/kernel/scheduler/task_queue.rs b/os/src/kernel/scheduler/task_queue.rs index 6bd20712..0eb8150f 100644 --- a/os/src/kernel/scheduler/task_queue.rs +++ b/os/src/kernel/scheduler/task_queue.rs @@ -50,18 +50,19 @@ impl TaskQueue { /// 弹出最高 realtime 优先级任务;同优先级保持 FIFO。 pub fn pop_highest_priority_task(&mut self) -> Option { - let mut best_idx = None; - let mut best_priority = i32::MIN; + let mut best: Option<(usize, i32)> = None; for (idx, task) in self.queue.iter().enumerate() { let priority = task.lock().sched_priority; - if priority > best_priority { - best_priority = priority; - best_idx = Some(idx); + if match best { + Some((_, best_priority)) => priority > best_priority, + None => true, + } { + best = Some((idx, priority)); } } - best_idx.map(|idx| self.queue.remove(idx)) + best.map(|(idx, _)| self.queue.remove(idx)) } /// 检查任务是否在队列中 diff --git a/os/src/kernel/syscall/io.rs b/os/src/kernel/syscall/io.rs index 81a0f926..6a5b7efb 100644 --- a/os/src/kernel/syscall/io.rs +++ b/os/src/kernel/syscall/io.rs @@ -957,15 +957,24 @@ fn select_common( }; loop { + let cleanup_timer = || { + if timeout_trigger.is_some() { + use crate::kernel::timer::TIMER_QUEUE; + TIMER_QUEUE.lock().remove_task(&task); + } + }; + // 关键:在阻塞等待前主动推进网络栈(同 ppoll),并分发 UDP crate::net::socket::poll_network_and_dispatch(); let (ready_count, read_set, write_set, except_set) = check_fds(); if ready_count < 0 { + cleanup_timer(); return ready_count; } // EBADF if ready_count > 0 { + cleanup_timer(); write_select_fd_sets( readfds, writefds, @@ -980,11 +989,13 @@ fn select_common( // If interrupted by a deliverable signal, return EINTR so userland can run the handler. // Signals are only checked on return-to-user; without this, we can sleep forever in-kernel. if crate::ipc::signal_interrupts_syscall(&task) { + cleanup_timer(); return -(EINTR as isize); } if let Some(trigger) = timeout_trigger { if trigger == 0 || crate::arch::get_time() >= trigger { + cleanup_timer(); clear_select_fd_sets(readfds, writefds, exceptfds); return 0; } @@ -1016,10 +1027,7 @@ fn select_common( if slept { crate::kernel::schedule(); - if timeout_trigger.is_some() { - use crate::kernel::timer::TIMER_QUEUE; - TIMER_QUEUE.lock().remove_task(&task); - } + cleanup_timer(); if crate::ipc::signal_interrupts_syscall(&task) { return -(EINTR as isize); diff --git a/os/src/kernel/syscall/ipc.rs b/os/src/kernel/syscall/ipc.rs index fd321252..f5fd46ba 100644 --- a/os/src/kernel/syscall/ipc.rs +++ b/os/src/kernel/syscall/ipc.rs @@ -3,6 +3,7 @@ use alloc::sync::Arc; use crate::{ + arch::{ArchImpl, virtual_memory::VirtualMemory}, config::PAGE_SIZE, ipc::{shm_check_access, shm_detach_segment, shm_mark_removed, shm_segment, shmget_segment}, kernel::{ShmAttachment, current_memory_space, current_task}, @@ -11,7 +12,7 @@ use crate::{ page_table::UniversalPTEFlag, }, uapi::{ - errno::{EFAULT, EINVAL}, + errno::{EFAULT, EINVAL, ENOMEM}, ipc::{ IPC_RMID, IPC_STAT, KeyT, SHM_EXEC, SHM_RDONLY, SHM_REMAP, SHM_RND, SHMLBA, ShmIdDs, }, @@ -157,6 +158,9 @@ pub fn shmat(shmid: i32, shmaddr: *const u8, shmflg: i32) -> isize { Some(end) => end, None => return -EINVAL as isize, }; + if end == 0 || end - 1 > ::USER_TOP { + return -EINVAL as isize; + } let start_vpn = Vpn::from_addr_floor(VA::from_usize(start)); let end_vpn = Vpn::from_addr_ceil(VA::from_usize(end)); @@ -171,24 +175,40 @@ pub fn shmat(shmid: i32, shmaddr: *const u8, shmflg: i32) -> isize { flags |= UniversalPTEFlag::EXECUTABLE; } + let task = current_task(); + let (pid, table) = { + let t = task.lock(); + (t.pid as i32, t.shm_attachments.clone()) + }; + if shmflg & SHM_REMAP == 0 && table.lock().contains_key(&start) { + return -EINVAL as isize; + } + let memory_space = current_memory_space(); let mut space = memory_space.lock(); - if shmflg & SHM_REMAP != 0 { - if let Err(_) = space.munmap(VA::from_usize(start), len) { - return -EINVAL as isize; + let old_attachment = if shmflg & SHM_REMAP != 0 { + table.lock().remove(&start) + } else { + None + }; + if shmflg & SHM_REMAP != 0 && space.munmap(VA::from_usize(start), len).is_err() { + if let Some(old) = old_attachment { + table.lock().insert(start, old); } - } - if let Err(_) = space.insert_shared_area(range, flags, segment.clone()) { return -EINVAL as isize; } + if space + .insert_shared_area(range, flags, segment.clone()) + .is_err() + { + if let Some(old) = old_attachment { + shm_detach_segment(&old.segment, pid); + } + return -ENOMEM as isize; + } drop(space); - let task = current_task(); - let (pid, table) = { - let t = task.lock(); - (t.pid as i32, t.shm_attachments.clone()) - }; - if let Some(old) = table.lock().remove(&start) { + if let Some(old) = old_attachment { shm_detach_segment(&old.segment, pid); } segment.mark_attached(pid); diff --git a/os/src/kernel/syscall/task/sched_ops.rs b/os/src/kernel/syscall/task/sched_ops.rs index efd8d932..356da893 100644 --- a/os/src/kernel/syscall/task/sched_ops.rs +++ b/os/src/kernel/syscall/task/sched_ops.rs @@ -222,5 +222,22 @@ pub fn sched_getaffinity(pid: c_int, cpusetsize: usize, mask: *mut u8) -> c_int { return -EFAULT; } + if cpusetsize > raw.len() { + let zero = [0u8; CPU_SET_BYTES]; + let mut offset = raw.len(); + while offset < cpusetsize { + let n = core::cmp::min(cpusetsize - offset, zero.len()); + // SAFETY: `zero` is a live kernel buffer and copy_to_user validates + // the user destination range for this chunk. + if unsafe { + ArchImpl::copy_to_user(zero.as_ptr(), UA::from_usize(mask as usize + offset), n) + } + .is_err() + { + return -EFAULT; + } + offset += n; + } + } CPU_SET_BYTES as c_int } diff --git a/third_party/ext4_rs/src/ext4_impls/file.rs b/third_party/ext4_rs/src/ext4_impls/file.rs index c7015f91..d9bde262 100644 --- a/third_party/ext4_rs/src/ext4_impls/file.rs +++ b/third_party/ext4_rs/src/ext4_impls/file.rs @@ -325,13 +325,12 @@ impl Ext4 { log::trace!("[Pre-allocation] Allocating {} blocks", blocks_to_allocate); // 使用append_inode_pblk_batch进行批量块分配 - let allocated_blocks = - self.append_inode_pblk_batch( - &mut inode_ref, - &mut start_bgid, - existing_file_blocks as u32, - blocks_to_allocate, - )?; + let allocated_blocks = self.append_inode_pblk_batch( + &mut inode_ref, + &mut start_bgid, + existing_file_blocks as u32, + blocks_to_allocate, + )?; let zero_block = vec![0u8; BLOCK_SIZE]; for block in &allocated_blocks {