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:
parent
5ca5c0cfd6
commit
20db99ff8f
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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记录的前台进程组等于当前进程,故退出前要置空
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondSuccess/*
|
||||
SharedPrivate/PrivateAndSharedFutexTest.WakeWrongKind*
|
||||
|
|
|
|||
Loading…
Reference in New Issue