sched_setaffinity(2)

NAZWA
sched_setaffinity, sched_getaffinity - ustawia i pobiera maskę koligacji procesora dla wątku

BIBLIOTEKA
Standardowa biblioteka C (libc, -lc)

SKŁADNIA
#define _GNU_SOURCE             /* Patrz feature_test_macros(7) */
#include <sched.h>

int sched_setaffinity(pid_t pid, size_t cpusetsize,
                      const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize,
                      cpu_set_t *mask);

OPIS
Maska koligacji procesora wątku określa zbiór procesorów, na których może on działać. W systemach wieloprocesorowych ustawiając maskę koligacji procesora można, w określonych sytuacjach, zyskać na wydajności. Na przykład przeznaczając jeden procesor na dany wątek (tj. ustawiając maskę koligacji tego wątku tak, aby określała pojedynczy procesor i ustawiając maskę koligacji wszystkich innych wątków tak, aby pomijała ten procesor) można zapewnić maksymalną szybkość wykonywania danego wątku. Ograniczenie możliwości działania wątku do pojedynczego procesora pozwala też na uniknięcie narzutu wynikającego z unieważniania bufora zachodzącego, gdy wątek przestaje być wykonywany na jednym procesorze, a następnie wznawia wykonywanie na kolejnym.

Maska koligacji procesora jest reprezentowana przez strukturę cpu_set_t, „zbiór procesorów”, na którą wskazuje mask. Zbiór makr do operowania na zbiorach procesorów opisano w podręczniku CPU_SET(3).

sched_setaffinity() ustawia maskę koligacji procesora wątku o identyfikatorze pid, na wartość określoną przez mask. Jeśli pid wynosi zero, używany jest wątek wywołujący. Argument cpusetsize jest długością (w bajtach) danych, na które wskazuje mask. Argument ten zwykle należy podać jako sizeof(cpu_set_t).

Jeśli wątek podany w pid nie działa obecnie na jednym z procesorów określonych w mask, to wątek ten zostanie zmigrowany na jeden z procesorów określonych w mask.

sched_getaffinity() zapisuje maskę koligacji procesora wątku o identyfikatorze pid do struktury cpu_set_t, na którą wskazuje mask. Argument cpusetsize określa rozmiar mask (w bajtach). Jeśli pid wynosi zero, wywołanie zwraca maskę wątku wywołującego.

WARTOŚĆ ZWRACANA
W przypadku powodzenia, sched_setaffinity() i sched_getaffinity() zwracają 0 (lecz zob. „Różnice biblioteki C/jądra” poniżej, gdzie opisano różnice w zwracanej wartości w podległym sched_getaffinity()). W razie wystąpienia błędu zwracane jest -1 i ustawiane errno, wskazując błąd.

BŁĘDY

EFAULT
    Podany adres pamięci był nieprawidłowy.

EINVAL
    Maska bitowa koligacji mask nie zawierała żadnego procesora obecnego fizycznie w systemie, który jest jednocześnie dozwolony do użycia przez wątek, biorąc pod uwagę wszelkie ograniczenia, jakie mogą wynikać z grup kontrolnych cpuset i mechanizmu „cpuset” opisanego w podręczniku cpuset(7).

EINVAL
    (sched_getaffinity() i, przed Linuksem 2.6.9, sched_setaffinity()) cpusetsize jest mniejszy od rozmiaru maski koligacji używanego przez jądro.

EPERM
    (sched_setaffinity()) Wątek wywołujący nie ma odpowiednich przywilejów. Wywołujący musi mieć efektywny identyfikator użytkownika równy rzeczywistemu identyfikatorowi użytkownika albo efektywnemu identyfikatorowi użytkownika wątku podanego w pid, albo musi posiadać przywilej CAP_SYS_NICE w przestrzeni nazw użytkownika wątku podanego w pid.

ESRCH
    Nie znaleziono wątku o identyfikatorze równym pid.

STANDARDY
Linux.

HISTORIA
Linux 2.5.8, glibc 2.3.

Początkowo, intefejsy glibc ujęte w argumencie cpusetsize były typu unsigned int. W glibc 2.3.3, usunięto argument cpusetsize, jednak przywrócono go w glibc 2.3.4, tym razem jako size_t.

UWAGI
Po wywołaniu do sched_setaffinity(), zbiór procesorów, na których wątek faktycznie będzie uruchomiony, jest iloczynem zbioru podanego w argumencie mask oraz zbioru procesorów faktycznie obecnych w systemie. System może dodatkowo ograniczyć zbiór procesorów, na których działa wątek, jeśli używany jest mechanizm „cpuset” opisany w podręczniku cpuset(7). Ograniczenia te, dotyczące faktycznego zbioru procesorów, na których wątek będzie działał, są ezgekwowane po cichu przez jądro.

Istnieje wiele sposobów na sprawdzenie liczby procesorów dostępnych w systemie, w tym: sprawdzenie zawartości /proc/cpuinfo; skorzystanie z sysconf(3) do pozyskania wartości parametrów _SC_NPROCESSORS_CONF i _SC_NPROCESSORS_ONLN oraz sprawdzenie listy katalogów procesorów w /sys/devices/system/cpu/.

Podręcznik sched(7) zawiera opis szeregowania zadań w Linuksie.

Maska koligacji procesora jest atrybutem przynależnym wątkowi, który można dostosować niezależnie dla każdego wątku w grupie wątków. Wartość zwracaną z wywołania do gettid(2) można przekazać w argumencie pid. Określenie pid jako 0 ustawi atrybut wątku wywołującego, a przekazanie wartości zwróconej z wywołania do getpid(2) ustawi atrybut głównego wątku w grupie wątków. Jeśli korzysta się z interfejsu wątków POSIX, zamiast sched_setaffinity() należy stosować pthread_setaffinity_np(3).

Za pomocą opcji rozruchowej isolcpus można wyizolować jeden lub więcej procesorów przy rozruchu, dzięki czemu żadne procesy nie zostaną na nich uruchomione. Po zastosowaniu tej opcji rozruchowej, jedynym sposobem przydzielenia wątków na wyizolowane procesory jest mechanizm cpuset(7) lub sched_setaffinity(). Więcej informacji znajduje się w pliku Documentation/admin-guide/kernel-parameters.txt w źródłach jądra. Jak wskazano w tym pliku, preferowanym sposobem izolowania procesorów jest isolcpus (w porównaniu do alternatywy, w postaci ręcznego ustawiania koligacji procesorów dla wszystkich procesów w systemie).

Potomek utworzony za pomocą fork(2) dziedziczy maskę koligacji procesora swojego rodzica. Maska koligacji jest zachowywana poprzez execve(2).

  Różnice biblioteki C/jądra
Niniejszy podręcznik opisuje interfejs glibc wywołań koligacji procesora. Rzeczywisty interfejs wywołania systemowego jest nieco odmienny, ponieważ mask jest typu unsigned long *, odzwierciedlając fakt, że podległa implementacja zbiorów procesorów jest prostą maską bitową.

W przypadku powodzenia, surowe wywołanie systemowe sched_getaffinity() zwraca liczbę umieszczonych bajtów, skopiowanych do bufora mask; będzie to mniejsza z: cpusetsize oraz rozmiaru (w bajtach) typu danych cpumask_t, który służy wewnętrznie w jądrze do reprezentacji maski bitowej zbioru procesorów.

  Obsługa systemów z dużymi maskami koligacji procesora
Podległe wywołanie systemowe (reprezentujące maski procesorów jako maski bitowe typu unsigned long *) nie nakładają ograniczeń na rozmiar masek procesorów. Jednak typ danych cpu_set_t używany przez bibliotekę glibc ma stały rozmiar 128 bajtów co oznacza, że maksymalny numer procesora jaki może odwzorować to 1023. Jeśli maska koligacji procesora jest większa niż 1024, wywołanie w postaci:

    sched_getaffinity(pid, sizeof(cpu_set_t), &mask);

zawiedzie z błędem EINVAL, błędem z poziomu podległego wywołania systemowego stosowanym w przypadku, gdy rozmiar mask podany w cpusetsize jest mniejszy od rozmiaru maski koligacji używanego przez jądro (w zależności od systemowej topologii procesorów, maska koligacji jądra może być znacznie większa od liczby aktywnych procesorów w systemie).

Przy pracy w systemach o dużych maskach koligacji procesora w jądrze, należy dynamicznie przydzielać argument mask (zob. CPU_ALLOC(3)). Obecnie, jedynym sposobem na to, jest sprawdzenie rozmiaru wymaganej maski za pomocą wywołań sched_getaffinity() o coraz większych rozmiarach masek (do momentu zwrócenia przez wywołanie błędu EINVAL).

Proszę zauważyć, że CPU_ALLOC(3) może przydzielić nieco większy zbiór procesorów niż zażądano (ponieważ zbiory procesorów zaimplementowano jako maski bitowe, korzystające z sizeof(long) jako jednostki). Zatem sched_getaffinity() może ustawiać bity poza zażądanym rozmiarem alokacji, ponieważ jądro widzi kilka dodatkowych bitów. Wywołujący powinien zatem iterować bity w zwróconym zbiorze, licząc które z nich są ustawione i zatrzymując się po osiągnięciu wartości zwróconej przez CPU_COUNT(3) (zamiast iterować po liczbie bitów, których alokacji zażądano).

PRZYKŁADY
Poniższy program tworzy proces potomny. Procesy macierzysty i potomny następnie przydzielają się na określony procesor i wykonują identyczne pętle, które zabierają jakiś czas procesora. Przed zakończeniem, rodzic czeka na ukończenie procesu potomnego. Program przyjmuje trzy argumenty wiersza poleceń: numer procesora dla rodzica, numer procesora dla potomka i liczbę iteracji pętli, jakie mają wykonać oba procesy.

Jak pokazuje niżej przykładowe wykonanie, ilość czasu rzeczywistego i czasu procesora działającego programu zależy od buforowania na poziomie wewnątrz rdzenia i od tego, czy procesy używają tego samego procesora.

Najpierw korzystamy z lscpu(1) sprawdzając, że dany system (x86) ma dwa rdzenie, każdy z dwoma procesorami:

    $ lscpu | egrep -i 'core.*:|socket';
    Wątków na rdzeń:       2
    Rdzeni na gniazdo:     2
    Gniazd:                1

Następnie mierzymy czas działania przykładowego programu dla trzech przypadków: gdy oba procesy działają na tym samym procesorze; gdy procesy działają na różnych procesorach na tym samym rdzeniu; gdy procesy działają na różnych procesorach na różnych rdzeniach.

    $ time -p ./a.out 0 0 100000000;
    real 14.75
    user 3.02
    sys 11.73
    $ time -p ./a.out 0 1 100000000;
    real 11.52
    user 3.98
    sys 19.06
    $ time -p ./a.out 0 3 100000000;
    real 7.89
    user 3.29
    sys 12.07

  Kod źródłowy programu
    #define _GNU_SOURCE
    #include <err.h>
    #include <sched.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <unistd.h>

    int
    main(int argc, char *argv[])
    {
        int           parentCPU, childCPU;
        cpu_set_t     set;
        unsigned int  nloops;

        if (argc != 4) {
            fprintf(stderr, "Użycie: %s cpu-rodzica cpu-potomka l-pętli\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }

        parentCPU = atoi(argv[1]);
        childCPU = atoi(argv[2]);
        nloops = atoi(argv[3]);

        CPU_ZERO(&set);

        switch (fork()) {
        case -1:            /* Błąd */
            err(EXIT_FAILURE, "fork");

        case 0:             /* Potomek */
            CPU_SET(childCPU, &set);

            if (sched_setaffinity(0, sizeof(set), &set) == -1)
                err(EXIT_FAILURE, "sched_setaffinity");

            for (unsigned int j = 0; j < nloops; j++)
                getppid();

            exit(EXIT_SUCCESS);

        default:            /* Rodzic */
            CPU_SET(parentCPU, &set);

            if (sched_setaffinity(0, sizeof(set), &set) == -1)
                err(EXIT_FAILURE, "sched_setaffinity");

            for (unsigned int j = 0; j < nloops; j++)
                getppid();

            wait(NULL);     /* Oczekiwanie na zakończenie potomka */
            exit(EXIT_SUCCESS);
        }
    }

ZOBACZ TAKŻE
lscpu(1), nproc(1), taskset(1), clone(2), getcpu(2), getpriority(2), gettid(2), nice(2), sched_get_priority_max(2), sched_get_priority_min(2), sched_getscheduler(2), sched_setscheduler(2), setpriority(2), CPU_SET(3), get_nprocs(3), pthread_setaffinity_np(3), sched_getcpu(3), capabilities(7), cpuset(7), sched(7), numactl(8)

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.