Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sched::sched_getaffinity() #1148

Merged
merged 1 commit into from Nov 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -30,6 +30,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `linkat`
([#1101](https://github.com/nix-rust/nix/pull/1101))

- Added `sched_getaffinity`.
([#1148](https://github.com/nix-rust/nix/pull/1148))

### Changed
- `sys::socket::recvfrom` now returns
`Result<(usize, Option<SockAddr>)>` instead of `Result<(usize, SockAddr)>`.
Expand Down
79 changes: 76 additions & 3 deletions src/sched.rs
Expand Up @@ -46,44 +46,82 @@ mod sched_linux_like {

pub type CloneCb<'a> = Box<dyn FnMut() -> isize + 'a>;

/// CpuSet represent a bit-mask of CPUs.
/// CpuSets are used by sched_setaffinity and
/// sched_getaffinity for example.
///
/// This is a wrapper around `libc::cpu_set_t`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CpuSet {
cpu_set: libc::cpu_set_t,
}

impl CpuSet {
/// Create a new and empty CpuSet.
pub fn new() -> CpuSet {
CpuSet {
cpu_set: unsafe { mem::zeroed() },
}
}

/// Test to see if a CPU is in the CpuSet.
/// `field` is the CPU id to test
pub fn is_set(&self, field: usize) -> Result<bool> {
if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
if field >= CpuSet::count() {
Err(Error::Sys(Errno::EINVAL))
} else {
Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) })
}
}

/// Add a CPU to CpuSet.
/// `field` is the CPU id to add
pub fn set(&mut self, field: usize) -> Result<()> {
if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
if field >= CpuSet::count() {
Err(Error::Sys(Errno::EINVAL))
} else {
Ok(unsafe { libc::CPU_SET(field, &mut self.cpu_set) })
}
}

/// Remove a CPU from CpuSet.
/// `field` is the CPU id to remove
pub fn unset(&mut self, field: usize) -> Result<()> {
if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
if field >= CpuSet::count() {
Err(Error::Sys(Errno::EINVAL))
} else {
Ok(unsafe { libc::CPU_CLR(field, &mut self.cpu_set) })
}
}

/// Return the maximum number of CPU in CpuSet
pub fn count() -> usize {
8 * mem::size_of::<libc::cpu_set_t>()
}
}

/// `sched_setaffinity` set a thread's CPU affinity mask
/// ([`sched_setaffinity(2)`](http://man7.org/linux/man-pages/man2/sched_setaffinity.2.html))
///
/// `pid` is the thread ID to update.
/// If pid is zero, then the calling thread is updated.
///
/// The `cpuset` argument specifies the set of CPUs on which the thread
/// will be eligible to run.
///
/// # Example
///
/// Binding the current thread to CPU 0 can be done as follows:
///
/// ```rust,no_run
/// use nix::sched::{CpuSet, sched_setaffinity};
/// use nix::unistd::Pid;
///
/// let mut cpu_set = CpuSet::new();
/// cpu_set.set(0);
/// sched_setaffinity(Pid::from_raw(0), &cpu_set);
/// ```
pub fn sched_setaffinity(pid: Pid, cpuset: &CpuSet) -> Result<()> {
let res = unsafe {
libc::sched_setaffinity(
Expand All @@ -96,6 +134,41 @@ mod sched_linux_like {
Errno::result(res).map(drop)
}

/// `sched_getaffinity` get a thread's CPU affinity mask
/// ([`sched_getaffinity(2)`](http://man7.org/linux/man-pages/man2/sched_getaffinity.2.html))
///
/// `pid` is the thread ID to check.
/// If pid is zero, then the calling thread is checked.
///
/// Returned `cpuset` is the set of CPUs on which the thread
/// is eligible to run.
///
/// # Example
///
/// Checking if the current thread can run on CPU 0 can be done as follows:
///
/// ```rust,no_run
/// use nix::sched::sched_getaffinity;
/// use nix::unistd::Pid;
///
/// let cpu_set = sched_getaffinity(Pid::from_raw(0)).unwrap();
/// if cpu_set.is_set(0).unwrap() {
/// println!("Current thread can run on CPU 0");
/// }
/// ```
pub fn sched_getaffinity(pid: Pid) -> Result<CpuSet> {
let mut cpuset = CpuSet::new();
let res = unsafe {
libc::sched_getaffinity(
pid.into(),
mem::size_of::<CpuSet>() as libc::size_t,
&mut cpuset.cpu_set,
)
};

Errno::result(res).and(Ok(cpuset))
}

pub fn clone(
mut cb: CloneCb,
stack: &mut [u8],
Expand Down
3 changes: 3 additions & 0 deletions test/test.rs
Expand Up @@ -118,6 +118,9 @@ mod test_net;
mod test_nix_path;
mod test_poll;
mod test_pty;
#[cfg(any(target_os = "android",
target_os = "linux"))]
mod test_sched;
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "ios",
Expand Down
32 changes: 32 additions & 0 deletions test/test_sched.rs
@@ -0,0 +1,32 @@
use nix::sched::{sched_getaffinity, sched_setaffinity, CpuSet};
use nix::unistd::Pid;

#[test]
fn test_sched_affinity() {
// If pid is zero, then the mask of the calling process is returned.
let initial_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap();
let mut at_least_one_cpu = false;
let mut last_valid_cpu = 0;
for field in 0..CpuSet::count() {
if initial_affinity.is_set(field).unwrap() {
at_least_one_cpu = true;
last_valid_cpu = field;
}
}
assert!(at_least_one_cpu);

// Now restrict the running CPU
let mut new_affinity = CpuSet::new();
new_affinity.set(last_valid_cpu).unwrap();
sched_setaffinity(Pid::from_raw(0), &new_affinity).unwrap();

// And now re-check the affinity which should be only the one we set.
let updated_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap();
for field in 0..CpuSet::count() {
// Should be set only for the CPU we set previously
assert_eq!(updated_affinity.is_set(field).unwrap(), field==last_valid_cpu)
}

// Finally, reset the initial CPU set
sched_setaffinity(Pid::from_raw(0), &initial_affinity).unwrap();
}