From 8f22b81fc57be2cc6d895e649513ec1349a79eb4 Mon Sep 17 00:00:00 2001
From: WitcherTheWhite <1240749052@qq.com>
Date: Thu, 9 Jul 2026 18:00:29 +0800
Subject: [PATCH 1/5] feat(kuaccess): add atomic user u32 cmpxchg for futex
paths
---
arch/kcpu/docs/security.md | 2 +-
arch/kcpu/src/aarch64/atomic_user.S | 49 ++++++++++
arch/kcpu/src/aarch64/instrs.rs | 17 ++++
arch/kcpu/src/loongarch64/atomic_user.S | 41 ++++++++
arch/kcpu/src/loongarch64/instrs.rs | 17 ++++
arch/kcpu/src/riscv/atomic_user.S | 37 ++++++++
arch/kcpu/src/riscv/instrs.rs | 17 ++++
arch/kcpu/src/x86_64/atomic_user.S | 40 ++++++++
arch/kcpu/src/x86_64/instrs.rs | 17 ++++
core/ksyscall/src/sync/futex.rs | 12 ++-
core/kuaccess/Cargo.toml | 1 +
core/kuaccess/docs/design.md | 19 +++-
core/kuaccess/docs/security.md | 8 +-
core/kuaccess/src/lib.rs | 119 +++++++++++++++++++++++-
posix/process/Cargo.toml | 1 +
posix/process/src/runtime.rs | 20 ++++
16 files changed, 404 insertions(+), 13 deletions(-)
create mode 100644 arch/kcpu/src/aarch64/atomic_user.S
create mode 100644 arch/kcpu/src/loongarch64/atomic_user.S
create mode 100644 arch/kcpu/src/riscv/atomic_user.S
create mode 100644 arch/kcpu/src/x86_64/atomic_user.S
diff --git a/arch/kcpu/docs/security.md b/arch/kcpu/docs/security.md
index a2aad2953..4a024572d 100644
--- a/arch/kcpu/docs/security.md
+++ b/arch/kcpu/docs/security.md
@@ -359,7 +359,7 @@ $f0–$f31 的寄存器编号。
- [ ] 修改 GDT 段选择子常量后验证 `UserContext::new` 中的 `cs`/`ss` 值。
- [ ] 新增异常向量处理时检查是否需要更新异常表或注册新 handler。
- [ ] LoongArch64 新增指令模拟时验证操作码掩码和寄存器编号范围。
-- [ ] 修改 `copy_user.S` 后确认每条访存指令均有对应的 `_asm_extable` 条目。
+- [ ] 修改 `copy_user.S` / `atomic_user.S` 后确认每条访存指令均有对应的 `_asm_extable` 条目。
- [ ] 新增 per-CPU 变量时验证初始化顺序(percpu init → `init_trap`)。
- [ ] 修改页表切换逻辑后验证 TLB 刷新语义是否正确。
- [ ] 修改 `fp-simd` / `tls` feature 门控代码后验证所有架构的一致性。
diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S
new file mode 100644
index 000000000..6ae8d0686
--- /dev/null
+++ b/arch/kcpu/src/aarch64/atomic_user.S
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2025 KylinSoft Co., Ltd.
+// See LICENSES for license details.
+//
+// Atomically compare-exchange a 32-bit word at a (possibly user) address.
+//
+// Prototype:
+// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out);
+// Args: x0=addr, w1=old, w2=new, x3=old_out
+// Returns: 0 if no fault occurred; non-zero if a data abort occurred.
+// On success, *old_out receives the value observed at *addr. If that value
+// equals `old`, `new` was stored.
+
+.macro _asm_extable, from, to
+ .pushsection __ex_table, "a"
+ .balign 8
+ .quad \from
+ .quad \to
+ .popsection
+.endm
+
+.section .text
+.global user_atomic_cmpxchg_u32
+.type user_atomic_cmpxchg_u32, @function
+
+user_atomic_cmpxchg_u32:
+.Lretry:
+1: ldxr w4, [x0]
+ // old_out is a kernel pointer; no exception table needed.
+ str w4, [x3]
+ cmp w4, w1
+ b.ne .Ldone
+2: stxr w5, w2, [x0]
+ cbnz w5, .Lretry
+ dmb ish
+.Ldone:
+ clrex
+ mov x0, #0
+ ret
+
+.Lfault:
+ clrex
+ mov x0, #1
+ ret
+
+ _asm_extable 1b, .Lfault
+ _asm_extable 2b, .Lfault
+
+.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/aarch64/instrs.rs b/arch/kcpu/src/aarch64/instrs.rs
index 904501e4b..165e8ec7d 100644
--- a/arch/kcpu/src/aarch64/instrs.rs
+++ b/arch/kcpu/src/aarch64/instrs.rs
@@ -5,6 +5,7 @@
//! Wrapper functions for assembly instructions.
core::arch::global_asm!(include_str!("copy_user.S"));
+core::arch::global_asm!(include_str!("atomic_user.S"));
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
@@ -17,6 +18,22 @@ unsafe extern "C" {
/// Returns the number of bytes not copied. This means 0 indicates success,
/// while a value > 0 indicates failure.
pub fn raw_copy_from_user(dst: *mut u8, src: *const u8, size: usize) -> usize;
+
+ /// Atomically compare-exchanges a 32-bit word at `addr`.
+ ///
+ /// On success (return `0`), writes the previous `*addr` value into
+ /// `old_out`. If that previous value equals `old`, stores `new`.
+ ///
+ /// # Safety
+ ///
+ /// `addr` must be a 4-byte-aligned address that is currently accessible in
+ /// the active address space under the user-access window. `old_out` must
+ /// point to writable kernel memory.
+ ///
+ /// # Returns
+ ///
+ /// `0` if no fault occurred; non-zero if a data abort occurred.
+ pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize;
}
/// Alias for compatibility with other architectures
diff --git a/arch/kcpu/src/loongarch64/atomic_user.S b/arch/kcpu/src/loongarch64/atomic_user.S
new file mode 100644
index 000000000..f89b310d2
--- /dev/null
+++ b/arch/kcpu/src/loongarch64/atomic_user.S
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2025 KylinSoft Co., Ltd.
+// See LICENSES for license details.
+//
+// Atomically compare-exchange a 32-bit word at a (possibly user) address.
+//
+// Prototype:
+// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out);
+// Args: a0=addr, a1=old, a2=new, a3=old_out
+// Returns: 0 if no fault occurred; non-zero if a fault occurred.
+// On success, *old_out receives the value observed at *addr. If that value
+// equals `old`, `new` was stored.
+//
+// LoongArch `sc.w rd, rj, si12` overwrites `rd` with 1 on success / 0 on fail,
+// so the desired store value must be reloaded into `rd` on each retry.
+
+.section .text
+.global user_atomic_cmpxchg_u32
+.type user_atomic_cmpxchg_u32, @function
+
+user_atomic_cmpxchg_u32:
+.Lretry:
+1: ll.w $t0, $a0, 0
+ st.w $t0, $a3, 0
+ bne $t0, $a1, .Ldone
+ move $t1, $a2
+2: sc.w $t1, $a0, 0
+ beqz $t1, .Lretry
+ dbar 0
+.Ldone:
+ move $a0, $zero
+ jr $ra
+
+.Lfault:
+ ori $a0, $zero, 1
+ jr $ra
+
+ _asm_extable 1b, .Lfault
+ _asm_extable 2b, .Lfault
+
+.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/loongarch64/instrs.rs b/arch/kcpu/src/loongarch64/instrs.rs
index 5005f16f4..7bba6dc17 100644
--- a/arch/kcpu/src/loongarch64/instrs.rs
+++ b/arch/kcpu/src/loongarch64/instrs.rs
@@ -5,6 +5,7 @@
//! Wrapper functions for assembly instructions.
core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S"));
+core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S"));
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
@@ -17,4 +18,20 @@ unsafe extern "C" {
/// Returns the number of bytes not copied. This means 0 indicates success,
/// while a value > 0 indicates failure.
pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
+
+ /// Atomically compare-exchanges a 32-bit word at `addr`.
+ ///
+ /// On success (return `0`), writes the previous `*addr` value into
+ /// `old_out`. If that previous value equals `old`, stores `new`.
+ ///
+ /// # Safety
+ ///
+ /// `addr` must be a 4-byte-aligned address that is currently accessible in
+ /// the active address space under the user-access window. `old_out` must
+ /// point to writable kernel memory.
+ ///
+ /// # Returns
+ ///
+ /// `0` if no fault occurred; non-zero if a fault occurred.
+ pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize;
}
diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S
new file mode 100644
index 000000000..254ad5238
--- /dev/null
+++ b/arch/kcpu/src/riscv/atomic_user.S
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2025 KylinSoft Co., Ltd.
+// See LICENSES for license details.
+//
+// Atomically compare-exchange a 32-bit word at a (possibly user) address.
+//
+// Prototype:
+// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out);
+// Args: a0=addr, a1=old, a2=new, a3=old_out
+// Returns: 0 if no fault occurred; non-zero if a fault occurred.
+// On success, *old_out receives the value observed at *addr. If that value
+// equals `old`, `new` was stored.
+
+.section .text
+.global user_atomic_cmpxchg_u32
+.type user_atomic_cmpxchg_u32, @function
+
+user_atomic_cmpxchg_u32:
+.Lretry:
+1: lr.w t0, (a0)
+ sw t0, 0(a3)
+ bne t0, a1, .Ldone
+2: sc.w t1, a2, (a0)
+ bnez t1, .Lretry
+ fence rw, rw
+.Ldone:
+ li a0, 0
+ ret
+
+.Lfault:
+ li a0, 1
+ ret
+
+ _asm_extable 1b, .Lfault
+ _asm_extable 2b, .Lfault
+
+.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/riscv/instrs.rs b/arch/kcpu/src/riscv/instrs.rs
index 5005f16f4..7bba6dc17 100644
--- a/arch/kcpu/src/riscv/instrs.rs
+++ b/arch/kcpu/src/riscv/instrs.rs
@@ -5,6 +5,7 @@
//! Wrapper functions for assembly instructions.
core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S"));
+core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S"));
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
@@ -17,4 +18,20 @@ unsafe extern "C" {
/// Returns the number of bytes not copied. This means 0 indicates success,
/// while a value > 0 indicates failure.
pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
+
+ /// Atomically compare-exchanges a 32-bit word at `addr`.
+ ///
+ /// On success (return `0`), writes the previous `*addr` value into
+ /// `old_out`. If that previous value equals `old`, stores `new`.
+ ///
+ /// # Safety
+ ///
+ /// `addr` must be a 4-byte-aligned address that is currently accessible in
+ /// the active address space under the user-access window. `old_out` must
+ /// point to writable kernel memory.
+ ///
+ /// # Returns
+ ///
+ /// `0` if no fault occurred; non-zero if a fault occurred.
+ pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize;
}
diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S
new file mode 100644
index 000000000..fb3256172
--- /dev/null
+++ b/arch/kcpu/src/x86_64/atomic_user.S
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2025 KylinSoft Co., Ltd.
+// See LICENSES for license details.
+//
+// Atomically compare-exchange a 32-bit word at a (possibly user) address.
+//
+// Prototype:
+// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out);
+// Args: rdi=addr, esi=old, edx=new, rcx=old_out
+// Returns: 0 if no fault occurred; non-zero if a fault occurred.
+// On success, *old_out receives the value observed at *addr. If that value
+// equals `old`, `new` was stored.
+
+.macro _asm_extable, from, to
+ .pushsection __ex_table, "a"
+ .balign 8
+ .quad \from
+ .quad \to
+ .popsection
+.endm
+
+.section .text
+.global user_atomic_cmpxchg_u32
+.type user_atomic_cmpxchg_u32, @function
+
+user_atomic_cmpxchg_u32:
+ mov eax, esi
+1: lock cmpxchg dword ptr [rdi], edx
+ // After cmpxchg, eax always holds the previous *addr value.
+ mov dword ptr [rcx], eax
+ xor eax, eax
+ ret
+
+.Lfault:
+ mov eax, 1
+ ret
+
+ _asm_extable 1b, .Lfault
+
+.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/x86_64/instrs.rs b/arch/kcpu/src/x86_64/instrs.rs
index 604c7088e..abab00964 100644
--- a/arch/kcpu/src/x86_64/instrs.rs
+++ b/arch/kcpu/src/x86_64/instrs.rs
@@ -5,6 +5,7 @@
//! Wrapper functions for assembly instructions.
core::arch::global_asm!(include_str!("copy_user.S"));
+core::arch::global_asm!(include_str!("atomic_user.S"));
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
@@ -17,4 +18,20 @@ unsafe extern "C" {
/// Returns the number of bytes not copied. This means 0 indicates success,
/// while a value > 0 indicates failure.
pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
+
+ /// Atomically compare-exchanges a 32-bit word at `addr`.
+ ///
+ /// On success (return `0`), writes the previous `*addr` value into
+ /// `old_out`. If that previous value equals `old`, stores `new`.
+ ///
+ /// # Safety
+ ///
+ /// `addr` must be a 4-byte-aligned address that is currently accessible in
+ /// the active address space under the user-access window. `old_out` must
+ /// point to writable kernel memory.
+ ///
+ /// # Returns
+ ///
+ /// `0` if no fault occurred; non-zero if a fault occurred.
+ pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize;
}
diff --git a/core/ksyscall/src/sync/futex.rs b/core/ksyscall/src/sync/futex.rs
index 1bb711a65..d427020c7 100644
--- a/core/ksyscall/src/sync/futex.rs
+++ b/core/ksyscall/src/sync/futex.rs
@@ -9,6 +9,7 @@ use core::{mem::size_of, sync::atomic::Ordering};
use kerrno::{KError, KResult, LinuxError};
use kfutex::FutexKey;
use kprocess::{AsThread, current_futex_key};
+use kuaccess::atomic_u32_eq;
use linux_raw_sys::general::{
FUTEX_CMD_MASK, FUTEX_CMP_REQUEUE, FUTEX_PRIVATE_FLAG, FUTEX_REQUEUE, FUTEX_WAIT,
FUTEX_WAIT_BITSET, FUTEX_WAKE, FUTEX_WAKE_BITSET, robust_list_head, timespec,
@@ -57,9 +58,10 @@ pub fn sys_futex(
let futex_table = process.futex_state()?.table_for(&key);
let command = futex_op & (FUTEX_CMD_MASK as u32);
+ let uaddr_usize = uaddr.as_ptr() as usize;
match command {
FUTEX_WAIT | FUTEX_WAIT_BITSET => {
- if uaddr.read_vm()? != value {
+ if !atomic_u32_eq(uaddr_usize, value)? {
return Err(KError::WouldBlock);
}
@@ -79,9 +81,9 @@ pub fn sys_futex(
u32::MAX
};
- let wait_result = futex
- .wq
- .wait_if(bitset, timeout, || uaddr.read_vm() == Ok(value));
+ let wait_result = futex.wq.wait_if(bitset, timeout, || {
+ atomic_u32_eq(uaddr_usize, value).unwrap_or(false)
+ });
match wait_result {
Ok(false) => {
return Err(KError::WouldBlock);
@@ -118,7 +120,7 @@ pub fn sys_futex(
}
FUTEX_REQUEUE | FUTEX_CMP_REQUEUE => {
validate_non_negative(value)?;
- if command == FUTEX_CMP_REQUEUE && uaddr.read_vm()? != value3 {
+ if command == FUTEX_CMP_REQUEUE && !atomic_u32_eq(uaddr_usize, value3)? {
return Err(KError::WouldBlock);
}
let value2 = validate_non_negative(timeout_or_value2 as u32)?;
diff --git a/core/kuaccess/Cargo.toml b/core/kuaccess/Cargo.toml
index 40b089e0b..50fc65f19 100644
--- a/core/kuaccess/Cargo.toml
+++ b/core/kuaccess/Cargo.toml
@@ -21,3 +21,4 @@ memaddr.workspace = true
memspace.workspace = true
osvm.workspace = true
unittest.workspace = true
+unittest_support.workspace = true
diff --git a/core/kuaccess/docs/design.md b/core/kuaccess/docs/design.md
index 5f93c3a5e..ecff9c7b6 100644
--- a/core/kuaccess/docs/design.md
+++ b/core/kuaccess/docs/design.md
@@ -6,7 +6,8 @@
- 将 `osvm` 的通用虚拟内存访问入口接到当前线程地址空间;
- 处理“内核在访问用户地址时允许页错误回填”的 trap 路径;
-- 提供少量高频的用户态字符串装载辅助函数。
+- 提供少量高频的用户态字符串装载辅助函数;
+- 提供用户态 32-bit 原子 cmpxchg 原语(供 futex 等路径使用)。
调用方包括 syscall 实现、用户线程 runtime,以及依赖 `osvm` 指针包装的其他 crate。
@@ -25,6 +26,7 @@ syscall / runtime
kuaccess
| \
| \-- vm_load_string* -> osvm load helpers
+ | \-- atomic_cmpxchg_u32 -> user_atomic_cmpxchg_u32
|
\-- VirtMemIo(Vm) -> user_copy + current thread address space
```
@@ -47,9 +49,19 @@ syscall / runtime
5. `kuaccess` 消费 `MmSpace::handle_page_fault()` 返回的 typed fault outcome:
`Resolved` / retry-class outcome 让 fault 指令重试;unmapped、permission、bus、
OOM、no-progress 和 generic failure 都返回 false,交给架构 exception-table
- fixup 使 `user_copy` 返回失败。
+ fixup 使 `user_copy` / `user_atomic_cmpxchg_u32` 返回失败。
6. 访问结束后恢复线程标志,并将失败映射为 `MemError` / `KError`。
+### 用户态原子 cmpxchg
+
+1. 校验 4 字节对齐与用户地址范围。
+2. 在 `IrqSave` 保护下、于 `access_user_memory()` 窗口内调用架构原语
+ `user_atomic_cmpxchg_u32`。
+3. 成功时返回 `(exchanged, observed)`;fault 时由 `__ex_table` fixup 返回非零,
+ 映射为 `MemError::NoAccess`。
+
+该原语供 futex / robust-list 等路径对用户态 futex word 做无 TOCTOU 的更新。
+
### 字符串装载
1. 从用户地址读取字节向量或 NUL 终止字节流。
@@ -58,7 +70,8 @@ syscall / runtime
## 并发模型
-- `Vm` 使用 `IrqSave` 建立访问窗口,避免访问期间的本地中断干扰。
+- `Vm` 与 `atomic_cmpxchg_u32` 使用 `IrqSave` 建立访问窗口,避免本地中断在
+ exclusive/atomic 序列期间清除 monitor。
- 不维护全局共享状态;真正的并发控制由线程状态和地址空间锁负责。
## 设计决策
diff --git a/core/kuaccess/docs/security.md b/core/kuaccess/docs/security.md
index 75afd35c4..bb340857d 100644
--- a/core/kuaccess/docs/security.md
+++ b/core/kuaccess/docs/security.md
@@ -15,15 +15,18 @@
- `user_copy(...)`:
依赖调用前的用户地址范围检查,以及当前线程 `accessing_user_memory` 标志保证 trap handler 只在受控窗口内接管。
+- `user_atomic_cmpxchg_u32(...)`:
+ 依赖 4 字节对齐检查、用户地址范围检查,以及同一用户访问窗口与 exception-table fixup。
## 内存安全不变量
- `check_access()` 只允许访问用户空间有效区间。
+- `atomic_cmpxchg_u32()` / `atomic_load_u32()` / `atomic_u32_eq()` 额外要求地址 4 字节对齐。
- `dispatch_irq_page_fault()` 仅在当前线程显式进入用户内存访问窗口时处理 fault。
- `kuaccess` 必须保留 `MmSpace::handle_page_fault()` 的 typed outcome 分类:
只有 `Resolved` 和 retry-class outcome 可以转换为 trap handled;unmapped、
permission denied、bus error、OOM、no-progress 和 generic failure 必须转换为
- user-copy failure,而不是继续重试。
+ user-copy / atomic-user 失败,而不是继续重试。
- 字符串 helper 只在成功读取完整字节流后再做 UTF-8 解释。
## 线程安全
@@ -52,4 +55,5 @@
## 已知限制
-- 当前 helper 只覆盖字符串装载,复杂结构体解析仍由各调用方自行组织。
+- 当前 helper 只覆盖字符串装载与 `u32` 原子 cmpxchg;复杂结构体解析仍由各调用方自行组织。
+- 原子原语目前仅提供 32-bit cmpxchg;更大宽度或其它 RMW 操作按需再加。
diff --git a/core/kuaccess/src/lib.rs b/core/kuaccess/src/lib.rs
index f2cc9a642..b3dc35a67 100644
--- a/core/kuaccess/src/lib.rs
+++ b/core/kuaccess/src/lib.rs
@@ -17,7 +17,7 @@ use extern_trait::extern_trait;
use kaddr_layout::{USER_SPACE_BASE, USER_SPACE_SIZE};
use kerrno::{KError, KResult};
use khal::{
- asm::user_copy,
+ asm::{user_atomic_cmpxchg_u32, user_copy},
paging::MappingFlags,
trap::{PAGE_FAULT, register_trap_handler},
};
@@ -102,6 +102,61 @@ pub fn vm_load_string_with_len(ptr: *const c_char, len: usize) -> KResult MemResult {
+ let (_, observed) = atomic_cmpxchg_u32(addr, 0, 0)?;
+ Ok(observed)
+}
+
+/// Atomically tests whether `*addr == expected` without changing the word.
+pub fn atomic_u32_eq(addr: usize, expected: u32) -> MemResult {
+ let (exchanged, _) = atomic_cmpxchg_u32(addr, expected, expected)?;
+ Ok(exchanged)
+}
+
+/// Atomically compare-exchanges a 32-bit word in user memory.
+///
+/// Returns `Ok((true, old))` when `*addr == old` and the store of `new`
+/// succeeded. Returns `Ok((false, observed))` when the word differed from
+/// `old` (no store). Returns `Err` on misalignment, out-of-range address, or
+/// page fault that could not be resolved inside the user-access window.
+///
+/// This is the primitive futex / robust-list paths need for race-free updates
+/// of userspace futex words.
+pub fn atomic_cmpxchg_u32(addr: usize, old: u32, new: u32) -> MemResult<(bool, u32)> {
+ if !addr.is_multiple_of(core::mem::align_of::()) {
+ return Err(MemError::InvalidAddr);
+ }
+ check_access(addr, core::mem::size_of::())?;
+
+ // Match `VirtMemIo`: keep IRQs masked for the exclusive/atomic sequence so
+ // a local interrupt cannot clear the monitor between load and store.
+ let _irq = IrqSave::new();
+ let mut observed = 0u32;
+ let failed = access_user_memory(|| {
+ // SAFETY: `check_access` validated the 4-byte user range, `addr` is
+ // 4-byte aligned, and `observed` is a live kernel stack slot. The
+ // call runs inside `access_user_memory`, so page faults are handled
+ // by the exception-table fixup on `user_atomic_cmpxchg_u32`.
+ unsafe {
+ user_atomic_cmpxchg_u32(
+ addr as *mut u32,
+ old,
+ new,
+ core::ptr::addr_of_mut!(observed),
+ )
+ }
+ });
+ if unlikely(failed != 0) {
+ Err(MemError::NoAccess)
+ } else {
+ Ok((observed == old, observed))
+ }
+}
+
#[extern_trait]
// SAFETY: `Vm` validates the user range up front and performs raw copies only
// inside the temporary user-access window established by `access_user_memory`.
@@ -144,8 +199,12 @@ mod tests {
use memspace::PageFaultOutcome;
use osvm::MemError;
use unittest::def_test;
+ use unittest_support::TestUserValue;
- use super::{USER_SPACE_BASE, USER_SPACE_SIZE, check_access, fault_outcome_to_trap_result};
+ use super::{
+ USER_SPACE_BASE, USER_SPACE_SIZE, atomic_cmpxchg_u32, atomic_load_u32, atomic_u32_eq,
+ check_access, fault_outcome_to_trap_result,
+ };
#[def_test]
fn test_check_access_valid() {
@@ -177,6 +236,62 @@ mod tests {
assert!(matches!(res, Err(MemError::NoAccess)));
}
+ #[def_test]
+ fn test_atomic_cmpxchg_u32_rejects_misaligned() {
+ let res = atomic_cmpxchg_u32(USER_SPACE_BASE + 1, 0, 1);
+ assert!(matches!(res, Err(MemError::InvalidAddr)));
+ }
+
+ #[def_test]
+ fn test_atomic_cmpxchg_u32_rejects_out_of_range() {
+ let res = atomic_cmpxchg_u32(USER_SPACE_BASE - 4, 0, 1);
+ assert!(matches!(res, Err(MemError::NoAccess)));
+ }
+
+ #[def_test(custom)]
+ fn test_atomic_cmpxchg_u32_match_updates_user_word() {
+ let word = TestUserValue::::from_value(10).unwrap();
+ let addr = word.as_user_ptr() as usize;
+
+ let res = atomic_cmpxchg_u32(addr, 10, 20).unwrap();
+ assert_eq!(res, (true, 10));
+ assert_eq!(word.read(), 20);
+ }
+
+ #[def_test(custom)]
+ fn test_atomic_cmpxchg_u32_mismatch_leaves_user_word() {
+ let word = TestUserValue::::from_value(10).unwrap();
+ let addr = word.as_user_ptr() as usize;
+
+ let res = atomic_cmpxchg_u32(addr, 5, 20).unwrap();
+ assert_eq!(res, (false, 10));
+ assert_eq!(word.read(), 10);
+ }
+
+ #[def_test(custom)]
+ fn test_atomic_load_u32_reads_user_word() {
+ let word = TestUserValue::::from_value(42).unwrap();
+ let addr = word.as_user_ptr() as usize;
+
+ assert_eq!(atomic_load_u32(addr).unwrap(), 42);
+ }
+
+ #[def_test(custom)]
+ fn test_atomic_u32_eq_matches_user_word() {
+ let word = TestUserValue::::from_value(7).unwrap();
+ let addr = word.as_user_ptr() as usize;
+
+ assert!(atomic_u32_eq(addr, 7).unwrap());
+ assert!(!atomic_u32_eq(addr, 8).unwrap());
+ }
+
+ #[def_test(custom)]
+ fn test_atomic_cmpxchg_u32_faults_on_unmapped_user_addr() {
+ let unmapped = USER_SPACE_BASE + 0x1000;
+ let res = atomic_cmpxchg_u32(unmapped, 0, 1);
+ assert!(matches!(res, Err(MemError::NoAccess)));
+ }
+
#[def_test]
fn test_check_access_rejects_far_above_user_space() {
let res = check_access(USER_SPACE_BASE + USER_SPACE_SIZE + 0x1000, 1);
diff --git a/posix/process/Cargo.toml b/posix/process/Cargo.toml
index e3a54feef..8b2611e5a 100644
--- a/posix/process/Cargo.toml
+++ b/posix/process/Cargo.toml
@@ -23,6 +23,7 @@ khal.workspace = true
kidentity.workspace = true
klogger.workspace = true
kprocess.workspace = true
+kuaccess.workspace = true
ksignal.workspace = true
ksync.workspace = true
ktask.workspace = true
diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs
index 1c5e0393c..c2eb85c57 100644
--- a/posix/process/src/runtime.rs
+++ b/posix/process/src/runtime.rs
@@ -18,6 +18,7 @@ use kprocess::{
};
use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo};
use ktask::{TaskInner, current};
+use kuaccess::{atomic_cmpxchg_u32, atomic_load_u32};
use linux_raw_sys::general::ROBUST_LIST_LIMIT;
use linux_sysno::Sysno;
use memspace::PageFaultOutcome;
@@ -155,11 +156,30 @@ struct RobustListHead {
list_op_pending: *mut RobustList,
}
+/// Bits in the userspace futex word used by robust mutexes.
+const FUTEX_OWNER_DIED: u32 = 0x0010_0000;
+const FUTEX_WAITERS: u32 = 0x8000_0000;
+
+fn mark_futex_owner_died(address: usize) -> KResult<()> {
+ loop {
+ let observed = atomic_load_u32(address).map_err(KError::from)?;
+ if observed & FUTEX_OWNER_DIED != 0 {
+ return Ok(());
+ }
+ let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED;
+ let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?;
+ if exchanged {
+ return Ok(());
+ }
+ }
+}
+
fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()> {
let address = (entry as u64)
.checked_add_signed(offset)
.ok_or(KError::InvalidInput)?;
let address: usize = address.try_into().map_err(|_| KError::InvalidInput)?;
+ mark_futex_owner_died(address)?;
let key = current_futex_key(address);
let futex_state = current_user_process().futex_state()?;
--
Gitee
From 896162f3d603bd2f3826c96e57d2bf647580a347 Mon Sep 17 00:00:00 2001
From: WitcherTheWhite <1240749052@qq.com>
Date: Mon, 13 Jul 2026 10:02:54 +0800
Subject: [PATCH 2/5] update
---
arch/kcpu/src/riscv/atomic_user.S | 2 +-
posix/process/src/runtime.rs | 30 ++++++++++++++++++------------
2 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S
index 254ad5238..efcb37d79 100644
--- a/arch/kcpu/src/riscv/atomic_user.S
+++ b/arch/kcpu/src/riscv/atomic_user.S
@@ -22,8 +22,8 @@ user_atomic_cmpxchg_u32:
bne t0, a1, .Ldone
2: sc.w t1, a2, (a0)
bnez t1, .Lretry
- fence rw, rw
.Ldone:
+ fence rw, rw
li a0, 0
ret
diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs
index c2eb85c57..05d915b6d 100644
--- a/posix/process/src/runtime.rs
+++ b/posix/process/src/runtime.rs
@@ -13,8 +13,8 @@ use kerrno::{KError, KResult, LinuxError};
use khal::uspace::{ExceptionKind, ReturnReason, UserContext};
use kidentity::PidHandle;
use kprocess::{
- CpuTimeState, Pid, Thread, UserThreadRuntimeAction, current_futex_key, current_user_process,
- current_user_thread, poll_cpu_timers, process_exit, process_signals,
+ CpuTimeState, Pid, Thread, Tid, UserThreadRuntimeAction, current_futex_key,
+ current_user_process, current_user_thread, poll_cpu_timers, process_exit, process_signals,
};
use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo};
use ktask::{TaskInner, current};
@@ -157,29 +157,35 @@ struct RobustListHead {
}
/// Bits in the userspace futex word used by robust mutexes.
-const FUTEX_OWNER_DIED: u32 = 0x0010_0000;
+const FUTEX_TID_MASK: u32 = 0x3fff_ffff;
+const FUTEX_OWNER_DIED: u32 = 0x4000_0000;
const FUTEX_WAITERS: u32 = 0x8000_0000;
-fn mark_futex_owner_died(address: usize) -> KResult<()> {
+fn mark_futex_owner_died(address: usize, tid: Tid) -> KResult {
loop {
let observed = atomic_load_u32(address).map_err(KError::from)?;
+ if (observed & FUTEX_TID_MASK) != tid {
+ return Ok(false);
+ }
if observed & FUTEX_OWNER_DIED != 0 {
- return Ok(());
+ return Ok(true);
}
let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED;
let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?;
if exchanged {
- return Ok(());
+ return Ok(true);
}
}
}
-fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()> {
+fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64, tid: Tid) -> KResult<()> {
let address = (entry as u64)
.checked_add_signed(offset)
.ok_or(KError::InvalidInput)?;
let address: usize = address.try_into().map_err(|_| KError::InvalidInput)?;
- mark_futex_owner_died(address)?;
+ if !mark_futex_owner_died(address, tid)? {
+ return Ok(());
+ }
let key = current_futex_key(address);
let futex_state = current_user_process().futex_state()?;
@@ -194,7 +200,7 @@ fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()>
}
/// Process robust futex list on thread exit and wake waiting threads.
-fn exit_robust_list(head: *const RobustListHead) {
+fn exit_robust_list(head: *const RobustListHead, tid: Tid) {
let mut limit = ROBUST_LIST_LIMIT;
// SAFETY: `head` comes from the task's registered robust-list head, and we
@@ -211,7 +217,7 @@ fn exit_robust_list(head: *const RobustListHead) {
let Some(next_entry) = entry.read_vm().map(|node| node.next).ok() else {
return;
};
- if entry != pending && dispatch_irq_futex_death(entry, offset).is_err() {
+ if entry != pending && dispatch_irq_futex_death(entry, offset, tid).is_err() {
return;
}
entry = next_entry;
@@ -224,7 +230,7 @@ fn exit_robust_list(head: *const RobustListHead) {
}
if !pending.is_null() {
- let _ = dispatch_irq_futex_death(pending, offset);
+ let _ = dispatch_irq_futex_death(pending, offset, tid);
}
}
@@ -249,7 +255,7 @@ pub fn do_exit(exit_code: i32, group_exit: bool) {
let head = thr.robust_list_head() as *const RobustListHead;
if !head.is_null() {
- exit_robust_list(head);
+ exit_robust_list(head, thr.tid());
}
// Per-thread TEE session cleanup when this thread holds a session context.
--
Gitee
From 57b4ecf113ebfd091f4dd52a8b6b3c330b6fdefb Mon Sep 17 00:00:00 2001
From: WitcherTheWhite <1240749052@qq.com>
Date: Mon, 13 Jul 2026 10:14:42 +0800
Subject: [PATCH 3/5] update
---
arch/kcpu/src/aarch64/atomic_user.S | 5 +++++
arch/kcpu/src/aarch64/copy_user.S | 5 +++++
arch/kcpu/src/loongarch64/instrs.rs | 7 +++++--
arch/kcpu/src/riscv/instrs.rs | 7 +++++--
arch/kcpu/src/riscv/macros.rs | 10 ++++++++++
arch/kcpu/src/x86_64/atomic_user.S | 5 +++++
arch/kcpu/src/x86_64/copy_user.S | 5 +++++
7 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S
index 6ae8d0686..44796df55 100644
--- a/arch/kcpu/src/aarch64/atomic_user.S
+++ b/arch/kcpu/src/aarch64/atomic_user.S
@@ -11,6 +11,9 @@
// On success, *old_out receives the value observed at *addr. If that value
// equals `old`, `new` was stored.
+.ifndef _ASM_EXTABLE_DEFINED
+.equ _ASM_EXTABLE_DEFINED, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 8
@@ -19,6 +22,8 @@
.popsection
.endm
+.endif
+
.section .text
.global user_atomic_cmpxchg_u32
.type user_atomic_cmpxchg_u32, @function
diff --git a/arch/kcpu/src/aarch64/copy_user.S b/arch/kcpu/src/aarch64/copy_user.S
index b475c32d7..84b59edb8 100644
--- a/arch/kcpu/src/aarch64/copy_user.S
+++ b/arch/kcpu/src/aarch64/copy_user.S
@@ -3,6 +3,9 @@
// Arguments: x0=dst, x1=src, x2=size
// Returns: 0 on success; remaining bytes (not copied) if a data abort occurs
+.ifndef _ASM_EXTABLE_DEFINED
+.equ _ASM_EXTABLE_DEFINED, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 8
@@ -11,6 +14,8 @@
.popsection
.endm
+.endif
+
.section .text
.global raw_copy_from_user
diff --git a/arch/kcpu/src/loongarch64/instrs.rs b/arch/kcpu/src/loongarch64/instrs.rs
index 7bba6dc17..8aa9b5a49 100644
--- a/arch/kcpu/src/loongarch64/instrs.rs
+++ b/arch/kcpu/src/loongarch64/instrs.rs
@@ -4,8 +4,11 @@
//! Wrapper functions for assembly instructions.
-core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S"));
-core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S"));
+core::arch::global_asm!(
+ include_asm_macros!(),
+ include_str!("copy_user.S"),
+ include_str!("atomic_user.S"),
+);
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
diff --git a/arch/kcpu/src/riscv/instrs.rs b/arch/kcpu/src/riscv/instrs.rs
index 7bba6dc17..8aa9b5a49 100644
--- a/arch/kcpu/src/riscv/instrs.rs
+++ b/arch/kcpu/src/riscv/instrs.rs
@@ -4,8 +4,11 @@
//! Wrapper functions for assembly instructions.
-core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S"));
-core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S"));
+core::arch::global_asm!(
+ include_asm_macros!(),
+ include_str!("copy_user.S"),
+ include_str!("atomic_user.S"),
+);
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
diff --git a/arch/kcpu/src/riscv/macros.rs b/arch/kcpu/src/riscv/macros.rs
index bce104610..12c00f92b 100644
--- a/arch/kcpu/src/riscv/macros.rs
+++ b/arch/kcpu/src/riscv/macros.rs
@@ -26,6 +26,11 @@ macro_rules! __asm_macros {
sw \rs2, \off*XLENB(\rs1)
.endm
+ .endif
+
+ .ifndef ASM_EXTABLE_FLAG
+ .equ ASM_EXTABLE_FLAG, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 4
@@ -52,6 +57,11 @@ macro_rules! __asm_macros {
sd \rs2, \off*XLENB(\rs1)
.endm
+ .endif
+
+ .ifndef ASM_EXTABLE_FLAG
+ .equ ASM_EXTABLE_FLAG, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 8
diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S
index fb3256172..f90829fde 100644
--- a/arch/kcpu/src/x86_64/atomic_user.S
+++ b/arch/kcpu/src/x86_64/atomic_user.S
@@ -11,6 +11,9 @@
// On success, *old_out receives the value observed at *addr. If that value
// equals `old`, `new` was stored.
+.ifndef _ASM_EXTABLE_DEFINED
+.equ _ASM_EXTABLE_DEFINED, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 8
@@ -19,6 +22,8 @@
.popsection
.endm
+.endif
+
.section .text
.global user_atomic_cmpxchg_u32
.type user_atomic_cmpxchg_u32, @function
diff --git a/arch/kcpu/src/x86_64/copy_user.S b/arch/kcpu/src/x86_64/copy_user.S
index 6b748a75d..f85107ec5 100644
--- a/arch/kcpu/src/x86_64/copy_user.S
+++ b/arch/kcpu/src/x86_64/copy_user.S
@@ -1,3 +1,6 @@
+.ifndef _ASM_EXTABLE_DEFINED
+.equ _ASM_EXTABLE_DEFINED, 1
+
.macro _asm_extable, from, to
.pushsection __ex_table, "a"
.balign 8
@@ -6,6 +9,8 @@
.popsection
.endm
+.endif
+
.section .text
.global user_copy
user_copy:
--
Gitee
From 5e674b18fe9c97f6b496e53ba5022e5d22097786 Mon Sep 17 00:00:00 2001
From: WitcherTheWhite <1240749052@qq.com>
Date: Mon, 13 Jul 2026 10:30:07 +0800
Subject: [PATCH 4/5] update
---
arch/kcpu/src/aarch64/atomic_user.S | 27 ++++-----------
arch/kcpu/src/aarch64/copy_user.S | 45 +++++++++----------------
arch/kcpu/src/aarch64/instrs.rs | 7 ++--
arch/kcpu/src/asm/extable.inc | 15 +++++++++
arch/kcpu/src/loongarch64/atomic_user.S | 14 ++++----
arch/kcpu/src/riscv/atomic_user.S | 14 ++++----
arch/kcpu/src/x86_64/atomic_user.S | 17 ++--------
arch/kcpu/src/x86_64/copy_user.S | 13 -------
arch/kcpu/src/x86_64/instrs.rs | 7 ++--
9 files changed, 64 insertions(+), 95 deletions(-)
create mode 100644 arch/kcpu/src/asm/extable.inc
diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S
index 44796df55..59c90eb00 100644
--- a/arch/kcpu/src/aarch64/atomic_user.S
+++ b/arch/kcpu/src/aarch64/atomic_user.S
@@ -11,44 +11,31 @@
// On success, *old_out receives the value observed at *addr. If that value
// equals `old`, `new` was stored.
-.ifndef _ASM_EXTABLE_DEFINED
-.equ _ASM_EXTABLE_DEFINED, 1
-
-.macro _asm_extable, from, to
- .pushsection __ex_table, "a"
- .balign 8
- .quad \from
- .quad \to
- .popsection
-.endm
-
-.endif
-
.section .text
.global user_atomic_cmpxchg_u32
.type user_atomic_cmpxchg_u32, @function
user_atomic_cmpxchg_u32:
-.Lretry:
+.Lcmpxchg_retry:
1: ldxr w4, [x0]
// old_out is a kernel pointer; no exception table needed.
str w4, [x3]
cmp w4, w1
- b.ne .Ldone
+ b.ne .Lcmpxchg_done
2: stxr w5, w2, [x0]
- cbnz w5, .Lretry
+ cbnz w5, .Lcmpxchg_retry
dmb ish
-.Ldone:
+.Lcmpxchg_done:
clrex
mov x0, #0
ret
-.Lfault:
+.Lcmpxchg_fault:
clrex
mov x0, #1
ret
- _asm_extable 1b, .Lfault
- _asm_extable 2b, .Lfault
+ _asm_extable 1b, .Lcmpxchg_fault
+ _asm_extable 2b, .Lcmpxchg_fault
.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/aarch64/copy_user.S b/arch/kcpu/src/aarch64/copy_user.S
index 84b59edb8..99004e51a 100644
--- a/arch/kcpu/src/aarch64/copy_user.S
+++ b/arch/kcpu/src/aarch64/copy_user.S
@@ -3,19 +3,6 @@
// Arguments: x0=dst, x1=src, x2=size
// Returns: 0 on success; remaining bytes (not copied) if a data abort occurs
-.ifndef _ASM_EXTABLE_DEFINED
-.equ _ASM_EXTABLE_DEFINED, 1
-
-.macro _asm_extable, from, to
- .pushsection __ex_table, "a"
- .balign 8
- .quad \from
- .quad \to
- .popsection
-.endm
-
-.endif
-
.section .text
.global raw_copy_from_user
@@ -25,7 +12,7 @@
// 3. Bulk copy 64 bytes per iteration using 8x LDP/STP pairs
// 4. Copy remaining 8-byte chunks
// 5. Tail copy bytes
-// All load/store pairs are covered by exception table entries pointing to .Lfault
+// All load/store pairs are covered by exception table entries pointing to .Lcopy_fault
// so that on fault we compute remaining = original_end - current_dst.
raw_copy_from_user:
@@ -95,22 +82,22 @@ raw_copy_from_user:
// Fault handler: x0 currently points just past last successfully written byte.
// Remaining = dst_end - current_dst (x3 - x0)
-.Lfault:
+.Lcopy_fault:
sub x0, x3, x0
ret
// Exception table entries for every faultable memory access.
- _asm_extable 1b, .Lfault
- _asm_extable 2b, .Lfault
- _asm_extable 3b, .Lfault
- _asm_extable 4b, .Lfault
- _asm_extable 5b, .Lfault
- _asm_extable 6b, .Lfault
- _asm_extable 7b, .Lfault
- _asm_extable 8b, .Lfault
- _asm_extable 9b, .Lfault
- _asm_extable 10b, .Lfault
- _asm_extable 11b, .Lfault
- _asm_extable 12b, .Lfault
- _asm_extable 13b, .Lfault
- _asm_extable 14b, .Lfault
+ _asm_extable 1b, .Lcopy_fault
+ _asm_extable 2b, .Lcopy_fault
+ _asm_extable 3b, .Lcopy_fault
+ _asm_extable 4b, .Lcopy_fault
+ _asm_extable 5b, .Lcopy_fault
+ _asm_extable 6b, .Lcopy_fault
+ _asm_extable 7b, .Lcopy_fault
+ _asm_extable 8b, .Lcopy_fault
+ _asm_extable 9b, .Lcopy_fault
+ _asm_extable 10b, .Lcopy_fault
+ _asm_extable 11b, .Lcopy_fault
+ _asm_extable 12b, .Lcopy_fault
+ _asm_extable 13b, .Lcopy_fault
+ _asm_extable 14b, .Lcopy_fault
diff --git a/arch/kcpu/src/aarch64/instrs.rs b/arch/kcpu/src/aarch64/instrs.rs
index 165e8ec7d..bae9af526 100644
--- a/arch/kcpu/src/aarch64/instrs.rs
+++ b/arch/kcpu/src/aarch64/instrs.rs
@@ -4,8 +4,11 @@
//! Wrapper functions for assembly instructions.
-core::arch::global_asm!(include_str!("copy_user.S"));
-core::arch::global_asm!(include_str!("atomic_user.S"));
+core::arch::global_asm!(
+ include_str!("../asm/extable.inc"),
+ include_str!("copy_user.S"),
+ include_str!("atomic_user.S"),
+);
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
diff --git a/arch/kcpu/src/asm/extable.inc b/arch/kcpu/src/asm/extable.inc
new file mode 100644
index 000000000..e3965e799
--- /dev/null
+++ b/arch/kcpu/src/asm/extable.inc
@@ -0,0 +1,15 @@
+// Shared exception-table macro for user-access assembly helpers.
+// Include once per global_asm! block before any .S file that uses _asm_extable.
+
+.ifndef _ASM_EXTABLE_DEFINED
+.equ _ASM_EXTABLE_DEFINED, 1
+
+.macro _asm_extable, from, to
+ .pushsection __ex_table, "a"
+ .balign 8
+ .quad \from
+ .quad \to
+ .popsection
+.endm
+
+.endif
diff --git a/arch/kcpu/src/loongarch64/atomic_user.S b/arch/kcpu/src/loongarch64/atomic_user.S
index f89b310d2..77dd5d5ee 100644
--- a/arch/kcpu/src/loongarch64/atomic_user.S
+++ b/arch/kcpu/src/loongarch64/atomic_user.S
@@ -19,23 +19,23 @@
.type user_atomic_cmpxchg_u32, @function
user_atomic_cmpxchg_u32:
-.Lretry:
+.Lcmpxchg_retry:
1: ll.w $t0, $a0, 0
st.w $t0, $a3, 0
- bne $t0, $a1, .Ldone
+ bne $t0, $a1, .Lcmpxchg_done
move $t1, $a2
2: sc.w $t1, $a0, 0
- beqz $t1, .Lretry
+ beqz $t1, .Lcmpxchg_retry
dbar 0
-.Ldone:
+.Lcmpxchg_done:
move $a0, $zero
jr $ra
-.Lfault:
+.Lcmpxchg_fault:
ori $a0, $zero, 1
jr $ra
- _asm_extable 1b, .Lfault
- _asm_extable 2b, .Lfault
+ _asm_extable 1b, .Lcmpxchg_fault
+ _asm_extable 2b, .Lcmpxchg_fault
.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S
index efcb37d79..7e0a7a096 100644
--- a/arch/kcpu/src/riscv/atomic_user.S
+++ b/arch/kcpu/src/riscv/atomic_user.S
@@ -16,22 +16,22 @@
.type user_atomic_cmpxchg_u32, @function
user_atomic_cmpxchg_u32:
-.Lretry:
+.Lcmpxchg_retry:
1: lr.w t0, (a0)
sw t0, 0(a3)
- bne t0, a1, .Ldone
+ bne t0, a1, .Lcmpxchg_done
2: sc.w t1, a2, (a0)
- bnez t1, .Lretry
-.Ldone:
+ bnez t1, .Lcmpxchg_retry
+.Lcmpxchg_done:
fence rw, rw
li a0, 0
ret
-.Lfault:
+.Lcmpxchg_fault:
li a0, 1
ret
- _asm_extable 1b, .Lfault
- _asm_extable 2b, .Lfault
+ _asm_extable 1b, .Lcmpxchg_fault
+ _asm_extable 2b, .Lcmpxchg_fault
.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S
index f90829fde..3d61900c8 100644
--- a/arch/kcpu/src/x86_64/atomic_user.S
+++ b/arch/kcpu/src/x86_64/atomic_user.S
@@ -11,19 +11,6 @@
// On success, *old_out receives the value observed at *addr. If that value
// equals `old`, `new` was stored.
-.ifndef _ASM_EXTABLE_DEFINED
-.equ _ASM_EXTABLE_DEFINED, 1
-
-.macro _asm_extable, from, to
- .pushsection __ex_table, "a"
- .balign 8
- .quad \from
- .quad \to
- .popsection
-.endm
-
-.endif
-
.section .text
.global user_atomic_cmpxchg_u32
.type user_atomic_cmpxchg_u32, @function
@@ -36,10 +23,10 @@ user_atomic_cmpxchg_u32:
xor eax, eax
ret
-.Lfault:
+.Lcmpxchg_fault:
mov eax, 1
ret
- _asm_extable 1b, .Lfault
+ _asm_extable 1b, .Lcmpxchg_fault
.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32
diff --git a/arch/kcpu/src/x86_64/copy_user.S b/arch/kcpu/src/x86_64/copy_user.S
index f85107ec5..9df869d69 100644
--- a/arch/kcpu/src/x86_64/copy_user.S
+++ b/arch/kcpu/src/x86_64/copy_user.S
@@ -1,16 +1,3 @@
-.ifndef _ASM_EXTABLE_DEFINED
-.equ _ASM_EXTABLE_DEFINED, 1
-
-.macro _asm_extable, from, to
- .pushsection __ex_table, "a"
- .balign 8
- .quad \from
- .quad \to
- .popsection
-.endm
-
-.endif
-
.section .text
.global user_copy
user_copy:
diff --git a/arch/kcpu/src/x86_64/instrs.rs b/arch/kcpu/src/x86_64/instrs.rs
index abab00964..4917debdd 100644
--- a/arch/kcpu/src/x86_64/instrs.rs
+++ b/arch/kcpu/src/x86_64/instrs.rs
@@ -4,8 +4,11 @@
//! Wrapper functions for assembly instructions.
-core::arch::global_asm!(include_str!("copy_user.S"));
-core::arch::global_asm!(include_str!("atomic_user.S"));
+core::arch::global_asm!(
+ include_str!("../asm/extable.inc"),
+ include_str!("copy_user.S"),
+ include_str!("atomic_user.S"),
+);
unsafe extern "C" {
/// Copies data from source to destination, where addresses may be in user
--
Gitee
From ac9439d4d45d8632ab1b89c96abf46d01d87d151 Mon Sep 17 00:00:00 2001
From: WitcherTheWhite <1240749052@qq.com>
Date: Tue, 14 Jul 2026 10:27:47 +0800
Subject: [PATCH 5/5] update
---
posix/process/src/runtime.rs | 47 ++++++++++++++++++++++++++++--------
1 file changed, 37 insertions(+), 10 deletions(-)
diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs
index 05d915b6d..9c7119478 100644
--- a/posix/process/src/runtime.rs
+++ b/posix/process/src/runtime.rs
@@ -19,7 +19,7 @@ use kprocess::{
use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo};
use ktask::{TaskInner, current};
use kuaccess::{atomic_cmpxchg_u32, atomic_load_u32};
-use linux_raw_sys::general::ROBUST_LIST_LIMIT;
+use linux_raw_sys::general::{FUTEX_OWNER_DIED, FUTEX_TID_MASK, FUTEX_WAITERS, ROBUST_LIST_LIMIT};
use linux_sysno::Sysno;
use memspace::PageFaultOutcome;
use osvm::{VirtMutPtr, VirtPtr};
@@ -156,22 +156,49 @@ struct RobustListHead {
list_op_pending: *mut RobustList,
}
-/// Bits in the userspace futex word used by robust mutexes.
-const FUTEX_TID_MASK: u32 = 0x3fff_ffff;
-const FUTEX_OWNER_DIED: u32 = 0x4000_0000;
-const FUTEX_WAITERS: u32 = 0x8000_0000;
+/// Userspace robust-mutex lock word (`u32` futex value).
+///
+/// Layout matches the Linux robust-futex ABI: TID in the low bits, plus
+/// `FUTEX_OWNER_DIED` / `FUTEX_WAITERS` in the high bits.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(transparent)]
+struct RobustFutexWord(u32);
+
+impl RobustFutexWord {
+ fn from_bits(bits: u32) -> Self {
+ Self(bits)
+ }
+
+ fn bits(self) -> u32 {
+ self.0
+ }
+
+ fn tid(self) -> Tid {
+ self.0 & FUTEX_TID_MASK
+ }
+
+ fn is_owner_died(self) -> bool {
+ self.0 & FUTEX_OWNER_DIED != 0
+ }
+
+ /// Preserve `WAITERS`, clear the TID, and set `OWNER_DIED`.
+ fn with_owner_died(self) -> Self {
+ Self((self.0 & FUTEX_WAITERS) | FUTEX_OWNER_DIED)
+ }
+}
fn mark_futex_owner_died(address: usize, tid: Tid) -> KResult {
loop {
- let observed = atomic_load_u32(address).map_err(KError::from)?;
- if (observed & FUTEX_TID_MASK) != tid {
+ let observed = RobustFutexWord::from_bits(atomic_load_u32(address).map_err(KError::from)?);
+ if observed.tid() != tid {
return Ok(false);
}
- if observed & FUTEX_OWNER_DIED != 0 {
+ if observed.is_owner_died() {
return Ok(true);
}
- let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED;
- let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?;
+ let newval = observed.with_owner_died();
+ let (exchanged, _) =
+ atomic_cmpxchg_u32(address, observed.bits(), newval.bits()).map_err(KError::from)?;
if exchanged {
return Ok(true);
}
--
Gitee