Implement FreeBSD syscall cpuset_getaffinity.

This commit is contained in:
LorrensP-2158466 2025-04-22 21:43:01 +02:00
parent febe98807f
commit 5dfcb12a20
3 changed files with 117 additions and 2 deletions

View file

@ -165,8 +165,8 @@ case $HOST_TARGET in
# Partially supported targets (tier 2)
BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe
TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe affinity
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe affinity
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency epoll eventfd
TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std

View file

@ -56,6 +56,70 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(res, dest)?;
}
"cpuset_getaffinity" => {
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
let [level, which, id, set_size, mask] =
this.check_shim(abi, Conv::C, link_name, args)?;
let level = this.read_scalar(level)?.to_i32()?;
let which = this.read_scalar(which)?.to_i32()?;
let id = this.read_scalar(id)?.to_i64()?;
let set_size = this.read_target_usize(set_size)?; // measured in bytes
let mask = this.read_pointer(mask)?;
let _level_root = this.eval_libc_i32("CPU_LEVEL_ROOT");
let _level_cpuset = this.eval_libc_i32("CPU_LEVEL_CPUSET");
let level_which = this.eval_libc_i32("CPU_LEVEL_WHICH");
let _which_tid = this.eval_libc_i32("CPU_WHICH_TID");
let which_pid = this.eval_libc_i32("CPU_WHICH_PID");
let _which_jail = this.eval_libc_i32("CPU_WHICH_JAIL");
let _which_cpuset = this.eval_libc_i32("CPU_WHICH_CPUSET");
let _which_irq = this.eval_libc_i32("CPU_WHICH_IRQ");
// For sched_getaffinity, the current process is identified by -1.
// TODO: Use gettid? I'm (LorrensP-2158466) not that familiar with this api .
let id = match id {
-1 => this.active_thread(),
_ =>
throw_unsup_format!(
"`cpuset_getaffinity` is only supported with a pid of -1 (indicating the current thread)"
),
};
if this.ptr_is_null(mask)? {
this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
}
// We only support CPU_LEVEL_WHICH and CPU_WHICH_PID for now.
// This is the bare minimum to make the tests pass.
else if level != level_which || which != which_pid {
throw_unsup_format!(
"`cpuset_getaffinity` is only supported with `level` set to CPU_LEVEL_WHICH and `which` set to CPU_WHICH_PID."
);
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&id) {
// `cpusetsize` must be large enough to contain the entire CPU mask.
// FreeBSD only uses `cpusetsize` to verify that it's sufficient for the kernel's CPU mask.
// If it's too small, the syscall returns ERANGE.
// If it's large enough, copying the kernel mask to user space is safe, regardless of the actual size.
// See https://github.com/freebsd/freebsd-src/blob/909aa6781340f8c0b4ae01c6366bf1556ee2d1be/sys/kern/kern_cpuset.c#L1985
if set_size < u64::from(this.machine.num_cpus).div_ceil(8) {
this.set_last_error_and_return(LibcError("ERANGE"), dest)?;
} else {
let cpuset = cpuset.clone();
let byte_count =
Ord::min(cpuset.as_slice().len(), set_size.try_into().unwrap());
this.write_bytes_ptr(
mask,
cpuset.as_slice()[..byte_count].iter().copied(),
)?;
this.write_null(dest)?;
}
} else {
// `id` is always that of the active thread, so this is currently unreachable.
unreachable!();
}
}
// Synchronization primitives
"_umtx_op" => {
let [obj, op, val, uaddr, uaddr2] =

View file

@ -0,0 +1,51 @@
//@only-target: freebsd
//@compile-flags: -Zmiri-num-cpus=256
use std::mem;
fn getaffinity() {
let mut set: libc::cpuset_t = unsafe { mem::zeroed() };
unsafe {
if libc::cpuset_getaffinity(
libc::CPU_LEVEL_WHICH,
libc::CPU_WHICH_PID,
-1,
size_of::<libc::cpuset_t>(),
&mut set,
) == 0
{
assert!(libc::CPU_COUNT(&set) == 256);
}
}
}
fn get_small_cpu_mask() {
let mut set: libc::cpuset_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
// 256 CPUs so we need 32 bytes to represent this mask.
// According to Freebsd only when `cpusetsize` is smaller than this value, does it return with ERANGE
let err = unsafe {
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 32, &mut set)
};
assert_eq!(err, 0, "Success Expected");
// 31 is not enough, so it should fail.
let err = unsafe {
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 31, &mut set)
};
assert_eq!(err, -1, "Expected Failure");
assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
// Zero should fail as well.
let err = unsafe {
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 0, &mut set)
};
assert_eq!(err, -1, "Expected Failure");
assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
}
fn main() {
getaffinity();
get_small_cpu_mask();
}