refactor(futex): 优化futex唤醒逻辑和共享键生成机制 (#1376)

- 重构futex等待唤醒逻辑,明确区分正常唤醒、超时和信号唤醒
- 引入SharedKeyKind枚举,改进共享futex键的生成方式
- 为AddressSpace添加全局唯一ID,支持私有匿名映射的跨线程同步
- 修复clear_child_tid唤醒逻辑,使用FUTEX_SHARED标志
- 更新测试用例,移除不再适用的阻塞项

Signed-off-by: longjin <longjin@DragonOS.org>
This commit is contained in:
LoGin 2025-11-15 13:55:29 +08:00 committed by GitHub
parent 5ca5c0cfd6
commit 20db99ff8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 47 deletions

View File

@ -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_SHAREDLinux 仍然使用 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);
}

View File

@ -62,20 +62,35 @@ pub const DEFAULT_MMAP_MIN_ADDR: usize = 65536;
static LOCKEDVMA_ID_ALLOCATOR: SpinLock<IdAllocator> =
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<InnerAddressSpace>,
}
impl AddressSpace {
pub fn new(create_stack: bool) -> Result<Arc<Self>, 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<Arc<AddressSpace>, SystemError> {
let vm = ProcessManager::current_pcb()

View File

@ -463,12 +463,10 @@ impl ProcessManager {
// 按 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,
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记录的前台进程组等于当前进程故退出前要置空

View File

@ -1,2 +1 @@
SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondSuccess/*
SharedPrivate/PrivateAndSharedFutexTest.WakeWrongKind*