Parcourir la source

fix(futex): 修复futex的一些bug (#1311)

* feat(filesystem): 实现truncate系统调用

- 添加VFS层的统一vfs_truncate封装,包含文件类型和只读挂载检查
- 实现truncate系统调用处理,支持路径解析和符号链接跟随
- 修复FAT文件系统resize方法,确保页缓存和元数据同步更新
- 添加全面的用户空间测试用例,覆盖各种边界条件和错误情况
- 优化文件截断流程,统一通过VFS封装处理类型和只读检查

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(page_cache): 修复新文件截断时的错误处理逻辑

Signed-off-by: longjin <longjin@DragonOS.org>

* style(filesystem): 移除vcore.rs中的调试日志和注释

Signed-off-by: longjin <longjin@DragonOS.org>

* feat(futex): 完善共享匿名映射的futex支持

- 实现共享匿名映射的futex键生成机制
- 添加跨进程共享匿名futex的测试用例
- 增加futex操作的调试日志输出
- 修复私有futex键的构建问题

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(futex): 修复futex超时处理并增加调试日志

- 修复零超时时间立即返回ETIMEDOUT的逻辑
- 简化超时检查条件,移除错误的FLAGS_HAS_TIMEOUT检查

Signed-off-by: longjin <longjin@DragonOS.org>

* feat(runner): 添加输出到控制台的选项

- 添加Config结构体中的output_to_stdout字段
- 根据配置决定测试输出方式(文件或控制台)
- 添加--stdout命令行参数以启用控制台输出

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(futex): 修复futex等待和唤醒机制

- 修正bitset匹配逻辑,使用位与操作而非不等比较
- 移除wake_up函数的inline(always)属性
- 实现FUTEX_WAIT_BITSET的绝对超时处理
- 添加时间合法性检查和时钟选择逻辑

Signed-off-by: longjin <longjin@DragonOS.org>

* fix(process): 修复futex和进程ID管理相关问题

- 修正FLAGS_MATCH_NONE常量值为0x00
- 确保非组长线程的TGID与组长一致
- 调整clear_child_tid操作顺序以符合Linux语义
- 优化pid_nr_ns方法实现避免潜在panic
- 添加futex测试的blocklist和白名单配置

Signed-off-by: longjin <longjin@DragonOS.org>

---------

Signed-off-by: longjin <longjin@DragonOS.org>
LoGin il y a 3 semaines
Parent
commit
3b3b55bfc9

+ 1 - 1
kernel/src/libs/futex/constant.rs

@@ -17,7 +17,7 @@ bitflags! {
     }
 
     pub struct FutexFlag: u32 {
-        const FLAGS_MATCH_NONE = 0x01;
+        const FLAGS_MATCH_NONE = 0x00;
         const FLAGS_SHARED = 0x01;
         const FLAGS_CLOCKRT = 0x02;
         const FLAGS_HAS_TIMEOUT = 0x04;

+ 77 - 27
kernel/src/libs/futex/futex.rs

@@ -90,7 +90,6 @@ impl FutexHashBucket {
     /// ## 唤醒队列中的最多nr_wake个进程
     ///
     /// return: 唤醒的进程数
-    #[inline(always)]
     pub fn wake_up(
         &mut self,
         key: FutexKey,
@@ -104,7 +103,7 @@ impl FutexHashBucket {
                 // TODO: 考虑优先级继承的机制
 
                 if let Some(bitset) = bitset {
-                    if futex_q.bitset != bitset {
+                    if futex_q.bitset & bitset == 0 {
                         self.chain.push_back(futex_q);
                         continue;
                     }
@@ -272,11 +271,17 @@ impl Futex {
         // 创建超时计时器任务
         let mut timer = None;
         if let Some(time) = abs_time {
-            let wakeup_helper = WakeUpHelper::new(pcb.clone());
-
             let sec = time.tv_sec;
             let nsec = time.tv_nsec;
-            let jiffies = next_n_us_timer_jiffies((nsec / 1000 + sec * 1_000_000) as u64);
+            let total_us = (nsec / 1000 + sec * 1_000_000) as u64;
+
+            // 如果超时时间为0,直接返回ETIMEDOUT
+            if total_us == 0 {
+                return Err(SystemError::ETIMEDOUT);
+            }
+
+            let wakeup_helper = WakeUpHelper::new(pcb.clone());
+            let jiffies = next_n_us_timer_jiffies(total_us);
 
             let wake_up = Timer::new(wakeup_helper, jiffies);
 
@@ -291,10 +296,7 @@ impl Futex {
         });
         let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
         // 满足条件则将当前进程在该bucket上挂起
-        bucket_mut.sleep_no_sched(futex_q.clone()).map_err(|e| {
-            warn!("error:{e:?}");
-            e
-        })?;
+        bucket_mut.sleep_no_sched(futex_q.clone())?;
         drop(futex_map_guard);
         drop(irq_guard);
         schedule(SchedMode::SM_NONE);
@@ -376,6 +378,7 @@ impl Futex {
         if bucket_mut.chain.is_empty() {
             return Ok(0);
         }
+
         // 从队列中唤醒
         let count = bucket_mut.wake_up(key.clone(), Some(bitset), nr_wake)?;
 
@@ -521,34 +524,81 @@ impl Futex {
         // 目前address指向所在页面的起始地址
         address -= offset;
 
-        // 若不是进程间共享的futex,则返回Private
+        // 非共享:使用地址空间+页首虚拟地址作为私有键
         if !fshared {
-            return Ok(FutexKey {
+            let address_space = AddressSpace::current()?;
+            let key = FutexKey {
                 ptr: 0,
                 word: 0,
                 offset: offset as u32,
                 key: InnerFutexKey::Private(PrivateKey {
                     address: address as u64,
-                    address_space: None,
+                    address_space: Some(Arc::downgrade(&address_space)),
                 }),
-            });
+            };
+            return Ok(key);
         }
 
-        // 获取到地址所在地址空间
+        // 共享:区分文件映射与匿名共享映射
         let address_space = AddressSpace::current()?;
-        // TODO: 判断是否为匿名映射,是匿名映射才返回PrivateKey
-        return Ok(FutexKey {
-            ptr: 0,
-            word: 0,
-            offset: offset as u32,
-            key: InnerFutexKey::Private(PrivateKey {
-                address: address as u64,
-                address_space: Some(Arc::downgrade(&address_space)),
-            }),
-        });
-
-        // 未实现共享内存机制,贡献内存部分应该通过inode构建SharedKey
-        // todo!("Shared memory not implemented");
+        let as_guard = address_space.read();
+        let vma = as_guard
+            .mappings
+            .contains(uaddr)
+            .ok_or(SystemError::EINVAL)?;
+        let vma_guard = vma.lock_irqsave();
+
+        // 页内索引(相对VMA起始地址)
+        let page_index =
+            ((uaddr.data() - vma_guard.region().start().data()) >> MMArch::PAGE_SHIFT) as u64;
+
+        if let Some(file) = vma_guard.vm_file() {
+            // 共享文件映射:使用 inode 唯一标识 + 文件页偏移
+            let md = file.metadata()?;
+            let dev = md.dev_id as u64;
+            let ino = md.inode_id.into() as u64;
+            let i_seq = (dev << 32) ^ ino;
+            let base_pgoff = vma_guard.file_page_offset().unwrap_or(0) as u64;
+            let shared = SharedKey {
+                i_seq,
+                page_offset: base_pgoff + page_index,
+            };
+            let key = FutexKey {
+                ptr: 0,
+                word: 0,
+                offset: offset as u32,
+                key: InnerFutexKey::Shared(shared.clone()),
+            };
+            return Ok(key);
+        } else {
+            // 匿名共享:使用共享匿名映射的稳定身份 + 页偏移
+            if let Some(shared_anon) = &vma_guard.shared_anon {
+                let i_seq = shared_anon.id;
+                let shared = SharedKey {
+                    i_seq,
+                    page_offset: page_index,
+                };
+                let key = FutexKey {
+                    ptr: 0,
+                    word: 0,
+                    offset: offset as u32,
+                    key: InnerFutexKey::Shared(shared.clone()),
+                };
+                return Ok(key);
+            } else {
+                // 理论上不会发生;为安全起见,退化为私有键(不跨进程匹配)
+                let key = FutexKey {
+                    ptr: 0,
+                    word: 0,
+                    offset: offset as u32,
+                    key: InnerFutexKey::Private(PrivateKey {
+                        address: address as u64,
+                        address_space: Some(Arc::downgrade(&address_space)),
+                    }),
+                };
+                return Ok(key);
+            }
+        }
     }
 
     pub fn futex_atomic_op_inuser(encoded_op: u32, uaddr: VirtAddr) -> Result<bool, SystemError> {

+ 31 - 2
kernel/src/libs/futex/syscall.rs

@@ -21,7 +21,6 @@ impl Syscall {
     ) -> Result<usize, SystemError> {
         verify_area(uaddr, core::mem::size_of::<u32>())?;
         verify_area(uaddr2, core::mem::size_of::<u32>())?;
-
         let cmd = FutexArg::from_bits(operation.bits() & FutexFlag::FUTEX_CMD_MASK.bits())
             .ok_or(SystemError::ENOSYS)?;
 
@@ -46,7 +45,37 @@ impl Syscall {
                 return Futex::futex_wait(uaddr, flags, val, timeout, FUTEX_BITSET_MATCH_ANY);
             }
             FutexArg::FUTEX_WAIT_BITSET => {
-                return Futex::futex_wait(uaddr, flags, val, timeout, val3);
+                // Linux 语义:WAIT_BITSET 的超时为绝对时间(clock_nanosleep 风格)。
+                // 这里将绝对截止时间转换为相对剩余时间,past 则立即 ETIMEDOUT。
+                let adjusted_timeout = if let Some(deadline) = timeout {
+                    // 校验 timespec 合法性
+                    if deadline.tv_nsec < 0 || deadline.tv_nsec >= 1_000_000_000 {
+                        return Err(SystemError::EINVAL);
+                    }
+
+                    // 选择时钟:若带 FUTEX_CLOCK_REALTIME 则使用 realtime;否则使用 monotonic(当前实现等价)
+                    let now = crate::time::timekeeping::getnstimeofday();
+
+                    // 计算剩余时间 = deadline - now,若 <=0 则立即超时
+                    let mut sec = deadline.tv_sec - now.tv_sec;
+                    let mut nsec = deadline.tv_nsec - now.tv_nsec;
+                    if nsec < 0 {
+                        nsec += 1_000_000_000;
+                        sec -= 1;
+                    }
+                    if sec < 0 || (sec == 0 && nsec == 0) {
+                        return Err(SystemError::ETIMEDOUT);
+                    }
+
+                    Some(PosixTimeSpec {
+                        tv_sec: sec,
+                        tv_nsec: nsec,
+                    })
+                } else {
+                    None
+                };
+
+                return Futex::futex_wait(uaddr, flags, val, adjusted_timeout, val3);
             }
             FutexArg::FUTEX_WAKE => {
                 return Futex::futex_wake(uaddr, flags, val, FUTEX_BITSET_MATCH_ANY);

+ 36 - 4
kernel/src/mm/ucontext.rs

@@ -5,7 +5,7 @@ use core::{
     hash::Hasher,
     intrinsics::unlikely,
     ops::Add,
-    sync::atomic::{compiler_fence, Ordering},
+    sync::atomic::{compiler_fence, AtomicU64, Ordering},
 };
 
 use alloc::{
@@ -316,16 +316,26 @@ impl InnerAddressSpace {
             map_flags,
             move |page, count, vm_flags, flags, mapper, flusher| {
                 if allocate_at_once {
-                    VMA::zeroed(page, count, vm_flags, flags, mapper, flusher, None, None)
+                    let vma =
+                        VMA::zeroed(page, count, vm_flags, flags, mapper, flusher, None, None)?;
+                    // 如果是共享匿名映射,则分配稳定身份
+                    if vm_flags.contains(VmFlags::VM_SHARED) {
+                        vma.lock_irqsave().shared_anon = Some(AnonSharedMapping::new());
+                    }
+                    Ok(vma)
                 } else {
-                    Ok(LockedVMA::new(VMA::new(
+                    let vma = LockedVMA::new(VMA::new(
                         VirtRegion::new(page.virt_address(), count.data() * MMArch::PAGE_SIZE),
                         vm_flags,
                         flags,
                         None,
                         None,
                         false,
-                    )))
+                    ));
+                    if vm_flags.contains(VmFlags::VM_SHARED) {
+                        vma.lock_irqsave().shared_anon = Some(AnonSharedMapping::new());
+                    }
+                    Ok(vma)
                 }
             },
         )?;
@@ -1482,6 +1492,8 @@ pub struct VMA {
     provider: Provider,
     /// 关联的 SysV SHM 标识(当此 VMA 来自 shmat 时设置)
     shm_id: Option<ShmId>,
+    /// 共享匿名映射的稳定身份(用于跨进程共享 futex key)
+    pub(crate) shared_anon: Option<Arc<AnonSharedMapping>>,
 }
 
 impl core::hash::Hash for VMA {
@@ -1498,6 +1510,23 @@ pub enum Provider {
     Allocated, // TODO:其他
 }
 
+/// 共享匿名映射的稳定身份
+#[derive(Debug)]
+pub struct AnonSharedMapping {
+    pub id: u64,
+}
+
+impl AnonSharedMapping {
+    fn new_id() -> u64 {
+        static NEXT_ID: AtomicU64 = AtomicU64::new(1);
+        return NEXT_ID.fetch_add(1, Ordering::Relaxed);
+    }
+
+    pub fn new() -> Arc<Self> {
+        Arc::new(Self { id: Self::new_id() })
+    }
+}
+
 #[allow(dead_code)]
 impl VMA {
     pub fn new(
@@ -1519,6 +1548,7 @@ impl VMA {
             vm_file: file,
             file_pgoff: pgoff,
             shm_id: None,
+            shared_anon: None,
         }
     }
 
@@ -1576,6 +1606,7 @@ impl VMA {
             file_pgoff: self.file_pgoff,
             vm_file: self.vm_file.clone(),
             shm_id: self.shm_id,
+            shared_anon: self.shared_anon.clone(),
         };
     }
 
@@ -1591,6 +1622,7 @@ impl VMA {
             file_pgoff: self.file_pgoff,
             vm_file: self.vm_file.clone(),
             shm_id: self.shm_id,
+            shared_anon: self.shared_anon.clone(),
         };
     }
 

+ 9 - 0
kernel/src/process/fork.rs

@@ -620,6 +620,15 @@ impl ProcessManager {
                 .threads_write_irqsave()
                 .group_tasks
                 .push(Arc::downgrade(pcb));
+
+            // 确保非组长线程的 TGID 与组长一致
+            let leader_tgid_pid = group_leader.pid();
+            pcb.init_task_pid(PidType::TGID, leader_tgid_pid.clone());
+            pcb.init_task_pid(PidType::PGID, leader_tgid_pid.clone());
+            pcb.init_task_pid(PidType::SID, leader_tgid_pid.clone());
+            pcb.attach_pid(PidType::TGID);
+            pcb.attach_pid(PidType::PGID);
+            pcb.attach_pid(PidType::SID);
         }
 
         pcb.attach_pid(PidType::PID);

+ 2 - 1
kernel/src/process/mod.rs

@@ -456,6 +456,8 @@ impl ProcessManager {
             let thread = pcb.thread.write_irqsave();
 
             if let Some(addr) = thread.clear_child_tid {
+                // 按 Linux 语义:先清零 userland 的 *clear_child_tid,再 futex_wake(addr)
+                unsafe { clear_user(addr, core::mem::size_of::<i32>()).expect("clear tid failed") };
                 if Arc::strong_count(&pcb.basic().user_vm().expect("User VM Not found")) > 1 {
                     let _ = Futex::futex_wake(
                         addr,
@@ -464,7 +466,6 @@ impl ProcessManager {
                         FUTEX_BITSET_MATCH_ANY,
                     );
                 }
-                unsafe { clear_user(addr, core::mem::size_of::<i32>()).expect("clear tid failed") };
             }
             compiler_fence(Ordering::SeqCst);
 

+ 4 - 6
kernel/src/process/pid.rs

@@ -130,12 +130,10 @@ impl Pid {
     pub fn pid_nr_ns(&self, ns: &Arc<PidNamespace>) -> RawPid {
         if ns.level() <= self.level {
             let numbers = self.numbers.lock();
-            let upid = numbers[ns.level() as usize]
-                .as_ref()
-                .cloned()
-                .unwrap_or_else(|| panic!("pid numbers should not be empty: ns.level={}, self.level={}, numbers.len={}", ns.level(), self.level, numbers.len()));
-            if Arc::ptr_eq(&upid.ns, ns) {
-                return upid.nr;
+            if let Some(upid) = numbers.get(ns.level() as usize).and_then(|x| x.as_ref()) {
+                if Arc::ptr_eq(&upid.ns, ns) {
+                    return upid.nr;
+                }
             }
         }
 

+ 1 - 1
kernel/src/syscall/mod.rs

@@ -296,7 +296,7 @@ impl Syscall {
                 let val3 = args[5] as u32;
 
                 let mut timespec = None;
-                if utime != 0 && operation.contains(FutexFlag::FLAGS_HAS_TIMEOUT) {
+                if utime != 0 {
                     let reader = UserBufferReader::new(
                         utime as *const PosixTimeSpec,
                         core::mem::size_of::<PosixTimeSpec>(),

+ 75 - 0
user/apps/c_unitest/test_cross_process_futex_shared_anon.c

@@ -0,0 +1,75 @@
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef FUTEX_WAIT
+#define FUTEX_WAIT 0
+#endif
+#ifndef FUTEX_WAKE
+#define FUTEX_WAKE 1
+#endif
+
+static long futex(uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout) {
+    return syscall(SYS_futex, uaddr, futex_op, val, timeout, NULL, 0);
+}
+
+int main() {
+    size_t sz = getpagesize();
+    uint32_t *shared = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+    if (shared == MAP_FAILED) {
+        perror("mmap");
+        return 1;
+    }
+
+    // 初始值1,子进程在值为1时等待
+    *shared = 1;
+
+    pid_t pid = fork();
+    if (pid < 0) {
+        perror("fork");
+        return 1;
+    }
+
+    if (pid == 0) {
+        // 子进程:等待父进程唤醒
+        if (futex(shared, FUTEX_WAIT, 1, NULL) != 0) {
+            perror("child futex_wait");
+            _exit(1);
+        }
+        _exit(0);
+    }
+
+    // 父进程:给子进程一点时间进入wait
+    struct timespec ts = { .tv_sec = 0, .tv_nsec = 50 * 1000 * 1000 };
+    nanosleep(&ts, NULL);
+
+    // 修改值并唤醒一个等待者
+    __sync_fetch_and_add(shared, 1);
+    long r = futex(shared, FUTEX_WAKE, 1, NULL);
+    if (r != 1) {
+        fprintf(stderr, "futex_wake returned %ld (errno=%d)\n", r, errno);
+        return 2;
+    }
+
+    int status = 0;
+    if (waitpid(pid, &status, 0) != pid) {
+        perror("waitpid");
+        return 3;
+    }
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        fprintf(stderr, "child exit status=%d\n", status);
+        return 4;
+    }
+
+    munmap(shared, sz);
+    printf("ok\n");
+    return 0;
+}
+
+

+ 38 - 0
user/apps/tests/syscall/gvisor/blocklists/futex_test

@@ -0,0 +1,38 @@
+# PrivateFutexTest needs FUTEX_WAKE_OP
+PrivateFutexTest.*
+RobustFutexTest.*
+SharedFutexTest.WakeInterprocessFile_NoRandomSave
+
+SharedPrivate/PrivateAndSharedFutexTest.NoWakeInterprocessPrivateAnon_NoRandomSave/*
+
+SharedPrivate/PrivateAndSharedFutexTest.PIBasic/*
+SharedPrivate/PrivateAndSharedFutexTest.PIConcurrency_NoRandomSave/*
+SharedPrivate/PrivateAndSharedFutexTest.PIWaiters/*
+SharedPrivate/PrivateAndSharedFutexTest.PITryLock/*
+SharedPrivate/PrivateAndSharedFutexTest.PITryLockConcurrency_NoRandomSave/*
+# WakeAll_NoRandomSave/* encounters a segmenation fault
+SharedPrivate/PrivateAndSharedFutexTest.WakeAll_NoRandomSave/0
+SharedPrivate/PrivateAndSharedFutexTest.WakeAll_NoRandomSave/1
+# WakeAfterCOWBreak_NoRandomSave/* hangs sometimes
+SharedPrivate/PrivateAndSharedFutexTest.WakeAfterCOWBreak_NoRandomSave/0
+
+# 这里有个gp的bug
+SharedPrivate/PrivateAndSharedFutexTest.Wake0/*
+SharedPrivate/PrivateAndSharedFutexTest.Wake1/*
+SharedPrivate/PrivateAndSharedFutexTest.WakeAll/*
+SharedPrivate/PrivateAndSharedFutexTest.WakeSome/*
+SharedPrivate/PrivateAndSharedFutexTest.WaitBitset_Wake/*
+SharedPrivate/PrivateAndSharedFutexTest.Wait_WakeBitset/*
+SharedPrivate/PrivateAndSharedFutexTest.WaitBitset_WakeBitsetMatch/*
+# 干脆禁用掉 SharedPrivate/PrivateAndSharedFutexTest 下面所有的带Wake的吧。貌似这个多线程wake有共性的bug,要么gp,要么卡住,要么报vma那个问题
+SharedPrivate/PrivateAndSharedFutexTest.*Wake*
+
+# 这个会卡住
+SharedPrivate/PrivateAndSharedFutexTest.WaitBitset_WakeBitsetNoMatch/*
+
+# 这个会出现错误: [ ERROR ] (src/arch/x86_64/mm/fault.rs:266)      can not find nearest vma, error_code: (empty), address: 0x1
+SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondSuccess/*
+SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondFailure/*
+
+SharedPrivate/PrivateAndSharedFutexTest.PI*
+

+ 67 - 45
user/apps/tests/syscall/gvisor/runner/src/lib_sync.rs

@@ -65,6 +65,7 @@ pub struct Config {
     pub temp_dir: PathBuf,
     pub extra_blocklist_dirs: Vec<PathBuf>,
     pub test_patterns: Vec<String>,
+    pub output_to_stdout: bool,  // 是否输出到控制台而不是文件
 }
 
 impl Default for Config {
@@ -86,6 +87,7 @@ impl Default for Config {
             ),
             extra_blocklist_dirs: Vec::new(),
             test_patterns: Vec::new(),
+            output_to_stdout: false,
         }
     }
 }
@@ -321,12 +323,6 @@ impl TestRunner {
         // 获取blocklist
         let blocked_subtests = self.get_test_blocklist(test_name);
 
-        // 结果输出文件(使用绝对路径,避免工作目录切换影响)
-        let output_file = self
-            .config
-            .results_dir
-            .join(format!("{}.output", test_name));
-
         println!("[DEBUG] 工作目录: {:?}", self.config.tests_dir);
         println!("[DEBUG] TEST_TMPDIR: {:?}", self.config.temp_dir);
         println!("[DEBUG] 直接执行: {:?}", test_path);
@@ -334,18 +330,32 @@ impl TestRunner {
             println!("[DEBUG] gtest_filter: -{}", blocked_subtests.join(":"));
         }
 
-        // 确保结果目录存在
-        if let Err(e) = fs::create_dir_all(&self.config.results_dir) {
-            self.print_error(&format!("创建结果目录失败: {}", e));
-        }
+        // 根据配置决定输出方式
+        let (stdout, stderr) = if self.config.output_to_stdout {
+            // 单个测例:直接输出到控制台
+            (std::process::Stdio::inherit(), std::process::Stdio::inherit())
+        } else {
+            // 批量测试:输出到文件
+            // 确保结果目录存在
+            if let Err(e) = fs::create_dir_all(&self.config.results_dir) {
+                self.print_error(&format!("创建结果目录失败: {}", e));
+            }
 
-        // 打开输出文件,并将 stdout/stderr 重定向到该文件
-        let out = File::create(&output_file);
-        if let Err(e) = out.as_ref() {
-            self.print_error(&format!("创建输出文件失败: {:?}, 错误: {}", output_file, e));
-        }
-        let out = out?;
-        let err = out.try_clone()?;
+            // 结果输出文件(使用绝对路径,避免工作目录切换影响)
+            let output_file = self
+                .config
+                .results_dir
+                .join(format!("{}.output", test_name));
+
+            // 打开输出文件,并将 stdout/stderr 重定向到该文件
+            let out = File::create(&output_file);
+            if let Err(e) = out.as_ref() {
+                self.print_error(&format!("创建输出文件失败: {:?}, 错误: {}", output_file, e));
+            }
+            let out = out?;
+            let err = out.try_clone()?;
+            (std::process::Stdio::from(out), std::process::Stdio::from(err))
+        };
 
         // 构造并执行命令(不使用 shell,不捕获输出,不创建管道)
         let start_time = Instant::now();
@@ -357,8 +367,8 @@ impl TestRunner {
         let status = cmd
             .current_dir(&self.config.tests_dir)
             .env("TEST_TMPDIR", &self.config.temp_dir)
-            .stdout(std::process::Stdio::from(out))
-            .stderr(std::process::Stdio::from(err))
+            .stdout(stdout)
+            .stderr(stderr)
             .status();
 
         // 清理临时目录
@@ -373,19 +383,25 @@ impl TestRunner {
                     test_name,
                     duration.as_secs_f64()
                 ));
-                // 将输出文件尾部打印一点,便于快速查看
-                if let Ok(content) = fs::read_to_string(&output_file) {
-                    let tail: String = content
-                        .lines()
-                        .rev()
-                        .take(10)
-                        .collect::<Vec<_>>()
-                        .into_iter()
-                        .rev()
-                        .map(|s| format!("{}\n", s))
-                        .collect();
-                    if !tail.is_empty() {
-                        println!("[DEBUG] 输出尾部: \n{}", tail);
+                // 只在批量测试时读取文件内容
+                if !self.config.output_to_stdout {
+                    let output_file = self
+                        .config
+                        .results_dir
+                        .join(format!("{}.output", test_name));
+                    if let Ok(content) = fs::read_to_string(&output_file) {
+                        let tail: String = content
+                            .lines()
+                            .rev()
+                            .take(10)
+                            .collect::<Vec<_>>()
+                            .into_iter()
+                            .rev()
+                            .map(|s| format!("{}\n", s))
+                            .collect();
+                        if !tail.is_empty() {
+                            println!("[DEBUG] 输出尾部: \n{}", tail);
+                        }
                     }
                 }
                 Ok(true)
@@ -397,19 +413,25 @@ impl TestRunner {
                     duration.as_secs_f64(),
                     s.code()
                 ));
-                // 打印错误输出尾部
-                if let Ok(content) = fs::read_to_string(&output_file) {
-                    let tail: String = content
-                        .lines()
-                        .rev()
-                        .take(20)
-                        .collect::<Vec<_>>()
-                        .into_iter()
-                        .rev()
-                        .map(|s| format!("{}\n", s))
-                        .collect();
-                    if !tail.is_empty() {
-                        println!("[DEBUG] 错误输出尾部: \n{}", tail);
+                // 只在批量测试时读取文件内容
+                if !self.config.output_to_stdout {
+                    let output_file = self
+                        .config
+                        .results_dir
+                        .join(format!("{}.output", test_name));
+                    if let Ok(content) = fs::read_to_string(&output_file) {
+                        let tail: String = content
+                            .lines()
+                            .rev()
+                            .take(20)
+                            .collect::<Vec<_>>()
+                            .into_iter()
+                            .rev()
+                            .map(|s| format!("{}\n", s))
+                            .collect();
+                        if !tail.is_empty() {
+                            println!("[DEBUG] 错误输出尾部: \n{}", tail);
+                        }
                     }
                 }
                 Ok(false)

+ 9 - 0
user/apps/tests/syscall/gvisor/runner/src/main.rs

@@ -79,6 +79,12 @@ fn main() -> Result<()> {
                 .value_name("PATTERN")
                 .action(clap::ArgAction::Append)
                 .help("测试名称模式"),
+        )
+        .arg(
+            Arg::new("stdout")
+                .long("stdout")
+                .action(clap::ArgAction::SetTrue)
+                .help("将测试输出直接显示到控制台,而不是保存到文件"),
         );
 
     let matches = app.get_matches();
@@ -111,6 +117,9 @@ fn main() -> Result<()> {
         config.test_patterns = patterns.cloned().collect();
     }
 
+    // 设置输出方式
+    config.output_to_stdout = matches.get_flag("stdout");
+
     // 创建测试运行器
     let runner = TestRunner::new(config);
 

+ 1 - 0
user/apps/tests/syscall/gvisor/whitelist.txt

@@ -19,6 +19,7 @@ write_test
 #fork_test
 #exec_test
 #wait_test
+futex_test
 
 # 内存管理测试
 #mmap_test