sched_setaffinity

SCHED_SETAFFINITY(2)     Руководство программиста Linux     SCHED_SETAFFINITY(2)



ИМЯ
       sched_setaffinity, sched_getaffinity - устанавливает и получает
       процессорную маску увязывания для нити

ОБЗОР
       #define _GNU_SOURCE             /* Смотрите 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);

ОПИСАНИЕ
       Процессорной маской увязывания нити задаётся набор процессоров, на
       которых разрешено выполняться нити. В многопроцессорных системах задание
       процессорной маски увязывания можно использовать для получения большей
       производительности. Например, выделение специального процессора
       определённой нити (т.е., задание в процессорной маске увязывания для нити
       одного ЦП и исключение этого ЦП из процессорных масок увязывания для
       остальных нитей) обеспечивает максимальную скорость выполнения этой нити.
       Ограничение для нити одним ЦП также исключает сокращение
       производительности в следствие недостоверности данных кэша, которая
       возникает, когда нить прекращает выполнение на одном ЦП и затем
       продолжает выполнение на другом.

       Маска увязывания ЦП представляется в виде структуры cpu_set_t, «набором
       процессоров», на которую указывает mask. В CPU_SET(3) описаны макросы для
       изменения набора ЦП.

       Вызов sched_setaffinity() устанавливает маску увязывания ЦП mask для
       нити, чей ID указан в pid. Если значение pid равно нулю, то используется
       вызывающая нить. В аргументе cpusetsize задаётся количество данных (в
       байтах), на которые указывает mask. Обычно его значение указывается как
       sizeof(cpu_set_t).

       Если нить, указанная в pid, в данный момент не выполняется на одном из
       ЦП, заданном в mask, то эта нить переносится на один из процессоров,
       назначаемых mask.

       Вызов sched_getaffinity() записывает в структуру cpu_set_t, на которую
       указывает mask, значение маски увязывания ЦП для нити, чей ID указан в
       pid. В аргументе cpusetsize задаётся размер mask (в байтах). Если
       значение pid равно нулю, то возвращается маска вызывающей нити.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
       При успешном выполнении sched_setaffinity() и sched_getaffinity()
       возвращают 0. В случае ошибки возвращается -1, а errno устанавливается в
       соответствующее значение.

ОШИБКИ
       EFAULT Указан некорректный адрес памяти.

       EINVAL В маске увязывания ЦП mask указаны процессоры, которых физически
              нет в системе, и которые разрешены нити согласно любым
              ограничениям, которые могут налагаться механизмом «cpuset»,
              описанном в cpuset(7).

       EINVAL (sched_getaffinity() и, в ядрах до 2.6.9, sched_setaffinity())
              Значение cpusetsize меньше размера маски увязывания, используемой
              в ядре.

       EPERM  (sched_setaffinity()) Вызывающая нить не имеет достаточно прав.
              Вызывающему требуется иметь эффективный пользовательский ID равный
              реальному пользовательскому ID или эффективному пользовательскому
              ID нити, указанной в pid, или он должен обладать мандатом
              CAP_SYS_NICE в пространстве имён пользователя нити pid.

       ESRCH  Нить с идентификатором pid не найдена.

ВЕРСИИ
       Системные вызовы увязывания ЦП появились в ядре Linux версии 2.5.8.
       Обёрточные функции появились в glibc 2.3. Первоначально, в интерфейсе
       glibc присутствовал аргумент cpusetsize, имевший тип unsigned int. В
       glibc 2.3.3 аргумент cpusetsize был удалён, но появился вновь в glibc
       2.3.4 с типом size_t.

СООТВЕТСТВИЕ СТАНДАРТАМ
       Данные системные вызовы есть только в Linux.

ЗАМЕЧАНИЯ
       После вызова sched_setaffinity() набор процессоров, на которых
       действительно будет выполняться нить, вычисляется пересечением набора из
       аргумента mask и набором процессоров, присутствующих в системе. В
       дальнейшем, система может ограничить набор процессоров нити, если
       задействован механизм «cpuset», описанный в cpuset(7). Эти ограничения на
       действительный набор процессоров, используемых для нити, без уведомления
       налагаются ядром.

       Есть несколько способов определения количества процессоров в системе: по
       содержимому /proc/cpuinfo; с помощью sysconf(3) получить значение
       параметров _SC_NPROCESSORS_CONF и _SC_NPROCESSORS_ONLN; посчитать
       количество подкаталогов cpu в /sys/devices/system/cpu/.

       В sched(7) приведено описание схемы планирования Linux.

       Маска увязывания является атрибутом нити, которая может изменяться
       независимо для каждой нити в группе нитей. В аргументе pid можно
       передавать значение, возвращаемое вызовом gettid(2). При значении pid
       равным 0 будет установлен атрибут вызывающей нити, а при передаче
       значения, возвращаемого вызовом getpid(2), устанавливается атрибут
       главной нити группы нитей (при работе с программным интерфейсом POSIX
       используйте функцию pthread_setaffinity_np(3) вместо
       sched_setaffinity()).

       Параметр начальной загрузки isolcpus можно использовать для изоляции
       одного и более ЦП во время загрузки, и ни один процесс не будет
       запланирован к выполнению на этих ЦП. После использования этого параметра
       единственный способ запланировать процессы на изолированных ЦП —
       использовать sched_setaffinity()  или механизм cpuset(7). Подробности
       смотрите в файле исходного кода ядра Documentation/kernel-parameters.txt.
       Согласно тексту файла, isolcpus является предпочтительным механизмом
       изоляции ЦП (по сравнению с ручным увязыванием ЦП всех процессов в
       системе).

       Потомок, создаваемый с помощью fork(2), наследует маску увязывания ЦП.
       Маска увязывания сохраняется при вызове execve(2).

   Отличия между библиотекой C и ядром
       В данной справочной странице описан интерфейс увязывания ЦП, используемый
       в glibc. Реальный интерфейс системных вызов чуть отличается: аргумент
       mask имеет тип unsigned long *, отражая факт того, что используемая
       реализация наборов ЦП представляет собой просто битовую маску. При
       успешном выполнении ядерный системный вызов sched_getaffinity()
       возвращает размер типа данных cpumask_t (в байтах), который используется
       в ядре для представления битовой маски процессоров.

   Работа систем с масками увязывания ЦП большого размера
       Лежащие в основе системные вызовы (которые представляют маски ЦП в виде
       маски битов с типом unsigned long *) не накладывают ограничений на размер
       маски ЦП. Однако, тип данных cpu_set_t, используемый в glibc, имеет
       постоянный размер 128 байт, то есть максимальный номер представляемых ЦП
       равен 1023. Если ядерная маска увязывания ЦП больше 1024, то вызовы вида:

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

       завершатся с ошибкой EINVAL; ошибка выдаётся подлежащим системным вызовом
       в случае, когда размер mask, указанный в cpusetsize, меньше чем размер
       маски увязывания используемой ядром (в зависимости от топологии ЦП
       системы, ядерная маска увязывания может быть значительно больше, чем
       количество активных ЦП в системе).

       При работе в системах с ядерными масками увязывания ЦП большого размера,
       место под аргумент mask должно выделяться динамически (смотрите
       CPU_ALLOC(3)). В настоящее время единственный способ сделать это —
       определить размер требуемой маски с помощью вызовов sched_getaffinity() с
       увеличиваемым размером маски (пока вызов не перестанет выдавать ошибку
       EINVAL).

       Учтите, что CPU_ALLOC(3) может выделить несколько больший набор ЦП, чем
       запрашивается (так как наборы ЦП реализованы как битовые маски,
       выделяемые в объёмах sizeof(long)). Следовательно, sched_getaffinity()
       может задать биты за границами запрашиваемого выделяемого размера, так
       как ядро видит несколько дополнительных бит. Поэтому вызывающий должен
       пройтись по всем возвращённым битам, считая установленные и остановиться
       при достижении значения, полученного от CPU_COUNT(3) (а не
       останавливаться на количестве запрошенных к выделению бит).

ПРИМЕР
       Программа, представленная ниже, создаёт дочерний процесс. Затем родитель
       и потомок назначают выполнение себя на указанных ЦП и выполняют
       одинаковые циклы, которые выполняются на ЦП какое-то время. Перед
       завершением, родитель ждёт завершения потомка. Программа имеет три
       аргумента командной строки: номер ЦП для родителя, номер ЦП для потомка и
       количество итераций цикла, который будут выполнять оба процесса.

       В примере работы, показанном ниже, количество реального времени и времени
       использованного ЦП при работе программы, будет зависеть он меж ядерного
       кэширования и будут ли процессы использовать одинаковый ЦП.

       Сначала запустим lscpu(1) для определения, что эта система (x86) имеет по
       два потока выполнения в двух ЦП:

           $ lscpu | grep -i 'core.*:|socket'
           Thread(s) per core:    2
           Core(s) per socket:    2
           Socket(s):             1

       Затем запустим подсчёт времени выполнения программы для трёх случаев: оба
       процесс выполняются на одном ЦП; оба процесса выполняются на разных ЦП
       одного ядра; оба процесса выполняются на разных ЦП разных ядер.

           $ 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

   Исходный код программы

       #define _GNU_SOURCE
       #include <sched.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <sys/wait.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;
           int nloops, j;

           if (argc != 4) {
               fprintf(stderr, "Использование: %s parent-cpu child-cpu num-loops\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:            /* Ошибка */
               errExit("fork");

           case 0:             /* потомок */
               CPU_SET(childCPU, &set);

               if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
                   errExit("sched_setaffinity");

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

               exit(EXIT_SUCCESS);

           default:            /* родитель */
               CPU_SET(parentCPU, &set);

               if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
                   errExit("sched_setaffinity");

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

               wait(NULL);     /* ждём завершения потомка */
               exit(EXIT_SUCCESS);
           }
       }

СМОТРИТЕ ТАКЖЕ
       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),
       pthread_setaffinity_np(3), sched_getcpu(3), capabilities(7), cpuset(7),
       sched(7)



Linux                              2016-03-15               SCHED_SETAFFINITY(2)