diff --git a/kernel/src/libs/futex/futex.rs b/kernel/src/libs/futex/futex.rs index 81dba3bb6..593dcdc07 100644 --- a/kernel/src/libs/futex/futex.rs +++ b/kernel/src/libs/futex/futex.rs @@ -174,10 +174,22 @@ pub struct FutexKey { key: InnerFutexKey, } -/// 不同进程间通过文件共享futex变量,表明该变量在文件中的位置 +/// 共享 futex 的类型 +#[derive(Hash, PartialEq, Eq, Clone, Debug)] +pub enum SharedKeyKind { + /// 文件映射的 futex + File { dev: u64, ino: u64 }, + /// 显式共享的匿名映射(MAP_SHARED | MAP_ANONYMOUS) + SharedAnon { id: u64 }, + /// 私有匿名映射上的 FUTEX_SHARED(栈、堆等) + /// 只能在同一进程的线程间同步 + PrivateAnonShared { as_id: u64 }, +} + +/// 不同进程间通过文件或共享内存共享futex变量 #[derive(Hash, PartialEq, Eq, Clone, Debug)] pub struct SharedKey { - i_seq: u64, + kind: SharedKeyKind, page_offset: u64, } @@ -310,51 +322,61 @@ impl Futex { drop(irq_guard); schedule(SchedMode::SM_NONE); - // 被唤醒后的检查 + // ========== 被唤醒后的检查 ========== + // 进程被唤醒可能有以下几种情况: + // 1. futex_wake 显式唤醒(正常情况)- futex_q 已从队列移除 + // 2. 超时唤醒 - futex_q 仍在队列中 + // 3. 信号唤醒 - futex_q 仍在队列中 + // 4. 伪唤醒 - futex_q 仍在队列中 + let mut futex_map_guard = FutexData::futex_map(); + + // 首先检查超时,优先级最高 + // 注意:必须在检查队列之前先检查超时,否则可能漏掉超时情况 + let is_timeout = timer.as_ref().is_some_and(|t| t.timeout()); + + if is_timeout { + // 超时唤醒:从队列中移除并返回 ETIMEDOUT + if let Some(bucket_mut) = futex_map_guard.get_mut(&key) { + bucket_mut.remove(futex_q.clone()); + } + return Err(SystemError::ETIMEDOUT); + } + + // 检查是否被正常唤醒(futex_wake) let bucket = futex_map_guard.get_mut(&key); - let bucket_mut = match bucket { - // 如果该pcb不在链表里面了或者该链表已经被释放,就证明是正常的Wake操作 + match bucket { Some(bucket_mut) => { if !bucket_mut.contains(&futex_q) { - // 取消定时器任务 + // futex_q 不在队列中,说明被 futex_wake 正常唤醒 if let Some(timer) = timer { timer.cancel(); } return Ok(0); } - // 非正常唤醒,返回交给下层 - bucket_mut + // futex_q 仍在队列中,说明是信号或伪唤醒 + // 从队列中移除 + bucket_mut.remove(futex_q.clone()); } None => { - // 取消定时器任务 + // 队列已被清空,说明被正常唤醒 if let Some(timer) = timer { timer.cancel(); } return Ok(0); } - }; - - // 如果是超时唤醒,则返回错误 - if timer.is_some() && timer.clone().unwrap().timeout() { - bucket_mut.remove(futex_q); - - return Err(SystemError::ETIMEDOUT); } - // TODO: 如果没有挂起的信号,则重新判断是否满足wait要求,重新进入wait + drop(futex_map_guard); - // 经过前面的几个判断,到这里之后, - // 当前进程被唤醒大概率是其他进程更改了uval,需要重新去判断当前进程是否满足wait - - // 到这里之后,前面的唤醒条件都不满足,则是被信号唤醒 - // 需要处理信号然后重启futex系统调用 - - // 取消定时器任务 + // 取消定时器 if let Some(timer) = timer { - if !timer.timeout() { - timer.cancel(); - } + timer.cancel(); + } + + // 检查是否有待处理的信号 + if ProcessManager::current_pcb().has_pending_signal() { + return Err(SystemError::ERESTARTSYS); } Ok(0) @@ -558,7 +580,8 @@ impl Futex { return Ok(key); } - // 共享:区分文件映射与匿名共享映射 + // 共享:需要生成能跨进程匹配的键 + // 按照 Linux 语义,共享 futex 基于物理页帧号(PFN)或文件身份 let address_space = AddressSpace::current()?; let as_guard = address_space.read(); let vma = as_guard @@ -576,44 +599,68 @@ impl Futex { 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, + kind: SharedKeyKind::File { dev, ino }, page_offset: base_pgoff + page_index, }; let key = FutexKey { ptr: 0, word: 0, offset: offset as u32, - key: InnerFutexKey::Shared(shared.clone()), + key: InnerFutexKey::Shared(shared), }; return Ok(key); } else { - // 匿名共享:使用共享匿名映射的稳定身份 + 页偏移 + // 匿名映射(包括栈、堆、匿名mmap等) if let Some(shared_anon) = &vma_guard.shared_anon { - let i_seq = shared_anon.id; + // 显式共享的匿名映射(MAP_SHARED | MAP_ANONYMOUS) let shared = SharedKey { - i_seq, + kind: SharedKeyKind::SharedAnon { id: shared_anon.id }, page_offset: page_index, }; let key = FutexKey { ptr: 0, word: 0, offset: offset as u32, - key: InnerFutexKey::Shared(shared.clone()), + key: InnerFutexKey::Shared(shared), }; return Ok(key); } else { - // 理论上不会发生;为安全起见,退化为私有键(不跨进程匹配) + // 私有匿名映射(栈、堆等)+ FUTEX_SHARED 标志 + // + // 按照 Linux 内核的实际实现(kernel/futex/core.c: get_futex_key): + // 对于匿名页的 FUTEX_SHARED,Linux 仍然使用 mm + 虚拟地址作为 key + // (只是添加了一个 FUT_OFF_MMSHARED 标记) + // + // 这种设计的原因: + // 1. 栈/堆这种私有匿名映射本质上不能跨进程共享 + // 2. 只能在同一进程的线程间同步(它们共享地址空间) + // 3. 使用虚拟地址而非物理地址,与 swap 机制兼容 + // + // DragonOS 的实现: + // 使用 AddressSpace 的全局唯一 ID + 虚拟页号作为 shared key + // - 同一进程的线程共享 AddressSpace,因此会生成相同的 key + // - 不同进程的 AddressSpace 有不同的 ID,即使虚拟地址相同也不会冲突 + // - AddressSpace ID 是递增分配的,永不重复,避免了地址重用问题 + + let address_space = AddressSpace::current()?; + let as_id = address_space.id(); + + drop(vma_guard); + drop(as_guard); + + let shared = SharedKey { + kind: SharedKeyKind::PrivateAnonShared { as_id }, + // 使用虚拟页号(不是物理页号!) + page_offset: (address >> MMArch::PAGE_SHIFT) as u64, + }; + 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)), - }), + key: InnerFutexKey::Shared(shared), }; return Ok(key); } diff --git a/kernel/src/mm/ucontext.rs b/kernel/src/mm/ucontext.rs index b56fcacb7..686871282 100644 --- a/kernel/src/mm/ucontext.rs +++ b/kernel/src/mm/ucontext.rs @@ -62,20 +62,35 @@ pub const DEFAULT_MMAP_MIN_ADDR: usize = 65536; static LOCKEDVMA_ID_ALLOCATOR: SpinLock = SpinLock::new(IdAllocator::new(0, usize::MAX).unwrap()); +/// AddressSpace的全局唯一ID分配器 +/// 用于为每个地址空间分配一个全局唯一且递增的ID +static ADDRESS_SPACE_ID_ALLOCATOR: AtomicU64 = AtomicU64::new(1); + #[derive(Debug)] pub struct AddressSpace { + /// 全局唯一的地址空间ID,用于标识不同的地址空间 + /// 该ID在地址空间的整个生命周期内保持不变,且永不重复 + id: u64, inner: RwLock, } impl AddressSpace { pub fn new(create_stack: bool) -> Result, SystemError> { let inner = InnerAddressSpace::new(create_stack)?; + let id = ADDRESS_SPACE_ID_ALLOCATOR.fetch_add(1, Ordering::Relaxed); let result = Self { + id, inner: RwLock::new(inner), }; return Ok(Arc::new(result)); } + /// 获取地址空间的全局唯一ID + #[inline(always)] + pub fn id(&self) -> u64 { + self.id + } + /// 从pcb中获取当前进程的地址空间结构体的Arc指针 pub fn current() -> Result, SystemError> { let vm = ProcessManager::current_pcb() diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 9762cabc3..5fd70d454 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -463,12 +463,10 @@ impl ProcessManager { // 按 Linux 语义:先清零 userland 的 *clear_child_tid,再 futex_wake(addr) unsafe { clear_user(addr, core::mem::size_of::()).expect("clear tid failed") }; if Arc::strong_count(&pcb.basic().user_vm().expect("User VM Not found")) > 1 { - let _ = Futex::futex_wake( - addr, - FutexFlag::FLAGS_MATCH_NONE, - 1, - FUTEX_BITSET_MATCH_ANY, - ); + // Linux 使用 FUTEX_SHARED 标志来唤醒 clear_child_tid + // 这允许跨进程/线程的同步(例如 pthread_join) + let _ = + Futex::futex_wake(addr, FutexFlag::FLAGS_SHARED, 1, FUTEX_BITSET_MATCH_ANY); } } compiler_fence(Ordering::SeqCst); @@ -479,6 +477,7 @@ impl ProcessManager { thread.vfork_done.as_ref().unwrap().complete_all(); } drop(thread); + unsafe { pcb.basic_mut().set_user_vm(None) }; pcb.exit_files(); // TODO 由于未实现进程组,tty记录的前台进程组等于当前进程,故退出前要置空 diff --git a/user/apps/tests/syscall/gvisor/blocklists/futex_test b/user/apps/tests/syscall/gvisor/blocklists/futex_test index 76cdfc64a..c9f3c5359 100644 --- a/user/apps/tests/syscall/gvisor/blocklists/futex_test +++ b/user/apps/tests/syscall/gvisor/blocklists/futex_test @@ -1,2 +1 @@ SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondSuccess/* -SharedPrivate/PrivateAndSharedFutexTest.WakeWrongKind*