Skip to content

Commit 15278a1

Browse files
authored
Implement rustix::process::sched_getcpu. (#921)
On some architectures, there is a `getcpu` entry in the vDSO, so use that if available, and use the syscall otherwise.
1 parent 560b2e8 commit 15278a1

File tree

10 files changed

+230
-19
lines changed

10 files changed

+230
-19
lines changed

src/backend/libc/process/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
1+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
22
pub(crate) mod cpu_set;
33
#[cfg(not(windows))]
44
pub(crate) mod syscalls;

src/backend/libc/process/syscalls.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! libc syscalls supporting `rustix::process`.
22
3-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
3+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
44
use super::types::RawCpuSet;
55
use crate::backend::c;
66
#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
@@ -72,6 +72,14 @@ use {
7272
super::super::conv::ret_owned_fd, crate::process::PidfdFlags, crate::process::PidfdGetfdFlags,
7373
};
7474

75+
#[cfg(any(linux_kernel, target_os = "dragonfly"))]
76+
#[inline]
77+
pub(crate) fn sched_getcpu() -> usize {
78+
let r = unsafe { libc::sched_getcpu() };
79+
debug_assert!(r >= 0);
80+
r as usize
81+
}
82+
7583
#[cfg(feature = "fs")]
7684
#[cfg(not(target_os = "wasi"))]
7785
pub(crate) fn chdir(path: &CStr) -> io::Result<()> {
@@ -181,7 +189,7 @@ pub(crate) fn getpgrp() -> Pid {
181189
}
182190
}
183191

184-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
192+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
185193
#[inline]
186194
pub(crate) fn sched_getaffinity(pid: Option<Pid>, cpuset: &mut RawCpuSet) -> io::Result<()> {
187195
unsafe {
@@ -193,7 +201,7 @@ pub(crate) fn sched_getaffinity(pid: Option<Pid>, cpuset: &mut RawCpuSet) -> io:
193201
}
194202
}
195203

196-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
204+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
197205
#[inline]
198206
pub(crate) fn sched_setaffinity(pid: Option<Pid>, cpuset: &RawCpuSet) -> io::Result<()> {
199207
unsafe {

src/backend/libc/process/types.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,18 @@ pub type RawCpuid = u32;
155155
#[cfg(freebsdlike)]
156156
pub type RawId = c::id_t;
157157

158-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
158+
#[cfg(any(linux_kernel, target_os = "fuchsia"))]
159159
pub(crate) type RawCpuSet = c::cpu_set_t;
160+
#[cfg(freebsdlike)]
161+
pub(crate) type RawCpuSet = c::cpuset_t;
160162

161-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
163+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
162164
#[inline]
163165
pub(crate) fn raw_cpu_set_new() -> RawCpuSet {
164166
let mut set = unsafe { core::mem::zeroed() };
165167
super::cpu_set::CPU_ZERO(&mut set);
166168
set
167169
}
168170

169-
#[cfg(any(linux_kernel, target_os = "fuchsia"))]
171+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
170172
pub(crate) const CPU_SETSIZE: usize = c::CPU_SETSIZE as usize;
171-
#[cfg(target_os = "dragonfly")]
172-
pub(crate) const CPU_SETSIZE: usize = 256;

src/backend/linux_raw/process/syscalls.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ use {crate::backend::conv::ret_c_uint_infallible, crate::fs::Mode};
3636
#[cfg(feature = "alloc")]
3737
use {crate::backend::conv::slice_just_addr_mut, crate::process::Gid};
3838

39+
// `sched_getcpu` has special optimizations via the vDSO on some architectures.
40+
#[cfg(any(
41+
target_arch = "x86_64",
42+
target_arch = "x86",
43+
target_arch = "riscv64",
44+
target_arch = "powerpc64"
45+
))]
46+
pub(crate) use crate::backend::vdso_wrappers::sched_getcpu;
47+
48+
// `sched_getcpu` on platforms without a vDSO entry for it.
49+
#[cfg(not(any(
50+
target_arch = "x86_64",
51+
target_arch = "x86",
52+
target_arch = "riscv64",
53+
target_arch = "powerpc64"
54+
)))]
55+
#[inline]
56+
pub(crate) fn sched_getcpu() -> usize {
57+
let mut cpu = MaybeUninit::<u32>::uninit();
58+
unsafe {
59+
let r = ret(syscall!(__NR_getcpu, &mut cpu, zero(), zero()));
60+
debug_assert!(r.is_ok());
61+
cpu.assume_init() as usize
62+
}
63+
}
64+
3965
#[cfg(feature = "fs")]
4066
#[inline]
4167
pub(crate) fn chdir(filename: &CStr) -> io::Result<()> {

src/backend/linux_raw/vdso_wrappers.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ use super::reg::{ArgReg, RetReg, SyscallNumber, A0, A1, A2, A3, A4, A5, R0};
1414
use super::vdso;
1515
#[cfg(target_arch = "x86")]
1616
use core::arch::global_asm;
17+
#[cfg(feature = "process")]
18+
#[cfg(any(
19+
target_arch = "x86_64",
20+
target_arch = "x86",
21+
target_arch = "riscv64",
22+
target_arch = "powerpc64"
23+
))]
24+
use core::ffi::c_void;
1725
use core::mem::transmute;
1826
use core::ptr::null_mut;
1927
use core::sync::atomic::AtomicPtr;
@@ -96,6 +104,30 @@ pub(crate) fn clock_gettime_dynamic(which_clock: DynamicClockId<'_>) -> io::Resu
96104
}
97105
}
98106

107+
#[cfg(feature = "process")]
108+
#[cfg(any(
109+
target_arch = "x86_64",
110+
target_arch = "x86",
111+
target_arch = "riscv64",
112+
target_arch = "powerpc64"
113+
))]
114+
#[inline]
115+
pub(crate) fn sched_getcpu() -> usize {
116+
// SAFETY: `GETCPU` contains either null or the address of a function with
117+
// an ABI like libc `getcpu`, and calling it has the side effect of writing
118+
// to the result buffers, and no others.
119+
unsafe {
120+
let mut cpu = MaybeUninit::<u32>::uninit();
121+
let callee = match transmute(GETCPU.load(Relaxed)) {
122+
Some(callee) => callee,
123+
None => init_getcpu(),
124+
};
125+
let r0 = callee(cpu.as_mut_ptr(), null_mut(), null_mut());
126+
debug_assert_eq!(r0, 0);
127+
cpu.assume_init() as usize
128+
}
129+
}
130+
99131
#[cfg(target_arch = "x86")]
100132
pub(super) mod x86_via_vdso {
101133
use super::{transmute, ArgReg, Relaxed, RetReg, SyscallNumber, A0, A1, A2, A3, A4, A5, R0};
@@ -223,6 +255,15 @@ pub(super) mod x86_via_vdso {
223255
#[cfg(feature = "time")]
224256
type ClockGettimeType = unsafe extern "C" fn(c::c_int, *mut Timespec) -> c::c_int;
225257

258+
#[cfg(feature = "process")]
259+
#[cfg(any(
260+
target_arch = "x86_64",
261+
target_arch = "x86",
262+
target_arch = "riscv64",
263+
target_arch = "powerpc64"
264+
))]
265+
type GetcpuType = unsafe extern "C" fn(*mut u32, *mut u32, *mut c_void) -> c::c_int;
266+
226267
/// The underlying syscall functions are only called from asm, using the
227268
/// special syscall calling convention to pass arguments and return values,
228269
/// which the signature here doesn't reflect.
@@ -239,6 +280,22 @@ fn init_clock_gettime() -> ClockGettimeType {
239280
unsafe { transmute(CLOCK_GETTIME.load(Relaxed)) }
240281
}
241282

283+
/// Initialize `GETCPU` and return its value.
284+
#[cfg(feature = "process")]
285+
#[cfg(any(
286+
target_arch = "x86_64",
287+
target_arch = "x86",
288+
target_arch = "riscv64",
289+
target_arch = "powerpc64"
290+
))]
291+
#[cold]
292+
fn init_getcpu() -> GetcpuType {
293+
init();
294+
// SAFETY: Load the function address from static storage that we just
295+
// initialized.
296+
unsafe { transmute(GETCPU.load(Relaxed)) }
297+
}
298+
242299
/// Initialize `SYSCALL` and return its value.
243300
#[cfg(target_arch = "x86")]
244301
#[cold]
@@ -254,6 +311,14 @@ fn init_syscall() -> SyscallType {
254311
struct Function;
255312
#[cfg(feature = "time")]
256313
static mut CLOCK_GETTIME: AtomicPtr<Function> = AtomicPtr::new(null_mut());
314+
#[cfg(feature = "process")]
315+
#[cfg(any(
316+
target_arch = "x86_64",
317+
target_arch = "x86",
318+
target_arch = "riscv64",
319+
target_arch = "powerpc64"
320+
))]
321+
static mut GETCPU: AtomicPtr<Function> = AtomicPtr::new(null_mut());
257322
#[cfg(target_arch = "x86")]
258323
static mut SYSCALL: AtomicPtr<Function> = AtomicPtr::new(null_mut());
259324

@@ -315,6 +380,24 @@ unsafe fn _rustix_clock_gettime_via_syscall(
315380
ret(syscall!(__NR_clock_gettime, c_int(clockid), res))
316381
}
317382

383+
#[cfg(feature = "process")]
384+
#[cfg(any(
385+
target_arch = "x86_64",
386+
target_arch = "x86",
387+
target_arch = "riscv64",
388+
target_arch = "powerpc64"
389+
))]
390+
unsafe extern "C" fn rustix_getcpu_via_syscall(
391+
cpu: *mut u32,
392+
node: *mut u32,
393+
unused: *mut c_void,
394+
) -> c::c_int {
395+
match ret(syscall!(__NR_getcpu, cpu, node, unused)) {
396+
Ok(()) => 0,
397+
Err(err) => err.raw_os_error().wrapping_neg(),
398+
}
399+
}
400+
318401
#[cfg(target_arch = "x86")]
319402
extern "C" {
320403
/// A symbol pointing to an `int 0x80` instruction. This “function” is only
@@ -361,6 +444,24 @@ fn minimal_init() {
361444
.ok();
362445
}
363446

447+
#[cfg(feature = "process")]
448+
#[cfg(any(
449+
target_arch = "x86_64",
450+
target_arch = "x86",
451+
target_arch = "riscv64",
452+
target_arch = "powerpc64"
453+
))]
454+
{
455+
GETCPU
456+
.compare_exchange(
457+
null_mut(),
458+
rustix_getcpu_via_syscall as *mut Function,
459+
Relaxed,
460+
Relaxed,
461+
)
462+
.ok();
463+
}
464+
364465
#[cfg(target_arch = "x86")]
365466
{
366467
SYSCALL
@@ -430,6 +531,60 @@ fn init() {
430531
}
431532
}
432533

534+
#[cfg(feature = "process")]
535+
#[cfg(any(
536+
target_arch = "x86_64",
537+
target_arch = "x86",
538+
target_arch = "riscv64",
539+
target_arch = "powerpc64"
540+
))]
541+
{
542+
// Look up the platform-specific `getcpu` symbol as documented
543+
// [here].
544+
//
545+
// [here]: https://man7.org/linux/man-pages/man7/vdso.7.html
546+
#[cfg(target_arch = "x86_64")]
547+
let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_getcpu"));
548+
#[cfg(target_arch = "x86")]
549+
let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_getcpu"));
550+
#[cfg(target_arch = "riscv64")]
551+
let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__kernel_getcpu"));
552+
#[cfg(target_arch = "powerpc64")]
553+
let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_getcpu"));
554+
555+
#[cfg(any(
556+
target_arch = "x86_64",
557+
target_arch = "riscv64",
558+
target_arch = "powerpc64"
559+
))]
560+
let ok = true;
561+
562+
// On 32-bit x86, the symbol doesn't appear present sometimes.
563+
#[cfg(target_arch = "x86")]
564+
let ok = !ptr.is_null();
565+
566+
#[cfg(any(
567+
target_arch = "aarch64",
568+
target_arch = "arm",
569+
target_arch = "mips",
570+
target_arch = "mips32r6",
571+
target_arch = "mips64",
572+
target_arch = "mips64r6"
573+
))]
574+
let ok = false;
575+
576+
if ok {
577+
assert!(!ptr.is_null());
578+
579+
// SAFETY: Store the computed function addresses in static
580+
// storage so that we don't need to compute it again (but if
581+
// we do, it doesn't hurt anything).
582+
unsafe {
583+
GETCPU.store(ptr.cast(), Relaxed);
584+
}
585+
}
586+
}
587+
433588
// On x86, also look up the vsyscall entry point.
434589
#[cfg(target_arch = "x86")]
435590
{

src/process/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod procctl;
3232
target_os = "wasi"
3333
)))]
3434
mod rlimit;
35-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
35+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
3636
mod sched;
3737
mod sched_yield;
3838
#[cfg(not(target_os = "wasi"))] // WASI doesn't have umask.
@@ -71,7 +71,7 @@ pub use procctl::*;
7171
target_os = "wasi"
7272
)))]
7373
pub use rlimit::*;
74-
#[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))]
74+
#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
7575
pub use sched::*;
7676
pub use sched_yield::sched_yield;
7777
#[cfg(not(target_os = "wasi"))]

src/process/sched.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,18 @@ pub fn sched_getaffinity(pid: Option<Pid>) -> io::Result<CpuSet> {
108108
let mut cpuset = CpuSet::new();
109109
backend::process::syscalls::sched_getaffinity(pid, &mut cpuset.cpu_set).and(Ok(cpuset))
110110
}
111+
112+
/// `sched_getcpu()`—Get the CPU that the current thread is currently on.
113+
///
114+
/// # References
115+
/// - [Linux]
116+
/// - [DragonFly BSD]
117+
///
118+
/// [Linux]: https://man7.org/linux/man-pages/man2/sched_getcpu.2.html
119+
/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sched_getcpu&section=2
120+
// FreeBSD added `sched_getcpu` in 13.0.
121+
#[cfg(any(linux_kernel, target_os = "dragonfly"))]
122+
#[inline]
123+
pub fn sched_getcpu() -> usize {
124+
backend::process::syscalls::sched_getcpu()
125+
}

tests/process/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ mod priority;
1919
mod procctl;
2020
#[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))]
2121
mod rlimit;
22-
mod sched_yield;
22+
mod sched;
2323
#[cfg(not(target_os = "wasi"))] // WASI doesn't have umask.
2424
mod umask;
2525
#[cfg(not(any(target_os = "espidf", target_os = "wasi")))] // WASI doesn't have waitpid.

tests/process/sched.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use rustix::process::sched_yield;
2+
3+
#[test]
4+
fn test_sched_yield() {
5+
// Just make sure we can call it.
6+
sched_yield();
7+
}
8+
9+
#[cfg(any(linux_kernel, target_os = "dragonfly"))]
10+
#[test]
11+
fn test_sched_getcpu() {
12+
let n = rustix::process::sched_getcpu();
13+
assert!(n < rustix::process::CpuSet::MAX_CPU);
14+
}

tests/process/sched_yield.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)