Как защитить процессор от планировщика linux (предотвратить его планирование потоков на этот процессор)? - программирование
Подтвердить что ты не робот

Как защитить процессор от планировщика linux (предотвратить его планирование потоков на этот процессор)?

Можно использовать sched_setaffinity для привязки потока к процессору, увеличивая производительность (в некоторых ситуациях)

На странице руководства linux:

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

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

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

Мои вопросы таковы:

  • Можно ли запретить планировщику планировать любые потоки на данный процессор? (например: либо полностью скрыть процессор из планировщика, либо каким-либо другим способом)
  • Есть ли какие-то потоки, которые обязательно должны работать на этом процессоре? (например: потоки ядра/потоки прерываний)
  • Если мне нужно иметь потоки ядра, запущенные на этом процессоре, то какое разумное значение максимального приоритета использовать, чтобы я не голодал из потоков ядра?
4b9b3361

Ответ 1

Ответ заключается в использовании cpusets. утилита python cpuset упрощает их настройку.

Основные понятия

3 cpusets

  • root: присутствует во всех конфигурациях и содержит все cpus (неэкранированные)
  • system: содержит cpus, используемый для системных задач - те, которые должны выполняться, но не являются "важными" (неэкранированными).
  • user: содержит cpus, используемый для "важных" задач - те, которые мы хотим запустить в режиме "реального времени" (экранированные)

Команда shield управляет этими тремя cpusets.

Во время настройки он перемещает все подвижные задачи в неэкранированный cpuset (system), и во время разговора он перемещает все подвижные задачи в root cpuset. После настройки подкоманда позволяет перемещать задачи в экран (user) cpuset и, кроме того, перемещать специальные задачи (потоки ядра) от root до system (и, следовательно, из cpuset user).

Команды

Сначала мы создаем щит. Естественно, макет экрана будет зависящим от машины/задачи. Например, скажем, у нас есть 4-ядерная машина, отличная от NUMA: мы хотим выделить 3 ядра для экрана и оставить 1 ядро ​​для несущественных задач; поскольку он не является NUMA, нам не нужно указывать какие-либо параметры памяти node, и мы оставляем потоки ядра, запущенные в

cpuset root (то есть: через все cpus)

$ cset shield --cpu 1-3

Некоторые потоки ядра (те, которые не привязаны к конкретному процессору) могут быть перемещены в system cpuset. (В общем, не рекомендуется перемещать потоки ядра, привязанные к определенному процессору)

$ cset shield --kthread on

Теперь перечислим, что работает в экранах (user) или неэкранированных (system) cpusets: (-v для подробных, в которых будут перечислены имена процессов) (добавьте второй -v, чтобы отобразить больше, чем 80 символов)

$ cset shield --shield -v
$ cset shield --unshield -v -v

Если мы хотим остановить экран (отрыв)

$ cset shield --reset

Теперь позвольте выполнить процесс в экране (команды, следующие за '--', передаются команде, которая должна быть выполнена, а не cset)

$ cset shield --exec mycommand -- -arg1 -arg2

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

$ cset shield --shield --pid 1234
$ cset shield --shield --pid 1234,1236
$ cset shield --shield --pid 1234,1237,1238-1240

Расширенные понятия

cset set/proc - это дает вам более точное управление cpusets

Set

Создавать, настраивать, переименовывать, перемещать и уничтожать cpusets

Команды

Создайте cpuset, используя cpus 1-3, используйте NUMA node 1 и назовите его "my_cpuset1"

$ cset set --cpu=1-3 --mem=1 --set=my_cpuset1

Измените "my_cpuset1", чтобы использовать только cpus 1 и 3

$ cset set --cpu=1,3 --mem=1 --set=my_cpuset1

Уничтожить cpuset

$ cset set --destroy --set=my_cpuset1

Переименуйте существующий cpuset

$ cset set --set=my_cpuset1 --newname=your_cpuset1

Создайте иерархический cpuset

$ cset set --cpu=3 --mem=1 --set=my_cpuset1/my_subset1

Список существующих cpusets (глубина уровня 1)

$ cset set --list

Список существующих cpuset и его дочерних элементов

$ cset set --list --set=my_cpuset1

Список всех существующих cpusets

$ cset set --list --recurse

Proc

Управление потоками и процессами

Команды

Список задач, выполняемых в cpuset

$ cset proc --list --set=my_cpuset1 --verbose

Выполнение задачи в cpuset

$ cset proc --set=my_cpuset1 --exec myApp -- --arg1 --arg2

Перемещение задачи

$ cset proc --toset=my_cpuset1 --move --pid 1234
$ cset proc --toset=my_cpuset1 --move --pid 1234,1236
$ cset proc --toset=my_cpuset1 --move --pid 1238-1340

Перемещение задачи и всех ее братьев и сестер

$ cset proc --move --toset=my_cpuset1 --pid 1234 --threads

Переместить все задачи из одного cpuset в другой

$ cset proc --move --fromset=my_cpuset1 --toset=system

Перемещайте потоки ядра без изменений в cpuset

$ cset proc --kthread --fromset=root --toset=system

Принудительно перемещать потоки ядра (включая те, которые привязаны к определенному процессору) в cpuset (обратите внимание: это может иметь ужасные последствия для системы - убедитесь, что вы знаете, что вы делаете)

$ cset proc --kthread --fromset=root --toset=system --force

Пример иерархии

Мы можем использовать иерархические cpusets для создания приоритетных группировок

  • Создайте cpuset system с 1 cpu (0)
  • Создайте cpuset prio_low с 1 cpu (1)
  • Создайте cpuset prio_met с 2 cpus (1-2)
  • Создайте cpuset prio_high с 3 cpus (1-3)
  • Создайте cpuset prio_all со всеми 4 cpus (0-3) (обратите внимание, что это то же самое, что и root, считается хорошей практикой сохранять разделение от корня)

Для достижения вышеуказанного вы создаете prio_all, а затем создаете подпрограмму prio_high под prio_all и т.д.

$ cset set --cpu=0 --set=system
$ cset set --cpu=0-3 --set=prio_all
$ cset set --cpu=1-3 --set=/prio_all/prio_high
$ cset set --cpu=1-2 --set=/prio_all/prio_high/prio_med
$ cset set --cpu=1 --set=/prio_all/prio_high/prio_med/prio_low

Ответ 2

Есть два других способа, которыми я могу думать об этом (хотя и не такой элегантный, как cset, который, похоже, не обладает фантастическим уровнем поддержки от Redhat):

1) Taskset все, включая PID 1 - приятный и легкий (но, alledgly - я никогда не видел никаких проблем сам по себе - может привести к неэффективности в планировщике). Ниже приведен список script (который должен запускаться как root) запускает набор задач для всех запущенных процессов, включая init (pid 1); это свяжет все запущенные процессы с одним или несколькими "мусорными ядрами", а также закрепив init, он обеспечит запуск любых будущих процессов в списке "мусорных ядер":

#!/bin/bash

if [[ -z $1 ]]; then
  printf "Usage: %s '<csv list of cores to set as junk in double quotes>'", $0
  exit -1;
fi

for i in `ps -eLfad |awk '{ print $4 } '|grep -v PID | xargs echo `; do 
   taskset -pc $1 $i;
done

2) используйте параметр ядра isolcpus (здесь документация из https://www.kernel.org/doc/Documentation/kernel-parameters.txt):

isolcpus=   [KNL,SMP] Isolate CPUs from the general scheduler.
            Format:
            <cpu number>,...,<cpu number>
            or
            <cpu number>-<cpu number>
            (must be a positive range in ascending order)
            or a mixture
            <cpu number>,...,<cpu number>-<cpu number>

        This option can be used to specify one or more CPUs
        to isolate from the general SMP balancing and scheduling
        algorithms. You can move a process onto or off an
        "isolated" CPU via the CPU affinity syscalls or cpuset.
        <cpu number> begins at 0 and the maximum value is
        "number of CPUs in system - 1".

        This option is the preferred way to isolate CPUs. The
        alternative -- manually setting the CPU mask of all
        tasks in the system -- can cause problems and
        suboptimal load balancer performance.

Я использовал эти два плюс механизмы cset для нескольких проектов (кстати, прошу прощения за откровенную саморекламу:-)), я только что подал патент на инструмент под названием Pontus Vision ThreadManager, который предлагает оптимальные стратегии закрепления для любой платформы x86 для любых заданных загрузок программного обеспечения; после тестирования на клиентском сайте у меня действительно хорошие результаты (снижение пиковых латентностей на 270%), поэтому стоит делать фиксацию и изоляцию процессора.

Ответ 3

Вот как это сделать по старинке, используя cgroups. У меня есть машина Fedora 28, и RedHat/Fedora хотят, чтобы вы использовали systemd-run, но я не смог найти эту функцию там. Я хотел бы знать, как это сделать с помощью systemd-run, если кто-нибудь захочет меня просветить.

Допустим, я хочу исключить мой четвертый процессор (из процессоров 0-3) из планирования и переместить все существующие процессы в процессоры 0-2. Затем я хочу поставить процесс на CPU 3 сам по себе.

sudo su -
cgcreate -g cpuset:not_cpu_3
echo 0-2 > /sys/fs/cgroup/cpuset/not_cpu_3/cpuset.cpus
# This "0" is the memory node. See https://utcc.utoronto.ca/~cks/space/blog/linux/NUMAMemoryInfo
# for more information *
echo 0 > /sys/fs/cgroup/cpuset/not_cpu_3/cpuset.mems
  • В частности, на вашем компьютере вы захотите просмотреть /proc/zoneinfo и /proc/zoneinfo /sys/devices/system/node. Получение правильной информации об узле оставлено читателю в качестве упражнения.

Теперь, когда у нас есть наша cgroup, нам нужно создать нашу изолированную cgroup CPU 3:

cgcreate -g cpuset:cpu_3
echo 3 > /sys/fs/cgroup/cpuset/cpu_3/cpuset.cpus
# Again, the memory node(s) you want to specify.
echo 0 > /sys/fs/cgroup/cpuset/cpu_3/cpuset.mems

Поместите все процессы/потоки в группу not_cpu_3:

for pid in $(ps -eLo pid) ; do cgclassify -g cpuset:not_cpu_3 $pid; done

Обзор:

ps -eL k psr o psr,pid,tid,args | sort | cut -c -80

НОТА! Процессы, которые в данный момент находятся во сне, не будут двигаться. Они должны быть разбужены, чтобы планировщик поместил их в другой процессор. Чтобы увидеть это, выберите ваш любимый спящий процесс в вышеупомянутом list- процессе, скажем, в веб-браузере, который, как вы думали, должен быть на ЦПУ 0-2, но все еще на 3. Используя свой идентификатор потока из приведенного выше списка, выполните:

kill -CONT <thread_id>

пример

kill -CONT 9812

Повторите команду ps и обратите внимание, что она перенесена на другой процессор.

ДВОЙНОЕ ПРИМЕЧАНИЕ! Некоторые потоки ядра не могут и не будут двигаться! Например, вы можете заметить, что на каждом процессоре есть поток ядра [kthreadd]. Назначение процессов в cgroups работает для процессов в пространстве пользователя, а не для потоков ядра. Это жизнь в мире многозадачности.

Теперь для перемещения процесса и всех его дочерних элементов в группу управления cpu_3:

pid=12566 # for example
cgclassify -g cpuset:cpu_3 $pid
taskset -c -p 3 $pid

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

Чтобы отменить все это, просто удалите созданные вами группы. Все вернутся в корневую группу:

cgdelete -r cpuset:cpu_3
cgdelete -r cpuset:not_cpu_3

Нет необходимости перезагружаться.

(Извините, я не понимаю 3-й вопрос из оригинального постера. Я не могу это комментировать.)

Ответ 4

Если вы используете экземпляр rhel, вы можете использовать Tuna для этого (может быть доступен и для других дистрибутивов Linux, но не уверен в этом). Его можно легко установить из команды yum. Тунец может использоваться для изоляции ядра процессора, и он динамически перемещает процессы, запущенные в этом конкретном процессоре, в соседний процессор. Команда для изоляции ядра процессора выглядит следующим образом:

# tuna --cpus=CPU-LIST --isolate

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