diff --git a/Makefile b/Makefile index 694c96ff..20fd3236 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ LOCAL_TESTIMG_DIR ?= ../CCYOS TESTIMG_RV ?= $(firstword $(wildcard $(LOCAL_TESTIMG_DIR)/sdcard-rv.img sdcard-rv.img)) TESTIMG_LA ?= $(firstword $(wildcard $(LOCAL_TESTIMG_DIR)/sdcard-la.img sdcard-la.img)) RV_TEST_DRIVE := -drive file=$(TESTIMG_RV)$(COMMA)if=none$(COMMA)format=raw$(COMMA)id=test0 -device virtio-blk-device$(COMMA)drive=test0$(COMMA)bus=virtio-mmio-bus.0 -LA_TEST_DRIVE := -drive file=$(TESTIMG_LA)$(COMMA)if=none$(COMMA)format=raw$(COMMA)id=test0 -device virtio-blk-pci$(COMMA)drive=test0 +LA_TEST_DRIVE := -drive file=$(TESTIMG_LA)$(COMMA)if=none$(COMMA)format=raw$(COMMA)id=x0 -device virtio-blk-pci$(COMMA)drive=x0 # 评测构建 profile:默认 release(提交用)。本地调试可 `make all PROFILE=debug`。 PROFILE ?= release @@ -141,11 +141,11 @@ disk-la.img: kernel-la $(VFAT_IMG) $(ASSEMBLE_DISK) @$(ASSEMBLE_DISK) $(OS_DIR)/fs-loongarch.img $(VFAT_IMG) $@ # ------------------------------------------------------------ -# 本地运行评测形态:我们的 MBR 分区盘作为 /dev/vda(vda1=rootfs,vda2=VFAT); -# 官方测试盘作为额外裸 ext4 块设备 /dev/vdb,由 rcS 挂载到 /tests。 +# 本地运行评测形态:官方测试盘作为额外裸 ext4 块设备,由 rcS 挂载到 /tests。 # RISC-V virtio-mmio 设备树按高地址先探测,因此我们的分区盘放 bus.1, # 官方测试盘放 bus.0,注册出来才是 vda=disk、vdb=sdcard。 -# 设备型号对齐 os/qemu-run.sh(riscv: virtio-mmio)与 os/qemu-loongarch-run.sh(loongarch: pci)。 +# LoongArch 评测命令先挂官方测试盘、后挂 disk-la.img,因此 la 下是 +# vda=sdcard-la.img、vdb=disk-la.img、vdb1=rootfs。 # ------------------------------------------------------------ run-rv: kernel-rv disk.img @if [ -z "$(TESTIMG_RV)" ]; then \ @@ -165,12 +165,12 @@ run-la: kernel-la disk-la.img echo "Error: no LoongArch test image found. Set TESTIMG_LA=/path/to/sdcard-la.img" >&2; \ exit 1; \ fi - @echo "[Run] 运行 LoongArch QEMU(内核盘:vda1 rootfs,vda2 VFAT;测试盘:vdb)" + @echo "[Run] 运行 LoongArch QEMU(测试盘:vda;内核盘:vdb1 rootfs,vdb2 VFAT)" qemu-system-loongarch64 -machine virt -kernel kernel-la -m $(LA_MEM) -nographic \ -smp $(LA_SMP) -no-reboot -rtc base=utc \ - -drive file=disk-la.img,if=none,format=raw,id=x0 \ - -device virtio-blk-pci,drive=x0 \ $(LA_TEST_DRIVE) \ + -drive file=disk-la.img,if=none,format=raw,id=x1 \ + -device virtio-blk-pci,drive=x1 \ -device virtio-net-pci,netdev=net0 \ -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 diff --git a/data/loongarch_musl/etc/init.d/rcS b/data/loongarch_musl/etc/init.d/rcS index fb65aba0..2cdda3b4 100755 --- a/data/loongarch_musl/etc/init.d/rcS +++ b/data/loongarch_musl/etc/init.d/rcS @@ -58,7 +58,7 @@ mount_official_test_image_if_present() { # Official test images are raw ext4 filesystems whose root contains # /musl and /glibc. Mounting one at /tests exposes /tests/musl. - dev="/dev/vdb" + dev="/dev/vda" [ -e "$dev" ] || return 1 echo "[Tests] probing official test image on $dev" @@ -71,8 +71,8 @@ mount_official_test_image_if_present() { /bin/umount /tests 2>/dev/null || true fi - # Old multi-device probe disabled. The run scripts attach the official - # input image at /dev/vdb; our own partitioned kernel disk is /dev/vda. + # Old multi-device probe disabled. The LoongArch judge attaches the official + # input image at /dev/vda; our own partitioned kernel disk is /dev/vdb. # for dev in \ # /dev/vdb \ # /dev/vda \ @@ -116,7 +116,7 @@ stage_musl_tests_to_tmpfs() { ;; esac - if ! /bin/cp -a "$entry" "$dst/"; then + if ! /bin/cp -R "$entry" "$dst/"; then echo "[Tests] failed to stage $entry" /bin/rm -rf "$dst" return 1 diff --git a/data/risc-v_musl/etc/init.d/rcS b/data/risc-v_musl/etc/init.d/rcS index 848150f4..d27df698 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 -a "$entry" "$dst/"; then + if ! /bin/cp -R "$entry" "$dst/"; then echo "[Tests] failed to stage $entry" /bin/rm -rf "$dst" return 1 @@ -127,8 +127,8 @@ stage_musl_tests_to_tmpfs() { } run_musl_tests_if_present() { - # 官方测试镜像挂载到 /tests 后,自动扫描并运行 musl 下的全部测试脚本; - # glibc 分组保留在测试镜像中,后续需要时直接调整本脚本。 + # 官方测试镜像挂载到 /tests 后,自动扫描 musl 下的全部测试脚本; + # 需要排除的脚本维护在 skip-list 中。 mount_official_test_image_if_present || true [ -d /tests/musl ] || return 1 @@ -143,9 +143,9 @@ run_musl_tests_if_present() { echo "[Tests] detected musl test scripts; running in rcS" if [ -n "$staged_musl_dir" ]; then - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$staged_musl_dir:/tests/musl" + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$staged_musl_dir:/tests/musl:/tests/musl/ltp/testcases/bin" else - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl:/tests/musl/ltp/testcases/bin" fi export TMPDIR="/tmp" export HOME="/" @@ -155,8 +155,9 @@ run_musl_tests_if_present() { base="${src_f##*/}" case "$base" in - unixbench_testcode.sh|lmbench_testcode.sh|libcbench_testcode.sh|libctest_testcode.sh) - echo "[Tests] skipping already-validated $src_f" + ltp_testcode.sh|\ + unixbench_testcode.sh) + echo "[Tests] skipping $src_f" continue ;; esac diff --git a/os/src/config.rs b/os/src/config.rs index 4bd94797..d76cbece 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -34,7 +34,7 @@ pub const USER_SIGRETURN_TRAMPOLINE: usize = align_down(::USER_TOP, PAGE_SIZE); /// Maximum heap size (prevent OOM) -pub const MAX_USER_HEAP_SIZE: usize = 64 * 1024 * 1024; // 64MB +pub const MAX_USER_HEAP_SIZE: usize = 128 * 1024 * 1024; // 128MB pub const DEFAULT_MAX_FDS: usize = 256; diff --git a/os/src/fs/ext4/inode.rs b/os/src/fs/ext4/inode.rs index db0a9249..46a6d5ac 100644 --- a/os/src/fs/ext4/inode.rs +++ b/os/src/fs/ext4/inode.rs @@ -18,81 +18,11 @@ use crate::vfs::dev::{ decode_ext4_new_dev, decode_ext4_old_dev, encode_ext4_new_dev, encode_ext4_old_dev, major as dev_major, minor as dev_minor, }; +use crate::vfs::page_cache::{PAGE_CACHE_PAGE_SIZE, PageCache, PageCacheObjectId}; use crate::vfs::{Dentry, DirEntry, FileMode, FsError, Inode, InodeMetadata, InodeType}; -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, - age: u64, -} - -struct ReadCache { - pages: BTreeMap<(u32, usize), CachedReadPage>, - clock: u64, - generations: BTreeMap, -} - -impl ReadCache { - const fn new() -> Self { - Self { - pages: BTreeMap::new(), - clock: 0, - generations: BTreeMap::new(), - } - } - - 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, ino: u32) -> u64 { - self.generations.get(&ino).copied().unwrap_or(0) - } - - 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(&(ino, 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, 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); - 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(key, CachedReadPage { - data, - age: self.clock, - }); - } -} - struct LookupCache { entries: BTreeMap>, len: usize, @@ -156,14 +86,12 @@ impl LookupCache { } 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()), } } @@ -183,29 +111,70 @@ pub struct Ext4Inode { /// Shared filesystem-level caches for regular reads and directory lookup. caches: Arc, + + /// Shared VFS clean page cache. + page_cache: Arc, + + /// Stable filesystem instance id for page-cache object keys. + fs_id: u64, } impl Ext4Inode { /// 创建新的 Ext4Inode /// /// 注意:初始创建时 dentry 为空,VFS 会在创建 Dentry 后调用 set_dentry() - pub fn new(fs: Arc>, caches: Arc, ino: u32) -> Self { + pub fn new( + fs: Arc>, + caches: Arc, + page_cache: Arc, + fs_id: u64, + ino: u32, + ) -> Self { Self { fs, ino, dentry: SpinLock::new(Weak::new()), caches, + page_cache, + fs_id, } } fn invalidate_read_cache(&self) { - self.caches.read.lock().clear_inode(self.ino); + self.page_cache.invalidate_inode(self.cache_object_id()); + } + + fn invalidate_inode_no(&self, ino: u32) { + self.page_cache + .invalidate_inode(PageCacheObjectId::new(self.fs_id, ino as u64)); + } + + fn refresh_zero_cache_range(&self, offset: usize, len: usize) { + if len == 0 { + return; + } + + let object = self.cache_object_id(); + let zero_buf = alloc::vec![0u8; PAGE_CACHE_PAGE_SIZE]; + let mut refreshed = 0; + while refreshed < len { + let current_offset = offset + refreshed; + let page_offset = current_offset % PAGE_CACHE_PAGE_SIZE; + let chunk_len = (PAGE_CACHE_PAGE_SIZE - page_offset).min(len - refreshed); + self.page_cache + .refresh_clean_range(object, current_offset, &zero_buf[..chunk_len]); + refreshed += chunk_len; + } } fn drop_lookup_cache_entry(&self, name: &str) { self.caches.lookup.lock().remove(self.ino, name); } + fn cache_object_id(&self) -> PageCacheObjectId { + PageCacheObjectId::new(self.fs_id, self.ino as u64) + } + #[cfg(test)] pub(crate) fn set_blocks_count_for_test(&self, blocks: u64) { let fs = self.fs.lock(); @@ -397,17 +366,17 @@ impl Inode for Ext4Inode { } let target_len = buf.len().min(metadata.size - offset); + let object = self.cache_object_id(); 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.caches.read.lock().read( - self.ino, - page_index, - page_offset, + let page_index = current_offset / PAGE_CACHE_PAGE_SIZE; + let page_offset = current_offset % PAGE_CACHE_PAGE_SIZE; + let chunk_len = (PAGE_CACHE_PAGE_SIZE - page_offset).min(target_len - copied); + + if let Some(n) = self.page_cache.read_hit( + object, + current_offset, &mut buf[copied..copied + chunk_len], ) { copied += n; @@ -417,32 +386,22 @@ impl Inode for Ext4Inode { continue; } - 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]; - 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.caches - .read - .lock() - .insert(self.ino, generation, page_index, page_buf); + let page_start = page_index * PAGE_CACHE_PAGE_SIZE; + let page_len = PAGE_CACHE_PAGE_SIZE.min(metadata.size.saturating_sub(page_start)); + let page = + self.page_cache + .get_or_insert_clean_page(object, page_index, |page_buf| { + let fs = self.fs.lock(); + fs.read_at(self.ino, page_start, &mut page_buf[..page_len]) + .map_err(|_| FsError::IoError) + })?; + + if page_offset >= page.data().len() { 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]); + let n = page.copy_out(page_offset, &mut buf[copied..copied + chunk_len]); copied += n; - self.caches - .read - .lock() - .insert(self.ino, generation, page_index, page_buf); if n == 0 { break; @@ -465,7 +424,16 @@ impl Inode for Ext4Inode { let written = fs .write_at(self.ino, offset, buf) .map_err(|_| FsError::IoError)?; - self.invalidate_read_cache(); + if written > 0 { + if offset > metadata.size { + self.refresh_zero_cache_range(metadata.size, offset - metadata.size); + } + self.page_cache.refresh_clean_range( + self.cache_object_id(), + offset, + &buf[..written.min(buf.len())], + ); + } Ok(written) } @@ -480,6 +448,8 @@ impl Inode for Ext4Inode { return Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, child_ino, ))); } @@ -500,6 +470,8 @@ impl Inode for Ext4Inode { Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, child_ino, ))) } @@ -526,9 +498,12 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut child_inode); self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(child_inode.inode_num); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, child_inode.inode_num, ))) } @@ -562,9 +537,12 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut inode_ref); self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(inode_id); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, inode_id, ))) } @@ -605,9 +583,12 @@ impl Inode for Ext4Inode { } self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(new_inode.inode_num); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, new_inode.inode_num, ))) } @@ -635,6 +616,7 @@ impl Inode for Ext4Inode { .map_err(|_| FsError::NoSpace)?; self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(ext4_inode.ino); Ok(()) } @@ -681,6 +663,7 @@ impl Inode for Ext4Inode { } self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(child_ext4.ino); Ok(()) } @@ -697,6 +680,7 @@ impl Inode for Ext4Inode { fs.dir_remove(parent, name) .map(|_| { self.drop_lookup_cache_entry(name); + self.page_cache.invalidate_fs(self.fs_id); }) .map_err(|_| FsError::NotFound) } @@ -922,6 +906,10 @@ impl Inode for Ext4Inode { self.drop_lookup_cache_entry(old_name); new_parent_ext4.drop_lookup_cache_entry(new_name); + self.invalidate_inode_no(old_child_ext4.ino); + if let Some(replaced_ino) = replaced_inode { + self.invalidate_inode_no(replaced_ino); + } Ok(()) } @@ -975,6 +963,12 @@ impl Inode for Ext4Inode { let mut inode_ref = fs.get_inode_ref(self.ino); fs.truncate_inode(&mut inode_ref, size as u64) .map_err(|_| FsError::IoError)?; + let invalidate_start = (size / PAGE_CACHE_PAGE_SIZE) * PAGE_CACHE_PAGE_SIZE; + self.page_cache.invalidate_range( + self.cache_object_id(), + invalidate_start, + old_size - invalidate_start, + ); } else { // 扩展文件:ext4_rs 的 truncate_inode 不支持扩展(有 assert) // Workaround: 在文件末尾写入零字节来扩展 @@ -993,9 +987,9 @@ impl Inode for Ext4Inode { .map_err(|_| FsError::IoError)?; written += to_write; } + self.refresh_zero_cache_range(old_size, extend_size); } - self.invalidate_read_cache(); Ok(()) } @@ -1160,9 +1154,12 @@ impl Inode for Ext4Inode { fs.write_back_inode(&mut new_inode); self.drop_lookup_cache_entry(name); + self.invalidate_inode_no(new_inode.inode_num); Ok(Arc::new(Ext4Inode::new( self.fs.clone(), self.caches.clone(), + self.page_cache.clone(), + self.fs_id, new_inode.inode_num, ))) } diff --git a/os/src/fs/ext4/mod.rs b/os/src/fs/ext4/mod.rs index ef160f8a..42f98b92 100644 --- a/os/src/fs/ext4/mod.rs +++ b/os/src/fs/ext4/mod.rs @@ -62,6 +62,7 @@ pub use inode::{Ext4Inode, Ext4InodeCaches}; use crate::device::block::BlockDriver; use crate::pr_info; use crate::sync::Mutex; +use crate::vfs::page_cache::PageCache; use crate::vfs::{FileSystem, FsError, Inode, StatFs}; use alloc::sync::Arc; use ext4_rs::BlockDevice; @@ -80,9 +81,15 @@ pub struct Ext4FileSystem { /// 设备 ID device_id: usize, + /// VFS clean page cache object id prefix. + fs_id: u64, + /// ext4_rs 文件系统对象 ext4: Arc>, + /// Shared clean file page cache. + page_cache: Arc, + /// 根 inode root: Arc, } @@ -126,16 +133,26 @@ impl Ext4FileSystem { let ext4 = Arc::new(Mutex::new(ext4)); let inode_caches = Arc::new(Ext4InodeCaches::new()); + let fs_id = device_id as u64; + let page_cache = Arc::new(PageCache::new()); // 创建根 inode (inode 号 2 是 Ext4 的根目录) - let root = Arc::new(Ext4Inode::new(ext4.clone(), inode_caches, 2)); + let root = Arc::new(Ext4Inode::new( + ext4.clone(), + inode_caches, + page_cache.clone(), + fs_id, + 2, + )); let fs = Arc::new(Ext4FileSystem { device, block_size, total_blocks, device_id, + fs_id, ext4, + page_cache, root, }); diff --git a/os/src/fs/mod.rs b/os/src/fs/mod.rs index 2623dee6..f1b44004 100644 --- a/os/src/fs/mod.rs +++ b/os/src/fs/mod.rs @@ -94,7 +94,7 @@ use crate::fs::tmpfs::TmpFs; // use crate::fs::smfs::SimpleMemoryFileSystem; use crate::pr_info; use crate::vfs::dev::makedev; -use crate::vfs::devno::chrdev_major; +use crate::vfs::devno::{chrdev_major, misc_minor}; use crate::vfs::{FileMode, FsError, MOUNT_TABLE, MountFlags, vfs_lookup}; // lazy_static! { @@ -427,10 +427,21 @@ fn create_devices() -> Result<(), FsError> { .inode .mknod("rtc", char_mode, makedev(chrdev_major::MISC, 135))?; + // /dev/cpu_dma_latency (10, 123): PM QoS compatibility no-op for cyclictest. + match dev_inode.mknod( + "cpu_dma_latency", + char_mode, + makedev(chrdev_major::MISC, misc_minor::CPU_DMA_LATENCY), + ) { + Ok(_) | Err(FsError::AlreadyExists) => {} + Err(err) => return Err(err), + } + // 块设备:0660 权限 let block_mode = FileMode::S_IFBLK | FileMode::from_bits_truncate(0o660); - for dev_info in list_block_devices() { + let block_devices = list_block_devices(); + for dev_info in &block_devices { dev_inode.mknod( &dev_info.name, block_mode, @@ -438,6 +449,19 @@ fn create_devices() -> Result<(), FsError> { )?; } + #[cfg(target_arch = "loongarch64")] + if !block_devices.iter().any(|dev_info| dev_info.name == "vda2") { + if let Some(dev_info) = block_devices + .iter() + .find(|dev_info| dev_info.name == "vdb2") + { + match dev_inode.mknod("vda2", block_mode, makedev(dev_info.major, dev_info.minor)) { + Ok(_) | Err(FsError::AlreadyExists) => {} + Err(err) => return Err(err), + } + } + } + Ok(()) } diff --git a/os/src/fs/proc/generators/process/mod.rs b/os/src/fs/proc/generators/process/mod.rs index f09618ae..d9da3234 100644 --- a/os/src/fs/proc/generators/process/mod.rs +++ b/os/src/fs/proc/generators/process/mod.rs @@ -1,11 +1,15 @@ pub mod cmdline; pub mod maps; pub mod memory; +pub mod oom_score; +pub mod oom_score_adj; pub mod stat; pub mod status; pub use cmdline::CmdlineGenerator; pub use maps::MapsGenerator; pub use memory::collect_user_vm_stats; +pub use oom_score::OomScoreGenerator; +pub use oom_score_adj::{OomScoreAdjGenerator, OomScoreAdjWriter}; pub use stat::StatGenerator; pub use status::StatusGenerator; diff --git a/os/src/fs/proc/generators/process/oom_score.rs b/os/src/fs/proc/generators/process/oom_score.rs new file mode 100644 index 00000000..b77f77c5 --- /dev/null +++ b/os/src/fs/proc/generators/process/oom_score.rs @@ -0,0 +1,71 @@ +use alloc::{format, sync::Weak, vec::Vec}; + +use crate::{ + config::PAGE_SIZE, fs::proc::ContentGenerator, kernel::TaskStruct, + mm::frame_allocator::get_total_frames, sync::SpinLock, vfs::FsError, +}; + +use super::memory::collect_user_vm_stats; + +pub struct OomScoreGenerator { + task: Weak>, +} + +impl OomScoreGenerator { + pub fn new(task: Weak>) -> Self { + Self { task } + } +} + +fn calculate_oom_score(rss_bytes: usize, total_bytes: usize, oom_score_adj: i32) -> i32 { + if oom_score_adj <= -1000 || total_bytes == 0 || rss_bytes == 0 { + return 0; + } + + let base = rss_bytes.saturating_mul(1000) / total_bytes; + (base as i32 + oom_score_adj).clamp(0, 1000) +} + +impl ContentGenerator for OomScoreGenerator { + fn generate(&self) -> Result, FsError> { + let task_arc = self.task.upgrade().ok_or(FsError::NotFound)?; + let (oom_score_adj, memory_space) = { + let task = task_arc.lock(); + (task.oom_score_adj, task.memory_space.clone()) + }; + + let score = if oom_score_adj <= -1000 { + 0 + } else if let Some(memory_space) = memory_space { + let stats = collect_user_vm_stats(&memory_space.lock()); + let total_bytes = get_total_frames().saturating_mul(PAGE_SIZE); + calculate_oom_score(stats.rss_bytes, total_bytes, oom_score_adj) + } else { + 0 + }; + + Ok(format!("{}\n", score).into_bytes()) + } +} + +#[cfg(test)] +mod tests { + use super::calculate_oom_score; + use crate::{config::PAGE_SIZE, kassert, test_case}; + + test_case!(test_oom_score_uses_rss_bytes, { + let total = 100 * PAGE_SIZE; + let rss = 10 * PAGE_SIZE; + + kassert!(calculate_oom_score(rss, total, 0) == 100); + }); + + test_case!(test_oom_score_adj_is_applied_and_clamped, { + let total = 100 * PAGE_SIZE; + let rss = 10 * PAGE_SIZE; + + kassert!(calculate_oom_score(rss, total, 50) == 150); + kassert!(calculate_oom_score(rss, total, 1000) == 1000); + kassert!(calculate_oom_score(rss, total, -1000) == 0); + }); +} diff --git a/os/src/fs/proc/generators/process/oom_score_adj.rs b/os/src/fs/proc/generators/process/oom_score_adj.rs new file mode 100644 index 00000000..ccd5b5e0 --- /dev/null +++ b/os/src/fs/proc/generators/process/oom_score_adj.rs @@ -0,0 +1,81 @@ +use alloc::{format, sync::Weak, vec::Vec}; + +use crate::{ + fs::proc::inode::{ContentGenerator, ContentWriter}, + kernel::TaskStruct, + sync::SpinLock, + vfs::FsError, +}; + +const OOM_SCORE_ADJ_MIN: i32 = -1000; +const OOM_SCORE_ADJ_MAX: i32 = 1000; + +pub struct OomScoreAdjGenerator { + task: Weak>, +} + +impl OomScoreAdjGenerator { + pub fn new(task: Weak>) -> Self { + Self { task } + } +} + +impl ContentGenerator for OomScoreAdjGenerator { + fn generate(&self) -> Result, FsError> { + let task_arc = self.task.upgrade().ok_or(FsError::NotFound)?; + let oom_score_adj = task_arc.lock().oom_score_adj; + Ok(format!("{}\n", oom_score_adj).into_bytes()) + } +} + +pub struct OomScoreAdjWriter { + task: Weak>, +} + +impl OomScoreAdjWriter { + pub fn new(task: Weak>) -> Self { + Self { task } + } +} + +fn parse_oom_score_adj(buf: &[u8]) -> Result { + let input = core::str::from_utf8(buf).map_err(|_| FsError::InvalidArgument)?; + let nul_terminated = input.split('\0').next().unwrap_or(""); + let value = nul_terminated + .trim() + .parse::() + .map_err(|_| FsError::InvalidArgument)?; + if !(OOM_SCORE_ADJ_MIN..=OOM_SCORE_ADJ_MAX).contains(&value) { + return Err(FsError::InvalidArgument); + } + Ok(value) +} + +impl ContentWriter for OomScoreAdjWriter { + fn write(&self, buf: &[u8]) -> Result { + let value = parse_oom_score_adj(buf)?; + + let task_arc = self.task.upgrade().ok_or(FsError::NotFound)?; + task_arc.lock().oom_score_adj = value; + Ok(buf.len()) + } +} + +#[cfg(test)] +mod tests { + use super::parse_oom_score_adj; + use crate::{kassert, test_case, vfs::FsError}; + + test_case!(test_parse_oom_score_adj_accepts_nul_terminated_input, { + kassert!(parse_oom_score_adj(b"123\0").unwrap() == 123); + kassert!(parse_oom_score_adj(b" -250\0ignored").unwrap() == -250); + }); + + test_case!(test_parse_oom_score_adj_keeps_existing_validation, { + kassert!(parse_oom_score_adj(b"1001").is_err()); + kassert!(matches!( + parse_oom_score_adj(b"abc"), + Err(FsError::InvalidArgument) + )); + }); +} diff --git a/os/src/fs/proc/inode.rs b/os/src/fs/proc/inode.rs index d411ea8d..fb20a17e 100644 --- a/os/src/fs/proc/inode.rs +++ b/os/src/fs/proc/inode.rs @@ -36,6 +36,12 @@ pub trait ContentGenerator: Send + Sync { fn generate(&self) -> Result, FsError>; } +/// 动态内容写入器 trait +pub trait ContentWriter: Send + Sync { + /// 写入文件内容。procfs 动态文件通常忽略 offset。 + fn write(&self, buf: &[u8]) -> Result; +} + pub struct ProcInode { kind: ProcInodeKind, @@ -53,6 +59,12 @@ pub enum ProcInodeContent { /// 动态文件(每次读取时生成) Dynamic(Arc), + /// 可写动态文件(读取时生成,写入时调用回调) + WritableDynamic { + generator: Arc, + writer: Arc, + }, + /// 目录(包含子节点) Directory(Mutex>>), @@ -133,6 +145,45 @@ impl ProcInode { }) } + /// 创建可写动态文件 inode + pub fn new_writable_dynamic_file( + _name: &str, + generator: Arc, + writer: Arc, + mode: FileMode, + ) -> Arc { + Self::new_writable_dynamic_file_with_inode_no(generator, writer, mode, None) + } + + fn new_writable_dynamic_file_with_inode_no( + generator: Arc, + writer: Arc, + mode: FileMode, + inode_no: Option, + ) -> Arc { + let inode_no = inode_no.unwrap_or_else(|| NEXT_INODE_NO.fetch_add(1, Ordering::Relaxed)); + let now = TimeSpec::now(); + + Arc::new(Self { + kind: ProcInodeKind::Generic, + metadata: SpinLock::new(InodeMetadata { + inode_no, + inode_type: InodeType::File, + mode, + uid: 0, + gid: 0, + size: 0, // proc 文件总是返回 size = 0 + atime: now, + mtime: now, + ctime: now, + nlinks: 1, + blocks: 0, + rdev: 0, + }), + content: ProcInodeContent::WritableDynamic { generator, writer }, + }) + } + /// 创建目录 inode pub fn new_directory(mode: FileMode) -> Arc { Self::new_directory_with_inode_no(mode, None, ProcInodeKind::Generic) @@ -241,6 +292,7 @@ impl ProcInode { fn create_process_dir(&self, pid: u32) -> Option> { use crate::fs::proc::generators::{ CmdlineGenerator, MapsGenerator, StatGenerator, StatusGenerator, + process::{OomScoreAdjGenerator, OomScoreAdjWriter, OomScoreGenerator}, }; use crate::kernel::{TASK_MANAGER, TaskManagerTrait}; @@ -299,6 +351,21 @@ impl ProcInode { ); let _ = proc_dir.add_child("maps", maps); + let oom_score_adj = Self::new_writable_dynamic_file_with_inode_no( + Arc::new(OomScoreAdjGenerator::new(Arc::downgrade(&task))), + Arc::new(OomScoreAdjWriter::new(Arc::downgrade(&task))), + FileMode::from_bits_truncate(0o644), + Some(proc_pid_child_inode_no(pid, 6)), + ); + let _ = proc_dir.add_child("oom_score_adj", oom_score_adj); + + let oom_score = Self::new_dynamic_file_with_inode_no( + Arc::new(OomScoreGenerator::new(Arc::downgrade(&task))), + FileMode::from_bits_truncate(0o444), + Some(proc_pid_child_inode_no(pid, 7)), + ); + let _ = proc_dir.add_child("oom_score", oom_score); + Some(proc_dir) } } @@ -334,12 +401,24 @@ impl Inode for ProcInode { buf[..to_read].copy_from_slice(&data[offset..offset + to_read]); Ok(to_read) } + ProcInodeContent::WritableDynamic { generator, .. } => { + let data = generator.generate()?; + if offset >= data.len() { + return Ok(0); + } + let to_read = (data.len() - offset).min(buf.len()); + buf[..to_read].copy_from_slice(&data[offset..offset + to_read]); + Ok(to_read) + } _ => Err(FsError::IsDirectory), } } - fn write_at(&self, _offset: usize, _buf: &[u8]) -> Result { - Err(FsError::PermissionDenied) + fn write_at(&self, _offset: usize, buf: &[u8]) -> Result { + match &self.content { + ProcInodeContent::WritableDynamic { writer, .. } => writer.write(buf), + _ => Err(FsError::PermissionDenied), + } } fn lookup(&self, name: &str) -> Result, FsError> { @@ -457,8 +536,11 @@ impl Inode for ProcInode { !matches!(self.kind, ProcInodeKind::PidDir(_)) } - fn truncate(&self, _size: usize) -> Result<(), FsError> { - Err(FsError::PermissionDenied) + fn truncate(&self, size: usize) -> Result<(), FsError> { + match &self.content { + ProcInodeContent::WritableDynamic { .. } if size == 0 => Ok(()), + _ => Err(FsError::PermissionDenied), + } } fn sync(&self) -> Result<(), FsError> { diff --git a/os/src/fs/sysfs/device_registry.rs b/os/src/fs/sysfs/device_registry.rs index bbf33fdc..7a48442f 100644 --- a/os/src/fs/sysfs/device_registry.rs +++ b/os/src/fs/sysfs/device_registry.rs @@ -117,9 +117,18 @@ pub fn list_net_devices() -> Vec { /// 根据名称查找块设备 pub fn find_block_device(name: &str) -> Option { - list_block_devices() - .into_iter() - .find(|dev| dev.name == name) + let devices = list_block_devices(); + + if let Some(device) = devices.iter().find(|dev| dev.name == name) { + return Some(device.clone()); + } + + #[cfg(target_arch = "loongarch64")] + if name == "vda2" { + return devices.into_iter().find(|dev| dev.name == "vdb2"); + } + + None } /// 根据名称查找网络设备 diff --git a/os/src/fs/tests/ext4/ext4_io.rs b/os/src/fs/tests/ext4/ext4_io.rs index cb13d267..8e6151f2 100644 --- a/os/src/fs/tests/ext4/ext4_io.rs +++ b/os/src/fs/tests/ext4/ext4_io.rs @@ -168,6 +168,267 @@ test_case!(test_ext4_overwrite_existing_multiblock_file, { } }); +test_case!(test_ext4_cached_read_invalidated_by_write, { + let fs = create_test_ext4(); + let inode = create_test_file_with_content(&fs, "cached-write.bin", b"AAAA").unwrap(); + + let mut buf = vec![0u8; 4]; + kassert!(inode.read_at(0, &mut buf).unwrap() == 4); + kassert!(&buf[..] == b"AAAA"); + + kassert!(inode.write_at(0, b"BBBB").unwrap() == 4); + + let mut reread = vec![0u8; 4]; + kassert!(inode.read_at(0, &mut reread).unwrap() == 4); + kassert!(&reread[..] == b"BBBB"); +}); + +test_case!( + test_ext4_frame_cached_read_invalidated_by_cross_page_write, + { + const TOTAL_SIZE: usize = 4096 + 16; + let fs = create_test_ext4(); + let initial = vec![b'A'; TOTAL_SIZE]; + let inode = create_test_file_with_content(&fs, "cached-cross-write.bin", &initial).unwrap(); + + let mut cached = vec![0u8; 32]; + kassert!(inode.read_at(4096 - 8, &mut cached).unwrap() == 24); + kassert!(&cached[..24] == &initial[4096 - 8..]); + + kassert!(inode.write_at(4096 - 4, b"BBBBCCCC").unwrap() == 8); + + let mut reread = vec![0u8; 24]; + kassert!(inode.read_at(4096 - 8, &mut reread).unwrap() == 24); + kassert!(&reread[..4] == b"AAAA"); + kassert!(&reread[4..12] == b"BBBBCCCC"); + kassert!(&reread[12..] == &[b'A'; 12]); + } +); + +test_case!(test_ext4_cached_partial_write_refreshes_single_page, { + let fs = create_test_ext4(); + let inode = + create_test_file_with_content(&fs, "cached-partial-write.bin", b"0123456789").unwrap(); + + let mut cached = vec![0u8; 10]; + kassert!(inode.read_at(0, &mut cached).unwrap() == 10); + kassert!(&cached[..] == b"0123456789"); + + kassert!(inode.write_at(3, b"abc").unwrap() == 3); + + let mut reread = vec![0u8; 10]; + kassert!(inode.read_at(0, &mut reread).unwrap() == 10); + kassert!(&reread[..] == b"012abc6789"); +}); + +test_case!(test_ext4_cached_cross_page_write_refreshes_intersections, { + const TOTAL_SIZE: usize = 4096 * 2; + let fs = create_test_ext4(); + let initial = vec![b'A'; TOTAL_SIZE]; + let inode = + create_test_file_with_content(&fs, "cached-cross-page-refresh.bin", &initial).unwrap(); + + let mut warm = vec![0u8; TOTAL_SIZE]; + kassert!(inode.read_at(0, &mut warm).unwrap() == TOTAL_SIZE); + kassert!(warm == initial); + + kassert!(inode.write_at(4096 - 2, b"WXYZ").unwrap() == 4); + + let mut reread = vec![0u8; 8]; + kassert!(inode.read_at(4096 - 4, &mut reread).unwrap() == 8); + kassert!(&reread[..2] == b"AA"); + kassert!(&reread[2..6] == b"WXYZ"); + kassert!(&reread[6..] == b"AA"); +}); + +test_case!(test_ext4_cached_write_preserves_adjacent_cached_page, { + const TOTAL_SIZE: usize = 4096 * 3; + let fs = create_test_ext4(); + let mut initial = vec![0u8; TOTAL_SIZE]; + for page in 0..3 { + for byte in &mut initial[page * 4096..(page + 1) * 4096] { + *byte = b'0' + page as u8; + } + } + let inode = + create_test_file_with_content(&fs, "cached-adjacent-preserve.bin", &initial).unwrap(); + + let mut warm = vec![0u8; TOTAL_SIZE]; + kassert!(inode.read_at(0, &mut warm).unwrap() == TOTAL_SIZE); + kassert!(warm == initial); + + kassert!(inode.write_at(4096 + 17, b"MIDDLE").unwrap() == 6); + + let mut first_page = vec![0u8; 4096]; + let mut third_page = vec![0u8; 4096]; + kassert!(inode.read_at(0, &mut first_page).unwrap() == 4096); + kassert!(inode.read_at(4096 * 2, &mut third_page).unwrap() == 4096); + kassert!(first_page == vec![b'0'; 4096]); + kassert!(third_page == vec![b'2'; 4096]); +}); + +test_case!(test_ext4_cached_write_beyond_eof_keeps_zero_gap, { + const TAIL_OFFSET: usize = 4096 + 8; + let fs = create_test_ext4(); + let inode = create_test_file_with_content(&fs, "cached-gap-write.bin", b"head").unwrap(); + + let mut head = vec![0u8; 4]; + kassert!(inode.read_at(0, &mut head).unwrap() == 4); + kassert!(&head[..] == b"head"); + + kassert!(inode.write_at(TAIL_OFFSET, b"tail").unwrap() == 4); + + let mut all = vec![0xFF; TAIL_OFFSET + 4]; + kassert!(inode.read_at(0, &mut all).unwrap() == TAIL_OFFSET + 4); + kassert!(&all[..4] == b"head"); + for byte in &all[4..TAIL_OFFSET] { + kassert!(*byte == 0); + } + kassert!(&all[TAIL_OFFSET..] == b"tail"); +}); + +test_case!(test_ext4_cached_read_invalidated_by_truncate, { + let fs = create_test_ext4(); + let inode = create_test_file_with_content(&fs, "cached-truncate.bin", b"ABCDEFGH").unwrap(); + + let mut buf = vec![0u8; 8]; + kassert!(inode.read_at(0, &mut buf).unwrap() == 8); + kassert!(&buf[..] == b"ABCDEFGH"); + + kassert!(inode.truncate(3).is_ok()); + + let mut shrunk = vec![0xFF; 8]; + kassert!(inode.read_at(0, &mut shrunk).unwrap() == 3); + kassert!(&shrunk[..3] == b"ABC"); +}); + +test_case!(test_ext4_cached_truncate_shrink_invalidates_tail_page, { + const TOTAL_SIZE: usize = 4096 + 16; + let fs = create_test_ext4(); + let initial = vec![b'A'; TOTAL_SIZE]; + let inode = create_test_file_with_content(&fs, "cached-truncate-tail.bin", &initial).unwrap(); + + let mut warm = vec![0u8; TOTAL_SIZE]; + kassert!(inode.read_at(0, &mut warm).unwrap() == TOTAL_SIZE); + kassert!(warm == initial); + + kassert!(inode.truncate(4096 + 3).is_ok()); + + let mut reread = vec![0xFF; 32]; + kassert!(inode.read_at(4096 - 8, &mut reread).unwrap() == 11); + kassert!(&reread[..8] == &[b'A'; 8]); + kassert!(&reread[8..11] == &[b'A'; 3]); + kassert!(&reread[11..] == &[0xFF; 21]); +}); + +test_case!(test_ext4_cached_truncate_to_zero_drops_cached_pages, { + let fs = create_test_ext4(); + let inode = + create_test_file_with_content(&fs, "cached-truncate-zero.bin", &[b'Z'; 4096]).unwrap(); + + let mut warm = vec![0u8; 4096]; + kassert!(inode.read_at(0, &mut warm).unwrap() == 4096); + kassert!(warm == vec![b'Z'; 4096]); + + kassert!(inode.truncate(0).is_ok()); + + let mut reread = vec![0xEE; 8]; + kassert!(inode.read_at(0, &mut reread).unwrap() == 0); + kassert!(reread == vec![0xEE; 8]); +}); + +test_case!(test_ext4_cached_truncate_extend_zero_fills_new_range, { + let fs = create_test_ext4(); + let inode = create_test_file_with_content(&fs, "cached-truncate-extend.bin", b"head").unwrap(); + + let mut warm = vec![0u8; 4]; + kassert!(inode.read_at(0, &mut warm).unwrap() == 4); + kassert!(&warm[..] == b"head"); + + kassert!(inode.truncate(32).is_ok()); + + let mut reread = vec![0xFF; 32]; + kassert!(inode.read_at(0, &mut reread).unwrap() == 32); + kassert!(&reread[..4] == b"head"); + kassert!(reread[4..].iter().all(|byte| *byte == 0)); +}); + +test_case!( + test_ext4_cached_truncate_extend_across_pages_preserves_old_data, + { + const OLD_SIZE: usize = 4096 - 3; + const NEW_SIZE: usize = 4096 + 9; + let fs = create_test_ext4(); + let initial = vec![b'K'; OLD_SIZE]; + let inode = + create_test_file_with_content(&fs, "cached-truncate-cross-extend.bin", &initial) + .unwrap(); + + let mut warm = vec![0u8; OLD_SIZE]; + kassert!(inode.read_at(0, &mut warm).unwrap() == OLD_SIZE); + kassert!(warm == initial); + + kassert!(inode.truncate(NEW_SIZE).is_ok()); + + let mut reread = vec![0xFF; 16]; + kassert!(inode.read_at(OLD_SIZE - 4, &mut reread).unwrap() == 16); + kassert!(&reread[..4] == &[b'K'; 4]); + kassert!(reread[4..].iter().all(|byte| *byte == 0)); + } +); + +test_case!(test_ext4_unlink_recreate_does_not_read_old_cached_page, { + let fs = create_test_ext4(); + let root = fs.root_inode(); + let inode = root + .create("reuse-name.bin", FileMode::from_bits_truncate(0o644)) + .unwrap(); + + kassert!(inode.write_at(0, b"OLD!").unwrap() == 4); + let mut old = vec![0u8; 4]; + kassert!(inode.read_at(0, &mut old).unwrap() == 4); + kassert!(&old[..] == b"OLD!"); + + kassert!(root.unlink("reuse-name.bin").is_ok()); + let new_inode = root + .create("reuse-name.bin", FileMode::from_bits_truncate(0o644)) + .unwrap(); + kassert!(new_inode.write_at(0, b"NEW?").unwrap() == 4); + + let found = root.lookup("reuse-name.bin").unwrap(); + let mut new = vec![0u8; 4]; + kassert!(found.read_at(0, &mut new).unwrap() == 4); + kassert!(&new[..] == b"NEW?"); +}); + +test_case!( + test_ext4_unlink_recreate_inode_reuse_drops_frame_cached_page, + { + let fs = create_test_ext4(); + let root = fs.root_inode(); + let inode = root + .create("reuse-frame.bin", FileMode::from_bits_truncate(0o644)) + .unwrap(); + let old = vec![b'O'; 4096 + 16]; + + kassert!(inode.write_at(0, &old).unwrap() == old.len()); + let mut cached = vec![0u8; old.len()]; + kassert!(inode.read_at(0, &mut cached).unwrap() == old.len()); + kassert!(cached == old); + + kassert!(root.unlink("reuse-frame.bin").is_ok()); + let new_inode = root + .create("reuse-frame.bin", FileMode::from_bits_truncate(0o644)) + .unwrap(); + kassert!(new_inode.write_at(0, b"NEW").unwrap() == 3); + + let found = root.lookup("reuse-frame.bin").unwrap(); + let mut new = vec![0u8; 4096 + 16]; + kassert!(found.read_at(0, &mut new).unwrap() == 3); + kassert!(&new[..3] == b"NEW"); + } +); + test_case!(test_ext4_sync, { // 创建文件并写入 let fs = create_test_ext4(); @@ -195,6 +456,26 @@ test_case!(test_ext4_read_beyond_eof, { kassert!(bytes_read <= 5); // 最多只能读取 5 字节 }); +test_case!(test_ext4_cached_read_cross_page_partial_eof, { + const TOTAL_SIZE: usize = 4096 + 9; + let fs = create_test_ext4(); + let mut data = vec![0u8; TOTAL_SIZE]; + for i in 0..TOTAL_SIZE { + data[i] = (i % 251) as u8; + } + let inode = create_test_file_with_content(&fs, "partial-eof.bin", &data).unwrap(); + + let mut first = vec![0u8; 32]; + let read = inode.read_at(4096 - 8, &mut first).unwrap(); + kassert!(read == 17); + kassert!(&first[..17] == &data[4096 - 8..]); + + let mut cached = vec![0u8; 32]; + let read = inode.read_at(4096 - 8, &mut cached).unwrap(); + kassert!(read == 17); + kassert!(&cached[..17] == &data[4096 - 8..]); +}); + test_case!(test_ext4_empty_file_read, { // 创建空文件 let fs = create_test_ext4(); diff --git a/os/src/fs/tests/ext4/ext4_rename.rs b/os/src/fs/tests/ext4/ext4_rename.rs index f2a71b17..88e5ee38 100644 --- a/os/src/fs/tests/ext4/ext4_rename.rs +++ b/os/src/fs/tests/ext4/ext4_rename.rs @@ -2,6 +2,7 @@ use super::create_test_ext4_with_root; use crate::vfs::inode::FileMode; +use alloc::vec; /// Test basic file rename in same directory #[test_case] @@ -192,6 +193,130 @@ fn test_ext4_rename_overwrite_file() { assert_eq!(&buf, content1); } +/// Test rename overwrite after the target file has populated page cache. +#[test_case] +fn test_ext4_rename_overwrite_file_invalidates_cached_target() { + let (_fs, root_dentry) = create_test_ext4_with_root(); + let root = root_dentry.inode.clone(); + + let file1 = root + .create("cache-src.txt", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create source"); + let file2 = root + .create("cache-dst.txt", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create target"); + + file1 + .write_at(0, b"source") + .expect("Failed to write source"); + file2 + .write_at(0, b"target") + .expect("Failed to write target"); + + let mut cached_target = [0u8; 6]; + file2 + .read_at(0, &mut cached_target) + .expect("Failed to read target"); + assert_eq!(&cached_target, b"target"); + + root.rename("cache-src.txt", root.clone(), "cache-dst.txt") + .expect("Failed to overwrite target"); + + let result_file = root + .lookup("cache-dst.txt") + .expect("Failed to find overwritten target"); + let mut buf = [0u8; 6]; + result_file + .read_at(0, &mut buf) + .expect("Failed to read overwritten target"); + assert_eq!(&buf, b"source"); +} + +/// Test rename overwrite after the target has a frame-backed cached page. +#[test_case] +fn test_ext4_rename_overwrite_invalidates_frame_cached_target() { + let (_fs, root_dentry) = create_test_ext4_with_root(); + let root = root_dentry.inode.clone(); + + let file1 = root + .create("frame-cache-src.txt", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create source"); + let file2 = root + .create("frame-cache-dst.txt", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create target"); + + let source = vec![b'S'; 4096 + 9]; + let target = vec![b'T'; 4096 + 9]; + file1.write_at(0, &source).expect("Failed to write source"); + file2.write_at(0, &target).expect("Failed to write target"); + + let mut cached_target = vec![0u8; target.len()]; + file2 + .read_at(0, &mut cached_target) + .expect("Failed to read target"); + assert_eq!(cached_target, target); + + root.rename("frame-cache-src.txt", root.clone(), "frame-cache-dst.txt") + .expect("Failed to overwrite target"); + + let result_file = root + .lookup("frame-cache-dst.txt") + .expect("Failed to find overwritten target"); + let mut buf = vec![0u8; source.len()]; + result_file + .read_at(0, &mut buf) + .expect("Failed to read overwritten target"); + assert_eq!(buf, source); +} + +/// Test cross-directory rename replace after old target has frame-backed cache. +#[test_case] +fn test_ext4_cross_dir_rename_replace_invalidates_old_target_frame_cache() { + let (_fs, root_dentry) = create_test_ext4_with_root(); + let root = root_dentry.inode.clone(); + + let dir1 = root + .mkdir("replace-dir1", FileMode::from_bits_truncate(0o755)) + .expect("Failed to create dir1"); + let dir2 = root + .mkdir("replace-dir2", FileMode::from_bits_truncate(0o755)) + .expect("Failed to create dir2"); + let source_file = dir1 + .create("source.bin", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create source"); + let target_file = dir2 + .create("target.bin", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create target"); + + let source = vec![b'A'; 4096 + 17]; + let target = vec![b'B'; 4096 + 17]; + source_file + .write_at(0, &source) + .expect("Failed to write source"); + target_file + .write_at(0, &target) + .expect("Failed to write target"); + + let mut cached_target = vec![0u8; target.len()]; + target_file + .read_at(0, &mut cached_target) + .expect("Failed to read target"); + assert_eq!(cached_target, target); + + dir1.rename("source.bin", dir2.clone(), "target.bin") + .expect("Failed to replace target across directories"); + + assert!(dir1.lookup("source.bin").is_err()); + let moved = dir2 + .lookup("target.bin") + .expect("Failed to find moved source"); + let mut buf = vec![0u8; source.len()]; + moved + .read_at(0, &mut buf) + .expect("Failed to read moved source"); + assert_eq!(buf, source); +} + /// Test rename with target overwrite (empty directory) #[test_case] fn test_ext4_rename_overwrite_empty_dir() { diff --git a/os/src/fs/tests/tmpfs/tmpfs_io.rs b/os/src/fs/tests/tmpfs/tmpfs_io.rs index b4ed2a5f..a14e8100 100644 --- a/os/src/fs/tests/tmpfs/tmpfs_io.rs +++ b/os/src/fs/tests/tmpfs/tmpfs_io.rs @@ -137,3 +137,38 @@ test_case!(test_tmpfs_cross_page_write, { file.read_at(4095, &mut buf).unwrap(); kassert!(&buf[..] == data); }); + +test_case!(test_tmpfs_truncate_shrink_zeroes_retained_tail, { + let fs = create_test_tmpfs(); + let root = fs.root_inode(); + let file = root + .create("tail-zero.txt", FileMode::from_bits_truncate(0o644)) + .unwrap(); + + let data = vec![0xAA; 4096]; + kassert!(file.write_at(0, &data).unwrap() == data.len()); + kassert!(file.truncate(1024).is_ok()); + kassert!(file.truncate(4096).is_ok()); + + let mut buf = vec![0xFF; 4096]; + kassert!(file.read_at(0, &mut buf).unwrap() == 4096); + kassert!(buf[..1024].iter().all(|byte| *byte == 0xAA)); + kassert!(buf[1024..].iter().all(|byte| *byte == 0)); +}); + +test_case!(test_tmpfs_write_4095_4096_boundary, { + let fs = create_test_tmpfs(); + let root = fs.root_inode(); + let file = root + .create("boundary.txt", FileMode::from_bits_truncate(0o644)) + .unwrap(); + + let data = [0x11, 0x22]; + kassert!(file.write_at(4095, &data).unwrap() == data.len()); + + let mut buf = vec![0xFF; 4097]; + kassert!(file.read_at(0, &mut buf).unwrap() == 4097); + kassert!(buf[..4095].iter().all(|byte| *byte == 0)); + kassert!(buf[4095] == 0x11); + kassert!(buf[4096] == 0x22); +}); diff --git a/os/src/fs/tmpfs/inode.rs b/os/src/fs/tmpfs/inode.rs index 3f20b50a..a2647863 100644 --- a/os/src/fs/tmpfs/inode.rs +++ b/os/src/fs/tmpfs/inode.rs @@ -144,21 +144,6 @@ impl TmpfsInode { inode_no } - /// 检查是否有足够的空间分配新页 - fn can_alloc_pages(&self, num_pages: usize) -> bool { - let stats = self.stats.lock(); - if stats.max_pages == 0 { - return true; // 无限制 - } - stats.allocated_pages + num_pages <= stats.max_pages - } - - /// 增加已分配页数 - fn inc_allocated_pages(&self, num: usize) { - let mut stats = self.stats.lock(); - stats.allocated_pages += num; - } - /// 减少已分配页数 fn dec_allocated_pages(&self, num: usize) { let mut stats = self.stats.lock(); @@ -179,17 +164,68 @@ impl TmpfsInode { meta.ctime = now; } - fn reserve_page(&self) -> Result<(), FsError> { + fn page_range(offset: usize, len: usize) -> Option<(usize, usize)> { + if len == 0 { + return None; + } + + let start = offset / PAGE_SIZE; + let end = offset.saturating_add(len).div_ceil(PAGE_SIZE); + Some((start, end)) + } + + fn frame_kernel_addr(frame: &FrameTracker) -> usize { + frame.ppn().start_addr().to_va().as_usize() + } + + fn copy_from_page(frame: &FrameTracker, page_offset: usize, dst: &mut [u8]) { + let kernel_vaddr = Self::frame_kernel_addr(frame); + unsafe { + core::ptr::copy_nonoverlapping( + (kernel_vaddr + page_offset) as *const u8, + dst.as_mut_ptr(), + dst.len(), + ); + } + } + + fn copy_to_page(frame: &FrameTracker, page_offset: usize, src: &[u8]) { + let kernel_vaddr = Self::frame_kernel_addr(frame); + unsafe { + core::ptr::copy_nonoverlapping( + src.as_ptr(), + (kernel_vaddr + page_offset) as *mut u8, + src.len(), + ); + } + } + + fn zero_page_range(frame: &FrameTracker, page_offset: usize, len: usize) { + if len == 0 { + return; + } + + let kernel_vaddr = Self::frame_kernel_addr(frame); + unsafe { + core::ptr::write_bytes((kernel_vaddr + page_offset) as *mut u8, 0, len); + } + } + + fn reserve_pages(&self, num_pages: usize) -> Result<(), FsError> { let mut stats = self.stats.lock(); - if stats.max_pages != 0 && stats.allocated_pages >= stats.max_pages { + if stats.max_pages != 0 && stats.allocated_pages + num_pages > stats.max_pages { return Err(FsError::NoSpace); } - stats.allocated_pages += 1; + stats.allocated_pages += num_pages; Ok(()) } - fn cancel_page_reservation(&self) { - self.dec_allocated_pages(1); + fn cancel_page_reservations(&self, num_pages: usize) { + self.dec_allocated_pages(num_pages); + } + + fn alloc_data_frame() -> Result, FsError> { + alloc_frame().map(Arc::new).ok_or(FsError::NoSpace) } fn remove_link_from_child(&self, child: &Arc) { @@ -288,20 +324,13 @@ impl Inode for TmpfsInode { let read_len = (PAGE_SIZE - page_offset).min(read_size - bytes_read); // 如果页不存在,返回 0 - if page_index >= data.len() || data[page_index].is_none() { - buf[bytes_read..bytes_read + read_len].fill(0); - } else { - // 通过内核直接映射读取 - let frame = data[page_index].as_ref().unwrap(); - let kernel_vaddr = frame.ppn().start_addr().to_va(); - - unsafe { - core::ptr::copy_nonoverlapping( - (kernel_vaddr.as_usize() + page_offset) as *const u8, - buf[bytes_read..].as_mut_ptr(), - read_len, - ); - } + match data.get(page_index).and_then(Option::as_ref) { + Some(frame) => Self::copy_from_page( + frame, + page_offset, + &mut buf[bytes_read..bytes_read + read_len], + ), + None => buf[bytes_read..bytes_read + read_len].fill(0), } bytes_read += read_len; @@ -321,6 +350,34 @@ impl Inode for TmpfsInode { drop(meta); let mut data = self.data.lock(); + if let Some((start_page, end_page)) = Self::page_range(offset, buf.len()) { + if end_page > data.len() { + data.resize(end_page, None); + } + + let pages_needed = data[start_page..end_page] + .iter() + .filter(|page| page.is_none()) + .count(); + self.reserve_pages(pages_needed)?; + + let mut allocated = 0; + for page in &mut data[start_page..end_page] { + if page.is_none() { + match Self::alloc_data_frame() { + Ok(frame) => { + *page = Some(frame); + allocated += 1; + } + Err(err) => { + self.cancel_page_reservations(pages_needed - allocated); + return Err(err); + } + } + } + } + } + let mut bytes_written = 0; while bytes_written < buf.len() { @@ -328,40 +385,13 @@ impl Inode for TmpfsInode { let page_offset = (offset + bytes_written) % PAGE_SIZE; let write_len = (PAGE_SIZE - page_offset).min(buf.len() - bytes_written); - // 确保 Vec 足够大 - if page_index >= data.len() { - data.resize(page_index + 1, None); - } - - // 按需分配物理帧 - if data[page_index].is_none() { - if self.reserve_page().is_err() { - return Err(FsError::NoSpace); - } - - match alloc_frame() { - Some(frame) => { - data[page_index] = Some(Arc::new(frame)); - } - None => { - // 如果物理帧分配失败,回滚预留的页面计数 - self.cancel_page_reservation(); - return Err(FsError::NoSpace); - } - } - } - // 通过内核直接映射写入 let frame = data[page_index].as_ref().unwrap(); - let kernel_vaddr = frame.ppn().start_addr().to_va(); - - unsafe { - core::ptr::copy_nonoverlapping( - buf[bytes_written..].as_ptr(), - (kernel_vaddr.as_usize() + page_offset) as *mut u8, - write_len, - ); - } + Self::copy_to_page( + frame, + page_offset, + &buf[bytes_written..bytes_written + write_len], + ); bytes_written += write_len; } @@ -626,6 +656,15 @@ impl Inode for TmpfsInode { .filter(|f| f.is_some()) .count(); + if new_page_count > 0 { + let tail_offset = new_size % PAGE_SIZE; + if tail_offset != 0 + && let Some(frame) = data.get(new_page_count - 1).and_then(Option::as_ref) + { + Self::zero_page_range(frame, tail_offset, PAGE_SIZE - tail_offset); + } + } + data.truncate(new_page_count); drop(data); diff --git a/os/src/ipc/signal.rs b/os/src/ipc/signal.rs index 51dfa46d..11c6d3d5 100644 --- a/os/src/ipc/signal.rs +++ b/os/src/ipc/signal.rs @@ -14,9 +14,9 @@ use bitflags::bitflags; use crate::{ arch::{HwTrapFrame, TrapFrame}, kernel::{ - SharedTask, TASK_MANAGER, TaskManagerTrait, TaskState, cleanup_process_resources_on_exit, - current_cpu, current_task, exit_process, exit_task, schedule, sleep_task, - task_group_leader, wake_up_task, yield_task, + SharedTask, TASK_MANAGER, TaskExitStatus, TaskManagerTrait, TaskState, + cleanup_process_resources_on_exit, current_cpu, current_task, exit_process_with_status, + exit_task, schedule, sleep_task, task_group_leader, wake_up_task, yield_task, }, pr_err, uapi::signal::*, @@ -236,10 +236,17 @@ fn signal_from_flag(flag: SignalFlags) -> Option { /* 默认信号处理函数 */ /// 默认行为:进程中止 fn sig_terminate(sig_num: usize) -> ! { + sig_terminate_with_status(sig_num, false); +} + +fn sig_terminate_with_status(sig_num: usize, core_dumped: bool) -> ! { 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); + exit_process_with_status(leader, TaskExitStatus::Signaled { + signal: sig_num, + core_dumped, + }); schedule(); unreachable!("sig_terminate: exited task should not return"); } @@ -248,7 +255,7 @@ fn sig_terminate(sig_num: usize) -> ! { /// TODO: 实现生成 core dump 的功能 fn sig_dump(sig_num: usize) -> ! { pr_err!("signal {}: generating core (stub)", sig_num); - sig_terminate(sig_num); + sig_terminate_with_status(sig_num, true); } /// 默认行为:停止进程 diff --git a/os/src/kernel/scheduler/wait_queue.rs b/os/src/kernel/scheduler/wait_queue.rs index e4548a69..9f94362c 100644 --- a/os/src/kernel/scheduler/wait_queue.rs +++ b/os/src/kernel/scheduler/wait_queue.rs @@ -67,6 +67,12 @@ impl WaitQueue { } } + /// 弹出队首任务但不唤醒,用于 futex requeue。 + pub fn pop_task_no_wake(&mut self) -> Option { + let _g = self.lock.lock(); + self.tasks.pop_task() + } + /// 唤醒队列中所有任务:一次性把要唤醒的任务收集出来,释放锁后逐个唤醒 pub fn wake_up_all(&mut self) { let mut to_wake: Vec = Vec::new(); diff --git a/os/src/kernel/syscall/dispatch.rs b/os/src/kernel/syscall/dispatch.rs index beb0e96f..36b67121 100644 --- a/os/src/kernel/syscall/dispatch.rs +++ b/os/src/kernel/syscall/dispatch.rs @@ -188,6 +188,10 @@ pub fn dispatch_syscall(frame: &mut impl SyscallFrame) { crate::kernel::syscall::numbers::SYS_MUNMAP => sys_munmap(frame), crate::kernel::syscall::numbers::SYS_MMAP => sys_mmap(frame), crate::kernel::syscall::numbers::SYS_MPROTECT => sys_mprotect(frame), + crate::kernel::syscall::numbers::SYS_MLOCK => sys_mlock(frame), + crate::kernel::syscall::numbers::SYS_MUNLOCK => sys_munlock(frame), + crate::kernel::syscall::numbers::SYS_MLOCKALL => sys_mlockall(frame), + crate::kernel::syscall::numbers::SYS_MUNLOCKALL => sys_munlockall(frame), // 文件系统同步 (续) crate::kernel::syscall::numbers::SYS_SYNCFS => sys_syncfs(frame), diff --git a/os/src/kernel/syscall/fs/metadata_ops.rs b/os/src/kernel/syscall/fs/metadata_ops.rs index d864fddd..46be2703 100644 --- a/os/src/kernel/syscall/fs/metadata_ops.rs +++ b/os/src/kernel/syscall/fs/metadata_ops.rs @@ -186,6 +186,12 @@ pub fn fchmodat(dirfd: i32, pathname: *const c_char, mode: u32, flags: u32) -> i } } +/// Linux syscall 53 is the historical 3-argument fchmodat. The 4-argument +/// fchmodat2 variant uses a different syscall number and is not wired here. +pub fn fchmodat_legacy(dirfd: i32, pathname: *const c_char, mode: u32) -> isize { + fchmodat(dirfd, pathname, mode, 0) +} + /// mknodat 系统调用 /// /// # 参数 diff --git a/os/src/kernel/syscall/fs/stat_ops.rs b/os/src/kernel/syscall/fs/stat_ops.rs index 9c89493b..cf115d75 100644 --- a/os/src/kernel/syscall/fs/stat_ops.rs +++ b/os/src/kernel/syscall/fs/stat_ops.rs @@ -74,16 +74,9 @@ pub fn getdents64(fd: usize, dirp: *mut u8, count: usize) -> isize { Err(e) => return e.to_errno(), }; - // 获取 inode(目录必须通过 inode 读取) - let inode = match file.inode() { - Ok(i) => i, - Err(e) => return e.to_errno(), - }; - - // 读取目录项 - // TODO: readdir 返回所有项,对于大目录效率低,且 racey。 - // 应改进为支持从 offset 读取,或者缓存 readdir 结果。 - let entries = match inode.readdir() { + // 读取目录项。普通文件会在 File 会话层缓存目录快照,避免 getdents64 + // 使用小缓冲区时对同一目录反复触发底层 readdir。 + let entries = match file.readdir_cached() { Ok(e) => e, Err(e) => return e.to_errno(), }; @@ -424,9 +417,13 @@ pub fn statx( pub fn utimensat(dirfd: i32, pathname: *const c_char, times: *const TimeSpec, flags: u32) -> isize { // 解析路径 - let path_str = match get_path_safe(pathname as usize) { - Ok(s) => s, - Err(e) => return e.to_errno(), + let path_str = if pathname.is_null() { + None + } else { + match get_path_safe(pathname as usize) { + Ok(s) => Some(s), + Err(e) => return e.to_errno(), + } }; const UTIMENSAT_ALLOWED_FLAGS: u32 = @@ -438,7 +435,17 @@ pub fn utimensat(dirfd: i32, pathname: *const c_char, times: *const TimeSpec, fl let at_flags = AtFlags::from_bits_retain(flags); // 查找文件 - let dentry = if path_str.is_empty() { + let dentry = if pathname.is_null() { + let task = current_task(); + let file = match task.lock().fd_table.get(dirfd as usize) { + Ok(file) => file, + Err(e) => return e.to_errno(), + }; + match file.dentry() { + Ok(dentry) => dentry, + Err(e) => return e.to_errno(), + } + } else if path_str.as_deref() == Some("") { if !at_flags.contains(AtFlags::EMPTY_PATH) { return FsError::NotFound.to_errno(); } @@ -460,8 +467,9 @@ pub fn utimensat(dirfd: i32, pathname: *const c_char, times: *const TimeSpec, fl } } } else { + let path_str = path_str.as_deref().unwrap_or(""); let follow_symlink = !at_flags.contains(AtFlags::SYMLINK_NOFOLLOW); - match resolve_at_path_with_flags(dirfd, &path_str, follow_symlink) { + match resolve_at_path_with_flags(dirfd, path_str, follow_symlink) { Ok(d) => d, Err(e) => return e.to_errno(), } diff --git a/os/src/kernel/syscall/mm.rs b/os/src/kernel/syscall/mm.rs index dad5b5e3..5d42ed98 100644 --- a/os/src/kernel/syscall/mm.rs +++ b/os/src/kernel/syscall/mm.rs @@ -6,8 +6,9 @@ use crate::mm::address::{PageNum, VA, Vpn, VpnRange}; use crate::mm::memory_space::MmapFile; use crate::mm::memory_space::mapping_area::AreaType; use crate::mm::page_table::UniversalPTEFlag; -use crate::uapi::errno::{EACCES, EBADF, EEXIST, EINVAL, EIO, ENOMEM}; +use crate::uapi::errno::{EACCES, EAGAIN, EBADF, EEXIST, EINVAL, EIO, ENOMEM}; use crate::uapi::mm::{MAP_FAILED, MapFlags, ProtFlags}; +use crate::uapi::resource::ResourceId; use crate::{pr_err, pr_warn}; /// brk - 改变数据段的结束地址(堆顶) @@ -428,3 +429,108 @@ pub fn mprotect(addr: *mut c_void, len: usize, prot: i32) -> isize { } } } + +const MCL_CURRENT: i32 = 1; +const MCL_FUTURE: i32 = 2; + +fn range_is_mapped(start: usize, len: usize) -> bool { + let Some(end) = start.checked_add(len) else { + return false; + }; + if len == 0 { + return true; + } + + let start_vpn = Vpn::from_addr_floor(VA::from_usize(start)); + let end_vpn = Vpn::from_addr_ceil(VA::from_usize(end)); + let mut cursor = start_vpn; + let memory_space = current_memory_space(); + let space = memory_space.lock(); + + while cursor < end_vpn { + let Some(area) = space + .areas() + .iter() + .find(|area| area.vpn_range().contains(cursor)) + else { + return false; + }; + cursor = core::cmp::min(area.vpn_range().end(), end_vpn); + } + + true +} + +fn memlock_limit_allows(len: usize) -> bool { + let limit = current_task().lock().rlimit.lock().limits[ResourceId::Memlock as usize].rlim_cur; + limit == crate::uapi::resource::rlimit_value::RLIM_INFINITY || len <= limit +} + +/// mlock - lock a user address range in memory. +/// +/// CCYOS does not swap user pages out, so the lock operation is a Linux-compatible no-op after +/// validating the range and RLIMIT_MEMLOCK. This still matters for userland feature probing. +pub fn mlock(addr: *const c_void, len: usize) -> isize { + if len == 0 { + return 0; + } + + let start = addr as usize; + if start.checked_add(len).is_none() { + return -EINVAL as isize; + } + if !memlock_limit_allows(len) { + return -EAGAIN as isize; + } + if !range_is_mapped(start, len) { + return -ENOMEM as isize; + } + + 0 +} + +/// munlock - unlock a user address range. +pub fn munlock(addr: *const c_void, len: usize) -> isize { + if len == 0 { + return 0; + } + + let start = addr as usize; + if start.checked_add(len).is_none() { + return -EINVAL as isize; + } + if !range_is_mapped(start, len) { + return -ENOMEM as isize; + } + + 0 +} + +/// mlockall - lock current/future user mappings. +pub fn mlockall(flags: i32) -> isize { + if flags & !(MCL_CURRENT | MCL_FUTURE) != 0 || flags == 0 { + return -EINVAL as isize; + } + + if flags & MCL_CURRENT != 0 { + let mapped_bytes = { + let memory_space = current_memory_space(); + let space = memory_space.lock(); + space + .areas() + .iter() + .map(|area| area.vpn_range().len() * PAGE_SIZE) + .sum::() + }; + if !memlock_limit_allows(mapped_bytes) { + return -EAGAIN as isize; + } + } + + 0 +} + +/// munlockall - unlock all user mappings. +pub fn munlockall() -> isize { + 0 +} diff --git a/os/src/kernel/syscall/mod.rs b/os/src/kernel/syscall/mod.rs index f6c87e1b..b27a32a3 100644 --- a/os/src/kernel/syscall/mod.rs +++ b/os/src/kernel/syscall/mod.rs @@ -83,7 +83,7 @@ impl_syscall!(sys_statfs, statfs, (*const c_char, *mut LinuxStatFs)); // 文件大小/权限/所有权 (File Size/Permissions/Ownership) impl_syscall!(sys_faccessat, faccessat, (i32, *const c_char, i32, u32)); impl_syscall!(sys_chdir, chdir, (*const c_char)); -impl_syscall!(sys_fchmodat, fchmodat, (i32, *const c_char, u32, u32)); +impl_syscall!(sys_fchmodat, fchmodat_legacy, (i32, *const c_char, u32)); impl_syscall!(sys_fchownat, fchownat, (i32, *const c_char, u32, u32, u32)); // 文件描述符操作 (File Descriptor Operations) @@ -323,6 +323,10 @@ impl_syscall!(sys_brk, brk, (usize)); impl_syscall!(sys_mmap, mmap, (*mut c_void, usize, i32, i32, i32, i64)); impl_syscall!(sys_munmap, munmap, (*mut c_void, usize)); impl_syscall!(sys_mprotect, mprotect, (*mut c_void, usize, i32)); +impl_syscall!(sys_mlock, mlock, (*const c_void, usize)); +impl_syscall!(sys_munlock, munlock, (*const c_void, usize)); +impl_syscall!(sys_mlockall, mlockall, (i32)); +impl_syscall!(sys_munlockall, munlockall, ()); // 文件系统同步 (续) impl_syscall!(sys_syncfs, syncfs, (usize)); diff --git a/os/src/kernel/syscall/network/addr_ops.rs b/os/src/kernel/syscall/network/addr_ops.rs index 69c7d54e..0d924391 100644 --- a/os/src/kernel/syscall/network/addr_ops.rs +++ b/os/src/kernel/syscall/network/addr_ops.rs @@ -99,6 +99,15 @@ pub fn sendto( ) .ok(); } + let handle = match handle { + SocketHandle::Udp(h) => { + match ensure_udp_bound_for_peer(tid, sockfd as usize, &file, h, endpoint) { + Ok(h) => SocketHandle::Udp(h), + Err(e) => return e.to_errno(), + } + } + SocketHandle::Tcp(_) => handle, + }; socket_sendto(handle, &kernel_buf, endpoint) }; match result { diff --git a/os/src/kernel/syscall/network/connection_ops.rs b/os/src/kernel/syscall/network/connection_ops.rs index 24654344..32e0de33 100644 --- a/os/src/kernel/syscall/network/connection_ops.rs +++ b/os/src/kernel/syscall/network/connection_ops.rs @@ -155,55 +155,7 @@ pub fn connect(sockfd: i32, addr: *const u8, addrlen: u32) -> isize { SocketHandle::Udp(h) => { pr_debug!("connect: sockfd={} UDP", sockfd); - // Ensure this fd is attached to the shared per-port UDP socket. - // If not yet bound, implicitly bind to an ephemeral port (49152-65535). - let local_port = match file - .as_any() - .downcast_ref::() - .and_then(|sf| sf.get_local_endpoint()) - { - Some(ep) if ep.port != 0 => ep.port, - _ => alloc_ephemeral_port(), - }; - - if let Some(sf) = file.as_any().downcast_ref::() { - // IMPORTANT: use a concrete local source address for loopback, otherwise smoltcp - // will emit packets with src=0.0.0.0 and iperf3 UDP server will "connect()" to - // 127.0.0.1 and then drop subsequent datagrams (remote endpoint mismatch). - let local_addr = match endpoint.addr { - IpAddress::Ipv4(a) if a.octets()[0] == 127 => { - IpAddress::Ipv4(Ipv4Address::LOCALHOST) - } - #[cfg(feature = "proto-ipv6")] - IpAddress::Ipv6(a) if a.is_loopback() => { - use smoltcp::wire::Ipv6Address; - IpAddress::Ipv6(Ipv6Address::LOCALHOST) - } - _ => IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), - }; - sf.set_local_endpoint(IpEndpoint::new(local_addr, local_port)); - } - - let bind_addr = match endpoint.addr { - IpAddress::Ipv4(a) if a.octets()[0] == 127 => { - Some(IpAddress::Ipv4(Ipv4Address::LOCALHOST)) - } - #[cfg(feature = "proto-ipv6")] - IpAddress::Ipv6(a) if a.is_loopback() => { - use smoltcp::wire::Ipv6Address; - Some(IpAddress::Ipv6(Ipv6Address::LOCALHOST)) - } - _ => None, - }; - - if let Err(e) = crate::net::socket::udp_attach_fd_to_port( - tid, - sockfd as usize, - &file, - h, - local_port, - bind_addr, - ) { + if let Err(e) = ensure_udp_bound_for_peer(tid, sockfd as usize, &file, h, endpoint) { return e.to_errno(); } pr_debug!("connect: sockfd={} UDP -> success", sockfd); diff --git a/os/src/kernel/syscall/network/mod.rs b/os/src/kernel/syscall/network/mod.rs index a95ad98f..f4428917 100644 --- a/os/src/kernel/syscall/network/mod.rs +++ b/os/src/kernel/syscall/network/mod.rs @@ -104,7 +104,8 @@ use crate::{ interface::NETWORK_INTERFACE_MANAGER, socket::{ SocketFile, SocketHandle, create_tcp_socket, create_udp_socket, get_socket_handle, - parse_sockaddr_in, register_socket_fd, unregister_socket_fd, write_sockaddr_in, + parse_sockaddr_in, read_sockaddr_family, register_socket_fd, unregister_socket_fd, + write_sockaddr_in, }, stack::{TcpConnectionState, TcpListenState, network_stack}, unix_socket::{ @@ -121,6 +122,60 @@ use crate::{ use alloc::sync::Arc; use smoltcp::wire::{IpAddress, IpEndpoint, Ipv4Address}; +fn udp_local_addr_for_peer(endpoint: IpEndpoint) -> IpAddress { + match endpoint.addr { + IpAddress::Ipv4(a) if a.octets()[0] == 127 => IpAddress::Ipv4(Ipv4Address::LOCALHOST), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(a) if a.is_loopback() => { + use smoltcp::wire::Ipv6Address; + IpAddress::Ipv6(Ipv6Address::LOCALHOST) + } + _ => IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), + } +} + +fn udp_bind_addr_for_peer(endpoint: IpEndpoint) -> Option { + match endpoint.addr { + IpAddress::Ipv4(a) if a.octets()[0] == 127 => Some(IpAddress::Ipv4(Ipv4Address::LOCALHOST)), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(a) if a.is_loopback() => { + use smoltcp::wire::Ipv6Address; + Some(IpAddress::Ipv6(Ipv6Address::LOCALHOST)) + } + _ => None, + } +} + +fn ensure_udp_bound_for_peer( + tid: usize, + fd: usize, + file: &Arc, + old_handle: smoltcp::iface::SocketHandle, + peer: IpEndpoint, +) -> Result { + let local_port = file + .as_any() + .downcast_ref::() + .and_then(|sf| sf.get_local_endpoint()) + .filter(|ep| ep.port != 0) + .map(|ep| ep.port) + .unwrap_or_else(alloc_ephemeral_port); + + if let Some(sf) = file.as_any().downcast_ref::() { + let local_addr = udp_local_addr_for_peer(peer); + sf.set_local_endpoint(IpEndpoint::new(local_addr, local_port)); + } + + crate::net::socket::udp_attach_fd_to_port( + tid, + fd, + file, + old_handle, + local_port, + udp_bind_addr_for_peer(peer), + ) +} + /// 安全地从用户空间拷贝C字符串 fn copy_c_str_from_user(ptr: *const c_char) -> Option { if ptr.is_null() { diff --git a/os/src/kernel/syscall/network/socket_ops.rs b/os/src/kernel/syscall/network/socket_ops.rs index 07c26ac7..8a0329d8 100644 --- a/os/src/kernel/syscall/network/socket_ops.rs +++ b/os/src/kernel/syscall/network/socket_ops.rs @@ -1,6 +1,36 @@ use super::*; const TCP_LISTENER_POOL_LIMIT: usize = 16; +const AF_INET_U16: u16 = 2; + +fn is_local_bind_address(addr: IpAddress) -> bool { + match addr { + IpAddress::Ipv4(addr) => { + addr.is_unspecified() + || addr.octets()[0] == 127 + || NETWORK_INTERFACE_MANAGER + .lock() + .get_interfaces() + .iter() + .any(|iface| { + iface + .ip_addresses() + .iter() + .any(|cidr| match cidr.address() { + IpAddress::Ipv4(local) => local == addr, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(_) => false, + #[cfg(not(feature = "proto-ipv6"))] + _ => false, + }) + }) + } + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => addr.is_unspecified() || addr.is_loopback(), + #[cfg(not(feature = "proto-ipv6"))] + _ => false, + } +} /// 创建套接字 pub fn socket(domain: i32, socket_type: i32, _protocol: i32) -> isize { @@ -132,24 +162,41 @@ pub fn socketpair(domain: i32, socket_type: i32, _protocol: i32, sv: *mut i32) - pub fn bind(sockfd: i32, addr: *const u8, addrlen: u32) -> isize { let task = current_task(); let task_lock = task.lock(); + let tid = task_lock.tid as usize; let file = match task_lock.fd_table.get(sockfd as usize) { Ok(f) => f, Err(_) => return -9, // EBADF }; - if let Some(unix_socket) = file.as_any().downcast_ref::() { + drop(task_lock); + + let is_unix_socket = file.as_any().is::(); + if is_unix_socket { let unix_addr = match parse_sockaddr_un(addr, addrlen) { Ok(addr) => addr, Err(e) => return e, }; + let unix_socket = match file.as_any().downcast_ref::() { + Some(socket) => socket, + None => return -88, // ENOTSOCK + }; return unix_socket.bind(unix_addr); } + let family = match read_sockaddr_family(addr, addrlen) { + Ok(family) => family, + Err(e) => return e.to_errno(), + }; + if family != AF_INET_U16 { + return -(crate::uapi::errno::EAFNOSUPPORT as isize); + } + let endpoint = match parse_sockaddr_in(addr, addrlen) { Ok(e) => e, Err(e) => return e.to_errno(), }; - - let tid = task_lock.tid as usize; + if !is_local_bind_address(endpoint.addr) { + return -(crate::uapi::errno::EADDRNOTAVAIL as isize); + } pr_debug!( "bind: tid={}, sockfd={}, endpoint={}", @@ -163,12 +210,6 @@ pub fn bind(sockfd: i32, addr: *const u8, addrlen: u32) -> isize { None => return -88, // ENOTSOCK }; - let file = match task_lock.fd_table.get(sockfd as usize) { - Ok(f) => f, - Err(_) => return -9, // EBADF - }; - drop(task_lock); - // For TCP: just save the endpoint, listen() will call smoltcp's listen() // For UDP: bind immediately match handle { @@ -395,6 +436,10 @@ pub fn accept(sockfd: i32, addr: *mut u8, addrlen: *mut u32) -> isize { None => return -88, // ENOTSOCK }; + if matches!(socket_file.handle(), SocketHandle::Udp(_)) { + return -95; // EOPNOTSUPP - UDP doesn't support accept + } + if !socket_file.is_listener() { return -22; // EINVAL - not a listening socket } diff --git a/os/src/kernel/syscall/numbers.rs b/os/src/kernel/syscall/numbers.rs index 11cefe0e..7d672aff 100644 --- a/os/src/kernel/syscall/numbers.rs +++ b/os/src/kernel/syscall/numbers.rs @@ -154,6 +154,10 @@ pub const SYS_BRK: usize = 214; pub const SYS_MUNMAP: usize = 215; pub const SYS_MMAP: usize = 222; pub const SYS_MPROTECT: usize = 226; +pub const SYS_MLOCK: usize = 228; +pub const SYS_MUNLOCK: usize = 229; +pub const SYS_MLOCKALL: usize = 230; +pub const SYS_MUNLOCKALL: usize = 231; // ---- 网络 (续) ---- pub const SYS_ACCEPT4: usize = 242; diff --git a/os/src/kernel/syscall/sys.rs b/os/src/kernel/syscall/sys.rs index 52838b78..737faaa7 100644 --- a/os/src/kernel/syscall/sys.rs +++ b/os/src/kernel/syscall/sys.rs @@ -193,14 +193,15 @@ pub fn clock_settime(clk_id: c_int, tp: *const TimeSpec) -> c_int { /// * **成功**:返回 0,`tp` 被填充时钟分辨率 /// * **失败**:返回负的 errno pub fn clock_getres(clk_id: c_int, tp: *mut TimeSpec) -> c_int { + let nsec = core::cmp::max(1, 1_000_000_000 / (clock_freq() as c_long)); let res = match clk_id { CLOCK_REALTIME | CLOCK_REALTIME_COARSE => TimeSpec { tv_sec: 0, - tv_nsec: 1_000_000_000 / (clock_freq() as c_long), + tv_nsec: nsec, }, CLOCK_MONOTONIC | CLOCK_MONOTONIC_COARSE | CLOCK_MONOTONIC_RAW => TimeSpec { tv_sec: 0, - tv_nsec: 1_000_000_000 / (clock_freq() as c_long), + tv_nsec: nsec, }, id if id < MAX_CLOCKS as c_int && id >= 0 => { return -ENOSYS; diff --git a/os/src/kernel/syscall/task/clone_ops.rs b/os/src/kernel/syscall/task/clone_ops.rs index 8c8ba2c4..3cc759c0 100644 --- a/os/src/kernel/syscall/task/clone_ops.rs +++ b/os/src/kernel/syscall/task/clone_ops.rs @@ -50,6 +50,7 @@ pub fn clone( sched_policy, sched_priority, sched_reset_on_fork, + oom_score_adj, cpu_affinity, shm_attachments, ) = { @@ -76,6 +77,7 @@ pub fn clone( task.sched_policy, task.sched_priority, task.sched_reset_on_fork, + task.oom_score_adj, task.cpu_affinity, if requested_flags.contains(CloneFlags::THREAD) { task.shm_attachments.clone() @@ -158,6 +160,7 @@ pub fn clone( child_task.sched_priority = sched_priority; child_task.sched_reset_on_fork = false; } + child_task.oom_score_adj = oom_score_adj; 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(); diff --git a/os/src/kernel/syscall/task/futex_ops.rs b/os/src/kernel/syscall/task/futex_ops.rs index cbea482f..5b1b5567 100644 --- a/os/src/kernel/syscall/task/futex_ops.rs +++ b/os/src/kernel/syscall/task/futex_ops.rs @@ -1,5 +1,167 @@ use super::*; -use crate::arch::Arch; +use crate::{arch::Arch, kernel::WaitQueue}; + +fn futex_paddr(uaddr: *mut u32) -> Result { + let Some(memory_space) = current_task().lock().memory_space.clone() else { + return Err(-EFAULT); + }; + memory_space + .lock() + .translate(VA::from_usize(uaddr as usize)) + .map(|pa| pa.as_usize()) + .ok_or(-EFAULT) +} + +fn read_futex_word(uaddr: *mut u32) -> Result { + let mut val = core::mem::MaybeUninit::::uninit(); + let copy_result = unsafe { + crate::arch::ArchImpl::copy_from_user( + UA::from_usize(uaddr as usize), + val.as_mut_ptr() as *mut u8, + core::mem::size_of::(), + ) + }; + if copy_result.is_err() { + return Err(-EFAULT); + } + Ok(unsafe { val.assume_init() }) +} + +fn timespec_to_timeout_ticks( + timeout: *const TimeSpec, + absolute: bool, + realtime: bool, +) -> Result, c_int> { + if timeout.is_null() { + return Ok(None); + } + + let ts = unsafe { read_from_user(timeout) }; + if ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec > 999999999 { + return Err(-EINVAL); + } + + let ticks = ts.into_freq(clock_freq()); + if absolute { + if realtime { + let realtime_now_ticks = realtime_now().into_freq(clock_freq()); + let mono_now = get_time(); + let rel = ticks.saturating_sub(realtime_now_ticks); + Ok(Some(mono_now.saturating_add(rel))) + } else { + Ok(Some(ticks)) + } + } else { + Ok(Some(get_time().saturating_add(ticks))) + } +} + +fn futex_wait_common( + uaddr: *mut u32, + val: u32, + timeout: *const TimeSpec, + absolute_timeout: bool, + realtime: bool, +) -> c_int { + let task = current_task(); + let paddr = match futex_paddr(uaddr) { + Ok(paddr) => paddr, + Err(e) => return e, + }; + + let user_val = match read_futex_word(uaddr) { + Ok(v) => v, + Err(e) => return e, + }; + if user_val != val { + return -EAGAIN; + } + + let trigger = match timespec_to_timeout_ticks(timeout, absolute_timeout, realtime) { + Ok(trigger) => trigger, + Err(e) => return e, + }; + if let Some(trigger) = trigger + && trigger <= get_time() + { + return -ETIMEDOUT; + } + + { + let mut fm = FUTEX_MANAGER.lock(); + fm.get_wait_queue(paddr).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 { + fm.get_wait_queue(paddr).remove_task(&task); + return -EINTR; + } + } + + if let Some(trigger) = trigger { + TIMER_QUEUE.lock().push(trigger, task.clone()); + } + + yield_task(); + + let timer_was_pending = if trigger.is_some() { + TIMER_QUEUE.lock().remove_task(&task).is_some() + } else { + false + }; + + let still_waiting = { + let mut fm = FUTEX_MANAGER.lock(); + let waitq = fm.get_wait_queue(paddr); + let still_waiting = waitq.contains(&task); + if still_waiting { + waitq.remove_task(&task); + } + still_waiting + }; + + if signal_pending(&task) { + return -EINTR; + } + + if trigger.is_some() && still_waiting && !timer_was_pending { + return -ETIMEDOUT; + } + + 0 +} + +fn futex_wake_common(uaddr: *mut u32, val: u32) -> c_int { + let paddr = match futex_paddr(uaddr) { + Ok(paddr) => paddr, + Err(e) => return e, + }; + let mut fm = FUTEX_MANAGER.lock(); + let waitq = fm.get_wait_queue(paddr); + let mut wake_count = 0; + for _ in 0..val { + if waitq.is_empty() { + break; + } + waitq.wake_up_one(); + wake_count += 1; + } + wake_count +} + +fn requeue_waiters(src: &mut WaitQueue, dst: &mut WaitQueue, count: u32) -> c_int { + let mut moved = 0; + for _ in 0..count { + let Some(task) = src.pop_task_no_wake() else { + break; + }; + dst.add_task(task); + moved += 1; + } + moved +} /// Futex 系统调用实现 /// # 参数 @@ -16,119 +178,75 @@ pub fn futex( op: c_int, val: u32, timeout: *const TimeSpec, - _uaddr2: *mut u32, - _val3: u32, + uaddr2: *mut u32, + val3: u32, ) -> c_int { 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); match op as u32 { - FUTEX_WAIT => { - // 必须保证获取锁 → 定位等待队列 → 读取用户数据 → 比较 → 入队/释放锁 的序列是原子的 - let task = current_task(); - let Some(memory_space) = task.lock().memory_space.clone() else { + FUTEX_WAIT => futex_wait_common(uaddr, val, timeout, false, realtime), + FUTEX_WAIT_BITSET => { + if val3 == 0 { + return -EINVAL; + } + futex_wait_common(uaddr, val, timeout, true, realtime) + } + FUTEX_WAKE | FUTEX_WAKE_BITSET => { + if op as u32 == FUTEX_WAKE_BITSET && val3 == 0 { + return -EINVAL; + } + futex_wake_common(uaddr, val) + } + FUTEX_REQUEUE | FUTEX_CMP_REQUEUE => { + if uaddr2.is_null() { return -EFAULT; + } + let paddr1 = match futex_paddr(uaddr) { + Ok(paddr) => paddr, + Err(e) => return e, }; - let paddr = if let Some(paddr) = memory_space - .lock() - .translate(VA::from_usize(uaddr as usize)) - { - paddr.as_usize() - } else { - return -EFAULT; + let paddr2 = match futex_paddr(uaddr2) { + Ok(paddr) => paddr, + Err(e) => return e, }; - let user_val = { - let mut val = core::mem::MaybeUninit::::uninit(); - let copy_result = unsafe { - crate::arch::ArchImpl::copy_from_user( - UA::from_usize(uaddr as usize), - val.as_mut_ptr() as *mut u8, - core::mem::size_of::(), - ) - }; - if copy_result.is_err() { - return -EFAULT; + let requeue_count = timeout as usize as u32; + if op as u32 == FUTEX_CMP_REQUEUE { + let cmp_val = val3; + match read_futex_word(uaddr) { + Ok(v) if v == cmp_val => {} + Ok(_) => return -EAGAIN, + Err(e) => return e, } - unsafe { val.assume_init() } - }; - if user_val != val { - return -EAGAIN; } - // HACK: 其实只需要锁定与 uaddr 对应的 Futex 等待队列 let mut fm = FUTEX_MANAGER.lock(); - let waitq = fm.get_wait_queue(paddr); - 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) }; - if ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec > 999999999 { - return -EINVAL; - } - let sleep_ticks = ts.into_freq(clock_freq()); - let trigger = if realtime { - let now = realtime_now().into_freq(clock_freq()); - now.saturating_add(sleep_ticks) - } else { - let now = get_time(); - now.saturating_add(sleep_ticks) - }; - TIMER_QUEUE.lock().push(trigger, task.clone()); - drop(fm); - yield_task(); - if TIMER_QUEUE.lock().remove_task(&task).is_none() { - // 超时唤醒 - let mut fm = FUTEX_MANAGER.lock(); - let waitq = fm.get_wait_queue(paddr); - // 虽然任务已经被唤醒, 但仍然需要从等待队列中移除 - waitq.remove_task(&task); - return -ETIMEDOUT; + if paddr1 == paddr2 { + let waitq = fm.get_wait_queue(paddr1); + let mut changed = 0; + for _ in 0..val { + if waitq.is_empty() { + break; + } + waitq.wake_up_one(); + changed += 1; } - } else { - drop(fm); - yield_task(); - } - if signal_pending(&task) { - // 信号唤醒 - let mut fm = FUTEX_MANAGER.lock(); - let waitq = fm.get_wait_queue(paddr); - waitq.remove_task(&task); - return -EINTR; + return changed; } - // 正常唤醒 - // NOTE: 此时任务已经不在等待队列中 - 0 - } - FUTEX_WAKE => { - let mut wake_count = 0; - let paddr = { - 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)) - { - paddr.as_usize() - } else { - return -EFAULT; - } - }; - let mut fm = FUTEX_MANAGER.lock(); - let waitq = fm.get_wait_queue(paddr); + + let mut src = fm.take_wait_queue(paddr1); + let dst = fm.get_wait_queue(paddr2); + let mut changed = 0; for _ in 0..val { - waitq.wake_up_one(); - wake_count += 1; + if src.is_empty() { + break; + } + src.wake_up_one(); + changed += 1; } - wake_count + changed += requeue_waiters(&mut src, dst, requeue_count); + fm.put_wait_queue(paddr1, src); + changed } _ => -ENOSYS, } diff --git a/os/src/kernel/syscall/task/mod.rs b/os/src/kernel/syscall/task/mod.rs index 8d2d00f7..e7d1f6a9 100644 --- a/os/src/kernel/syscall/task/mod.rs +++ b/os/src/kernel/syscall/task/mod.rs @@ -15,9 +15,9 @@ use crate::{ }, ipc::{SignalHandlerTable, SignalPending, signal_pending}, kernel::{ - FUTEX_MANAGER, Scheduler, SharedTask, TASK_MANAGER, TIMER, TIMER_QUEUE, TaskManagerTrait, - TaskState, TaskStruct, TimerEntry, current_cpu, current_task, exit_process, schedule, - sleep_task, sleep_task_prepare, + FUTEX_MANAGER, Scheduler, SharedTask, TASK_MANAGER, TIMER, TIMER_QUEUE, TaskExitStatus, + TaskManagerTrait, TaskState, TaskStruct, TimerEntry, current_cpu, current_task, + exit_process, schedule, sleep_task, sleep_task_prepare, syscall::util::{get_args_safe, get_path_safe}, time::{REALTIME, realtime_now}, yield_task, @@ -33,8 +33,11 @@ use crate::{ EACCES, EAGAIN, EFAULT, EINTR, EINVAL, EIO, EISDIR, ENOENT, ENOEXEC, ENOMEM, ENOSYS, EPERM, ESRCH, ETIMEDOUT, }, - futex::{FUTEX_CLOCK_REALTIME, FUTEX_PRIVATE, FUTEX_WAIT, FUTEX_WAKE, RobustListHead}, - resource::{RLIM_NLIMITS, Rlimit, Rusage}, + futex::{ + FUTEX_CLOCK_REALTIME, FUTEX_CMP_REQUEUE, FUTEX_PRIVATE, FUTEX_REQUEUE, FUTEX_WAIT, + FUTEX_WAIT_BITSET, FUTEX_WAKE, FUTEX_WAKE_BITSET, RobustListHead, + }, + resource::{RLIM_NLIMITS, ResourceId, Rlimit, Rusage}, sched::CloneFlags, signal::{NUM_SIGALRM, NUM_SIGPROF, NUM_SIGVTALRM}, time::{ diff --git a/os/src/kernel/syscall/task/process_ops.rs b/os/src/kernel/syscall/task/process_ops.rs index 5a0c1a28..ff36c33e 100644 --- a/os/src/kernel/syscall/task/process_ops.rs +++ b/os/src/kernel/syscall/task/process_ops.rs @@ -122,6 +122,12 @@ pub fn setrlimit(resource: c_int, rlim: *const Rlimit) -> c_int { let rlimit_lock = current_task().lock().rlimit.clone(); rlimit_lock.lock().limits[resource as usize] = new_limit; } + if resource == ResourceId::Nofile as c_int { + current_task() + .lock() + .fd_table + .set_max_fds(new_limit.rlim_cur); + } 0 // TODO: EPERM, EPERM 和 EFAULT } @@ -167,6 +173,9 @@ pub fn prlimit( } let rlimit_lock = target_task.lock().rlimit.clone(); rlimit_lock.lock().limits[resource as usize] = new_rlim; + if resource == ResourceId::Nofile as c_int { + target_task.lock().fd_table.set_max_fds(new_rlim.rlim_cur); + } } 0 diff --git a/os/src/kernel/syscall/task/wait_ops.rs b/os/src/kernel/syscall/task/wait_ops.rs index 47e43dab..b7e31d9c 100644 --- a/os/src/kernel/syscall/task/wait_ops.rs +++ b/os/src/kernel/syscall/task/wait_ops.rs @@ -1,4 +1,5 @@ use super::*; +use crate::uapi::errno::ECHILD; /// 等待子进程状态变化(wait4) /// # 说明 @@ -65,10 +66,12 @@ pub fn wait4(pid: c_int, wstatus: *mut c_int, options: c_int, _rusage: *mut Rusa let state = ch.lock().state; zombie(state) || continued(state) || stopped(state) }; + let has_matching_child = |children: &[SharedTask]| children.iter().any(match_pid); let task = loop { let mut found: Option = None; let mut nohang = false; + let mut no_child = false; let slept = sleep_task_prepare(cur_task.clone(), true, |t| { if let Some(res) = t.check_child(cond, !opt.contains(WaitFlags::NOWAIT)) { @@ -76,6 +79,10 @@ pub fn wait4(pid: c_int, wstatus: *mut c_int, options: c_int, _rusage: *mut Rusa found = Some(res); return true; } + if !has_matching_child(&t.children.lock()) { + no_child = true; + return true; + } if opt.contains(WaitFlags::NOHANG) { nohang = true; return true; @@ -91,6 +98,9 @@ pub fn wait4(pid: c_int, wstatus: *mut c_int, options: c_int, _rusage: *mut Rusa if let Some(res) = found { break res; } + if no_child { + return -ECHILD; + } if nohang { return 0; } @@ -98,16 +108,19 @@ pub fn wait4(pid: c_int, wstatus: *mut c_int, options: c_int, _rusage: *mut Rusa yield_task(); }; - let (tid, state, exit_code) = { + let (tid, state, exit_status) = { let t = task.lock(); - (t.tid, t.state, t.exit_code) + (t.tid, t.state, t.exit_status) }; let status = match state { - TaskState::Zombie => { - // TODO: 处理信号退出的情况 - WaitStatus::exit_code(exit_code.expect("Zombie must set exit code.") as u8, 0) - } + TaskState::Zombie => match exit_status.expect("Zombie must set exit status.") { + TaskExitStatus::Exited(code) => WaitStatus::exit_code(code as u8, 0), + TaskExitStatus::Signaled { + signal, + core_dumped, + } => WaitStatus::signaled(signal as u8, core_dumped), + }, TaskState::Stopped => { WaitStatus::stop_code(0) // TODO: 停止信号 } diff --git a/os/src/kernel/task/futex.rs b/os/src/kernel/task/futex.rs index d4b7d4e6..167af2fc 100644 --- a/os/src/kernel/task/futex.rs +++ b/os/src/kernel/task/futex.rs @@ -26,4 +26,14 @@ impl FutexManager { pub fn get_wait_queue(&mut self, uaddr: usize) -> &mut WaitQueue { self.futexes.entry(uaddr).or_insert_with(WaitQueue::new) } + + pub fn take_wait_queue(&mut self, uaddr: usize) -> WaitQueue { + self.futexes.remove(&uaddr).unwrap_or_else(WaitQueue::new) + } + + pub fn put_wait_queue(&mut self, uaddr: usize, waitq: WaitQueue) { + if !waitq.is_empty() { + self.futexes.insert(uaddr, waitq); + } + } } diff --git a/os/src/kernel/task/ktask.rs b/os/src/kernel/task/ktask.rs index 976306ba..72f77a13 100644 --- a/os/src/kernel/task/ktask.rs +++ b/os/src/kernel/task/ktask.rs @@ -128,7 +128,7 @@ pub unsafe fn kthread_join(tid: u32, return_value_ptr: Option) -> i32 { if let Some(task) = task_opt { let t = task.lock(); if t.state == TaskState::Zombie { - if let Some(rv) = t.exit_code { + if let Some(crate::kernel::TaskExitStatus::Exited(rv)) = t.exit_status { // SAFETY: 调用者保证了 return_value_ptr 指向的内存是合法可写的 unsafe { if let Some(ptr) = return_value_ptr { diff --git a/os/src/kernel/task/mod.rs b/os/src/kernel/task/mod.rs index 827e468c..3d5eef46 100644 --- a/os/src/kernel/task/mod.rs +++ b/os/src/kernel/task/mod.rs @@ -32,6 +32,7 @@ pub use task_struct::FsStruct; pub use task_struct::SharedTask; pub use task_struct::ShmAttachment; pub use task_struct::Task as TaskStruct; +pub use task_struct::TaskExitStatus; pub use work_queue::*; use alloc::sync::Arc; diff --git a/os/src/kernel/task/process.rs b/os/src/kernel/task/process.rs index d603fd2f..a1d4ea81 100644 --- a/os/src/kernel/task/process.rs +++ b/os/src/kernel/task/process.rs @@ -4,7 +4,10 @@ //! 故此模块变得相对简单,主要负责适配传统的进程概念与内核任务之间的关系。 use crate::{ - kernel::{SharedTask, TASK_MANAGER, TaskManagerTrait, TaskState, notify_parent, wake_up_task}, + kernel::{ + SharedTask, TASK_MANAGER, TaskExitStatus, TaskManagerTrait, TaskState, notify_parent, + wake_up_task, + }, uapi::signal::SignalFlags, }; @@ -16,12 +19,16 @@ use crate::{ /// * `task` - 进程对应的任务 /// * `code` - 退出码 pub fn exit_process(task: SharedTask, code: i32) { + exit_process_with_status(task, TaskExitStatus::Exited(code)); +} + +pub fn exit_process_with_status(task: SharedTask, status: TaskExitStatus) { if !task.lock().is_process() { panic!("exit_process called on a non-process task"); } let (children, threads, init_task) = { let mut t = TASK_MANAGER.lock(); - t.exit_task(task.clone(), code); + t.exit_task_with_status(task.clone(), status); ( t.get_process_children(task.clone()), t.get_process_threads(task.clone()), diff --git a/os/src/kernel/task/task_manager.rs b/os/src/kernel/task/task_manager.rs index c14a4cf0..8ab7c303 100644 --- a/os/src/kernel/task/task_manager.rs +++ b/os/src/kernel/task/task_manager.rs @@ -11,7 +11,7 @@ use alloc::vec::Vec; use crate::kernel::task::SharedTask; use crate::kernel::task::tid_allocator::TidAllocator; -use crate::kernel::{TaskState, exit_task, wake_up_task}; +use crate::kernel::{TaskExitStatus, TaskState, exit_task, wake_up_task}; use crate::sync::SpinLock; use crate::uapi::signal::SignalFlags; @@ -48,6 +48,7 @@ pub trait TaskManagerTrait { /// 参数: /// * `tid`: 需要退出的任务 ID fn exit_task(&mut self, task: SharedTask, code: i32); + fn exit_task_with_status(&mut self, task: SharedTask, status: TaskExitStatus); /// 释放一个已退出的任务 /// 参数: @@ -128,9 +129,13 @@ impl TaskManagerTrait for TaskManager { } fn exit_task(&mut self, task: SharedTask, code: i32) { + self.exit_task_with_status(task, TaskExitStatus::Exited(code)); + } + + fn exit_task_with_status(&mut self, task: SharedTask, status: TaskExitStatus) { { let mut task = task.lock(); - task.exit_code = Some(code); + task.exit_status = Some(status); // Linux 语义:线程退出时应释放其对用户地址空间的引用。 // 线程组共享的地址空间由 Arc 计数管理:最后一个线程退出时自动释放。 task.memory_space = None; @@ -266,7 +271,7 @@ mod tests { let g = exited_task.lock(); // 验证任务管理器设置了返回值 (新的责任) - kassert!(g.exit_code == Some(EXIT_CODE)); + kassert!(g.exit_status == Some(TaskExitStatus::Exited(EXIT_CODE))); // 验证调度器设置了状态 (调度器的责任) kassert!(g.state == TaskState::Zombie); diff --git a/os/src/kernel/task/task_struct.rs b/os/src/kernel/task/task_struct.rs index dc277a77..a8b55767 100644 --- a/os/src/kernel/task/task_struct.rs +++ b/os/src/kernel/task/task_struct.rs @@ -45,6 +45,12 @@ pub struct ShmAttachment { pub type ShmAttachmentTable = Arc>>; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TaskExitStatus { + Exited(i32), + Signaled { signal: usize, core_dumped: bool }, +} + /// 任务 /// 存放任务的核心信息 /// 其中的信息可以分为几大类: @@ -84,6 +90,8 @@ pub struct Task { pub sched_priority: i32, /// fork/clone 时是否将子任务调度属性重置为普通策略。 pub sched_reset_on_fork: bool, + /// Linux-ish OOM badness adjustment exposed through /proc/[pid]/oom_score_adj. + pub oom_score_adj: i32, /// 任务当前的状态 pub state: TaskState, /// 任务的id @@ -110,11 +118,9 @@ pub struct Task { /// 任务的内存空间 /// 对于内核任务,该字段为 None pub memory_space: Option>>, - /// 退出码 - /// 存储任务退出时的状态码,通常用于表示任务的执行结果 - /// 由 exit 接口设置 - /// 对应于 waitpid 的 exit_status - pub exit_code: Option, + /// 退出状态。 + /// 正常 exit 和致命信号退出在 wait(2) 中有不同编码,不能混用。 + pub exit_status: Option, /// 内核栈跟踪器 kstack_tracker: FrameRangeTracker, /// 任务的 TrapFrame 跟踪器 @@ -425,6 +431,7 @@ impl Task { sched_policy: SCHED_NORMAL, sched_priority: 0, sched_reset_on_fork: false, + oom_score_adj: 0, state: TaskState::Running, tid, pid, @@ -438,7 +445,7 @@ impl Task { trap_frame_tracker, trap_frame_ptr: AtomicPtr::new(trap_frame_ptr as *mut TrapFrame), memory_space, - exit_code: None, + exit_status: None, signal_handlers, signal_stack, exit_signal, diff --git a/os/src/mm/memory_space/space/tests.rs b/os/src/mm/memory_space/space/tests.rs index e0a53783..bfb0001e 100644 --- a/os/src/mm/memory_space/space/tests.rs +++ b/os/src/mm/memory_space/space/tests.rs @@ -13,6 +13,51 @@ mod memory_space_tests { } } + fn create_test_file( + name: &str, + inode: alloc::sync::Arc, + ) -> alloc::sync::Arc { + use crate::vfs::{Dentry, OpenFlags, RegFile}; + use alloc::string::String; + + let dentry = Dentry::new(String::from(name), inode); + alloc::sync::Arc::new(RegFile::new(dentry, OpenFlags::O_RDWR)) + } + + fn create_test_mmap_file( + name: &str, + inode: alloc::sync::Arc, + len: usize, + flags: crate::uapi::mm::MapFlags, + ) -> MmapFile { + MmapFile { + file: create_test_file(name, inode), + offset: 0, + len, + prot: crate::uapi::mm::ProtFlags::READ | crate::uapi::mm::ProtFlags::WRITE, + flags, + } + } + + fn create_test_ext4() -> alloc::sync::Arc { + use crate::config::EXT4_BLOCK_SIZE; + use crate::device::block::BlockDriver; + use crate::device::block::ram_disk::RamDisk; + use crate::fs::ext4::Ext4FileSystem; + + const SECTOR_SIZE: usize = 512; + const DEVICE_ID: usize = 0; + + let image_data = include_bytes!(env!("EXT4_FS_IMAGE")).to_vec(); + let ramdisk = RamDisk::from_bytes(image_data, SECTOR_SIZE, DEVICE_ID); + let total_blocks = ramdisk.total_blocks(); + let device_id = ramdisk.device_id(); + let block_driver: alloc::sync::Arc = ramdisk; + + Ext4FileSystem::open(block_driver, EXT4_BLOCK_SIZE, total_blocks, device_id) + .expect("failed to create ext4 test filesystem") + } + // 1. 创建内存空间 test_case!(test_memspace_create, { #[allow(unused)] @@ -446,93 +491,297 @@ mod memory_space_tests { // 16. 测试 mmap 文件映射基本功能 test_case!(test_mmap_file_basic, { use crate::fs::tmpfs::TmpFs; - - use crate::vfs::{File, FileMode, FileSystem}; + use crate::uapi::mm::MapFlags; + use crate::vfs::{FileMode, FileSystem}; println!("Testing mmap file mapping basic functionality"); - // 1. 创建临时文件系统和文件 - let tmpfs = TmpFs::new(16); // 16 MB + let tmpfs = TmpFs::new(16); let root = tmpfs.root_inode(); let inode = root .create("test_mmap.txt", FileMode::from_bits_truncate(0o644)) .expect("Failed to create file"); - // 2. 写入测试数据 let test_data = b"Hello, mmap! This is a test file for memory mapping."; let written = inode.write_at(0, test_data).expect("Failed to write data"); kassert!(written == test_data.len()); - println!(" Written {} bytes to file", written); - - // 3. 创建 File 包装器(需要实现一个简单的 File trait) - // 注意:这里我们直接使用 Inode,因为 File trait 可能需要额外实现 - // 由于测试环境限制,我们先跳过完整的 mmap 测试 - // 这个测试主要验证数据结构和编译正确性 - println!(" File mapping test structure validated"); + let mmap_file = create_test_mmap_file( + "test_mmap.txt", + inode.clone(), + test_data.len(), + MapFlags::PRIVATE, + ); + let file_inode = mmap_file + .file + .inode() + .expect("mmap file should expose inode"); + let mut reread = [0u8; 16]; + let read = file_inode.read_at(7, &mut reread).expect("mmap inode read"); + kassert!(read == reread.len()); + kassert!(&reread[..] == b"mmap! This is a "); + + println!(" File mapping test passed"); }); // 17. 测试 load_from_file 方法 test_case!(test_load_from_file, { use crate::fs::tmpfs::TmpFs; + use crate::uapi::mm::MapFlags; use crate::vfs::{FileMode, FileSystem}; println!("Testing load_from_file method"); - // 1. 创建文件并写入数据 let tmpfs = TmpFs::new(16); let root = tmpfs.root_inode(); let inode = root .create("test_load.txt", FileMode::from_bits_truncate(0o644)) .expect("Failed to create file"); - let test_data = b"Test data for loading into memory pages."; - inode.write_at(0, test_data).expect("Failed to write"); - println!(" Created file with {} bytes", test_data.len()); + let mut test_data = alloc::vec![0u8; PAGE_SIZE + 64]; + for (idx, byte) in test_data.iter_mut().enumerate() { + *byte = (idx % 251) as u8; + } + inode.write_at(0, &test_data).expect("Failed to write"); + + let mut ms = new_memory_space(); + let start_vpn = Vpn::from_usize(0x2000); + let vpn_range = VpnRange::new(start_vpn, Vpn::from_usize(start_vpn.as_usize() + 2)); + let mmap_file = create_test_mmap_file( + "test_load.txt", + inode.clone(), + test_data.len(), + MapFlags::PRIVATE, + ); + + ms.insert_framed_area( + vpn_range, + AreaType::UserMmap, + UniversalPTEFlag::user_rw(), + None, + Some(mmap_file), + ) + .expect("Failed to insert file mapping"); + + let area = ms.find_area_mut(start_vpn).expect("mmap area should exist"); + area.load_from_file() + .expect("load_from_file should succeed"); + + let mut actual = alloc::vec![0u8; test_data.len()]; + ms.read_bytes_at(start_vpn.start_addr().as_usize(), &mut actual) + .expect("read mapped bytes"); + kassert!(actual == test_data); + + println!(" load_from_file loaded mapped pages"); + }); + + test_case!(test_load_from_ext4_cached_file, { + use crate::uapi::mm::MapFlags; + use crate::vfs::{FileMode, FileSystem}; + + println!("Testing load_from_file through ext4 cached read path"); + + let fs = create_test_ext4(); + let root = fs.root_inode(); + let inode = root + .create("mmap-load-ext4.bin", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create ext4 file"); + + let mut test_data = alloc::vec![0u8; PAGE_SIZE + 37]; + for (idx, byte) in test_data.iter_mut().enumerate() { + *byte = ((idx * 7) % 251) as u8; + } + inode.write_at(0, &test_data).expect("Failed to write"); + + let mut warm_cache = alloc::vec![0u8; test_data.len()]; + kassert!(inode.read_at(0, &mut warm_cache).unwrap() == test_data.len()); + kassert!(warm_cache == test_data); + + let mut ms = new_memory_space(); + let start_vpn = Vpn::from_usize(0x2200); + let vpn_range = VpnRange::new(start_vpn, Vpn::from_usize(start_vpn.as_usize() + 2)); + let mmap_file = create_test_mmap_file( + "mmap-load-ext4.bin", + inode.clone(), + test_data.len(), + MapFlags::PRIVATE, + ); + + ms.insert_framed_area( + vpn_range, + AreaType::UserMmap, + UniversalPTEFlag::user_rw(), + None, + Some(mmap_file), + ) + .expect("Failed to insert ext4 file mapping"); + + let area = ms.find_area_mut(start_vpn).expect("mmap area should exist"); + area.load_from_file() + .expect("load_from_file should read ext4 cached pages"); - // 注意:由于 MmapFile 需要 Arc,而我们只有 Inode, - // 完整测试需要实现 File wrapper - // 这里主要验证结构编译正确 + let mut actual = alloc::vec![0u8; test_data.len()]; + ms.read_bytes_at(start_vpn.start_addr().as_usize(), &mut actual) + .expect("read ext4 mapped bytes"); + kassert!(actual == test_data); - println!(" load_from_file structure validated"); + println!(" load_from_file read ext4 cached pages"); }); // 18. 测试 sync_file 方法(验证写回逻辑) test_case!(test_sync_file_logic, { + use crate::mm::page_table::PageTableInner as _; + use crate::uapi::mm::MapFlags; + use crate::vfs::{FileMode, FileSystem}; + println!("Testing sync_file logic"); - // 由于 sync_file 需要: - // 1. MmapFile(包含 Arc) - // 2. 页表中的 Dirty 位 - // 3. 实际的文件系统操作 - // 完整测试需要更复杂的设置 + let fs = create_test_ext4(); + let root = fs.root_inode(); + let inode = root + .create("mmap-sync.bin", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create ext4 file"); + let initial = b"cached-before-mmap-sync"; + inode.write_at(0, initial).expect("Failed to write"); + + let mut cached = alloc::vec![0u8; initial.len()]; + kassert!(inode.read_at(0, &mut cached).unwrap() == initial.len()); + kassert!(&cached[..] == initial); - // 这里验证编译和结构正确性 + let updated = b"cache-after-mmap-sync!"; let mut ms = new_memory_space(); - let vpn_range = VpnRange::new(Vpn::from_usize(0x2000), Vpn::from_usize(0x2002)); + let start_vpn = Vpn::from_usize(0x2100); + let vpn_range = VpnRange::new(start_vpn, Vpn::from_usize(start_vpn.as_usize() + 1)); + let mmap_file = create_test_mmap_file( + "mmap-sync.bin", + inode.clone(), + initial.len(), + MapFlags::SHARED, + ); - // 创建一个没有文件映射的区域 ms.insert_framed_area( vpn_range, AreaType::UserMmap, UniversalPTEFlag::user_rw(), None, + Some(mmap_file), + ) + .expect("Failed to insert area"); + + { + let area = ms.find_area_mut(start_vpn).expect("mmap area should exist"); + area.load_from_file() + .expect("load_from_file should succeed"); + } + + ms.write_bytes_at(start_vpn.start_addr().as_usize(), updated) + .expect("write mapped bytes"); + + let (_, _, flags) = ms.page_table().walk(start_vpn).expect("mapped page"); + ms.page_table_mut() + .update_flags(start_vpn, flags | UniversalPTEFlag::DIRTY) + .expect("mark page dirty"); + + let areas_len = ms.areas().len(); + { + let page_table = &mut ms.page_table; + let area = &ms.areas[areas_len - 1]; + area.sync_file(page_table) + .expect("sync_file should write dirty shared mapping"); + } + + let mut reread = alloc::vec![0u8; initial.len()]; + kassert!(inode.read_at(0, &mut reread).unwrap() == initial.len()); + kassert!(&reread[..updated.len()] == updated); + + let (_, _, clean_flags) = ms.page_table().walk(start_vpn).expect("mapped page"); + kassert!(!clean_flags.contains(UniversalPTEFlag::DIRTY)); + + println!(" sync_file invalidated cached ext4 reads"); + }); + + test_case!(test_sync_file_refreshes_precise_ext4_cached_range, { + use crate::mm::page_table::PageTableInner as _; + use crate::uapi::mm::MapFlags; + use crate::vfs::{FileMode, FileSystem}; + + println!("Testing sync_file precise ext4 cache refresh"); + + let fs = create_test_ext4(); + let root = fs.root_inode(); + let inode = root + .create("mmap-sync-precise.bin", FileMode::from_bits_truncate(0o644)) + .expect("Failed to create ext4 file"); + + let mut initial = alloc::vec![0u8; PAGE_SIZE * 2]; + for (idx, byte) in initial.iter_mut().enumerate() { + *byte = (idx % 251) as u8; + } + inode.write_at(0, &initial).expect("Failed to write"); + + let mut cached = alloc::vec![0u8; initial.len()]; + kassert!(inode.read_at(0, &mut cached).unwrap() == initial.len()); + kassert!(cached == initial); + + let mut ms = new_memory_space(); + let start_vpn = Vpn::from_usize(0x2300); + let vpn_range = VpnRange::new(start_vpn, Vpn::from_usize(start_vpn.as_usize() + 2)); + let mmap_file = create_test_mmap_file( + "mmap-sync-precise.bin", + inode.clone(), + initial.len(), + MapFlags::SHARED, + ); + + ms.insert_framed_area( + vpn_range, + AreaType::UserMmap, + UniversalPTEFlag::user_rw(), None, + Some(mmap_file), ) .expect("Failed to insert area"); - // 对于没有文件映射的区域,sync_file 应该直接返回 Ok - // 需要分两步以避免借用冲突 + { + let area = ms.find_area_mut(start_vpn).expect("mmap area should exist"); + area.load_from_file() + .expect("load_from_file should succeed"); + } + + let write_offset = 128; + let update = [0xA5u8; 64]; + ms.write_bytes_at(start_vpn.start_addr().as_usize() + write_offset, &update) + .expect("write mapped bytes"); + + let (_, _, flags) = ms.page_table().walk(start_vpn).expect("mapped page"); + ms.page_table_mut() + .update_flags(start_vpn, flags | UniversalPTEFlag::DIRTY) + .expect("mark first page dirty"); + let areas_len = ms.areas().len(); - if areas_len > 0 { + { let page_table = &mut ms.page_table; let area = &ms.areas[areas_len - 1]; - let result = area.sync_file(page_table); - kassert!(result.is_ok()); - println!(" sync_file returns Ok for non-file mapping"); + area.sync_file(page_table) + .expect("sync_file should write dirty shared mapping"); } - println!(" sync_file logic validated"); + let mut expected = initial.clone(); + expected[write_offset..write_offset + update.len()].copy_from_slice(&update); + + let mut reread = alloc::vec![0u8; expected.len()]; + kassert!(inode.read_at(0, &mut reread).unwrap() == expected.len()); + kassert!(reread == expected); + + let (_, _, first_flags) = ms.page_table().walk(start_vpn).expect("first page"); + let (_, _, second_flags) = ms + .page_table() + .walk(Vpn::from_usize(start_vpn.as_usize() + 1)) + .expect("second page"); + kassert!(!first_flags.contains(UniversalPTEFlag::DIRTY)); + kassert!(!second_flags.contains(UniversalPTEFlag::DIRTY)); + + println!(" sync_file precisely refreshed ext4 cached range"); }); // 19. 测试 Drop trait 实现 diff --git a/os/src/net/socket.rs b/os/src/net/socket.rs index 6c442b1a..c7252698 100644 --- a/os/src/net/socket.rs +++ b/os/src/net/socket.rs @@ -362,6 +362,25 @@ impl File for SocketFile { const AF_INET: u16 = 2; const SOCKADDR_IN_SIZE: usize = 16; +/// Read the sa_family field from a user sockaddr. +pub fn read_sockaddr_family(addr: *const u8, addrlen: u32) -> Result { + if addr.is_null() || addrlen < core::mem::size_of::() as u32 { + return Err(NetworkError::InvalidAddress); + } + + let mut family = [0u8; core::mem::size_of::()]; + unsafe { + crate::arch::ArchImpl::copy_from_user( + crate::arch::address::UA::from_usize(addr as usize), + family.as_mut_ptr(), + family.len(), + ) + } + .map_err(|_| NetworkError::BadAddress)?; + + Ok(u16::from_ne_bytes(family)) +} + /// Parse sockaddr_in structure from user space pub fn parse_sockaddr_in(addr: *const u8, addrlen: u32) -> Result { if (addrlen as usize) < SOCKADDR_IN_SIZE { diff --git a/os/src/uapi/futex.rs b/os/src/uapi/futex.rs index 1890d258..73bae686 100644 --- a/os/src/uapi/futex.rs +++ b/os/src/uapi/futex.rs @@ -42,6 +42,12 @@ pub const FUTEX_TRYLOCK_PI: FutexOp = 8; /// 等待位集操作:等待,但只对 val3(位集)中包含的比特进行等待。用于高效的条件变量。 pub const FUTEX_WAIT_BITSET: FutexOp = 9; +/// 唤醒位集操作:唤醒等待位集与 val3 相交的线程。 +pub const FUTEX_WAKE_BITSET: FutexOp = 10; + +/// FUTEX_WAIT_BITSET/FUTEX_WAKE_BITSET 的默认位集。 +pub const FUTEX_BITSET_MATCH_ANY: u32 = 0xffff_ffff; + // --- Futex Flags --- // 这些标志通过位或操作(|)与操作码结合使用。 diff --git a/os/src/uapi/wait.rs b/os/src/uapi/wait.rs index 3e12da81..78167394 100644 --- a/os/src/uapi/wait.rs +++ b/os/src/uapi/wait.rs @@ -38,6 +38,12 @@ impl WaitStatus { Self::new(status as c_int) } + /// 构造一个子进程被信号终止的状态值。 + pub fn signaled(sig: u8, core_dumped: bool) -> Self { + let core = if core_dumped { __WCOREFLAG } else { 0 }; + Self::new(((sig as u32) | core) as c_int) + } + /// __W_STOPCODE: 构造一个子进程被停止的状态值。 /// /// 结构: (Signal that stopped the child) << 8 | 0x7f diff --git a/os/src/vfs/devno.rs b/os/src/vfs/devno.rs index d82fcd6b..c7989ae5 100644 --- a/os/src/vfs/devno.rs +++ b/os/src/vfs/devno.rs @@ -20,6 +20,7 @@ pub mod chrdev_major { /// MISC 设备 minor 号 pub mod misc_minor { + pub const CPU_DMA_LATENCY: u32 = 123; pub const RTC: u32 = 135; } @@ -87,7 +88,9 @@ pub fn get_chrdev_driver(dev: u64) -> Option> { } chrdev_major::MISC => { // misc 设备 - if min == misc_minor::RTC { + if min == misc_minor::CPU_DMA_LATENCY { + None + } else if min == misc_minor::RTC { // RTC 设备 (/dev/misc/rtc) RTC_DRIVERS .read() diff --git a/os/src/vfs/fd_table.rs b/os/src/vfs/fd_table.rs index 9e17251e..546c03d9 100644 --- a/os/src/vfs/fd_table.rs +++ b/os/src/vfs/fd_table.rs @@ -139,7 +139,10 @@ use crate::uapi::fcntl::{FdFlags, OpenFlags}; use crate::vfs::{File, FsError}; use alloc::sync::Arc; use alloc::vec::Vec; -use core::fmt; +use core::{ + fmt, + sync::atomic::{AtomicUsize, Ordering}, +}; /// 文件描述符表 /// @@ -156,7 +159,7 @@ pub struct FDTable { fd_flags: SpinLock>, /// 最大文件描述符数量 - max_fds: usize, + max_fds: AtomicUsize, } impl FdFlags { @@ -177,7 +180,7 @@ impl fmt::Debug for FDTable { let files = self.files.lock(); let used = files.iter().filter(|slot| slot.is_some()).count(); f.debug_struct("FDTable") - .field("max_fds", &self.max_fds) + .field("max_fds", &self.max_fds.load(Ordering::Relaxed)) .field("slots", &files.len()) .field("used", &used) .finish() @@ -190,10 +193,14 @@ impl FDTable { Self { files: SpinLock::new(Vec::new()), fd_flags: SpinLock::new(Vec::new()), - max_fds: DEFAULT_MAX_FDS, + max_fds: AtomicUsize::new(DEFAULT_MAX_FDS), } } + pub fn set_max_fds(&self, max_fds: usize) { + self.max_fds.store(max_fds, Ordering::Relaxed); + } + /// 取走并清空所有已打开的文件描述符。 /// /// 返回的列表包含 (fd, file);调用方 drop 这些 Arc 即可完成“关闭”语义。 @@ -226,9 +233,10 @@ impl FDTable { pub fn alloc_with_flags(&self, file: Arc, flags: FdFlags) -> Result { let mut files = self.files.lock(); let mut fd_flags = self.fd_flags.lock(); + let max_fds = self.max_fds.load(Ordering::Relaxed); // 查找最小可用 FD - for (fd, slot) in files.iter_mut().enumerate() { + for (fd, slot) in files.iter_mut().enumerate().take(max_fds) { if slot.is_none() { *slot = Some(file); fd_flags[fd] = flags; @@ -238,7 +246,7 @@ impl FDTable { // 如果没有空闲槽位,扩展数组 let fd = files.len(); - if fd >= self.max_fds { + if fd >= max_fds { return Err(FsError::TooManyOpenFiles); } @@ -261,8 +269,9 @@ impl FDTable { ) -> Result<(), FsError> { let mut files = self.files.lock(); let mut fd_flags = self.fd_flags.lock(); + let max_fds = self.max_fds.load(Ordering::Relaxed); - if fd >= self.max_fds { + if fd >= max_fds { return Err(FsError::InvalidArgument); } @@ -314,7 +323,8 @@ impl FDTable { /// 返回新的 fd,与 old_fd 指向同一个 `Arc` (共享 offset)。 /// 新分配的 fd 是 >= min_fd 的最小未使用文件描述符。 pub fn dup_from(&self, old_fd: usize, min_fd: usize, flags: FdFlags) -> Result { - if min_fd >= self.max_fds { + let max_fds = self.max_fds.load(Ordering::Relaxed); + if min_fd >= max_fds { return Err(FsError::InvalidArgument); } @@ -329,22 +339,28 @@ impl FDTable { } // 2. 从 min_fd 开始查找最小可用 FD - for (fd, slot) in files.iter_mut().enumerate().skip(min_fd) { - if slot.is_none() { - *slot = Some(file); + let search_end = core::cmp::min(files.len(), max_fds); + for fd in min_fd..search_end { + if files[fd].is_none() { + files[fd] = Some(file); fd_flags[fd] = flags; return Ok(fd); } } // 3. 如果没有空闲槽位,在数组末尾分配新的 fd - let fd = files.len(); - if fd >= self.max_fds { + let fd = core::cmp::max(files.len(), min_fd); + if fd >= max_fds { return Err(FsError::TooManyOpenFiles); } - files.push(Some(file)); - fd_flags.push(flags); + while files.len() <= fd { + files.push(None); + fd_flags.push(FdFlags::empty()); + } + + files[fd] = Some(file); + fd_flags[fd] = flags; Ok(fd) } @@ -398,7 +414,7 @@ impl FDTable { Self { files: SpinLock::new(files), fd_flags: SpinLock::new(fd_flags), - max_fds: self.max_fds, + max_fds: AtomicUsize::new(self.max_fds.load(Ordering::Relaxed)), } } diff --git a/os/src/vfs/file.rs b/os/src/vfs/file.rs index e879648c..6f7a5def 100644 --- a/os/src/vfs/file.rs +++ b/os/src/vfs/file.rs @@ -83,8 +83,8 @@ //! ``` use crate::uapi::fcntl::{OpenFlags, SeekWhence}; -use crate::vfs::{Dentry, FsError, Inode, InodeMetadata}; -use alloc::sync::Arc; +use crate::vfs::{Dentry, DirEntry, FsError, Inode, InodeMetadata}; +use alloc::{sync::Arc, vec::Vec}; /// 文件操作的统一接口 /// @@ -151,6 +151,11 @@ pub trait File: Send + Sync { Err(FsError::NotSupported) } + /// 读取目录项,可由有状态 File 实现缓存目录快照。 + fn readdir_cached(&self) -> Result>, FsError> { + Ok(Arc::new(self.inode()?.readdir()?)) + } + /// 设置文件状态标志(可选方法,用于 F_SETFL) /// /// 默认返回 `NotSupported`,适用于不支持动态修改标志的文件类型 diff --git a/os/src/vfs/impls/char_dev_file.rs b/os/src/vfs/impls/char_dev_file.rs index c9cd6bdc..3c426f5b 100644 --- a/os/src/vfs/impls/char_dev_file.rs +++ b/os/src/vfs/impls/char_dev_file.rs @@ -87,7 +87,9 @@ impl CharDeviceFile { // 检查设备是否支持 let maj = major(dev); - if driver.is_none() && maj != chrdev_major::MEM { + let is_builtin_misc = + maj == chrdev_major::MISC && minor(dev) == misc_minor::CPU_DMA_LATENCY; + if driver.is_none() && maj != chrdev_major::MEM && !is_builtin_misc { // 既不是内存设备,也找不到驱动 return Err(FsError::NoDevice); } @@ -169,6 +171,9 @@ impl File for CharDeviceFile { if maj == chrdev_major::MEM { return self.mem_device_read(buf); } + if maj == chrdev_major::MISC && minor(self.dev) == misc_minor::CPU_DMA_LATENCY { + return Ok(0); + } // 其他设备:委托给驱动 if let Some(ref driver) = self.driver { @@ -253,6 +258,9 @@ impl File for CharDeviceFile { if maj == chrdev_major::MEM { return self.mem_device_write(buf); } + if maj == chrdev_major::MISC && minor(self.dev) == misc_minor::CPU_DMA_LATENCY { + return Ok(buf.len()); + } // 其他设备:委托给驱动 if let Some(ref driver) = self.driver { diff --git a/os/src/vfs/impls/reg_file.rs b/os/src/vfs/impls/reg_file.rs index 3f19b95e..378e09c5 100644 --- a/os/src/vfs/impls/reg_file.rs +++ b/os/src/vfs/impls/reg_file.rs @@ -1,8 +1,10 @@ //! 普通文件(Regular File)的 File trait 实现 use crate::sync::SpinLock; -use crate::vfs::{Dentry, File, FsError, Inode, InodeMetadata, OpenFlags, SeekWhence}; -use alloc::sync::Arc; +use crate::vfs::{ + Dentry, DirEntry, File, FsError, Inode, InodeMetadata, InodeType, OpenFlags, SeekWhence, +}; +use alloc::{sync::Arc, vec::Vec}; /// 普通文件的 File 实现 /// @@ -29,6 +31,9 @@ pub struct RegFile { /// 异步 I/O 所有者 PID (接收 SIGIO 信号的进程) owner: SpinLock>, + + /// Per-open directory snapshot used by getdents64 style iteration. + dir_entries: SpinLock>>>, } impl RegFile { @@ -41,6 +46,7 @@ impl RegFile { offset: SpinLock::new(0), flags: SpinLock::new(flags), owner: SpinLock::new(None), + dir_entries: SpinLock::new(None), } } @@ -138,6 +144,9 @@ impl File for RegFile { return Err(FsError::InvalidArgument); } + if whence == SeekWhence::Set && new_offset == 0 && *offset_guard != 0 { + *self.dir_entries.lock() = None; + } *offset_guard = new_offset as usize; Ok(new_offset as usize) } @@ -154,6 +163,24 @@ impl File for RegFile { Ok(self.inode()) } + fn readdir_cached(&self) -> Result>, FsError> { + if self.inode.metadata()?.inode_type != InodeType::Directory { + return Err(FsError::NotDirectory); + } + + if let Some(entries) = self.dir_entries.lock().as_ref().cloned() { + return Ok(entries); + } + + let entries = Arc::new(self.inode.readdir()?); + let mut guard = self.dir_entries.lock(); + if guard.is_none() { + *guard = Some(entries); + } + + Ok(guard.as_ref().unwrap().clone()) + } + fn dentry(&self) -> Result, FsError> { Ok(self.dentry()) } diff --git a/os/src/vfs/mod.rs b/os/src/vfs/mod.rs index 86eed4f0..ac3750ac 100644 --- a/os/src/vfs/mod.rs +++ b/os/src/vfs/mod.rs @@ -160,6 +160,7 @@ pub mod file_system; pub mod impls; pub mod inode; pub mod mount; +pub mod page_cache; pub mod path; pub use adapter::inode_type_to_d_type; diff --git a/os/src/vfs/page_cache.rs b/os/src/vfs/page_cache.rs new file mode 100644 index 00000000..bf397463 --- /dev/null +++ b/os/src/vfs/page_cache.rs @@ -0,0 +1,474 @@ +//! Clean file page cache shared by VFS-backed filesystems. +//! +//! This cache only stores clean file data. Filesystems remain responsible for +//! all writes and must invalidate affected clean pages after successful +//! mutations. + +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use alloc::vec::Vec; + +use crate::arch::pa_to_va; +use crate::mm::address::PageNum; +use crate::mm::frame_allocator::{FrameTracker, alloc_frame}; +use crate::sync::SpinLock; +use crate::vfs::FsError; + +/// Size of one cached file page. +pub const PAGE_CACHE_PAGE_SIZE: usize = 4096; + +/// Default maximum number of clean pages retained by a page cache. +pub const DEFAULT_PAGE_CACHE_MAX_PAGES: usize = 512; + +/// Stable identity for one cacheable file object. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PageCacheObjectId { + /// Filesystem instance id. This must differ between mounts/devices. + pub fs_id: u64, + /// Inode number inside the filesystem instance. + pub inode_no: u64, +} + +impl PageCacheObjectId { + /// Creates a file object identity from a filesystem id and inode number. + pub const fn new(fs_id: u64, inode_no: u64) -> Self { + Self { fs_id, inode_no } + } +} + +/// Key for one cached file page. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PageCacheKey { + /// File object this page belongs to. + pub object: PageCacheObjectId, + /// Zero-based page index within the file. + pub page_index: usize, +} + +impl PageCacheKey { + /// Creates a cache key for one file page. + pub const fn new(object: PageCacheObjectId, page_index: usize) -> Self { + Self { object, page_index } + } +} + +/// Backing storage for a clean cached file page. +#[derive(Clone, Debug)] +enum CachedPageStorage { + Bytes(Vec), + Frame(Arc, usize), +} + +impl CachedPageStorage { + fn from_bytes(mut data: Vec) -> Self { + data.truncate(PAGE_CACHE_PAGE_SIZE); + Self::Bytes(data) + } + + fn as_slice(&self) -> &[u8] { + match self { + Self::Bytes(data) => data, + Self::Frame(frame, len) => { + let va = pa_to_va(frame.ppn().start_addr()); + unsafe { core::slice::from_raw_parts(va.as_usize() as *const u8, *len) } + } + } + } + + fn as_mut_page_slice(&mut self) -> &mut [u8] { + match self { + Self::Bytes(data) => data.as_mut_slice(), + Self::Frame(frame, _) => { + let va = pa_to_va(frame.ppn().start_addr()); + unsafe { + core::slice::from_raw_parts_mut(va.as_usize() as *mut u8, PAGE_CACHE_PAGE_SIZE) + } + } + } + } + + fn is_frame_backed(&self) -> bool { + matches!(self, Self::Frame(_, _)) + } +} + +/// A clean cached file page. +#[derive(Clone, Debug)] +pub struct CachedPage { + storage: CachedPageStorage, +} + +impl CachedPage { + /// Creates a clean cached page, truncating data to one page. + pub fn new(data: Vec) -> Self { + Self { + storage: CachedPageStorage::from_bytes(data), + } + } + + /// Allocates a frame-backed clean cached page initialized from `data`. + pub fn new_frame_backed(data: &[u8]) -> Result { + let mut page = Self { + storage: CachedPageStorage::Frame( + Arc::new(alloc_frame().ok_or(FsError::NoMemory)?), + data.len().min(PAGE_CACHE_PAGE_SIZE), + ), + }; + page.write_prefix(data); + Ok(page) + } + + /// Returns the bytes stored in this clean page. + pub fn data(&self) -> &[u8] { + self.storage.as_slice() + } + + /// Copies bytes from this page into `buf`, starting at `page_offset`. + pub fn copy_out(&self, page_offset: usize, buf: &mut [u8]) -> usize { + let data = self.data(); + if page_offset >= data.len() { + return 0; + } + + let n = (data.len() - page_offset).min(buf.len()); + buf[..n].copy_from_slice(&data[page_offset..page_offset + n]); + n + } + + fn write_prefix(&mut self, data: &[u8]) { + let len = data.len().min(PAGE_CACHE_PAGE_SIZE); + let dst = self.storage.as_mut_page_slice(); + dst[..len].copy_from_slice(&data[..len]); + } + + fn full_page_mut(&mut self) -> &mut [u8] { + self.storage.as_mut_page_slice() + } + + fn set_len(&mut self, len: usize) { + let len = len.min(PAGE_CACHE_PAGE_SIZE); + match &mut self.storage { + CachedPageStorage::Bytes(data) => data.truncate(len), + CachedPageStorage::Frame(_, page_len) => *page_len = len, + } + } + + /// Returns whether this page is backed by a physical frame. + pub fn is_frame_backed(&self) -> bool { + self.storage.is_frame_backed() + } +} + +/// Page cache counters. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PageCacheStats { + /// Number of successful page lookups. + pub hits: usize, + /// Number of failed page lookups. + pub misses: usize, + /// Number of clean page insertions. + pub inserts: usize, + /// Number of pages evicted by capacity pressure. + pub evicts: usize, + /// Number of pages removed by explicit invalidation. + pub invalidates: usize, + /// Number of fill closures that returned an error. + pub fill_errors: usize, + /// Current number of cached pages. + pub resident_pages: usize, + /// Current number of cached pages backed by physical frames. + pub frame_pages: usize, +} + +struct CacheEntry { + page: CachedPage, + age: u64, +} + +struct PageCacheInner { + pages: BTreeMap, + clock: u64, + stats: PageCacheStats, +} + +impl PageCacheInner { + const fn new() -> Self { + Self { + pages: BTreeMap::new(), + clock: 0, + stats: PageCacheStats { + hits: 0, + misses: 0, + inserts: 0, + evicts: 0, + invalidates: 0, + fill_errors: 0, + resident_pages: 0, + frame_pages: 0, + }, + } + } + + fn tick(&mut self) -> u64 { + self.clock = self.clock.wrapping_add(1); + self.clock + } + + fn evict_until_space(&mut self, max_pages: usize) { + while self.pages.len() >= max_pages { + let Some(oldest_key) = self + .pages + .iter() + .min_by_key(|(_, entry)| entry.age) + .map(|(key, _)| *key) + else { + break; + }; + self.pages.remove(&oldest_key); + self.stats.evicts += 1; + } + } + + fn stats_snapshot(&self) -> PageCacheStats { + let mut stats = self.stats; + stats.resident_pages = self.pages.len(); + stats.frame_pages = self + .pages + .values() + .filter(|entry| entry.page.is_frame_backed()) + .count(); + stats + } +} + +/// Capacity-limited clean file page cache. +pub struct PageCache { + max_pages: usize, + inner: SpinLock, +} + +impl PageCache { + /// Creates a clean page cache with a fixed page capacity. + pub const fn with_capacity(max_pages: usize) -> Self { + Self { + max_pages, + inner: SpinLock::new(PageCacheInner::new()), + } + } + + /// Creates a clean page cache using the default page capacity. + pub const fn new() -> Self { + Self::with_capacity(DEFAULT_PAGE_CACHE_MAX_PAGES) + } + + /// Returns a cloned clean page for `key` if it is cached. + pub fn lookup(&self, key: PageCacheKey) -> Option { + let mut inner = self.inner.lock(); + let age = inner.tick(); + if inner.pages.contains_key(&key) { + let page = { + let entry = inner.pages.get_mut(&key).unwrap(); + entry.age = age; + entry.page.clone() + }; + inner.stats.hits += 1; + Some(page) + } else { + inner.stats.misses += 1; + None + } + } + + /// Copies a cached range from one page into `buf`. + /// + /// Returns `None` when the page containing `offset` is not cached. Returns + /// `Some(0)` when `offset` lies past the cached page data. + pub fn read_hit( + &self, + object: PageCacheObjectId, + offset: usize, + buf: &mut [u8], + ) -> Option { + let page_index = offset / PAGE_CACHE_PAGE_SIZE; + let page_offset = offset % PAGE_CACHE_PAGE_SIZE; + let page = self.lookup(PageCacheKey::new(object, page_index))?; + + if page_offset >= page.data().len() { + return Some(0); + } + + let n = (page.data().len() - page_offset).min(buf.len()); + buf[..n].copy_from_slice(&page.data()[page_offset..page_offset + n]); + Some(n) + } + + /// Inserts a clean page for `object` at `page_index`. + pub fn insert_clean(&self, object: PageCacheObjectId, page_index: usize, data: Vec) { + if self.max_pages == 0 || data.is_empty() { + return; + } + + let key = PageCacheKey::new(object, page_index); + let mut inner = self.inner.lock(); + let age = inner.tick(); + + if !inner.pages.contains_key(&key) { + inner.evict_until_space(self.max_pages); + } + + inner.pages.insert(key, CacheEntry { + page: CachedPage::new(data), + age, + }); + inner.stats.inserts += 1; + } + + /// Returns an existing clean page or fills and inserts a new frame-backed page. + pub fn get_or_insert_clean_page( + &self, + object: PageCacheObjectId, + page_index: usize, + fill_fn: F, + ) -> Result + where + F: FnOnce(&mut [u8]) -> Result, + { + let key = PageCacheKey::new(object, page_index); + + if let Some(page) = self.lookup(key) { + return Ok(page); + } + + if self.max_pages == 0 { + let mut data = alloc::vec![0u8; PAGE_CACHE_PAGE_SIZE]; + let len = match fill_fn(&mut data) { + Ok(len) => len.min(PAGE_CACHE_PAGE_SIZE), + Err(err) => { + self.inner.lock().stats.fill_errors += 1; + return Err(err); + } + }; + data.truncate(len); + return Ok(CachedPage::new(data)); + } + + let mut page = CachedPage::new_frame_backed(&[])?; + let len = { + let buffer = page.full_page_mut(); + match fill_fn(buffer) { + Ok(len) => len.min(PAGE_CACHE_PAGE_SIZE), + Err(err) => { + self.inner.lock().stats.fill_errors += 1; + return Err(err); + } + } + }; + page.set_len(len); + + if len == 0 { + return Ok(page); + } + + let mut inner = self.inner.lock(); + let age = inner.tick(); + if !inner.pages.contains_key(&key) { + inner.evict_until_space(self.max_pages); + } + inner.pages.insert(key, CacheEntry { + page: page.clone(), + age, + }); + inner.stats.inserts += 1; + Ok(page) + } + + /// Invalidates cached pages intersecting byte range `[offset, offset + len)`. + pub fn invalidate_range(&self, object: PageCacheObjectId, offset: usize, len: usize) { + if len == 0 { + return; + } + + let start = offset / PAGE_CACHE_PAGE_SIZE; + let last = offset.saturating_add(len - 1) / PAGE_CACHE_PAGE_SIZE; + let end = last.saturating_add(1); + + let mut inner = self.inner.lock(); + let before = inner.pages.len(); + inner.pages.retain(|key, _| { + key.object != object || key.page_index < start || key.page_index >= end + }); + inner.stats.invalidates += before - inner.pages.len(); + } + + /// Invalidates cached clean pages intersecting `[offset, offset + data.len())`. + /// + /// Pages that are not already cached are left untouched. This is intended + /// for write-through filesystems after the backing write has succeeded. + /// Existing `CachedPage` clones may still point at the old page storage, so + /// writes must not mutate cached pages in place. + pub fn refresh_clean_range( + &self, + object: PageCacheObjectId, + offset: usize, + data: &[u8], + ) -> usize { + if data.is_empty() { + return 0; + } + + let mut invalidated = 0; + let mut copied = 0; + while copied < data.len() { + let current_offset = offset.saturating_add(copied); + let page_index = current_offset / PAGE_CACHE_PAGE_SIZE; + let page_offset = current_offset % PAGE_CACHE_PAGE_SIZE; + let chunk_len = (PAGE_CACHE_PAGE_SIZE - page_offset).min(data.len() - copied); + invalidated += self.remove_cached_page(object, page_index); + + copied += chunk_len; + } + + invalidated + } + + fn remove_cached_page(&self, object: PageCacheObjectId, page_index: usize) -> usize { + let mut inner = self.inner.lock(); + if inner + .pages + .remove(&PageCacheKey::new(object, page_index)) + .is_some() + { + inner.stats.invalidates += 1; + 1 + } else { + 0 + } + } + + /// Invalidates every cached page for one file object. + pub fn invalidate_inode(&self, object: PageCacheObjectId) { + let mut inner = self.inner.lock(); + let before = inner.pages.len(); + inner.pages.retain(|key, _| key.object != object); + inner.stats.invalidates += before - inner.pages.len(); + } + + /// Invalidates every cached page for one filesystem instance. + pub fn invalidate_fs(&self, fs_id: u64) { + let mut inner = self.inner.lock(); + let before = inner.pages.len(); + inner.pages.retain(|key, _| key.object.fs_id != fs_id); + inner.stats.invalidates += before - inner.pages.len(); + } + + /// Returns a snapshot of page cache counters. + pub fn stats(&self) -> PageCacheStats { + self.inner.lock().stats_snapshot() + } +} + +impl Default for PageCache { + fn default() -> Self { + Self::new() + } +} diff --git a/os/src/vfs/tests/fd_table.rs b/os/src/vfs/tests/fd_table.rs index 5074d9a7..45013e9a 100644 --- a/os/src/vfs/tests/fd_table.rs +++ b/os/src/vfs/tests/fd_table.rs @@ -78,6 +78,21 @@ test_case!(test_fdtable_dup, { kassert!(Arc::ptr_eq(&file1, &file2)); }); +test_case!(test_fdtable_dup_from_respects_min_fd_past_table_end, { + let fd_table = FDTable::new(); + let fs = create_test_simplefs(); + let inode = create_test_file_with_content(&fs, "test.txt", b"test").unwrap(); + let file = create_test_file("test.txt", inode, OpenFlags::O_RDONLY); + + let old_fd = fd_table.alloc(file.clone()).unwrap(); + let new_fd = fd_table.dup_from(old_fd, 8, FdFlags::empty()).unwrap(); + + kassert!(new_fd == 8); + kassert!(fd_table.get(7).is_err()); + let duplicated = fd_table.get(new_fd).unwrap(); + kassert!(Arc::ptr_eq(&duplicated, &file)); +}); + test_case!(test_fdtable_dup2, { // 创建 FDTable 和文件 let fd_table = FDTable::new(); diff --git a/os/src/vfs/tests/file.rs b/os/src/vfs/tests/file.rs index e5ebebe3..2cfd6e69 100644 --- a/os/src/vfs/tests/file.rs +++ b/os/src/vfs/tests/file.rs @@ -193,3 +193,30 @@ test_case!(test_file_lseek_negative_set, { kassert!(result.is_err()); kassert!(matches!(result, Err(FsError::InvalidArgument))); }); + +test_case!(test_directory_readdir_cached_until_rewind, { + let fs = create_test_simplefs(); + let root = fs.root_inode(); + root.create("before.txt", FileMode::from_bits_truncate(0o644)) + .unwrap(); + + let file = create_test_file("/", root.clone(), OpenFlags::O_RDONLY); + + let first = file.readdir_cached().unwrap(); + kassert!(first.iter().any(|entry| entry.name == "before.txt")); + kassert!(!first.iter().any(|entry| entry.name == "after.txt")); + + root.create("after.txt", FileMode::from_bits_truncate(0o644)) + .unwrap(); + + let second = file.readdir_cached().unwrap(); + kassert!(Arc::ptr_eq(&first, &second)); + kassert!(!second.iter().any(|entry| entry.name == "after.txt")); + + file.lseek(1, SeekWhence::Set).unwrap(); + file.lseek(0, SeekWhence::Set).unwrap(); + + let refreshed = file.readdir_cached().unwrap(); + kassert!(!Arc::ptr_eq(&first, &refreshed)); + kassert!(refreshed.iter().any(|entry| entry.name == "after.txt")); +}); diff --git a/os/src/vfs/tests/mod.rs b/os/src/vfs/tests/mod.rs index b36be004..64052bce 100644 --- a/os/src/vfs/tests/mod.rs +++ b/os/src/vfs/tests/mod.rs @@ -55,6 +55,7 @@ pub mod devno; pub mod fd_table; pub mod file; pub mod mount; +pub mod page_cache; pub mod path; pub mod pipe; pub mod stdio; diff --git a/os/src/vfs/tests/page_cache.rs b/os/src/vfs/tests/page_cache.rs new file mode 100644 index 00000000..4b1b5dca --- /dev/null +++ b/os/src/vfs/tests/page_cache.rs @@ -0,0 +1,472 @@ +use crate::kassert; +use crate::test_case; +use crate::vfs::FsError; +use crate::vfs::page_cache::{ + CachedPage, PAGE_CACHE_PAGE_SIZE, PageCache, PageCacheKey, PageCacheObjectId, +}; +use alloc::vec; +use core::sync::atomic::{AtomicUsize, Ordering}; + +fn object(fs_id: u64, inode_no: u64) -> PageCacheObjectId { + PageCacheObjectId::new(fs_id, inode_no) +} + +test_case!(test_page_cache_lookup_and_read_hit, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 2); + + cache.insert_clean(obj, 0, b"hello".to_vec()); + + let page = cache.lookup(PageCacheKey::new(obj, 0)).unwrap(); + kassert!(page.data() == b"hello"); + + let mut buf = [0u8; 3]; + let n = cache.read_hit(obj, 1, &mut buf).unwrap(); + kassert!(n == 3); + kassert!(&buf == b"ell"); + + let stats = cache.stats(); + kassert!(stats.hits == 2); + kassert!(stats.misses == 0); + kassert!(stats.inserts == 1); + kassert!(stats.resident_pages == 1); + kassert!(stats.frame_pages == 0); +}); + +test_case!(test_page_cache_miss_counter, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 2); + let mut buf = [0u8; 4]; + + kassert!(cache.read_hit(obj, 0, &mut buf).is_none()); + + let stats = cache.stats(); + kassert!(stats.hits == 0); + kassert!(stats.misses == 1); +}); + +test_case!(test_page_cache_cross_page_read, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 2); + + cache.insert_clean(obj, 0, vec![b'a'; PAGE_CACHE_PAGE_SIZE]); + cache.insert_clean(obj, 1, b"bcdef".to_vec()); + + let mut first = [0u8; 4]; + let n = cache + .read_hit(obj, PAGE_CACHE_PAGE_SIZE - first.len(), &mut first) + .unwrap(); + kassert!(n == first.len()); + kassert!(first == [b'a'; 4]); + + let mut second = [0u8; 5]; + let n = cache + .read_hit(obj, PAGE_CACHE_PAGE_SIZE, &mut second) + .unwrap(); + kassert!(n == second.len()); + kassert!(&second == b"bcdef"); +}); + +test_case!(test_page_cache_boundary_offsets, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 20); + + cache.insert_clean(obj, 0, vec![b'a'; PAGE_CACHE_PAGE_SIZE]); + cache.insert_clean(obj, 1, b"xyz".to_vec()); + + let mut at_4095 = [0u8; 1]; + let n = cache + .read_hit(obj, PAGE_CACHE_PAGE_SIZE - 1, &mut at_4095) + .unwrap(); + kassert!(n == 1); + kassert!(at_4095 == [b'a']); + + let mut at_4096 = [0u8; 3]; + let n = cache + .read_hit(obj, PAGE_CACHE_PAGE_SIZE, &mut at_4096) + .unwrap(); + kassert!(n == 3); + kassert!(&at_4096 == b"xyz"); + + let mut past_partial = [0xEE; 1]; + let n = cache + .read_hit(obj, PAGE_CACHE_PAGE_SIZE + 3, &mut past_partial) + .unwrap(); + kassert!(n == 0); + kassert!(past_partial == [0xEE]); +}); + +test_case!(test_page_cache_lru_eviction, { + let cache = PageCache::with_capacity(2); + let obj = object(1, 2); + + cache.insert_clean(obj, 0, b"zero".to_vec()); + cache.insert_clean(obj, 1, b"one".to_vec()); + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_some()); + cache.insert_clean(obj, 2, b"two".to_vec()); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_some()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 2)).is_some()); + + let stats = cache.stats(); + kassert!(stats.evicts == 1); +}); + +test_case!(test_page_cache_lru_eviction_counts_multiple_pages, { + let cache = PageCache::with_capacity(2); + let obj = object(1, 21); + + cache.insert_clean(obj, 0, b"zero".to_vec()); + cache.insert_clean(obj, 1, b"one".to_vec()); + cache.insert_clean(obj, 2, b"two".to_vec()); + cache.insert_clean(obj, 3, b"three".to_vec()); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 2)).is_some()); + kassert!(cache.lookup(PageCacheKey::new(obj, 3)).is_some()); + + let stats = cache.stats(); + kassert!(stats.evicts == 2); +}); + +test_case!(test_page_cache_range_inode_and_fs_invalidation, { + let cache = PageCache::with_capacity(8); + let obj1 = object(1, 10); + let obj2 = object(1, 11); + let obj3 = object(2, 10); + + cache.insert_clean(obj1, 0, b"a".to_vec()); + cache.insert_clean(obj1, 1, b"b".to_vec()); + cache.insert_clean(obj2, 0, b"c".to_vec()); + cache.insert_clean(obj3, 0, b"d".to_vec()); + + cache.invalidate_range(obj1, PAGE_CACHE_PAGE_SIZE - 1, 2); + kassert!(cache.lookup(PageCacheKey::new(obj1, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj1, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj2, 0)).is_some()); + + cache.invalidate_inode(obj2); + kassert!(cache.lookup(PageCacheKey::new(obj2, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj3, 0)).is_some()); + + cache.invalidate_fs(2); + kassert!(cache.lookup(PageCacheKey::new(obj3, 0)).is_none()); + + let stats = cache.stats(); + kassert!(stats.invalidates == 4); +}); + +test_case!(test_page_cache_invalidate_range_inside_single_page, { + let cache = PageCache::with_capacity(8); + let obj = object(1, 22); + + cache.insert_clean(obj, 0, b"zero".to_vec()); + cache.insert_clean(obj, 1, b"one".to_vec()); + cache.insert_clean(obj, 2, b"two".to_vec()); + + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE + 7, 11); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_some()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 2)).is_some()); + + let stats = cache.stats(); + kassert!(stats.invalidates == 1); +}); + +test_case!(test_page_cache_invalidate_range_zero_len_is_noop, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 23); + + cache.insert_clean(obj, 0, b"zero".to_vec()); + + cache.invalidate_range(obj, 0, 0); + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE, 0); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_some()); + let stats = cache.stats(); + kassert!(stats.invalidates == 0); +}); + +test_case!(test_page_cache_invalidate_range_page_boundaries, { + let cache = PageCache::with_capacity(8); + let obj = object(1, 24); + + cache.insert_clean(obj, 0, b"zero".to_vec()); + cache.insert_clean(obj, 1, b"one".to_vec()); + cache.insert_clean(obj, 2, b"two".to_vec()); + + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE - 1, 1); + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_some()); + + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE, 1); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 2)).is_some()); + + let stats = cache.stats(); + kassert!(stats.invalidates == 2); +}); + +test_case!(test_page_cache_invalidate_range_crosses_multiple_pages, { + let cache = PageCache::with_capacity(8); + let obj = object(1, 25); + + for page_index in 0..4 { + cache.insert_clean(obj, page_index, vec![page_index as u8 + b'0']); + } + + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE - 8, PAGE_CACHE_PAGE_SIZE + 16); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 2)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 3)).is_some()); + + let stats = cache.stats(); + kassert!(stats.invalidates == 3); +}); + +test_case!(test_page_cache_invalidate_range_saturates_on_overflow, { + let cache = PageCache::with_capacity(8); + let obj = object(1, 26); + let last_page = usize::MAX / PAGE_CACHE_PAGE_SIZE; + + cache.insert_clean(obj, last_page - 1, b"before".to_vec()); + cache.insert_clean(obj, last_page, b"last".to_vec()); + + cache.invalidate_range(obj, usize::MAX - 7, 32); + + kassert!( + cache + .lookup(PageCacheKey::new(obj, last_page - 1)) + .is_some() + ); + kassert!(cache.lookup(PageCacheKey::new(obj, last_page)).is_none()); + + let stats = cache.stats(); + kassert!(stats.invalidates == 1); +}); + +test_case!(test_page_cache_invalidate_range_only_target_object, { + let cache = PageCache::with_capacity(8); + let target = object(1, 27); + let same_inode_other_fs = object(2, 27); + let same_fs_other_inode = object(1, 28); + + cache.insert_clean(target, 0, b"target".to_vec()); + cache.insert_clean(same_inode_other_fs, 0, b"other-fs".to_vec()); + cache.insert_clean(same_fs_other_inode, 0, b"other-inode".to_vec()); + + cache.invalidate_range(target, 0, 1); + + kassert!(cache.lookup(PageCacheKey::new(target, 0)).is_none()); + kassert!( + cache + .lookup(PageCacheKey::new(same_inode_other_fs, 0)) + .is_some() + ); + kassert!( + cache + .lookup(PageCacheKey::new(same_fs_other_inode, 0)) + .is_some() + ); + + let stats = cache.stats(); + kassert!(stats.invalidates == 1); +}); + +test_case!(test_page_cache_object_id_includes_fs_id, { + let cache = PageCache::with_capacity(4); + let left = object(1, 7); + let right = object(2, 7); + + cache.insert_clean(left, 0, b"left".to_vec()); + cache.insert_clean(right, 0, b"right".to_vec()); + + let mut buf = [0u8; 5]; + let n = cache.read_hit(right, 0, &mut buf).unwrap(); + kassert!(n == 5); + kassert!(&buf == b"right"); +}); + +test_case!(test_frame_backed_cached_page_copy_out, { + let page = CachedPage::new_frame_backed(b"frame-data").unwrap(); + + kassert!(page.data() == b"frame-data"); + + let mut buf = [0u8; 5]; + let n = page.copy_out(6, &mut buf); + kassert!(n == 4); + kassert!(&buf[..n] == b"data"); +}); + +test_case!(test_frame_backed_cached_page_truncates_to_page, { + let oversized = vec![0xAB; PAGE_CACHE_PAGE_SIZE + 17]; + let page = CachedPage::new_frame_backed(&oversized).unwrap(); + + kassert!(page.data().len() == PAGE_CACHE_PAGE_SIZE); + kassert!(page.data()[0] == 0xAB); + kassert!(page.data()[PAGE_CACHE_PAGE_SIZE - 1] == 0xAB); + + let mut buf = [0u8; 8]; + let n = page.copy_out(PAGE_CACHE_PAGE_SIZE - 4, &mut buf); + kassert!(n == 4); + kassert!(&buf[..n] == &[0xAB; 4]); +}); + +test_case!(test_get_or_insert_clean_page_fills_miss_once, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 42); + let fills = AtomicUsize::new(0); + + let page = cache + .get_or_insert_clean_page(obj, 0, |buf| { + fills.fetch_add(1, Ordering::Relaxed); + buf[..5].copy_from_slice(b"first"); + Ok(5) + }) + .unwrap(); + kassert!(page.data() == b"first"); + + let cached = cache + .get_or_insert_clean_page(obj, 0, |_| { + fills.fetch_add(1, Ordering::Relaxed); + Ok(0) + }) + .unwrap(); + kassert!(cached.data() == b"first"); + kassert!(fills.load(Ordering::Relaxed) == 1); + + let stats = cache.stats(); + kassert!(stats.inserts == 1); + kassert!(stats.resident_pages == 1); + kassert!(stats.frame_pages == 1); +}); + +test_case!(test_get_or_insert_clean_page_fill_error_does_not_insert, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 43); + let mut buf = [0u8; 4]; + + let result = cache.get_or_insert_clean_page(obj, 0, |_| Err(FsError::IoError)); + kassert!(matches!(result, Err(FsError::IoError))); + kassert!(cache.read_hit(obj, 0, &mut buf).is_none()); + + let stats = cache.stats(); + kassert!(stats.inserts == 0); + kassert!(stats.fill_errors == 1); + kassert!(stats.resident_pages == 0); + kassert!(stats.frame_pages == 0); +}); + +test_case!(test_page_cache_stats_tracks_resident_and_frame_pages, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 44); + + cache.insert_clean(obj, 0, b"bytes".to_vec()); + cache + .get_or_insert_clean_page(obj, 1, |buf| { + buf[..5].copy_from_slice(b"frame"); + Ok(5) + }) + .unwrap(); + + let stats = cache.stats(); + kassert!(stats.resident_pages == 2); + kassert!(stats.frame_pages == 1); + + cache.invalidate_range(obj, PAGE_CACHE_PAGE_SIZE, 1); + let stats = cache.stats(); + kassert!(stats.resident_pages == 1); + kassert!(stats.frame_pages == 0); +}); + +test_case!( + test_page_cache_refresh_clean_range_invalidates_cached_page, + { + let cache = PageCache::with_capacity(4); + let obj = object(1, 45); + + cache.insert_clean(obj, 0, b"abcdef".to_vec()); + let old_page = cache.lookup(PageCacheKey::new(obj, 0)).unwrap(); + + let invalidated = cache.refresh_clean_range(obj, 2, b"XYZ"); + kassert!(invalidated == 1); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(old_page.data() == b"abcdef"); + + let stats = cache.stats(); + kassert!(stats.invalidates == 1); + } +); + +test_case!(test_page_cache_refresh_clean_range_miss_is_noop, { + let cache = PageCache::with_capacity(4); + let obj = object(1, 46); + + let refreshed = cache.refresh_clean_range(obj, 0, b"miss"); + kassert!(refreshed == 0); + + let stats = cache.stats(); + kassert!(stats.inserts == 0); + kassert!(stats.invalidates == 0); + kassert!(stats.resident_pages == 0); +}); + +test_case!( + test_page_cache_refresh_clean_range_crosses_and_invalidates_cached_pages, + { + let cache = PageCache::with_capacity(4); + let obj = object(1, 47); + + cache.insert_clean(obj, 0, vec![b'a'; PAGE_CACHE_PAGE_SIZE]); + cache.insert_clean(obj, 1, vec![b'b'; 8]); + + let invalidated = cache.refresh_clean_range(obj, PAGE_CACHE_PAGE_SIZE - 2, b"WXYZ"); + kassert!(invalidated == 2); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(cache.lookup(PageCacheKey::new(obj, 1)).is_none()); + } +); + +test_case!( + test_page_cache_refresh_clean_range_keeps_old_frame_clone_readable, + { + let cache = PageCache::with_capacity(4); + let obj = object(1, 48); + + let old_page = cache + .get_or_insert_clean_page(obj, 0, |buf| { + buf[..6].copy_from_slice(b"abcdef"); + Ok(6) + }) + .unwrap(); + + let invalidated = cache.refresh_clean_range(obj, 1, b"23"); + kassert!(invalidated == 1); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + kassert!(old_page.data() == b"abcdef"); + kassert!(old_page.is_frame_backed()); + } +); + +test_case!( + test_page_cache_refresh_clean_range_invalidates_short_cached_page, + { + let cache = PageCache::with_capacity(4); + let obj = object(1, 49); + + cache.insert_clean(obj, 0, b"abc".to_vec()); + + let invalidated = cache.refresh_clean_range(obj, 5, b"Z"); + kassert!(invalidated == 1); + + kassert!(cache.lookup(PageCacheKey::new(obj, 0)).is_none()); + } +);