cpuset

title: cpuset(7)

text:
# 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)


credentials

package: man-pages-ru

title: credentials(7)

summary: идентификаторы процесса

text:
# credentials(7)

## ИМЯ
credentials - идентификаторы процесса

## ОПИСАНИЕ

### ID (PID) процесса
Каждый процесс имеет уникальный неотрицательный целочисленный идентификатор (PID), который ему назначается при создании с помощью fork(2). Процесс может узнать свой PID с помощью вызова getpid(2). PID имеет тип pid_t (определён в <sys/types.h>).

PID используется в различных системных вызовах для указания процесса, с которым работает вызов, например: kill(2), ptrace(2), setpriority(2) setpgid(2), setsid(2), sigqueue(3) и waitpid(2).

PID процесса сохраняется после execve(2).

### Родительский ID (PPID) процесса
ID родительского процесса — это ID процесса, который создал данный процесс с помощью fork(2). Процесс может получить свой PPID с помощью getppid(2). PPID имеет тип pid_t.

PPID процесса сохраняется после execve(2).

### ID группы процессов и сеанса
У каждого процесса есть ID сеанса и ID группы процессов; они имеют тип pid_t. Процесс может получить ID своего сеанса с помощью getsid(2), а ID своей группы процессов с помощью getpgrp(2).

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

Сеансы и группы процессов — это абстракции, предназначенные для поддержки управления заданиями оболочки. Группа процессов (иногда называемая «заданием» (job)) — это набор процессов, у которых одинаковый ID группы процессов; оболочка создаёт новую группу процессов для процессов, используемых в одной команде или конвейере (например, два процесса, созданные командой «ls | wc», помещаются в одну группу процессов). Членство в группе процессов может быть определено с помощью setpgid(2). Процесс, чей ID процесса совпадает с его ID группы процессов, называется лидером группы процессов этой группы.

Сеанс — это набор процессов, у которых одинаковый ID сеанса. Все члены группы процессов также имеют одинаковый ID сеанса (т. е., все члены группы процессов всегда принадлежат одному сеансу; таким образом, сеансы и группы процессов формируют из процессов жёсткую двухуровневую иерархию). Новый сеанс создаётся при вызове процессом setsid(2), который создаёт новый сеанс, чей ID сеанса совпадает с PID процесса, который вызвал setsid(2). Создатель сеанса также называется лидером сеанса.

Все процессы в сеансе используют общий управляющий терминал. Управляющий терминал назначается в момент, когда лидер сеанса впервые открывает терминал (если при вызове open(2) не указан флаг O_NOCTTY). Терминал может быть управляющим терминалом не более чем для одного сеанса.

В сеансе может быть только одно активное задание (foreground job); все остальные задания в сеансе считаются фоновыми заданиями (background jobs). Только активное задание может читать данные из терминала; когда процесс в фоне пытается прочитать данные с терминала, его группе процессов посылается сигнал SIGTTIN, который приостанавливает (suspends) задание. Если у терминала установлен флаг TOSTOP (смотрите termios(3)), то только активное задание может писать в терминал; попытка записи из фонового задания приводит к генерации сигнала SIGTTOU, который приостанавливает задание. Если нажимаются клавиши терминала, которые генерируют сигнал (например клавиша interrupt, обычно это комбинация control-C), то сигнал посылается процессам в активном задании.

С членами группы процессов могут работать различные системные вызовы и библиотечные функции, такие как: kill(2), killpg(3), getpriority(2), setpriority(2), ioprio_get(2), ioprio_set(2), waitid(2) и waitpid(2). Также смотрите описание операций F_GETOWN, F_GETOWN_EX, F_SETOWN и F_SETOWN_EX в fcntl(2).

### Идентификаторы пользователя и группы
С каждым процессом связаны идентификатор пользователя и различных групп. Эти идентификаторы представляются в виде целых чисел с типами gid_t и uid_t, соответственно (определены в <sys/types.h>).

В Linux каждый процесс имеет следующие идентификаторы пользователя и групп:

- Реальный ID пользователя (real user) и реальный ID группы. Эти ID определяют кто владелец процесса. Реальный ID пользователя (группы) процесса можно получить с помощью getuid(2) (getgid(2)).

- Эффективный ID пользователя (effective user) и эффективный ID группы. Эти ID используются ядром для определения прав, которые будет иметь процесс при доступе к общим ресурсам, таким как очереди сообщений, общая память и семафоры. В большинстве систем UNIX эти ID также определяют права доступа к файлам. Однако в Linux для этой задачи используются ID файловой системы, описанные далее. Эффективный ID пользователя (группы) процесса можно получить с помощью geteuid(2) (getegid(2)).

- Сохранённые set-user-ID и set-group-ID. Эти ID используются в программах с set-user-ID и set-group-ID битами для сохранения копии соответствующих эффективных ID, которые были установлены в момент запуска программы (смотрите execve(2)). Программа с set-user-ID может повышать и понижать права, переключая свой ID эффективного пользователя туда и обратно между значениями её ID реального пользователя и сохранённым set-user-ID. Такое переключение производится с помощью вызовов seteuid(2), setreuid(2) или setresuid(2). Программа с set-group-ID выполняет аналогичные задачи с помощью setegid(2), setregid(2) или setresgid(2). Сохранённый set-user-ID (set-group-ID) процесса можно получить с помощью getresuid(2) (getresgid(2)).

- ID пользователя файловой системы и ID группы файловой системы (есть только в Linux). Эти ID, совместно с ID дополнительных групп (supplementary group), описанных ниже, используются для определения прав доступа к файлам; подробней смотрите path_resolution(7). Каждый раз при изменении ID эффективного пользователя (группы) ядро также автоматически изменяет ID пользователя (группы) файловой системы на то же значение. Следовательно, ID файловой системы, обычно, равны соответствующим эффективным ID, а семантика проверки прав доступа к файлам в Linux такая же как и у других систем UNIX. ID файловой системы можно сделать отличным от эффективных ID с помощью вызова setfsuid(2) и setfsgid(2).

- ID дополнительных групп (supplementary group). Определяет ID добавочных групп, которые используются при проверке доступа к файлам и другим общим ресурсам. В ядрах Linux до версии 2.6.4 процесс может быть членом 32 дополнительных групп; начиная с версии 2.6.4 процесс может быть членом 65536 дополнительных групп. В помощью вызова sysconf(_SC_NGROUPS_MAX) можно узнать количество дополнительных групп, в которых процесс может быть членом. Процесс может получить список ID дополнительных групп с помощь getgroups(2), и изменить этот список с помощью setgroups(2).

Дочерний процесс, созданный fork(2), наследует копии ID пользователя и группы своего предка. При execve(2) сохраняются ID реального пользователя и группы процесса, а также ID дополнительных групп; эффективный и сохранённый ID могут измениться (описано в execve(2)).

Кроме целей, отмеченных выше, идентификаторы пользователя процесса также используются:

- при определении права на отправку сигналов (смотрите kill(2));

- при определении права на установку параметров планировщика процесса (значение уступчивости, политика и приоритет планирования в реальном времени, увязывание ЦП, приоритет ввода-вывода) с помощью setpriority(2), sched_setaffinity(2), sched_setscheduler(2), sched_setparam(2), sched_setattr(2) и ioprio_set(2);

- при проверке ограничения по ресурсам (смотрите getrlimit(2));

- при проверке ограничения на количество экземпляров inotify, которые процесс может создать (смотрите inotify(7)).

## СООТВЕТСТВИЕ СТАНДАРТАМ
ID процесса, ID родительского процесса, ID группы процессов и ID сеанса определены в POSIX.1. ID реального, эффективного и сохранённого пользователя и группы, а также ID дополнительных групп определены в POSIX.1. ID пользователя и группы файловой системы являются расширением Linux.

## ЗАМЕЧАНИЯ
Различные поля в файле /proc/[pid]/status отражают идентификаторы процесса, описанные выше. Подробности смотрите в proc(5).

Согласно спецификации на нити POSIX требуется, чтобы идентификаторы были одинаковыми у всех нитей процесса. Однако на уровне ядра в Linux ведутся отдельные идентификаторы пользователя и группы для каждой нити. Реализация нитей NPTL выполняет дополнительные действия, чтобы любое изменение идентификаторов пользователя и группы (например, с помощью setuid(2), setresuid(2)) отражалось на всех нитях POSIX процесса. Дополнительную информацию смотрите в nptl(7).

## СМОТРИТЕ ТАКЖЕ
bash(1), csh(1), groups(1), id(1), newgrp(1), ps(1), runuser(1), setpriv(1), sg(1), su(1), access(2), execve(2), faccessat(2), fork(2), getgroups(2), getpgrp(2), getpid(2), getppid(2), getsid(2), kill(2), setegid(2), seteuid(2), setfsgid(2), setfsuid(2), setgid(2), setgroups(2), setpgid(2), setresgid(2), setresuid(2), setsid(2), setuid(2), waitpid(2), euidaccess(3), initgroups(3), killpg(3), tcgetpgrp(3), tcgetsid(3), tcsetpgrp(3), group(5), passwd(5), shadow(5), capabilities(7), namespaces(7), path_resolution(7), pid_namespaces(7), pthreads(7), signal(7), unix(7), user_namespaces(7), sudo(8)


ddp

title: ddp(7)

summary: реализация протокола AppleTalk в Linux

text:
# ddp(7)

## ИМЯ
ddp - реализация протокола AppleTalk в Linux

## ОБЗОР
\#include <sys/socket.h>

\#include <netatalk/at.h>

ddp_socket = socket(AF_APPLETALK, SOCK_DGRAM, 0);

raw_socket = socket(AF_APPLETALK, SOCK_RAW, protocol);

## ОПИСАНИЕ
В Linux реализованы протоколы AppleTalk, описанные в Inside AppleTalk. В ядре есть лишь уровни DDP и AARP. Они проектировались с учётом того, что ими будут пользоваться через библиотеку протоколов netatalk. В этой странице описан интерфейс для тех, кому требуется использовать уровень DDP напрямую.

Связь между AppleTalk и пользовательской программой реализована через интерфейс сокетов, совместимых с BSD. Дополнительную информацию о сокетах можно найти в socket(7).

Сокет AppleTalk создаётся из вызова socket(2), которому в качестве аргумента семейства указывается значение AF_APPLETALK. Типом сокета может быть SOCK_DGRAM (чтобы открыть сокет ddp) или SOCK_RAW (для открытия сокета raw). Значением protocol устанавливается протокол приема-передачи AppleTalk. При SOCK_RAW необходимо указать значение ATPROTO_DDP.

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

### Формат адреса
Адрес сокета AppleTalk определяется как комбинация номера сети, номера узла и номера порта.

```
struct at_addr {

    unsigned short s_net;

    unsigned char  s_node;
};
struct sockaddr_atalk {

    sa_family_t    sat_family;    /* адресное семейство */

    unsigned char  sat_port;      /* порт */

    struct at_addr sat_addr;      /* сеть/узел */
};
```

Значение sat_family всегда устанавливается в AF_APPLETALK. В sat_port содержится номер порта. Порты с номерами меньше 129 называются зарезервированными портами. Вызов bind(2) для этих сокетов допустим только для процессов, у которых идентификатор эффективного пользователя равен нулю, либо имеется мандат CAP_NET_BIND_SERVICE. В sat_addr указан адрес машины (host). Поле net структуры struct at_addr содержит адрес сети машины в сетевом порядке расположения байт. Значение AT_ANYNET является шаблоном, включающим в себя и “эту сеть”. Поле node структуры struct at_addr содержит номер узла машины. Значение AT_ANYNODE является шаблоном, включающим в себя и “этот узел”. Значение ATADDR_BCAST является ссылкой на локальный широковещательный адрес.

### Параметры сокета
Специальные параметры сокета для протокола не поддерживаются.

### Интерфейсы /proc
IP поддерживает набор интерфейсов /proc для настройки некоторых общих параметров Appletalk. Доступ к параметрам можно получить путем чтения или записи файлов в каталоге /proc/sys/net/atalk/.

aarp-expiry-time

Временной интервал (в секундах) до момента устаревания записи в кэше AARP.

aarp-resolve-time

Временной интервал (в секундах) до определения записи в кэше AARP.

aarp-retransmit-limit

Количество повторных передач запроса AARP до того, как запись будет объявлена недействительной.

aarp-tick-time

Скорость таймера управляющего AARP в секундах.

Значения по умолчанию соответствуют спецификации и никогда не должны меняться.

### Вызовы ioctl
Все вызовы ioctl, описанные в socket(7), применимы к DDP.

## ОШИБКИ
EACCES

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

EADDRINUSE

Попытка связать сокет с уже используемым адресом.

EADDRNOTAVAIL

Был запрошен несуществующий интерфейс или запрошенный исходящий адрес не является локальным.

EAGAIN

Действие над неблокирующим сокетом привело бы к его блокировке.

EALREADY

Операция соединения на неблокирующем сокете уже находится в процессе выполнения.

ECONNABORTED

Соединение закрыто во время accept(2).

EHOSTUNREACH

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

EINVAL

Передан неверный аргумент.

EISCONN

Вызов connect(2) запущен для сокета, уже установившего соединение.

EMSGSIZE

Размер датаграммы больше размера DDP MTU.

ENODEV

Сетевое устройство недоступно или неспособно посылать IP.

ENOENT

Операция SIOCGSTAMP вызвана для сокета, который ещё не получил ни одного пакета.

ENOMEM и ENOBUFS

Недостаточно памяти.

ENOPKG

Не настроена подсистема ядра.

ENOPROTOOPT и EOPNOTSUPP

Передан недопустимый параметр сокета.

ENOTCONN

Операция определена только для сокета, установившего соединение, а этот сокет не соединён.

EPERM

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

EPIPE

Соединение неожиданно закрылось или завершено (shut down) другой стороной.

ESOCKTNOSUPPORT

Сокет не настроен или запрошен неизвестный тип сокета.

## ВЕРСИИ
AppleTalk поддерживается в Linux версии 2.0 и более поздних. Интерфейс /proc появился в Linux 2.2.

## ЗАМЕЧАНИЯ
Будьте очень осторожны при работе с параметром SO_BROADCAST: в Linux он не относится к привилегированным. Масштабная рассылка сообщений по широковещательному адресу может легко перегрузить сеть.

### Совместимость
Базовый интерфейс сокетов Appletalk совместим с netatalk в BSD-подобных системах. Многие системы BSD не проверяют SO_BROADCAST при посылке широковещательных кадров: это может привести к проблемам совместимости.

Режим неструктурированных сокетов поддерживается только в Linux и предназначен для облегчения поддержки альтернатив пакета CAP и утилит мониторинга AppleTalk.

## ДЕФЕКТЫ
Слишком много противоречий в значениях ошибок.

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

## СМОТРИТЕ ТАКЖЕ
recvmsg(2), sendmsg(2), capabilities(7), socket(7)


environ

title: environ(7)

summary: пользовательское окружение

text:
# environ(7)

## ИМЯ
environ - пользовательское окружение

## ОБЗОР

```
extern char **environ;
```

## ОПИСАНИЕ
Переменная environ указывает на массив указателей на строки, который называют «окружением» (environment). Последний указатель в этом массиве имеет значение NULL (данная переменная должна быть объявлена в программе пользователя, а кроме того она объявлена в заголовочном файле <unistd.h>, если определён макрос тестирования свойств _GNU_SOURCE). Указанный массив строк, становится доступным процессу после вызова exec(3), которая запускает процесс. Если дочерний процесс создаётся с помощью fork(2), то он наследует копию окружения своего родителя.

По договорённости, строки в environ имеют вид «имя=значение». Вот некоторые примеры:

USER

Имя пользователя, зарегистрированного в системе (используется некоторыми программами, написанными для BSD).

LOGNAME

Имя пользователя, зарегистрированного в системе (используется некоторыми программами, написанными для System-V).

HOME

Домашний каталог пользователя, устанавливается программой login(1) согласно значению из файла паролей passwd(5).

LANG

Имя локали используется для категорий локали, если не заменено значением LC_ALL или более специфичными переменными окружения, такими как LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC и LC_TIME (подробней смотрите описание переменных окружения LC_* в locale(7)).

PATH

Последовательность каталогов, которые sh(1) и многие другие программы будут применять в качестве префикса при поиске файла, если он задан неполным именем Каталоги разделяются «:» (похожим образом переменная CDPATH используется некоторыми командными оболочками для поиска каталога, в который нужно перейти, MANPATH используется командой man(1) для поиска справочных страниц и т. д.).

PWD

Текущий рабочий каталог. Устанавливается некоторыми командными оболочками.

SHELL

Имя регистрационной командной оболочки пользователя.

TERM

Тип терминала, для которого будет подготавливаться вывод.

PAGER

Предпочитаемая пользователем утилита просмотра текстовых файлов.

EDITOR/VISUAL

Предпочитаемая пользователем утилита редактирования текстовых файлов.

Имена можно помещать в окружение оболочки с помощью команды export в sh(1) или командой setenv, если используется csh(1).

Начальное окружение оболочки создаётся различными путями, например из файла /etc/environment, который обрабатывается pam_env(8) при входе любого пользователя (в системах с pam(8)). Также, различные сценарии инициализации оболочки, такие как системный сценарий /etc/profile и локальный пользовательский сценарий инициализации могут содержать команды, могут добавлять переменные в окружение оболочки; подробности смотрите в справочной странице на предпочитаемую оболочку.

Оболочки, подобные Bourne, поддерживают синтаксис

ИМЯ=значение команда

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

При вызове exec(3) в окружение также могут быть помещены и аргументы. Программа, написанная на языке C, может изменять своё окружение с помощью функций getenv(3), putenv(3), setenv(3) и unsetenv(3).

Заметим, что наличие или значения определённых переменных окружения может отказывать влияние на поведение многих программ и библиотечных процедур. Примеры:

- Переменные LANG, LANGUAGE, NLSPATH, LOCPATH, LC_ALL, LC_MESSAGES и т. д. влияют на работу с локалью; смотрите catopen(3), gettext(3) и locale(7).

- Переменная TMPDIR влияет на путь файлов, создаваемых tempnam(3) и некоторыми другими процедурами, а также на временный каталог, который используется sort(1) и некоторыми другими программами.

- Переменные LD_LIBRARY_PATH, LD_PRELOAD и другие LD_* влияют на поведение динамического загрузчика/компоновщика.

- Переменная POSIXLY_CORRECT влияет на определённые программы и библиотечные вызовы таким образом, что они включают режим соответствия POSIX.

- На поведение malloc(3) оказывают влияние переменные MALLOC_*.

- В переменной HOSTALIASES задаётся имя файла, содержащего псевдонимы имени узла, которые будут использованы в функции gethostbyname(3).

- В переменных TZ и TZDIR указывается информация о часовом поясе; они используются в функциях tzset(3), ctime(3), localtime(3), mktime(3), strftime(3). Смотрите также tzselect(8).

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

- Через переменные COLUMNS и LINES приложение может узнать о размере окна (возможно указать ненастоящий размер).

- В переменной PRINTER или LPDEST можно указать предпочтительный для печати принтер. Смотрите lpr(1).

## ЗАМЕЧАНИЯ
Операции prctl(2) PR_SET_MM_ENV_START и PR_SET_MM_ENV_END можно использовать для управления расположением окружения процесса.

## ДЕФЕКТЫ
Честно говоря, использование переменных окружения таит в себе проблемы с безопасностью. Многие системные команды могут неправильно работать, если пользователь укажет неправильные значения для переменной IFS или LD_LIBRARY_PATH.

Также есть риск засорения пространства имен. Такие программы как make и autoconf позволяют заменять имена используемых по умолчанию утилит через переменные окружения. Например, переменная CC используется для выбора нужного C-компилятора (и, похожим образом, можно использовать переменные MAKE, AR, AS, FC, LD, LEX, RM, YACC и т.д.). Однако, есть случаи, когда в переменных окружения задают параметры программ, а не их имена (например, MORE, LESS и GZIP). Использование таких имён для переменных окружения является ошибочным и не должно применяться в новых программах. Авторы gzip должны бы переименовать переменную в GZIP_OPT.

## СМОТРИТЕ ТАКЖЕ
bash(1), csh(1), env(1), login(1), printenv(1), sh(1), tcsh(1), execve(2), clearenv(3), exec(3), getenv(3), putenv(3), setenv(3), unsetenv(3), locale(7), ld.so(8), pam_env(8)


epoll

title: epoll(7)

summary: средство уведомления о событии ввода\-вывода

text:
# epoll(7)

## ИМЯ
epoll - средство уведомления о событии ввода-вывода

## ОБЗОР
\#include <sys/epoll.h>

## ОПИСАНИЕ
Программный интерфейс epoll выполняется схожую с poll(2) задачу: следит за несколькими файловыми дескрипторами и ждёт, когда станет возможен ввод-вывод для одного из них. Программный интерфейс epoll можно использовать либо в режиме edge-triggered, либо в level-triggered и применять для слежения за достаточно большим количеством файловых дескрипторов.

Центральным элементом программного интерфейса epoll является экземпляр epoll — структура данных ядра, которая с точки зрения пользовательского пространства может рассматриваться как контейнер с двумя списками:

- Список interest (иногда также называемый набором epoll): набор файловых дескрипторов, которые зарегистрировал процесс для слежения.

- Список ready: набор файловых дескрипторов, которые «готовы» для проведения операций ввода-вывода. Список ready — это часть набора (точнее, набор ссылок) файловых дескрипторов из списка interest, он динамически заполняется ядром в результате действий ввода-вывода с этими файловыми дескрипторами.

Для создания и управления экземпляром epoll служат следующие системные вызовы:

- Вызов epoll_create(2) создаёт экземпляр новый epoll и возвращает файловый дескриптор, указывающий на этот экземпляр (более новый epoll_create1(2) расширяет возможности epoll_create(2)).

- Затем с помощью epoll_ctl(2) регистрируются интересующие файловые дескрипторы, который добавляет их в список interest экземпляра epoll.

- Вызов epoll_wait(2) ждёт наступления событий ввода-вывода, блокируя вызывающую нить, если события пока недоступны (данный системный вызов можно рассматривать как выборщике элементов из списка готовности экземпляра epoll).

### Режимы level-triggered и edge-triggered
Существует два режима выдачи событий epoll: edge-triggered (ET) и level-triggered (LT). Разницу между ними можно описать так. Предположим, что реализован следующий сценарий событий:

1.

Файловый дескриптор, представляющий читающую сторону канала (rfd), регистрируется в экземпляре epoll.

2.

Пишущая сторона канала записывает 2 КБ данных на записываемой стороне канала.

3.

Вызов epoll_wait(2) завершается и возвращает rfd как готовый файловый дескриптор.

4.

Читающая сторона канала считывает 1 КБ данных из rfd.

5.

Вызов epoll_wait(2) завершается.

Если файловый дескриптор rfd добавлен к экземпляру epoll с указанным флагом EPOLLET (edge-triggered), то вызов epoll_wait(2) на шаге 5, вероятно, повиснет, несмотря на имеющие данные в буфере ввода; в это же время удалённая сторона может ожидать подтверждения приёма уже отправленных данных. Причиной этого является то, что в режиме edge-triggered события доставляются только когда происходит изменение состояния отслеживаемого файлового дескриптора. Поэтому в шаге 5 вызывающий может бесконечно ждать появления данных, хотя они уже есть в буфере ввода. В приведённом выше примере событие для rfd будет сгенерировано из-за операции записи, сделанной в шаге 2, и это событие будет обработано в шаге 3. Так как операция в шаге 4, не прочитала все данные из буфера, вызов epoll_wait(2) в шаге 5 может заблокироваться навсегда.

Приложение, которое применяет флаг EPOLLET, должно использовать неблокирующие файловые дескрипторы, чтобы избежать приостановки задания, обрабатывающего множество файловых дескрипторов, из-за блокировок чтения или записи. Предлагаемый способ использования epoll с интерфейсом Edge Triggered (EPOLLET):

i

неблокирующие файловые дескрипторы; и

ii

ожидание события только после того, как read(2) или write(2) возвратят EAGAIN.

Напротив, при использовании интерфейса level-triggered (по умолчанию, если не указан EPOLLET) epoll проще и быстрее poll(2), и может быть использован везде, где используется последний, так как имеет ту же семантику.

Так как даже с edge-triggered epoll при получении нескольких порций данных могут генерироваться множественные события, вызывающий может задать флаг EPOLLONESHOT, который указывает epoll отключить связанный файловый дескриптор после приёма события с помощью epoll_wait(2). Если указан флаг EPOLLONESHOT, то вызывающий должен переустановить файловый дескриптор с помощью epoll_ctl(2) с флагом EPOLL_CTL_MOD.

Если несколько нитей (или процессов, если дочерние процессы унаследовали файловый дескриптор epoll при fork(2)) блокируются в ожидании epoll_wait(2) одного и того же файлового дескриптора и файловый дескриптор в списке interest, помеченный для уведомления edge-triggered (EPOLLET), становится готовым, то только одна из нитей (или процессов) пробуждается из epoll_wait(2). Такая полезная оптимизация в некоторых случаях помогает избежать «лавины» пробуждений.

### Взаимодействие с autosleep
Если система в режиме autosleep посредством /sys/power/autosleep и происходит событие, которое пробуждает устройство, то драйвер устройства держит устройство проснувшимся только, пока событие ставится в очередь. Чтобы устройство не заснуло пока не обработает событие, необходимо использовать флаг epoll_ctl(2) EPOLLWAKEUP.

Флаг EPOLLWAKEUP задаётся в поле events для struct epoll_event; система будет оставаться разбуженной с момента когда событие поступает в очередь, пока не закончится работа вызова epoll_wait(2), возвращающий событие, и до последующего вызова epoll_wait(2). Если событие должно держать систему разбуженной дольше, то нужно применить отдельный wake_lock перед вторым вызовом epoll_wait(2).

### Интерфейсы /proc
Для ограничения потребления epoll памяти ядра, можно использовать следующие интерфейсы:

/proc/sys/fs/epoll/max_user_watches (начиная с Linux 2.6.28)

Задаёт ограничение на общее количество файловых дескрипторов, которые пользователь может зарегистрировать во всех экземплярах epoll в системе. Ограничение привязывается к реальному идентификатору пользователя. Каждый зарезервированный файловый дескриптор занимает, приблизительно, 90 байт в 32-битном ядре, и, приблизительно, 160 байт в 64-битном ядре. В настоящее время, значение по умолчанию для max_user_watches равно 1/25 (4%) доступной памяти ядра (low memory), поделённое на значение размера дескриптора в байтах.

### Примеры использования
При применении epoll с интерфейсом level-triggered он имеет ту же семантику что и poll(2), а при edge-triggered требует больших проверок для избежания зависаний приложения в событийном цикле. В этом примере, слушающим является неблокирующий сокет, для которого был вызван listen(2). Функция do_use_fd() использует новый готовый файловый дескриптор до тех пор, пока не возвратится EAGAIN от read(2) или write(2). Приложение на основе машины состояний должно после получения EAGAIN записать своё текущее состояние так, чтобы последующий вызов do_use_fd() продолжил выполнять read(2) или write(2) с места остановки.

```
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Код для настройки слушающего сокета, 'listen_sock',

   (socket(), bind(), listen()) не показаны */
epollfd = epoll_create1(0);
if (epollfd == -1) {

    perror("epoll_create1");

    exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {

    perror("epoll_ctl: listen_sock");

    exit(EXIT_FAILURE);
}
for (;;) {

    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

    if (nfds == -1) {

        perror("epoll_wait");

        exit(EXIT_FAILURE);

    }

    for (n = 0; n < nfds; ++n) {

        if (events[n].data.fd == listen_sock) {

            conn_sock = accept(listen_sock,

                               (struct sockaddr *) &addr, &addrlen);

            if (conn_sock == -1) {

                perror("accept");

                exit(EXIT_FAILURE);

            }

            setnonblocking(conn_sock);

            ev.events = EPOLLIN | EPOLLET;

            ev.data.fd = conn_sock;

            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,

                        &ev) == -1) {

                perror("epoll_ctl: conn_sock");

                exit(EXIT_FAILURE);

            }

        } else {

            do_use_fd(events[n].data.fd);

        }

    }
}
```

При использовании интерфейса edge-triggered для большей производительности можно однократно добавить файловый дескриптор внутрь интерфейса epoll (EPOLL_CTL_ADD), указав (EPOLLIN|EPOLLOUT). Это позволит вам избежать постоянного переключения между EPOLLIN и EPOLLOUT, вызывающими epoll_ctl(2) c EPOLL_CTL_MOD.

### Вопросы и ответы
0.

По какому ключу различать зарегистрированные файловые дескрипторы в списке interest?

Уникальной комбинацией является номер файлового дескриптора и описание открытого файла (так называемый «описатель открытого файла» — внутреннее представление открытого файла в ядре).

1.

Что случится, если зарегистрировать один файловый дескриптор в экземпляре epoll дважды?

Вероятно, вы получите EEXIST. Однако возможно добавить дубликат файлового дескриптора (dup(2), dup2(2), fcntl(2) F_DUPFD) в тот же экземпляр epoll. Это может быть полезно для фильтрующих событий, если дубликаты файловых дескрипторов регистрируются с разными масками events.

2.

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

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

3.

Могут ли операции poll/epoll/select применяться к самому файловому дескриптору epoll?

Да. Если файловый дескриптор epoll имеет ожидающие события, то он будет помечен как доступный для чтения.

4.

Что случится, если попытаться поместить файловый дескриптор epoll в свой собственный набор файловых дескрипторов?

Вызов epoll_ctl(2) завершается ошибкой (EINVAL). Однако вы можете добавить файловый дескриптор epoll внутрь другого набора файлового дескриптора epoll.

5.

Можно ли отправить файловый дескриптор epoll через доменный сокет UNIX другому процессу?

Да, но это не имеет смысла, так как принимающий процесс не имеет копий файловых дескрипторов в списке interest.

6.

Приводит ли закрытие файлового дескриптора к его удалению из всех списков interest epoll?

Да, но учтите следующий момент. Файловый дескриптор является ссылкой на открытое файловое описание (смотрите open(2)). При создании дубля файлового дескриптора с помощью dup(2), dup2(2), fcntl(2) F_DUPFD или fork(2) созданный новый файловый дескриптор указывает на то же открытое файловое описание. Открытое файловое описание продолжает существовать до тех пор, пока все указывающие на него файловые дескрипторы не будут закрыты.

Файловый дескриптор удаляется из списка interest только после того, как будут закрыты все файловые дескрипторы, ссылающиеся на открытое файловое описание. Это означает, что даже после закрытия файлового дескриптора, являющегося частью списка interest, могут поступать события от файлового дескриптора, если остались открытыми другие файловые дескрипторы, ссылающиеся на тоже файловое описание. Чтобы такого не случалось, файловый дескриптор должен быть удалён из списка interest явным образом (с помощью epoll_ctl(2) EPOLL_CTL_DEL) до создания его дубликата. Или же приложение может проверить,что закрыты все файловые дескрипторы (что может быть трудно, если дубли файлового дескриптора неявно создавались где-то в библиотечных функциях с помощью dup(2) или fork(2)).

7.

Если между вызовами epoll_wait(2) придёт более одного события, то они будут объединены или о них будет сообщено по отдельности?

Они будут объединены.

8.

Влияет ли операция над файловым дескриптором на уже собранные, но пока ещё не сообщенные события?

Вы можете выполнить две операции на существующем файловом дескрипторе. Удаление в этом случае бессмысленно. Изменение приведёт к повторному чтению доступного ввода/вывода.

9.

Должен ли я читать/записывать файловый дескриптор до пор пока, не получу EAGAIN при использовании флага EPOLLET (поведение edge-triggered)?

Получение события от epoll_wait(2) должно сообщить вам, что файловый дескриптор готов для запрошенной операции ввода/вывода. Вы должны предполагать, что он готов до тех пор, пока вы не получите следующий EAGAIN от (неблокирующего) чтения/записи. Когда и как вы будете использовать файловый дескриптор — полностью зависит от вас.

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

Для потокоориентированных файлов (например, каналы, FIFO, потоковые сокеты) условие, при которых чтение/запись пространства ввода/вывода закончилось, может быть определено проверкой количества считанных/записанных данных из/в целевого файлового дескриптора. Например, если вы вызвали read(2) для чтения определённого количества данных и read(2) вернул меньшее количество байтов, то можно быть уверенным, что пространство чтения ввода/вывода этого файлового дескриптора закончилось. То же самое справедливо для записи посредством write(2) (не используйте последнее, если вы не можете гарантировать, что отслеживаемый файловый дескриптор всегда ссылается на потокоориентированный файл).

### Возможные ловушки и способы их обхода
o Информационный голод (edge-triggered)

Если существует большое пространство ввода/вывода, то возможно, что пока вы его читаете, другие файлы не будут обрабатываться и возникнет недостаток данных (этого, обычно, не происходит с epoll).

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

o Если использовать кэш событий…

Если вы используете кэш событий или храните все файловые дескрипторы, возвращённые от epoll_wait(2), то убедитесь, что вы обеспечили способ его динамического закрытия (например, вызванное обработкой предыдущего события). Предположим, что вы получили 100 событий от epoll_wait(2), и что в событии №47 некоторое условие определяет, что событие №13 должно быть закрыто. Если вы удалите структуру и выполните close(2) файлового дескриптора для события №13, то кэш событий всё ещё может сообщать о том, что есть ожидаемые события для этого файлового дескриптора, что приводит к путнице.

Одним из решений будет вызов, во время обработки события №47, epoll_ctl(EPOLL_CTL_DEL) для удаления файлового дескриптора 13 и вызов close(2), а затем маркировка связанной с ним структуры данных как удалённой и связки его со списком очистки. Если при пакетной обработке найдется другое событие для файлового дескриптора 13, то обнаружится, что файловый дескриптор уже был удалён и конфликтов не будет.

## ВЕРСИИ
Программный интерфейс epoll был добавлен в ядро Linux версии 2.5.44. Поддержка в glibc доступна с версии 2.3.2.

## СООТВЕТСТВИЕ СТАНДАРТАМ
Программный интерфейс epoll есть только в Linux. В некоторых других системах есть подобные механизмы, например, в FreeBSD есть kqueue, а в Solaris — /dev/poll.

## ЗАМЕЧАНИЯ
Набор файловых дескрипторов, которые отслеживаются через файловый дескриптор epoll, можно найти в записи для файлового дескриптора epoll в каталоге процесса /proc/[pid]/fdinfo. Подробности смотрите в proc(5).

Вызов The kcmp(2) с операцией KCMP_EPOLL_TFD можно использовать для проверки, что файловый дескриптор присутствует в экземпляре epoll.

## СМОТРИТЕ ТАКЖЕ
epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)