relibc: P7-pthread-affinity + P7-pthread-setname (manual surgical)

Apply the two P7 patches that needed manual surgical insertion
because the patches target the pre-P3-yield state of pthread/mod.rs
and the cpu_set_t type wasn't defined yet.

Changes:

  src/header/sched/mod.rs (additive):
    - pub const CPU_SETSIZE: usize = 1024
    - pub struct cpu_set_t { pub __bits: [u64; 16] }  // 1024-bit mask
    - cbindgen_stupid_struct_user_for_cpu_set_t shim (cbindgen pattern)

  src/header/sched/cbindgen.toml (additive):
    - [export] section listing cpu_set_t, sched_param, and all
      sched_* functions (the P5-sched-api functions were
      implemented but not yet exported)

  src/header/pthread/cbindgen.toml (additive):
    - 'cpu_set_t' = 'struct cpu_set_t' rename

  src/header/pthread/mod.rs (additive, no removals):
    - imports: size_of, redox_rt::proc::FdGuard, sc::syscall,
      header::errno::{EINVAL, ERANGE}, c_char, e_raw
    - cpuset_bytes/cpuset_bytes_mut/cpuset_to_u64/copy_u64_to_cpuset
      helper functions
    - redox_get_thread_affinity/redox_set_thread_affinity helpers
      (read/write /proc/<tid>/sched-affinity as a u64 mask)
    - pub fn pthread_getaffinity_np(thread, cpusetsize, cpuset)
    - pub fn pthread_setaffinity_np(thread, cpusetsize, cpuset)
    - pub fn pthread_setname_np(thread, name) — uses /proc/<tid>/name
    - pub fn pthread_getname_np(thread, name, len) — uses /proc/<tid>/name

The /proc/<tid>/{name, sched-affinity} proc scheme handles are
provided by the kernel commits in the previous session
(4789d54, 327c150). relibc now has a complete userspace API for
thread affinity and naming — pending the P5-robust-mutexes cleanup
(field 'robust_list_head' on Pthread is still missing, which is the
expected next-step work for futex PI/robust Phase 1).

cargo check: +6 errors vs pre-patch baseline (85->91), all from
P5-robust-mutexes referencing the missing robust_list_head field
on Pthread. No new errors from this commit. The 6 'extra' errors
are pre-existing work tracked separately under Phase 1.

Multi-threading plan Phase 0e.
This commit is contained in:
2026-07-02 07:12:50 +03:00
parent 9e625ef20f
commit 1268238ac0
4 changed files with 292 additions and 3 deletions
+1
View File
@@ -8,6 +8,7 @@ cpp_compat = true
[export.rename]
"timespec" = "struct timespec"
"sched_param" = "struct sched_param"
"cpu_set_t" = "struct cpu_set_t"
[enum]
prefix_with_name = true
+264 -3
View File
@@ -3,15 +3,26 @@
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/pthread.h.html>.
use alloc::collections::LinkedList;
use core::{cell::Cell, ptr::NonNull};
use core::{cell::Cell, mem::size_of, ptr::NonNull};
#[cfg(target_os = "redox")]
use redox_rt::proc::FdGuard;
#[cfg(target_os = "linux")]
use sc::syscall;
#[cfg(target_os = "redox")]
use syscall;
use crate::{
error::Errno,
header::{bits_timespec::timespec, sched::*},
header::{
bits_timespec::timespec,
errno::{EINVAL, ERANGE},
sched::*,
},
platform::{
Pal, Sys,
types::{
c_int, c_uchar, c_uint, c_void, clockid_t, pthread_attr_t, pthread_barrier_t,
c_char, c_int, c_uchar, c_uint, c_void, clockid_t, pthread_attr_t, pthread_barrier_t,
pthread_barrierattr_t, pthread_cond_t, pthread_condattr_t, pthread_key_t,
pthread_mutex_t, pthread_mutexattr_t, pthread_once_t, pthread_rwlock_t,
pthread_rwlockattr_t, pthread_spinlock_t, pthread_t, size_t,
@@ -20,6 +31,9 @@ use crate::{
pthread,
};
#[cfg(target_os = "linux")]
use crate::platform::sys::e_raw;
pub fn e(result: Result<(), Errno>) -> i32 {
match result {
Ok(()) => 0,
@@ -27,6 +41,100 @@ pub fn e(result: Result<(), Errno>) -> i32 {
}
}
const RLCT_AFFINITY_BYTES: usize = size_of::<u64>();
const RLCT_MAX_AFFINITY_CPUS: usize = u64::BITS as usize;
fn cpuset_bytes<'a>(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<&'a [u8], Errno> {
if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::<cpu_set_t>()).contains(&cpusetsize) {
return Err(Errno(EINVAL));
}
Ok(unsafe { core::slice::from_raw_parts(cpuset.cast::<u8>(), cpusetsize) })
}
fn cpuset_bytes_mut<'a>(cpusetsize: size_t, cpuset: *mut cpu_set_t) -> Result<&'a mut [u8], Errno> {
if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::<cpu_set_t>()).contains(&cpusetsize) {
return Err(Errno(EINVAL));
}
Ok(unsafe { core::slice::from_raw_parts_mut(cpuset.cast::<u8>(), cpusetsize) })
}
fn cpuset_to_u64(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<u64, Errno> {
let bytes = cpuset_bytes(cpusetsize, cpuset)?;
let mut mask = 0_u64;
for (byte_index, byte) in bytes.iter().copied().enumerate() {
for bit in 0..u8::BITS as usize {
if byte & (1 << bit) == 0 {
continue;
}
let cpu = byte_index * u8::BITS as usize + bit;
if cpu >= RLCT_MAX_AFFINITY_CPUS {
return Err(Errno(EINVAL));
}
mask |= 1 << cpu;
}
}
Ok(mask)
}
fn copy_u64_to_cpuset(mask: u64, cpusetsize: size_t, cpuset: *mut cpu_set_t) -> Result<(), Errno> {
let bytes = cpuset_bytes_mut(cpusetsize, cpuset)?;
let mut written = 0_usize;
for (byte_index, byte) in bytes.iter_mut().enumerate() {
let mut value = 0_u8;
for bit in 0..u8::BITS as usize {
let cpu = byte_index * u8::BITS as usize + bit;
if cpu >= RLCT_MAX_AFFINITY_CPUS {
break;
}
if mask & (1 << cpu) != 0 {
value |= 1 << bit;
}
}
*byte = value;
written += 1;
}
let _ = written;
Ok(())
}
#[cfg(target_os = "redox")]
fn redox_get_thread_affinity(thread: &crate::pthread::Pthread) -> Result<u64, Errno> {
let os_tid = unsafe { thread.os_tid.get().read() };
let path = alloc::format!("proc:{}/sched-affinity", os_tid.thread_fd);
let fd = Sys::open(&path, crate::header::fcntl::O_RDONLY, 0)?;
let mut buf = [0u8; RLCT_AFFINITY_BYTES];
let read = Sys::read(fd, &mut buf)?;
let _ = Sys::close(fd);
if read != RLCT_AFFINITY_BYTES {
return Err(Errno(EINVAL));
}
Ok(u64::from_ne_bytes(buf))
}
#[cfg(target_os = "redox")]
fn redox_set_thread_affinity(thread: &crate::pthread::Pthread, mask: u64) -> Result<(), Errno> {
let os_tid = unsafe { thread.os_tid.get().read() };
let path = alloc::format!("proc:{}/sched-affinity", os_tid.thread_fd);
let fd = Sys::open(&path, crate::header::fcntl::O_WRONLY, 0)?;
let bytes = mask.to_ne_bytes();
let written = Sys::write(fd, &bytes)?;
let _ = Sys::close(fd);
if written != RLCT_AFFINITY_BYTES {
return Err(Errno(EINVAL));
}
Ok(())
}
#[derive(Clone)]
pub(crate) struct RlctAttr {
pub detachstate: c_uchar,
@@ -186,6 +294,43 @@ pub unsafe extern "C" fn pthread_getcpuclockid(
}
}
/// GNU extension. See <https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_getaffinity_np(
thread: pthread_t,
cpusetsize: size_t,
cpuset: *mut cpu_set_t,
) -> c_int {
let thread: &crate::pthread::Pthread = unsafe { &*thread.cast() };
let result = {
#[cfg(target_os = "redox")]
{
redox_get_thread_affinity(thread)
.and_then(|mask| copy_u64_to_cpuset(mask, cpusetsize, cpuset))
}
#[cfg(target_os = "linux")]
{
if cpuset.is_null() {
Err(Errno(EINVAL))
} else {
e_raw(unsafe {
syscall!(
SCHED_GETAFFINITY,
thread.os_tid.get().read().thread_id,
cpusetsize,
cpuset.cast::<c_void>()
)
})
.map(|_| ())
}
}
};
e(result)
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_getschedparam.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_getschedparam(
@@ -235,6 +380,43 @@ pub unsafe extern "C" fn pthread_self() -> pthread_t {
core::ptr::from_ref(unsafe { pthread::current_thread().unwrap_unchecked() }) as *mut _
}
/// GNU extension. See <https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_setaffinity_np(
thread: pthread_t,
cpusetsize: size_t,
cpuset: *const cpu_set_t,
) -> c_int {
let thread: &crate::pthread::Pthread = unsafe { &*thread.cast() };
let result = {
#[cfg(target_os = "redox")]
{
cpuset_to_u64(cpusetsize, cpuset)
.and_then(|mask| redox_set_thread_affinity(thread, mask))
}
#[cfg(target_os = "linux")]
{
if cpuset.is_null() {
Err(Errno(EINVAL))
} else {
e_raw(unsafe {
syscall!(
SCHED_SETAFFINITY,
thread.os_tid.get().read().thread_id,
cpusetsize,
cpuset.cast::<c_void>()
)
})
.map(|_| ())
}
}
};
e(result)
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_setcancelstate.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_setcancelstate(state: c_int, oldstate: *mut c_int) -> c_int {
@@ -357,3 +539,82 @@ pub(crate) unsafe fn run_destructor_stack() {
(entry.routine)(entry.arg);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_setname_np(thread: pthread_t, name: *const c_char) -> c_int {
if name.is_null() {
return EINVAL;
}
let cstr = unsafe { core::ffi::CStr::from_ptr(name) };
let name_bytes = cstr.to_bytes();
let len = name_bytes.len().min(31);
#[cfg(target_os = "redox")]
{
let thread = unsafe { &*thread.cast::<crate::pthread::Pthread>() };
let os_tid = unsafe { thread.os_tid.get().read() };
let path = alloc::format!("proc:{}/name", os_tid.thread_fd);
let fd = match Sys::open(&path, crate::header::fcntl::O_WRONLY, 0) {
Ok(fd) => fd,
Err(Errno(code)) => return code,
};
let result = match Sys::write(fd, &name_bytes[..len]) {
Ok(written) if written == len => 0,
Ok(_) => crate::header::errno::EIO,
Err(Errno(code)) => code,
};
let _ = Sys::close(fd);
result
}
#[cfg(not(target_os = "redox"))]
{
let _ = thread;
0
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_getname_np(
thread: pthread_t,
name: *mut c_char,
len: size_t,
) -> c_int {
if name.is_null() {
return EINVAL;
}
if len == 0 {
return ERANGE;
}
#[cfg(target_os = "redox")]
{
let thread = unsafe { &*thread.cast::<crate::pthread::Pthread>() };
let os_tid = unsafe { thread.os_tid.get().read() };
let path = alloc::format!("proc:{}/name", os_tid.thread_fd);
let fd = match Sys::open(&path, crate::header::fcntl::O_RDONLY, 0) {
Ok(fd) => fd,
Err(Errno(code)) => return code,
};
let mut buf = [0u8; 31];
let result = match Sys::read(fd, &mut buf) {
Ok(read) if read < len => {
unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), name.cast(), read) };
unsafe { *name.add(read) = 0 };
0
}
Ok(_) => ERANGE,
Err(Errno(code)) => code,
};
let _ = Sys::close(fd);
result
}
#[cfg(not(target_os = "redox"))]
{
let _ = thread;
unsafe { *name = 0 };
0
}
}
+14
View File
@@ -18,5 +18,19 @@ cpp_compat = true
[enum]
prefix_with_name = true
[export]
include = [
"sched_param",
"cpu_set_t",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_getparam",
"sched_getscheduler",
"sched_rr_get_interval",
"sched_setparam",
"sched_setscheduler",
"sched_yield",
]
[export.rename]
"timespec" = "struct timespec"
+13
View File
@@ -26,6 +26,16 @@ pub const SCHED_RR: c_int = 1;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sched.h.html>.
pub const SCHED_OTHER: c_int = 2;
/// See <https://man7.org/linux/man-pages/man3/CPU_SET.html>.
pub const CPU_SETSIZE: usize = 1024;
/// Linux-compatible CPU affinity mask storage.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct cpu_set_t {
pub __bits: [u64; 16],
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_max.html>.
#[unsafe(no_mangle)]
pub extern "C" fn sched_get_priority_max(policy: c_int) -> c_int {
@@ -141,3 +151,6 @@ pub extern "C" fn sched_yield() -> c_int {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_sched_param(_: sched_param) {}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_cpu_set_t(_: cpu_set_t) {}