sched_getaffinity - устанавливает и получает процессорную
sched_getaffinity(2)
устанавливает и получает процессорную
Other Alias
sched_setaffinity
ОБЗОР
#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.
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 должно выделяться динамически. В настоящее время
единственный способ сделать это — определить размер требуемой маски с
помощью вызовов sched_getaffinity() с увеличиваемым размером маски (пока
вызов не перестанет выдавать ошибку EINVAL).
ПРИМЕР
Программа, представленная ниже, создаёт дочерний процесс. Затем родитель и
потомок назначают выполнение себя на указанных ЦП и выполняют одинаковые
циклы, которые выполняются на ЦП какое-то время. Перед завершением, родитель
ждёт завершения потомка. Программа имеет три аргумента командной строки:
номер ЦП для родителя, номер ЦП для потомка и количество итераций цикла,
который будут выполнять оба процесса.
В примере работы, показанном ниже, количество реального времени и времени
использованного ЦП при работе программы, будет зависеть он меж ядерного
кэширования и будут ли процессы использовать одинаковый ЦП.
Сначала запустим 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);
}
}