How to Use the sched_setaffinity Function in C
-
Use the
sched_setaffinity
Function to Limit Process Execution to Certain CPU(s) -
Use the
CPU_SET
Macro to Indicate the CPU Cores to Bind the Process To
This article will explain several methods of how to use the sched_setaffinity
function in C.
Use the sched_setaffinity
Function to Limit Process Execution to Certain CPU(s)
Nowadays, multi-core hardware is ubiquitous, and operating systems need to manage multiple processes running simultaneously on these cores. The part of the operating system that deals with managing the process/threads execution is called a scheduler. A scheduler tries to efficiently distribute all existing processes/threads across the available cores and allocate the time slices accordingly. Scheduling is one of the hardest design problems in operating systems, as it is the main performance guarantee for the given system. There’s no standard C interface to interact with the scheduler, but certain OSes provide system calls to modify several process scheduling parameters.
sched_setaffinity
is part of the GNU C library, and it’s mostly based on Linux-specific functionality. The function sets the so-called CPU affinity mask, which indicates the set of CPU cores on which the process is eligible to execute. sched_setaffinity
takes PID value as the first argument and sizeof(cpu_set_t)
as the second one. The third argument is of type cpu_set_t
and it’s an opaque structure that needs to be manipulated using the predefined macros from the <sched.h>
header. Note though, _GNU_SOURCE
macro should be defined to make these functions and macros available. In the following example, we implement a program that takes three integers from the user as command-line arguments and stores them to represent parent/child process CPU numbers and several loop iterations, respectively. Then, the CPU_ZERO
macro is used to clear the cpu_set_t
variable, and fork
is called to spawn a child process.
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char *argv[]) {
cpu_set_t set;
int parentCPU, childCPU, wstatus;
long nloops;
if (argc != 4) {
fprintf(stderr, "Usage: %s parent-cpu child-cpu num-loops\n", argv[0]);
exit(EXIT_FAILURE);
}
parentCPU = strtol(argv[1], NULL, 0);
childCPU = strtol(argv[2], NULL, 0);
nloops = strtol(argv[3], NULL, 0);
CPU_ZERO(&set);
switch (fork()) {
case -1:
errExit("fork");
case 0:
CPU_SET(childCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity");
for (int j = 0; j < nloops; j++) getpid();
exit(EXIT_SUCCESS);
default:
CPU_SET(parentCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity");
for (int j = 0; j < nloops; j++) getpid();
wait(NULL);
exit(EXIT_SUCCESS);
}
}
Use the CPU_SET
Macro to Indicate the CPU Cores to Bind the Process To
sched_setaffinity
function is called per process or a thread; thus, once the fork
returns, we can specify the different CPU masks for the parent and child processes. CPU_SET
macro is used to modify the previously zeroed out cpu_set_t
structure and consequently pass it to the sched_setaffinity
call. Note that each process executes a loop in which they call getpid
to take up CPU resources and make it easier to demonstrate the example. The parent process waits for the child with the wait
call in the previous example and using waitpid
in the next. If you want to observe the demonstrated behavior, you can watch the system processes using the htop
command line utility, widely available on Linux systems.
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char *argv[]) {
cpu_set_t set;
int parentCPU, childCPU, wstatus;
long nloops;
if (argc != 4) {
fprintf(stderr, "Usage: %s parent-cpu child-cpu num-loops\n", argv[0]);
exit(EXIT_FAILURE);
}
parentCPU = strtol(argv[1], NULL, 0);
childCPU = strtol(argv[2], NULL, 0);
nloops = strtol(argv[3], NULL, 0);
CPU_ZERO(&set);
pid_t c_pid = fork();
if (c_pid == -1) errExit("fork");
switch (c_pid) {
case -1:
errExit("fork");
case 0:
CPU_SET(childCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity");
for (int j = 0; j < nloops; j++) getpid();
exit(EXIT_SUCCESS);
default:
CPU_SET(parentCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
errExit("sched_setaffinity");
for (int j = 0; j < nloops; j++) getpid();
if (waitpid(c_pid, &wstatus, WUNTRACED | WCONTINUED) == -1)
errExit("waitpid");
exit(EXIT_SUCCESS);
}
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook