How to Use the sched_setaffinity Function in C

Jinku Hu Feb 02, 2024
  1. Use the sched_setaffinity Function to Limit Process Execution to Certain CPU(s)
  2. Use the CPU_SET Macro to Indicate the CPU Cores to Bind the Process To
How to Use the sched_setaffinity Function in C

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);
  }
}
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

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

Related Article - C Process