Utilizzare la funzione sched_setaffinity in C

Jinku Hu 12 ottobre 2023
  1. Usa la funzione sched_setaffinity per limitare l’esecuzione del processo a determinate CPU
  2. Usa la macro CPU_SET per indicare i core della CPU a cui associare il processo
Utilizzare la funzione sched_setaffinity in C

Questo articolo spiegherà diversi metodi su come utilizzare la funzione sched_setaffinity in C.

Usa la funzione sched_setaffinity per limitare l’esecuzione del processo a determinate CPU

Al giorno d’oggi, l’hardware multi-core è onnipresente e i sistemi operativi devono gestire più processi in esecuzione simultaneamente su questi core. La parte del sistema operativo che si occupa della gestione dell’esecuzione di processi / thread è chiamata scheduler. Uno scheduler cerca di distribuire in modo efficiente tutti i processi / thread esistenti tra i core disponibili e allocare le fasce di tempo di conseguenza. La pianificazione è uno dei problemi di progettazione più difficili nei sistemi operativi, poiché è la principale garanzia di prestazioni per un determinato sistema. Non esiste un’interfaccia C standard per interagire con lo scheduler, ma alcuni sistemi operativi forniscono chiamate di sistema per modificare diversi parametri di pianificazione del processo.

sched_setaffinity fa parte della libreria GNU C, ed è principalmente basato su funzionalità specifiche di Linux. La funzione imposta la cosiddetta CPU affinity mask, che indica l’insieme di core CPU su cui il processo può essere eseguito. sched_setaffinity prende il valore PID come primo argomento e sizeof(cpu_set_t)come secondo. Il terzo argomento è di tipo cpu_set_t ed è una struttura opaca che deve essere manipolata usando le macro predefinite dall’intestazione <sched.h>. Nota però, la macro _GNU_SOURCE dovrebbe essere definita per rendere disponibili queste funzioni e macro. Nell’esempio seguente, implementiamo un programma che prende tre numeri interi dall’utente come argomenti della riga di comando e li memorizza per rappresentare rispettivamente i numeri di CPU del processo genitore / figlio e diverse iterazioni del bucle. Quindi, la macro CPU_ZERO viene usata per cancellare la variabile cpu_set_t, e fork viene chiamato per generare un processo figlio.

#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);
  }
}

Usa la macro CPU_SET per indicare i core della CPU a cui associare il processo

La funzione sched_setaffinity viene chiamata per processo o thread; quindi, una volta che il fork ritorna, possiamo specificare le diverse maschere della CPU per i processi genitore e figlio. La macro CPU_SET è usata per modificare la struttura cpu_set_t precedentemente azzerata e di conseguenza passarla alla chiamata sched_setaffinity. Nota che ogni processo esegue un bucle in cui chiama getpid per occupare le risorse della CPU e rendere più semplice la dimostrazione dell’esempio. Il processo genitore attende il figlio con la chiamata wait nell’esempio precedente e usando waitpid nel successivo. Se vuoi osservare il comportamento dimostrato, puoi guardare i processi di sistema usando l’utility della riga di comando htop, ampiamente disponibile sui sistemi Linux.

#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);
  }
}
Autore: 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

Articolo correlato - C Process