Решение проблемы cpuset и resource deadlock avoided

Развитие компьютерных технологий сопровождалось ростом количества ядер центральных процессоров, создающим возможность для распараллеливания вычислений. В зависимости от архитектуры, процессорные ядра могут иметь общие  ресурсы, например кэш второго уровня. Распределение процессов и их потоков по ядрам с учетом данных особенностей позволит немного повысить производительность системы, например, за счет обеспечивания постоянного нахождения одних и тех же инструкций в кэше процессора.
Современные операционные системы позволяют эффективно использовать ресурсы компьютера,  распределяя выполнение процессов и их потоков (threads) по отдельным ядрам процессоров. Данная привязка может задаваться вручную для ограничения нагрузки на процессор либо повышения скорости отклика приложений. Например, можно выделить целый процессор или отдельное его ядро для клетки (jail), виртуальной машины (bhyve) или для обработчиков прерываний сетевой карты. Грамотное распределение ресурсов позволит повысить производительность, надежность и отзывчивость системы.
Для управления привязкой прерываний, процессов и их потоков во FreeBSD разработана утилита cpuset, которая достаточно проста в использовании. В качестве аргументов она принимает идентификатор процесса (jail и т.п.) и номера ядер процессора либо номер группы ядер, к которым его нужно привязать. Возможно создавать до 255 разных групп процессорных ядер и привязывать к ним процессы, прерывания и т.п. На этапе запуска системы для всех процессов, прерываний и т.п. создается одна группа под номером 1, в которую включены все имеющиеся ядра.
Во FreeBSD 11.3  для клеток (jail), на этапе их старта, системой автоматически создается собственный набор разрешенных ядер, в котором по умолчанию разрешены все имеющиеся в системе ядра. Используется ли такой подход на всех версиях FreeBSD, начиная с момента внедрения утилиты cpuset, я не готов сказать. Поэтому это следует проверять.
Так вот в ходе указания через cpuset конкретных ядер для клетки (jail), в которой у меня крутился единственный сервис — DNS сервер bind 9.16, я получил ошибку resource deadlock avoided.

jls
  JID  IP Address      Hostname                      Path
    1  192.168.0.10    srv-dns.local                 /home/jails/dns
cpuset -l 0-1 -j 1
cpuset: setaffinity: Resource deadlock avoided

В ходе подробного изучения манов по cpuset стало понятно, что во FreeBSD привязку к ядрам процессора могут иметь не только сами процессы, а также созданные ими потоки (threads). Если процессу разрешено работать с 1-ое по 5-ое ядра, а один из его потоков привязан, например, только к 5 ядру, и мы сходу привязываем этот процесс к ядрам за исключением 5-го,  то появляется данная ошибка. Для избежания ее появления, необходимо сначала привязать потоки процесса к двум ядрам — текущему и желаемому, а потом еще раз выполнить команду с указанием желаемого ядра. Данная ситуация применима как для системных процессов (например, созданных ядром системы), так и для прикладных.
Как оказалось демон named автоматически создает потоки в количестве, равном количеству имеющихся ядер в системе, и привязывает каждый поток к конкретному ядру процессора. При этом, настройки, меняющие указанное поведение, отсутствуют.

procstat -aS | grep named
 PID    TID COMM                TDNAME              CPU CSID CPU MASK
24032 100124 named               isc-net-0000         -1    2 0
24032 100851 named               -                    -1    2 0-5
24032 100915 named               isc-net-0001         -1    2 1
24032 101064 named               isc-net-0002         -1    2 2
24032 101093 named               isc-net-0003         -1    2 3
24032 101094 named               isc-net-0004         -1    2 4
24032 101099 named               isc-net-0005         -1    2 5
24032 101101 named               isc-worker0000       -1    2 0
24032 101102 named               isc-worker0001       -1    2 1
24032 101103 named               isc-worker0002       -1    2 2
24032 101104 named               isc-worker0003       -1    2 3
24032 101109 named               isc-worker0004       -1    2 4
24032 101114 named               isc-worker0005       -1    2 5
24032 101121 named               isc-timer            -1    2 0-5
24032 101122 named               isc-socket-0         -1    2 0
24032 101129 named               isc-socket-1         -1    2 1
24032 101132 named               isc-socket-2         -1    2 2
24032 101137 named               isc-socket-3         -1    2 3
24032 101142 named               isc-socket-4         -1    2 4
24032 101150 named               isc-socket-5         -1    2 5

Для упрощения привязки клеток (jail) к ядрам процессора я написал скрипт, код которого будет приведен чуть ниже. Запускается он для каждой клетки отдельно, после ее старта. Для этого в файле /etc/jail.conf для каждой клетки я задал параметр exec.poststart = "/root/hosting/jail_cpuset.sh 1 4-5";.

#!/bin/sh

PS_BIN="/bin/ps"
AWK_BIN="/usr/bin/awk"
EGREP_BIN="/usr/bin/egrep"
CPUSET_BIN="/usr/bin/cpuset"

# Параметры командной строки: идентификатор клетки и номера ядер процессора
jail_id=$1
jail_cpus=$2

if ! test $jail_id; then
       echo "error: jail_id is empty"
       exit 1
fi

if ! test $jail_cpus; then
       echo "error: jail_cpus is empty"
       exit 1
fi

# Получаем PID всех запущенных в клетке процессов
jail_pids=`$PS_BIN -ax -J $jail_id -o pid=`
# Получаем номер созданной для клетки группы ядер
jail_cpuset=`jls -j $jail_id cpuset.id`
cpu_count=`sysctl -n kern.smp.cpus`
cpu_count=$(($cpu_count-1))

# reset jail cpuset
$CPUSET_BIN -l "0-$cpu_count" -j $jail_id
for pid in $jail_pids; do
       $CPUSET_BIN -l "0-$cpu_count" -p $pid
done

# set jail cpuset
$CPUSET_BIN -l $jail_cpus -j $jail_id

exit 0

Скрипт достаточно прост, поэтому в дополнительных комментариях не нуждается. Если по заметке появятся какие-либо вопросы или замечаия, то прошу писать в комменты либо на форум.

Добавить комментарий

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
Яндекс.Метрика