CPUSET(7)

ИМЯ
cpuset - наборы для ограничения процессов по процессору и памяти

ОПИСАНИЕ
Файловая система процессорного набора (cpuset) — это псевдо-файловый интерфейс для механизма ядра процессорного набора, который используется для управления распределением процессов по процессорам и памяти. Обычно, он монтируется в /dev/cpuset.

В системах, у которых ядра скомпилированы с поддержкой процессорного набора, все процессы прикрепляются к процессорному набору, и процессорные наборы всегда существуют. Если система поддерживает процессорные наборы, то в файле /proc/filesystems будет запись nodev cpuset. Смонтировав файловую систему процессорного набора (смотрите раздел ПРИМЕР далее), администратор может настроить процессорный набор в системе для управления размещением процессов по процессорам и памяти. По умолчанию, если настройки процессорного набора в системе не изменялись, и если файловая система процессорного набора даже не смонтирована, то механизм процессорного набора, хотя и имеется, но никак не влияет на работу системы.

В процессорном наборе задаёт список ЦП и узлов памяти.

К ЦП системы относятся все единицы обработки логики, на которых может выполняться процесс, включая, если есть, несколько ядер процессора в одном чипе и Hyper-Threads внутри ядра процессора. К узлам памяти относятся все отдельные банки основной памяти; в маленьких и SMP-системах, обычно, есть только один узел памяти, который содержит всю основную память системы, в то время как системы с NUMA (non-uniform memory access) имеют несколько узлов памяти.

Процессорные наборы представляются в иерархической псевдо-файловой системе в виде каталогов, где верхний каталог иерархии (/dev/cpuset) представляет систему полностью (все работающие ЦП и узлы памяти) и любой процессорный набор — это потомок другого родительского процессорного набора, содержащий поднабор родительских ЦП и узлов памяти. Каталоги и файлы представляют процессорные наборы с обычными правами доступа в файловой системе.

Каждый процесс в системе принадлежит только одному процессорному набору. Процесс ограничен в работе только на ЦП из процессорного набора, которому он принадлежит и для него выделяется память только из узлов памяти этого процессорного набора. Когда процесс запускает fork(2), дочерний процесс помещается в тот же процессорный набор что и родитель. Имея достаточно прав, процесс может переместиться из одного процессорного набора в другой и допустимые ЦП и узлы памяти существующего процессорного набора могут быть изменены.

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

Процессорные наборы объединены с механизмом увязывания в планировании (scheduling affinity) sched_setaffinity(2) и механизмами размещения памяти mbind(2) и set_mempolicy(2) в ядре. Ни один из этих механизмов не позволяет процессу использовать ЦП или узел памяти не разрешённый в процессорном наборе процесса. Если изменение размещения процессорного набора процесса конфликтует с этими механизмами, то учитывается размещение процессорного набора, даже если это означает замену значений других механизмов. Ядро замещает запрашиваемые ЦП и узлы памяти без уведомления других механизмов на разрешённые из процессорного набора вызвавшего процесса. Это может привести к ошибке при других вызовах, если, например, такой вызов заканчивается запросом пустого набора ЦП или узлов памяти, после чего запрос ограничивается процессорным набором вызывавшего процесса.

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

ФАЙЛЫ
Каждый каталог в /dev/cpuset представляет процессорный набор и содержит одинаковый набор псевдо-файлов, описывающий состояние этого процессорного набора.

Новые процессорные наборы создаются с помощью системного вызова mkdir(2) или команды mkdir(1). Свойства процессорного набора, такие как: флаги, разрешённые ЦП и узлы памяти, прикреплённые процессы, возвращаются и изменяются посредством чтения или записи соответствующего файла в каталоге этого процессорного набора как описано далее.

Псевдо-файлы каждого каталога процессорного набора автоматически создаются при создании процессорного набора, то есть в результате вызова mkdir(2). Нельзя напрямую добавлять или удалять эти псевдо-файлы.

Каталог процессорного набора, в котором нет каталогов дочерних процессорных наборов, и нет прикреплённых процессов, может быть удалён с помощью rmdir(2) или rmdir(1). Необязательно и невозможно удалять псевдо-файлы внутри каталога перед удалением.

Псевдо-файлы в каждом каталоге процессорного набора представляют собой маленькие текстовые файлы, которые можно читать и писать с помощью обычных утилит оболочки, таких как cat(1) и echo(1), или из программы с помощью файловых библиотечных функций ввода-вывода или системных вызовов, таких как open(2), read(2), write(2) и close(2).

Псевдо-файлы в каталоге процессорного набора описывают внутреннее состояние ядра и не хранятся на диске. Все файлы описаны ниже.

tasks
    Список ID (PID) процессов в этом процессорном наборе Список представляет собой серию десятичных цифр ASCII, разделённых символом новой строки. Процесс может быть добавлен в процессорный набор (автоматически удаляясь из предыдущего процессорного набора, в котором он находился) посредством записи его PID в файл tasks этого процессорного набора (с или без завершающего символа новой строки).

    Предупреждение: за один раз в файл tasks может быть записан только один PID. Если записывается строка с несколькими PID, то будет использован только первый.

notify_on_release
    Флаг (0 или 1). Если установлен (1), то для процессорного набора будут выполняться дополнительные действия после его освобождения, то есть после того, как все процессы перестанут его использовать (завершатся или перейдут в другой процессорный набор), и все каталоги дочерних процессорных наборов будут удалены. Смотрите раздел Уведомление об освобождении далее.

cpuset.cpus
    Список номеров физических ЦП, на которых процессам разрешено выполняться в этом процессорном наборе. О формате cpus смотрите описание в Формат списка далее.

    Разрешённые ЦП в процессорном наборе можно изменить записав новый список в его файл cpus.

cpuset.cpu_exclusive
    Флаг (0 или 1). Если установлен (1), то процессорный набор монопольно использует свои ЦП (одноуровневые (sibling) или родственные (cousin) процессорные наборы не могут иметь одинаковые ЦП). По умолчанию сброшен (0). У создаваемых процессорных наборов этот флаг также по умолчанию сброшен (0).

    Два процессорных набора являются одноуровневыми, если они имеют общий родительский процессорный набор в иерархии /dev/cpuset. Два процессорных набора являются родственными, если не один не является предком другого. Независимо от значения cpu_exclusive, если один процессорный набор — предок другого, и если оба этих процессорных набора имеют непустые cpus, то их cpus должны перекрываться, так как cpus любого процессорного набора всегда являются поднабором cpus своего родительского процессорного набора.

cpuset.mems
    Список узлов памяти, на которых процессам разрешено выделять память в этом процессорном наборе. О формате mems смотрите описание в Формат списка далее.

cpuset.mem_exclusive
    Флаг (0 или 1) Если установлен (1), то процессорный набор монопольно использует свои узлы памяти (одноуровневые или родственные процессорные наборы не могут иметь одинаковые узлы). Также, если флаг установлен (1), то процессорный набор — Hardwall (смотрите далее). По умолчанию флаг сброшен (0). У создаваемых процессорных наборов флаг также по умолчанию сброшен (0).

    Независимо от значения mem_exclusive, если один процессорный набор — предок другого, то их узлы памяти перекрываются, так как узлы памяти любого процессорного набора всегда входят в поднабор узлов памяти своего родительского процессорного набора.

cpuset.mem_hardwall (начиная с Linux 2.6.26)
    Флаг (0 или 1) Если установлен (1), то процессорный набор — Hardwall (смотрите далее). В отличии от mem_exclusive, процессорные наборы с установленным mem_hardwall (одноуровневые или родственные) не имеют ограничения по перекрытию по узлам памяти. По умолчанию флаг сброшен (0). У создаваемых процессорных наборов флаг также по умолчанию сброшен (0).

cpuset.memory_migrate (начиная с Linux 2.6.16)
    Флаг (0 или 1). Если установлен (1), то разрешён перенос (migration) памяти. По умолчанию сброшен (0). Смотрите раздел Перенос памяти далее.

cpuset.memory_pressure (начиная с Linux 2.6.16)
    Величина нагрузки памяти, которую вызывают процессы в этом процессорном наборе. Смотрите раздел Нагрузка памяти далее. Если флаг memory_pressure_enabled не установлен, то значение всегда равно нулю (0). Этот файл доступен только для чтения. Смотрите раздел ПРЕДУПРЕЖДЕНИЯ далее.

cpuset.memory_pressure_enabled (начиная с Linux 2.6.16)
    Флаг (0 или 1). Данный файл есть только в корневом процессорном наборе, обычно /dev/cpuset. Если установлен (1), то включается вычисление memory_pressure для всех процессорных наборов в системе. По умолчанию выключен (0). Смотрите раздел Нагрузка памяти далее.

cpuset.memory_spread_page (начиная с Linux 2.6.17)
    Флаг (0 или 1). Если установлен (1), то страницы в страничном кэше ядра (буферы файловой системы) будут равномерно распределены по всему процессорному набору. По умолчанию сброшен (0) в корневом процессорном наборе, и наследуется от родительского процессорного набора для новых процессорных наборов. Смотрите раздел Распространение в памяти далее.

cpuset.memory_spread_slab (начиная с Linux 2.6.17)
    Флаг (0 или 1). Если установлен (1), то кэши slab ядра для файлового ввода-вывода (структуры каталогов и inode) будут равномерно распределены по всему процессорному набору. По умолчанию сброшен (0) в корневом процессорном наборе, и наследуется от родительского процессорного набора для новых процессорных наборов. Смотрите раздел Распространение в памяти далее.

cpuset.sched_load_balance (начиная с Linux 2.6.24)
    Флаг (0 или 1). Если установлен (1), то ядро будет автоматически балансировать нагрузку процессов процессорного набора по всем разрешённым в процессорном наборе ЦП. Если сброшен (0), то ядро не будет выполнять балансировку в этом процессорном наборе, если нет какого-то другого процессорного набора с перекрывающимися ЦП и установленным флагом sched_load_balance. Подробней смотрите Балансировка нагрузки планировщиком далее.

cpuset.sched_relax_domain_level (начиная с Linux 2.6.26)
    Целое число от -1 до небольшого положительного значения (small positive value). Величина sched_relax_domain_level управляет шириной диапазона ЦП в котором планировщик ядра непосредственно выполняет балансировку работающих задач на ЦП. Если sched_load_balance выключен, то значение sched_relax_domain_level не учитывается, так как балансировка не выполняется. Если sched_load_balance включён, то чем больше величина sched_relax_domain_level, тем шире диапазон ЦП, на которых пытается выполняться балансировка. Подробней смотрите в Степень ослабления домена планировщиком далее.

Кроме показанных выше псевдо-файлов, в каждом каталоге в /dev/cpuset для каждого процесса есть псевдо-файл /proc/<pid>/cpuset, в котором содержится путь к каталогу процессорного набора процесса относительно корня файловой системы процессорного набора.

Также в файл /proc/<pid>/status для каждого процесса добавлены четыре строки, отражающие Cpus_allowed (на каких ЦП может планироваться выполнение) и Mems_allowed (на каких узлах памяти может получать память) процесса в двух форматах: в виде маски и в виде списка (смотрите далее). Пример:

    Cpus_allowed:   ffffffff,ffffffff,ffffffff,ffffffff
    Cpus_allowed_list:     0-127
    Mems_allowed:   ffffffff,ffffffff
    Mems_allowed_list:     0-63

Поля «allowed» были добавлены в Linux 2.6.24; поля «allowed_list» были добавлены в Linux 2.6.26.

ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ
Кроме контроля cpus и mems для процесса, процессорные наборы предоставляют следующие дополнительные возможности.

  Монополизирующие процессорные наборы
Если процессорный набор помечен как cpu_exclusive или mem_exclusive, то ни один другой процессорный набор, отличный от прямого предка или потомка, не может совместно использовать те же ЦП или узлы памяти.

Процессорный набор с установленным mem_exclusive ограничивает ядро при выделении страниц буферного кэша и других внутренних структур данных ядра, обычно совместно используемые ядром среди нескольких пользователей. Все процессорные наборы, с mem_exclusive или без, ограничивают выделение памяти для пользовательского адресного пространства. Это позволяет настроить систему так, что несколько независимых заданий могут совместно использовать общие данные ядра, изолируя каждое пользовательское выделение задания в его процессорном наборе. Для этого создаётся большой процессорный набор mem_exclusive для хранения всех заданий и его потомок без mem_exclusive, отдельный для каждого задания. Только маленькое количество памяти ядра, например для запросов от обработчиков прерываний, разрешено размещать вне узлов памяти даже в процессорном наборе с mem_exclusive.

  Hardwall
Процессорный набор, у которого установлен флаг mem_exclusive или mem_hardwall считается hardwall. Процессорный набор hardwall ограничивает выделение памяти ядром под страницы, буфер и другие данные, часто используемые ядром для нескольких пользователей. Все процессорные наборы, hardwall или нет, ограничивают выделение памяти для пространства пользователя.

Это позволяет настроить систему так, что несколько независимых заданий могут совместно использовать общие данные ядра, например для страниц файловой системы, изолируя каждое пользовательское выделение задания в его процессорном наборе. Для этого создаётся большой процессорный набор hardwall для хранения всех заданий и его потомок не hardwall, отдельный для каждого задания.

Только маленькое количество памяти ядра, например для запросов от обработчиков прерываний, разрешено размещать вне узлов памяти даже в процессорном наборе hardwall.

  Уведомление об освобождении
Если в процессорном наборе установлен флаг notify_on_release (1), то когда последний процесс в процессорном наборе выходит (завершается или прикрепляется к другому процессорному набору) и последний потомок этого процессорного набора удаляется, то ядро выполняет команду /sbin/cpuset_release_agent, передавая ей путь (относительно точки монтирования файловой системы процессорного набора) ликвидируемого процессорного набора. Это включает автоматическое удаление ликвидируемых процессорных наборов.

Значение по умолчанию notify_on_release в корневом процессорном наборе при запуске системы сброшено (0). Значение по умолчанию в других процессорных наборах при создании равно текущему значению notify_on_release их предка.

Вызываемой команде /sbin/cpuset_release_agent в argv[1] передаётся имя (относительно пути /dev/cpuset) освобождаемого процессорного набора.

Обычно, команда /sbin/cpuset_release_agent — простой сценарий оболочки:

    #!/bin/sh
    rmdir /dev/cpuset/$1

Для сброса и установки, как и других флагов, этот флаг можно изменить, записав в файл ASCII-номер 0 или 1 (с необязательным символом новой строки в конце), соответственно.

  Нагрузка памяти
Значение memory_pressure в процессорном наборе предоставляет индивидуальный простой средний уровень, с которым процессы в процессорном наборе пытаются освободить используемую память на узлах процессорного набора, чтобы удовлетворить дополнительные запросы памяти.

Это позволяет менеджерам заданий, отслеживающим задания, выполняемые на отдельных процессорных наборах, эффективно определять уровень нагрузки на память, которую вызывает задание.

Это полезно как в жёстко контролируемых системах, выполняющих совершенно разные поставленные задания, которые при попытке использовать больше памяти, чем разрешено на назначенных им узлам, могут выбирать — завершить задание или изменить его приоритет, так и для тесно связанных, непрерывных, научных вычислительных заданий с массовым параллелизмом, которые катастрофически теряют в производительности, если начинают использовать больше памяти, чем им позволено.

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

Если вычисление нагрузки памяти не включено в псевдо-файле /dev/cpuset/cpuset.memory_pressure_enabled, то она не вычисляется для всех процессорных наборов, и чтение из любого файла memory_pressure всегда возвращает ноль в виде строки ASCII «0\n». Смотрите раздел ПРЕДУПРЕЖДЕНИЯ далее.

Для каждого процессорного набора скользящее среднее (running average) используется по следующим причинам:

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

*   Поскольку измеряется скользящее среднее, а не накопительный счётчик, пакетный планировщик может обнаружить нагрузку памяти за одно чтение, а не читать и накапливать результаты за период времени.

*   Поскольку измерение проводится по каждому процессорному набору, а не по каждому процессу, пакетный планировщик может получить ключевую информацию — нагрузку памяти в процессорном наборе — за одну операцию чтения, а не выполняя запрос и не накапливая результаты по всему (динамически изменяющемуся) набору процессов в процессорном наборе.

Значение memory_pressure для процессорного набора вычисляется с помощью индивидуального простого цифрового фильтра, который хранится в ядре. Для каждого процессорного набора этот фильтр отслеживает недавний уровень, с которым процессы, прикреплённые к этому процессорному набору, использовали код непосредственного освобождения ядром (kernel direct reclaim code).

Код непосредственного освобождения ядром выполняется, когда процессы из-за нехватки готовых свободных страниц удовлетворяют запрос выделение страницы памяти приспособлением (repurpose) страницы, занятой под другие цели. Грязные страницы файловой системы приспосабливаются после первой записи обратно на диск. Неизменённые страницы буфера файловой системы приспосабливаются простым сбросом, и если такая страница потребуется снова, то она будет повторно прочитана с диска.

Файл cpuset.memory_pressure содержит целое число, представляющее недавний (период спада (half-life) за 10 секунд) уровень записей в код непосредственного освобождения, вызванный любым процессом в процессорном наборе, выражаемый в количестве освобождений в секунду, умноженном на 1000.

  Распространение в памяти
Для каждого процессорного набора есть два файла — логических флага, которые управляют местом, где ядро выделяет страницы для буферов файловой системы и других структур данных ядра. Они называются cpuset.memory_spread_page и cpuset.memory_spread_slab.

Если установлен индивидуальный логический флаг-файл cpuset.memory_spread_page, то ядро будет распространять буферы файловой системы (страничный кэш) последовательно по всем узлам, которые разрешено использовать процессу, а не располагать эти страницы на узле, на котором выполняется процесс.

Если установлен индивидуальный логический флаг-файл cpuset.memory_spread_slab, то ядро будет распространять slab-кэши файловой системы, например, для записей inode и каталогов, последовательно по всем узлам, которые разрешено использовать процессу, а не располагать эти страницы на узле, на котором выполняется процесс.

Установка этих флагов не влияет на страницы сегмента данных (смотрите brk(2)) или стека процесса.

По умолчанию, оба вида распространения в памяти выключены, и ядро предпочитает выделять страницы памяти на узле, на котором работает запросивший процесс. Если этот узел не разрешён политикой памяти NUMA процесса или настройкой процессорного набора или если на этом узле недостаточно свободных страниц памяти, то ядро ищет ближайший разрешённый узел с достаточным количеством свободной памяти.

При создании нового процессорного набора он наследует параметры распространения в памяти своего родителя.

Из-за настройки распространения в памяти при выделении страниц или кэшей slab происходит игнорирование политики памяти NUMA процесса и задействуется распространение. Однако, эффект этих изменений по расположению в памяти, вызванный указанным в процессорном наборе распространением в памяти скрыт, от вызовов mbind(2) или set_mempolicy(2). При работе с данными вызовами политики памяти NUMA кажется, что они всегда ведут себя как-будто распространение в памяти согласно процессорному набору не действует, даже если это не так. Если распространение в памяти согласно процессорному набору затем выключить, то автоматически повторно применится политика памяти NUMA, заданная этими вызовами последней.

Файлы cpuset.memory_spread_page и cpuset.memory_spread_slab хранят логический флаг. По умолчанию, они содержат «0»; это означает, что данное свойство в процессорном наборе выключено. Если в файл записать «1», то это включит данное свойство.

Распространение в памяти, указанное в процессорном наборе, похоже (в других контекстах) на циклический алгоритм или размещение памяти чередованием.

Распространение в памяти, указанное в процессорном наборе, может существенно повысить производительность заданий, которым:

1.  Необходимо разместить локальные данные нити в узлах памяти близко к ЦП, на которых выполняются нити, то есть наиболее наиболее востребованные данные;

2.  Необходим доступ к наборам данных больших файловых систем, которые из-за размера нужно разместить на нескольких узлах процессорного набора задания.

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

  Перенос памяти
Обычно, при настройке cpuset.memory_migrate по умолчанию (выключена) как только страница выделена (получена физическая страница в основной памяти), она остаётся на узле, на котором выделена до тех пор, пока остаётся выделенной, даже если в дальнейшем изменяется политика процессорного набора mems по размещению в памяти.

При включённом переносе памяти в процессорном наборе, если значение mems в процессорном наборе изменяется, то если страница памяти, используемая любым процессом в процессорном наборе, становится расположенной в запрещённом узле, то эта страница перемещается в разрешённый узел памяти.

Также, если процесс перемещается в процессорный набор, у которого включен memory_migrate, то все страницы памяти, которые он использует в узлах памяти предыдущего процессорного набора, но которые запрещены в новом процессорном наборе, будут перемещены на узел памяти, разрешённый в новом процессорном наборе.

Если возможно, при операции перемещения относительное размещение перемещаемых страниц в процессорном наборе сохраняется. Например, если страница была на втором разрешённом узле в предыдущем процессорном наборе, то страница будет помещена на второй допустимый узел нового процессорном наборе, если возможно.

  Балансировка нагрузки планировщиком
Планировщик ядра автоматически балансирует нагрузку, вызываемую процессами. Если один ЦП недозагружен, то ядро будет искать процессы на других, более загруженных ЦП и переместит процессы на недозагруженный ЦП, с учётом ограничений механизмов размещения в процессорных наборах и sched_setaffinity(2).

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

Индивидуальный флаг sched_load_balance предоставляет механизм для подавления этого автоматического планировщика балансировки нагрузки в случае когда он не требуется и его выключение принесло бы больше выгоды для производительности.

По умолчанию, балансировка нагрузки выполняется на всех ЦП, за исключением отмеченных как изолированные при запуске ядра с аргументом «isolcpus=» (смотрите Степень ослабления домена планировщиком далее для изменения этого значения по умолчанию).

Данное поведение балансировка нагрузки по умолчанию, выполняемое на всех ЦП, не подходит в двух следующих случаях:

*   На больших системах балансировка нагрузки на многих ЦП затратна. Если система управляется с помощью процессорных наборов для размещения независимых заданий на отдельных наборах ЦП, то полная балансировка нагрузки необязательна.

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

Когда индивидуальный флаг sched_load_balance установлен (по умолчанию), запрашивается балансировка нагрузки на всех разрешённых ЦП в этом процессорном наборе, при балансировке нагрузки может выполняться перемещение процесса (если он не привязан с помощью sched_setaffinity(2)) с любого ЦП в этом процессорном наборе на другой ЦП.

Когда индивидуальный флаг sched_load_balance сброшен, планировщик не будет балансировать нагрузку по ЦП в этом процессорном наборе, пока это не станет необходимым из-за какого-то перекрывающегося процессорного набора с включённым sched_load_balance.

Так, например, если верхний процессорный набор имеет установленный флаг sched_load_balance, то планировщик будет выполнять балансировку нагрузки на всех ЦП, и значение флага sched_load_balance других процессорных наборов не учитывается, так как уже включена полная балансировка нагрузки.

Поэтому в двух приведённых выше ситуациях флаг sched_load_balance должен быть сброшен у верхнего процессорного набора, и его нужно устанавливать только на отдельных дочерних процессорных наборах.

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

Конечно, процессы, прикреплённые к определённому ЦП, могут оставаться в процессорном наборе с выключенным sched_load_balance, поскольку такие процессы никуда не переместятся в любом случае.

  Степень ослабления домена планировщиком
Планировщик ядра безотлагательно (immediate) выполняет балансировку нагрузки как только ЦП становится свободным или ещё одна задача становится выполняемой. Такая балансировка нагрузки работает чтобы гарантировать, что все возможные ЦП заняты полезной работой по выполнению задач. Также ядро выполняет периодическую балансировку нагрузки по программным часам, описанным в time(7). Значение sched_relax_domain_level применяется только к безотлагательной балансировке нагрузки. Независимо от значения sched_relax_domain_level периодическая балансировка нагрузки пытается работать на всех ЦП (если она не выключена в sched_load_balance). В любом случае, задачи будут запланированы к выполнению только на разрешённых ЦП в их процессорных наборах, которые настраиваются с помощью системных вызовов sched_setaffinity(2).

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

Точные значения маленьких целых значений sched_relax_domain_level будет зависеть от внутренней реализации кода планировщика ядра и неоднородности архитектуры аппаратных средств. Оба из них постоянно развиваются и различаются в разных версиях ядра и архитектурах системы.

На момент написания когда это свойство появилось в Linux 2.6.26, на определённых популярных архитектурах положительные значения sched_relax_domain_level были такими:

(1) Выполнять безотлагательную балансировку нагрузки на одноуровневых потоках Hyper-Thread одного ядра.
(2) Выполнять безотлагательную балансировку нагрузки на других ядрах того же пакета.
(3) Выполнять безотлагательную балансировку нагрузки на других ЦП в том же узле или лезвии.
(4) Выполнять безотлагательную балансировку нагрузки на нескольких (зависит от реализации) узлах (в системах с NUMA).
(5) Выполнять безотлагательную балансировку нагрузки на всех ЦП системы (в системах с NUMA).

Значение sched_relax_domain_level равное нулю (0) всегда означает отключение безотлагательной балансировки нагрузки, то есть балансировка нагрузки выполняется только периодически, а не сразу после того, как ЦП становится доступным или другая задача становится выполняемой.

Значение sched_relax_domain_level равное минус одному (-1) всегда означает использование системного значения по умолчанию. Системное значение по умолчанию может различаться на разных архитектурах и версиях ядра. Его можно изменить через аргумент команды загрузки ядра «relax_domain_level=».

При наличии нескольких перекрывающихся процессорных наборов, которые имеют конфликтующие значения sched_relax_domain_level, во всех перекрывающихся процессорных наборах на всех ЦП применяется самое большое значение. В таких случаях значение минус один (-1) считается самым маленьким, заменяется любым значением, а значение ноль (0) является следующим самым маленьким значением.

ФОРМАТЫ
Для представления наборов ЦП и узлов памяти используются следующие форматы:

  В виде маски
Формат в виде маски используется для представления ЦП и узлов памяти в виде маски битов в файле /proc/<pid>/status.

Данный формат отображает каждое 32-битное слово в шестнадцатеричной форме (используя символы ASCII «0»-«9» и «a»-«f»); если нужно, слова дополняются ведущими нулями. Для масок длиннее слова между словами используется разделитель запятая. Слова отображаются в порядке от старшего к младшему, то есть первым стоит самый значимый бит. Шестнадцатеричные цифры в слове также расположены в порядке от старшего к младшему.

Минимальное количество отображаемых 32-битных слов подбирается таким образом, чтобы вместить все биты маски, то есть зависит от размера битовой маски.

Примеры формата в виде маски:

    00000001                        # установлен только бит 0
    40000000,00000000,00000000      # установлен только бит 94
    00000001,00000000,00000000      # установлен только бит 64
    000000ff,00000000               # установлены биты 32-39
    00000000,000e3862               # установлены 1,5,6,11-13,17-19

Маска с установленными битами 0, 1, 2, 4, 8, 16, 32 и 64 выглядит так:

    00000001,00000001,00010117

Первая «1» для бита 64, вторая — для бита 32, третья — для 16, четвёртая — для 8, пятая — для 4 и «7» — для битов 2, 1 и 0.

  В виде списка
Формат в виде списка — это список десятичных ASCII значений cpus и mems через запятую для перечисления номеров (и диапазонов) всех ЦП или узлов памяти.

Примеры формата в виде списка:

    0-4,9           # установлены биты 0, 1, 2, 3, 4 и 9
    0-2,7,12-14     # установлены биты 0, 1, 2, 7, 12, 13 и 14

ПРАВИЛА
К каждому процессорному набору применяются следующие правила:

*   ЦП и узлы памяти должны быть поднабором (возможно, одинаковым) своего предка.

*   Набор может быть помечен как cpu_exclusive только, если и его предок тоже помечен.

*   Набор может быть помечен как mem_exclusive только, если и его предок тоже помечен.

*   Если установлен cpu_exclusive, то ЦП набора не могут перекрываться с любым одноранговым процессорным набором.

*   Если установлен mem_exclusive, то узлы памяти набора не могут перекрываться с любым одноранговым процессорным набором.

ПРАВА ДОСТУПА
Права на процессорный набор определяются правами доступа к каталогам и псевдо-файлам в файловой системе процессорного набора, обычно смонтированной в /dev/cpuset.

Например, процесс может поместить себя в другой процессорный набор (отличный от текущего), если сможет выполнить запись в файл tasks другого процессорного набора. Для этого требуется право на выполнение для окружающих каталогов и право на запись в файл tasks.

Дополнительное ограничение устанавливается на запросы размещения другого процесса в процессорный набор. Один процесс не может прикрепить другой к процессорному набору, если не имеет право отправки этому процессу сигнала (смотрите kill(2)).

Процесс может создать потомка процессорного набора, если у него есть доступ и право на запись в родительский каталог процессорного набора. Он может изменить ЦП или узлы памяти в процессорном наборе, если у него есть доступ к каталогу этого процессорного набора (право на выполнение на каждый родительский каталог) и право на запись в соответствующий файл cpus или mems.

Есть незначительное отличие между тем, как эти права вычисляются здесь и при обычных операциях в файловой системе. Ядро интерпретирует относительные пути, начиная с текущего рабочего каталога процесса. Даже если он работает с файлом процессорного набора, относительные пути интерпретируются относительно текущего рабочего каталога процесса, а не относительно текущего процессорного набора процесса. Единственные способы, в которых могут использоваться пути процессорного набора относительно текущего процессорного набора процесса это когда текущий рабочий каталог процесса совпадает с его каталогом процессорного набора (сначала выполняется cd или chdir(2) в каталог его процессорного набора в /dev/cpuset, что немного необычно) или если некий код пользователя преобразует относительный путь процессорного набора в полный путь в файловой системе.

В теории это означает, то код пользователя должен задавать процессорные наборы с помощью абсолютных путей, для чего требуется знать точку монтирования файловой системы процессорного набора (обычно, но необязательно, /dev/cpuset). На практике, автор кода уровня пользователя делает простое предположение, что если файловая система процессорного набора монтируется, то в /dev/cpuset. Кроме этого в порядке вещей при написании корректного пользовательского кода выполняется проверка наличия псевдо-файла /dev/cpuset/tasks, чтобы убедиться, что файловая система процессорного набора смонтирована.

ПРЕДУПРЕЖДЕНИЯ

  Включение memory_pressure
По умолчанию, индивидуальный файл cpuset.memory_pressure всегда содержит ноль (0). Если это свойство не включить записью «1» в псевдо-файл /dev/cpuset/cpuset.memory_pressure_enabled, то ядро не вычисляет индивидуальное значение memory_pressure.

  Использование команды echo
При использовании команды echo в оболочке командной строки для изменения значений файлов процессорного набора остерегайтесь встроенной в некоторые оболочки команды echo; она не отображает сообщение об ошибке системного вызова write(2). Например, если команда:

    echo 19 > cpuset.mems

завершится с ошибкой из-за того, что узел памяти 19 не разрешён (возможно, текущая система не имеет узла памяти 19), то команда echo может не выдать ошибки. Для изменения настроек файла процессорного набора лучше использовать внешнюю команду /bin/echo , так как она выводит ошибки write(2), например:

    /bin/echo 19 > cpuset.mems
    /bin/echo: write error: Invalid argument

ИСКЛЮЧЕНИЯ

  Размещение в памяти
Не каждое выделение системной памяти ограничивается процессорным набором по следующим причинам:

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

Некоторые критичные для ядра запросы на выделение внутренней памяти, помеченные GFP_ATOMIC, должны быть выполнены немедленно. Ядро может отбросить какой-нибудь запрос или сломается, если одно из таких выделений завершится с ошибкой. Если такой запрос не может быть выполнен в текущем процессорном наборе процесса, то мы ослабляем требования процессорного набора, и ищем память где сможем найти. Лучше нарушить указания процессорного набора, чем испортить ядро.

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

  Переименование процессорных наборов
Для переименования процессорных наборов можно использовать системный вызов rename(2). Поддерживается только простое переименование; то есть разрешено изменение имени каталога процессорного набора — переместить каталог в другой каталог нельзя.

ОШИБКИ
Реализация процессорных наборов в ядре Linux изменяет errno для указания причины ошибки системного вызова при работе с процессорными наборами.

Возможные значения errno и их смысл при ошибках, возникающих при работе с процессорным набором:

E2BIG
    Попытка write(2) записать в специальный файл процессорного набора с длиной, превышающей установленное ядром ограничение на длину таких записей.

EACCES
    Попытка write(2) записать ID (PID) процесса в файл процессорного набора tasks при нехватке прав для перемещения этого процесса.

EACCES
    Попытка добавить, с помощью write(2), ЦП или узел памяти в процессорный набор, когда этот ЦП или узел памяти отсутствует в его родителе.

EACCES
    Попытка назначить, с помощью write(2), задать cpuset.cpu_exclusive или cpuset.mem_exclusive для процессорного набора, чей родитель не имеет таких флагов.

EACCES
    Попытка write(2) записать в файл cpuset.memory_pressure.

EACCES
    Попытка создать файл в каталоге процессорного набора.

EBUSY
    Попытка удалить, с помощь rmdir(2), процессорный набор с прикреплёнными процессами.

EBUSY
    Попытка удалить, с помощь rmdir(2), процессорный набор с дочерними процессорными наборами.

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

EEXIST
    Попытка создать, с помощью mkdir(2), процессорный набор, который уже существует.

EEXIST
    Попытка rename(2) задать процессорному набору имя, которое уже существует.

EFAULT
    Попытка read(2) прочитать или write(2) записать в файл процессорного набора используя буфер, который расположен вне доступного адресного пространства записи процесса.

EINVAL
    Попытка изменить процессорный набор с помощью write(2) способом, который нарушает флаг cpu_exclusive или mem_exclusive этого процессорного набора или его одноуровневого собрата.

EINVAL
    Попытка write(2) записать пустой список cpuset.cpus или cpuset.mems в процессорный набор, к которому прикреплёны процессы или дочерние процессорные наборы.

EINVAL
    Попытка write(2) записать список cpuset.cpus или cpuset.mems, который включает диапазон со вторым числом, меньшим первого числа.

EINVAL
    Попытка write(2) записать список cpuset.cpus или cpuset.mems, который содержит некорректный символ в строке.

EINVAL
    Попытка write(2) записать список в файл cpuset.cpus, в котором нет ни одного работающего ЦП.

EINVAL
    Попытка write(2) записать список в файл cpuset.mems, в котором нет ни одного работающего узла памяти.

EINVAL
    Попытка write(2) записать список в файл cpuset.mems, в котором есть узел без памяти.

EIO Попытка write(2) записать строку в файл tasks процессорного набора, которая не начинается с десятичного числа ASCII.

EIO Попытка rename(2) переименовать процессорный набор в другой каталог.

ENAMETOOLONG
    Попытка read(2) прочитать из файла /proc/<pid>/cpuset путь процессорного набора, который больше размера страницы ядра.

ENAMETOOLONG
    Попытка создать с помощью mkdir(2) процессорный набор, имя базового каталога которого длиннее 255 символов.

ENAMETOOLONG
    Попытка создать с помощью mkdir(2) процессорный набор, полное имя которого, включая префикс точки монтирования (обычно «/dev/cpuset/»), длиннее 4095 символов.

ENODEV
    Процессорный набор был удалён другим процессом одновременно с тем, когда была попытка write(2) записать в один из псевдо-файлов в каталоге процессорного набора.

ENOENT
    Попытка создать с помощью mkdir(2) процессорный набор, родителя которого не существует.

ENOENT
    Попытка выполнить access(2) или open(2) для несуществующего файла в каталоге процессорного набора.

ENOMEM
    В ядре недостаточно памяти; может возникать при различных системных вызовах для работы с процессорным набором, но только если в системе очень мало памяти.

ENOSPC
    Попытка write(2) записать ID (PID) процесса в файл процессорного набора tasks, когда у процессорного набора пустое значение в cpuset.cpus или cpuset.mems.

ENOSPC
    Попытка write(2) записать пустое значение в cpuset.cpus или cpuset.memsпроцессорного набора, у которого есть прикреплённые задачи.

ENOTDIR
    Попытка rename(2) переименовать несуществующий процессорный набор.

EPERM
    Попытка удалить файл из каталога процессорного набора.

ERANGE
    Указанный список cpuset.cpus или cpuset.mems в ядре, который содержит слишком большое число для ядра, чтобы задать его битовой маской.

ESRCH
    Попытка write(2) записать ID (PID) процесса несуществующего процесса в файл процессорного набора tasks.

ВЕРСИИ
Свойство процессорного набора появилось в ядре Linux версии 2.6.12.

ЗАМЕЧАНИЯ
Несмотря на имя, параметр pid в действительности является ID нити, и каждая нить группы нитей может быть прикреплена к различным процессорным наборам. В аргументе pid может передаваться значение, возвращаемое вызовом gettid(2).

ДЕФЕКТЫ
Файлы cpuset.memory_pressure процессорного набора можно открывать для записи, создания и обрезания, но после этого write(2) завершается с ошибкой errno, равной EACCES, и параметры создания и обрезания в open(2) не учитываются.

ПРИМЕР
В следующих примерах показано получения и установка параметров процессорного набора с помощью команд оболочки.

  Создание и прикрепление к процессорному набору.
Для создания нового процессорного набора и прикрепления текущей оболочки команд к нему выполняются следующие шаги:

1)  mkdir /dev/cpuset (если уже не сделано)
2)  mount -t cpuset none /dev/cpuset (если уже не сделано)
3)  Создание нового процессорного набора с помощью mkdir(1).
4)  Назначение ЦП и узлов памяти новому процессорному набору.
5)  Прикрепление оболочки к новому процессорному набору.

Например, следующая последовательность команд создаёт новый процессорный набор с именем «Charlie», содержащий только ЦП 2 и 3, и узел памяти 1, а затем прикрепляет к этому процессорному набору текущую оболочку.

    $ mkdir /dev/cpuset
    $ mount -t cpuset cpuset /dev/cpuset
    $ cd /dev/cpuset
    $ mkdir Charlie
    $ cd Charlie
    $ /bin/echo 2-3 > cpuset.cpus
    $ /bin/echo 1 > cpuset.mems
    $ /bin/echo $$ > tasks
    # Текущая оболочка теперь работает в процессорном наборе Charlie
    # Следующая строка должна вывести /Charlie
    $ cat /proc/self/cpuset

  Перенос задания в другие узлы памяти.
Чтобы перенести задание (набор процессов, присоединённых к процессорному набору) на другие ЦП и узлы памяти в системе, включая перемещение страниц памяти, выделенных под это задание, выполните следующие шаги:

1)  Предположим, мы хотим перенести задание из процессорного набора alpha (ЦП 4-–7 узлы памяти 2–3) в новый процессорный набор beta (ЦП 16-–19 и узлы памяти 8–9).
2)  Сначала создадим новый процессорный набор beta.
3)  Затем включим ЦП 16–19 и узлы памяти 8–9 в beta.
4)  Затем включим флаг memory_migration в beta.
5)  Затем переместим каждый процесс из alpha в beta.

Всё это выполняет следующая последовательность команд:

    $ cd /dev/cpuset
    $ mkdir beta
    $ cd beta
    $ /bin/echo 16-19 > cpuset.cpus
    $ /bin/echo 8-9 > cpuset.mems
    $ /bin/echo 1 > cpuset.memory_migrate
    $ while read i; do /bin/echo $i; done < ../alpha/tasks > tasks

Команды выше должны переместить все процессы из alpha в beta, и всю память этих процессов с узлов памяти 2–3 на узлы памяти 8–9, соответственно.

Заметим, что последний шаг не сделан как:

    $ cp ../alpha/tasks tasks

Цикл while, а не гораздо более простая команда cp(1), необходим, так как только один PID процесса за раз записывается в файл tasks.

Того же (запись одного PID за раз) как в цикле while можно достичь более эффективно несколькими командами и с синтаксисом, который доступен в любой оболочке, но выглядит непонятно: используя параметр -u (не буферизировать) в sed(1):

    $ sed -un p < ../alpha/tasks > tasks

СМОТРИТЕ ТАКЖЕ
taskset(1), get_mempolicy(2), getcpu(2), mbind(2), sched_getaffinity(2), sched_setaffinity(2), sched_setscheduler(2), set_mempolicy(2), CPU_SET(3), proc(5), cgroups(7), numa(7), sched(7), migratepages(8), numactl(8)

Файл Documentation/cgroup-v1/cpusets.txt из дерева исходного кода ядра Linux (или Documentation/cpusets.txt до Linux 2.6.29)