CPUSET(7) | Руководство программиста Linux | 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).
Псевдо-файлы в каталоге процессорного набора описывают внутреннее состояние ядра и не хранятся на диске. Все файлы описаны ниже.
Кроме показанных выше псевдо-файлов, в каждом каталоге в /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.
Процессорный набор, у которого установлен флаг 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», то это включит данное свойство.
Распространение в памяти, указанное в процессорном наборе, похоже (в других контекстах) на циклический алгоритм или размещение памяти чередованием.
Распространение в памяти, указанное в процессорном наборе, может существенно повысить производительность заданий, которым:
Без этой политики выделение памяти на узлах процессорного набора задания может быть очень неравномерным, особенно для заданий, которым нужно просто инициализировать единственную нить или прочитать набор данных.
Обычно, при настройке 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 были такими:
Значение 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
К каждому процессорному набору применяются следующие правила:
Права на процессорный набор определяются правами доступа к каталогам и псевдо-файлам в файловой системе процессорного набора, обычно смонтированной в /dev/cpuset.
Например, процесс может поместить себя в другой процессорный набор (отличный от текущего), если сможет выполнить запись в файл tasks другого процессорного набора. Для этого требуется право на выполнение для окружающих каталогов и право на запись в файл tasks.
Дополнительное ограничение устанавливается на запросы размещения другого процесса в процессорный набор. Один процесс не может прикрепить другой к процессорному набору, если не имеет право отправки этому процессу сигнала (смотрите kill(2)).
Процесс может создать потомка процессорного набора, если у него есть доступ и право на запись в родительский каталог процессорного набора. Он может изменить ЦП или узлы памяти в процессорном наборе, если у него есть доступ к каталогу этого процессорного набора (право на выполнение на каждый родительский каталог) и право на запись в соответствующий файл cpus или mems.
Есть незначительное отличие между тем, как эти права вычисляются здесь и при обычных операциях в файловой системе. Ядро интерпретирует относительные пути, начиная с текущего рабочего каталога процесса. Даже если он работает с файлом процессорного набора, относительные пути интерпретируются относительно текущего рабочего каталога процесса, а не относительно текущего процессорного набора процесса. Единственные способы, в которых могут использоваться пути процессорного набора относительно текущего процессорного набора процесса это когда текущий рабочий каталог процесса совпадает с его каталогом процессорного набора (сначала выполняется cd или chdir(2) в каталог его процессорного набора в /dev/cpuset, что немного необычно) или если некий код пользователя преобразует относительный путь процессорного набора в полный путь в файловой системе.
В теории это означает, то код пользователя должен задавать процессорные наборы с помощью абсолютных путей, для чего требуется знать точку монтирования файловой системы процессорного набора (обычно, но необязательно, /dev/cpuset). На практике, автор кода уровня пользователя делает простое предположение, что если файловая система процессорного набора монтируется, то в /dev/cpuset. Кроме этого в порядке вещей при написании корректного пользовательского кода выполняется проверка наличия псевдо-файла /dev/cpuset/tasks, чтобы убедиться, что файловая система процессорного набора смонтирована.
По умолчанию, индивидуальный файл cpuset.memory_pressure всегда содержит ноль (0). Если это свойство не включить записью «1» в псевдо-файл /dev/cpuset/cpuset.memory_pressure_enabled, то ядро не вычисляет индивидуальное значение memory_pressure.
При использовании команды 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 и их смысл при ошибках, возникающих при работе с процессорным набором:
Свойство процессорного набора появилось в ядре Linux версии 2.6.12.
Несмотря на имя, параметр pid в действительности является ID нити, и каждая нить группы нитей может быть прикреплена к различным процессорным наборам. В аргументе pid может передаваться значение, возвращаемое вызовом gettid(2).
Файлы cpuset.memory_pressure процессорного набора можно открывать для записи, создания и обрезания, но после этого write(2) завершается с ошибкой errno, равной EACCES, и параметры создания и обрезания в open(2) не учитываются.
В следующих примерах показано получения и установка параметров процессорного набора с помощью команд оболочки.
Для создания нового процессорного набора и прикрепления текущей оболочки команд к нему выполняются следующие шаги:
Например, следующая последовательность команд создаёт новый процессорный набор с именем «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
Чтобы перенести задание (набор процессов, присоединённых к процессорному набору) на другие ЦП и узлы памяти в системе, включая перемещение страниц памяти, выделенных под это задание, выполните следующие шаги:
Всё это выполняет следующая последовательность команд:
$ 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)
2017-09-15 | Linux |