1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Thread affinity support for Linux.
#![cfg(target_os = "linux")]
use std::io;
use std::sync::OnceLock;
use thiserror::Error;
/// A [`libc::cpu_set_t`] sized appropriately to the number of processors on
/// this machine.
///
/// This is needed to support more than 1024 processors, since the statically
/// sized `cpu_set_t` only has room for that many processors.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CpuSet(Box<[u64]>);
impl Default for CpuSet {
fn default() -> Self {
Self::new()
}
}
impl CpuSet {
/// Allocates a new empty CPU set.
pub fn new() -> Self {
// Size the buffer according to the maximum number of processors.
let size = (max_procs() + 63) as usize / 64;
Self(vec![0; size].into())
}
/// Gets the length of the buffer in bytes, for use with syscalls.
pub fn buffer_len(&self) -> usize {
self.0.len() * 8
}
/// Gets a pointer for use with syscalls.
pub fn as_ptr(&self) -> *const libc::cpu_set_t {
self.0.as_ptr().cast()
}
/// Gets a mutable pointer for use with syscalls.
pub fn as_mut_ptr(&mut self) -> *mut libc::cpu_set_t {
self.0.as_mut_ptr().cast()
}
/// Sets processor `index` in the CPU set.
///
/// Panics if `index` is greater than or equal to [`max_procs`].
pub fn set(&mut self, index: u32) -> &mut Self {
assert!(index < max_procs());
// Can't use libc::CPU_SET because it assumes a statically sized
// cpu_set_t (which raises the question of why they bother to expose
// CPU_ALLOC_SIZE...).
self.0[index as usize / 64] |= 1 << (index % 64);
self
}
/// Sets all the CPUs in the linear bitmask `mask`, which is an ASCII
/// hexadecimal string.
///
/// This is useful for parsing the output of `/sys/devices/system/cpu/topology`.
pub fn set_mask_hex_string(&mut self, string_mask: &[u8]) -> Result<(), InvalidHexString> {
let err = || InvalidHexString(String::from_utf8_lossy(string_mask).into_owned());
if string_mask.len() % 2 != 0 {
return Err(err());
}
let mask = string_mask
.chunks_exact(2)
.map(|s| u8::from_str_radix(std::str::from_utf8(s).ok()?, 16).ok());
for (i, byte) in mask.enumerate() {
let byte = byte.ok_or_else(err)?;
if byte != 0 {
*self.0.get_mut(i / 8).ok_or_else(err)? |= (byte as u64) << (i % 8);
}
}
Ok(())
}
/// Sets all the CPUs in the list `list`, which is a comma-separated list of
/// ranges and single CPUs.
///
/// This is useful for parsing the output of `/sys/devices/system/cpu/online`.
pub fn set_mask_list(&mut self, list: &str) -> Result<(), InvalidCpuList> {
let err = || InvalidCpuList(list.to_owned());
for range in list.trim_end().split(',') {
let range = match range.split_once('-') {
Some((start, end)) => {
start.parse().map_err(|_| err())?..=end.parse().map_err(|_| err())?
}
None => {
let cpu = range.parse().map_err(|_| err())?;
cpu..=cpu
}
};
for cpu in range {
self.set(cpu);
}
}
Ok(())
}
/// Returns whether processor `index` is set.
///
/// Panics if `index` is greater than or equal to [`max_procs`].
pub fn is_set(&self, index: u32) -> bool {
assert!(index < max_procs());
self.0[index as usize / 64] & (1 << (index % 64)) != 0
}
}
#[derive(Debug, Error)]
#[error("invalid hex string for bitmask: {0}")]
pub struct InvalidHexString(String);
#[derive(Debug, Error)]
#[error("invalid CPU list: {0}")]
pub struct InvalidCpuList(String);
/// Sets the current thread's affinity.
pub fn set_current_thread_affinity(cpu_set: &CpuSet) -> io::Result<()> {
// SAFETY: calling as documented, with an appropriately-sized buffer.
let r = unsafe { libc::sched_setaffinity(0, cpu_set.buffer_len(), cpu_set.as_ptr()) };
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
/// Gets the current thread's affinity.
pub fn get_current_thread_affinity(cpu_set: &mut CpuSet) -> io::Result<()> {
// SAFETY: calling as documented, with an appropriately-sized buffer.
let r = unsafe { libc::sched_getaffinity(0, cpu_set.buffer_len(), cpu_set.as_mut_ptr()) };
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
/// Returns the number of the processor the current thread was running on during the call to this function.
pub fn get_cpu_number() -> u32 {
// SAFETY: Calling external code.
unsafe { libc::sched_getcpu() as u32 }
}
/// Returns the total number of online processors.
pub fn num_procs() -> u32 {
static NUM_PROCS: OnceLock<u32> = OnceLock::new();
*NUM_PROCS.get_or_init(|| {
// Get the number of bits in the current affinity set. This isn't
// perfect--what if we have set affinity to something else--but it's
// what `sysconf(_SC_NPROCESSORS_ONLN)` does, except that it works with
// more than 1024 processors.
//
// FUTURE: find callers of this and choose a different strategy
// accordingly.
let mut set = CpuSet::new();
get_current_thread_affinity(&mut set).unwrap();
set.0.iter().map(|x| x.count_ones()).sum()
})
}
/// Returns the maximum CPU number of any present (but not necessarily online)
/// processor.
pub fn max_present_cpu() -> io::Result<u32> {
let mut max_cpu = 0;
for entry in fs_err::read_dir("/sys/devices/system/cpu")? {
let entry = entry?;
let name = entry.file_name();
let Some(cpu) = name
.to_str()
.and_then(|s| s.strip_prefix("cpu"))
.and_then(|s| s.parse::<u32>().ok())
else {
continue;
};
max_cpu = cpu.max(max_cpu);
}
Ok(max_cpu)
}
/// Returns the kernel compiled-in maximum number of processors.
pub fn max_procs() -> u32 {
static MAX_PROCS: OnceLock<u32> = OnceLock::new();
*MAX_PROCS.get_or_init(|| {
let max_cpu_index: u32 = std::fs::read_to_string("/sys/devices/system/cpu/kernel_max")
.expect("failed to read kernel_max")
.trim_end()
.parse()
.expect("failed to parse kernel_max");
max_cpu_index + 1
})
}
#[cfg(test)]
mod tests {
use super::max_procs;
#[test]
fn test_max_procs() {
let p = max_procs();
assert!(p > 0 && p < 32768);
}
#[test]
fn test_cpu_list() {
let mut set = super::CpuSet::new();
set.set_mask_list("0-3,5").unwrap();
assert_eq!(set.is_set(0), true);
assert_eq!(set.is_set(1), true);
assert_eq!(set.is_set(2), true);
assert_eq!(set.is_set(3), true);
assert_eq!(set.is_set(4), false);
assert_eq!(set.is_set(5), true);
assert_eq!(set.is_set(6), false);
}
}