Масштабные ARM-серверы столкнулись с классической бедой шумных соседей: один прожорливый процесс вымывает кеш и полосу памяти, а соседние службы отвечают медленнее без единого изменения в коде. Расширение MPAM (Memory Partitioning and Monitoring) из ARMv8.4 создано для такого случая: оно помечает каждое обращение к памяти, делит кеш и полосу на партиции и следит за потреблением в железе. Поддержка в ядре Linux появилась недавно, и вокруг неё накопились неочевидные подробности, от регистров PARTID и PMG до капризов прошивки. Дальше: устройство расширения, версии ядра, проверка и разметка ресурсов через resctrl.
MPAM в архитектуре ARMv8.4 и разметка трафика PARTID и PMG
MPAM относится к необязательным расширениям ARMv8.4, и идея у него обманчиво проста: каждое обращение к памяти снабжается парой чисел. PARTID (Partition identifier) отвечает за партиционирование, PMG (Performance Monitoring Group identifier) за мониторинг. Обе пары хранятся в регистрах MPAM1_EL1 и MPAM0_EL1: под PARTID выделено по 16 бит в полях PARTID_I и PARTID_D, под PMG по 8 бит в PMG_I и PMG_D. Потолок задаёт MPAMIDR_EL1, где поле PARTID_MAX шириной 16 бит обещает до 65536 значений PARTID, а восьмибитное PMG_MAX до 256 значений PMG. Бит MPAMEN в MPAM1_EL1 включает разметку, ядро перезаписывает пары идентификаторов на каждом переключении задач, и каждый процесс несёт свою метку. В терминах resctrl PARTID выступает CLOSID, а PMG – RMID.
Вторая половина живёт в памяти системы. Принимающее метки устройство называется MSC, memory system component: MMIO-блок в кеше или контроллере памяти. Карта регистров MSC стандартизирована спецификацией ARM с номером IHI0099B.a: MPAMF_IDR по смещению 0x0000 описывает возможности устройства, MPAMCFG_CPBM 0x1000 принимает маску порций кеша, MPAMCFG_MBW_MAX 0x0208 ставит потолок полосы, счётчики читаются из MSMON_CSU 0x0840 и MSMON_MBWU 0x0860, а для полосы есть 64-битный MSMON_MBWU_L 0x0880. На практике всё сходится просто: кеш отдаёт процессу только закреплённые за ним порции (управление CPOR, cache portion bitmap), контроллер памяти ограничивает полосу (MBW_MAX), счётчики CSU копят занятость кеша, MBWU потребление полосы. Человеческий интерфейс один: файловая система resctrl.
Путь поддержки MPAM в ядре Linux от версии 7.0 до 7.1
История долгая: первые RFC-патчи Джеймс Морс из Arm показал в 2020 году, а в главное дерево код попал в 2026-м, в два захода. Обобщённую часть resctrl, годами росшую внутри x86-части дерева, вынесли в каталог fs/resctrl, где теперь живут rdtgroup.c, ctrlmondata.c, monitor.c и pseudo_lock.c. Ядро 7.0 получило низкоуровневый слой: заголовок include/linux/arm_mpam.h и драйвер обнаружения drivers/resctrl/mpam_devices.c, который по описанию из ACPI находит все MSC. Ядро 7.1 добавило drivers/resctrl/mpam_resctrl.c и код arch/arm64/kernel/mpam.c, после чего resctrl заработал на arm64, а линейка 7.x несёт поддержку целиком.
Включается всё через конфигурацию сборки. Опция CONFIG_ARM64_MPAM в меню возможностей ARMv8.4 подтягивает драйвер ARM64_MPAM_DRIVER, а тот при наличии ACPI тянет парсер таблицы CONFIG_ACPI_MPAM и поддержку почтового канала PCC. Опция ARM64_MPAM_RESCTRL_FS включается по умолчанию, как только есть драйвер и файловая система, и выбирает режим RESCTRL_RMID_DEPENDS_ON_CLOSID, потому что в MPAM пространство мониторинговых индексов привязано к PARTID: индекс равен closid, умноженному на (pmg_max + 1), плюс rmid. В тестировании участвовали инженеры Ampere Computing, а драйвер обзавёлся обходом erratum T241-MPAM-1 для чипа NVIDIA T241. Осталось смонтировать файловую систему, но здесь начинаются тонкости.
Проверка поддержки и монтирование resctrl на ARM64
Диагностика сводится к последовательности шагов:
1. убедиться, что ядро собрано с нужными опциями, командой zcat /proc/config.gz | grep -E "ARM64_MPAM|RESCTRL_FS" либо просмотром файла /boot/config-$(uname -r);
2. пролистать загрузочные сообщения через dmesg | grep -i mpam, помня, что без отладочного параметра dyndbg="file mpam_resctrl.c +pl" драйвер почти молчит;
3. вывести топологию кешей командой lscpu -C, чтобы знать уровни L1d, L1i, L2, L3 и номера cache id, а раскладку по NUMA узлам через lscpu --extended;
4. смонтировать файловую систему mount -t resctrl resctrl /sys/fs/resctrl, добавив при необходимости опции cdp или debug;
5. заглянуть в каталог /sys/fs/resctrl/info и выяснить, какие схемы ядро экспонировало.
На ARM64 в списке флагов процессора MPAM не виден: разметка памяти не дотягивается до пользовательского пространства. На x86 флаги cat_l3, cdp_l3 и mba прямо видны в выводе lscpu | grep Flags, поэтому честный тест на ARM-платформе – попытка монтирования. Неудачный mount объясняет себя в dmesg: No MSC devices found in firmware означает, что таблица ACPI не описала ни одного устройства, а сообщения вида MPAM disabled due to ... сигналят принудительное отключение.
Каталог info раскрывает платформу по ресурсам: info/L3/num_closids выдаёт число доступных PARTID (драйвер берёт partid_max + 1 из MPAMIDR_EL1). В info/L3/cbm_mask лежит маска на сто процентов кеша, например ffffffff при 32 порциях, а info/L3/min_cbm_bits на MPAM всегда равен 1, так что годится даже одиночный бит. Для полосы сведения лежат в info/MB/min_bandwidth и info/MB/bandwidth_gran, для мониторинга в info/L3_MON/num_rmids и mon_features, где обычно перечислены только llc_occupancy и mbm_total_bytes. Для глубокой отладки пригодится debugfs: после mount -t debugfs none /sys/kernel/debug появляется каталог /sys/kernel/debug/resctrl.
Разделение кеша и полосы памяти через schemata
Группа создаётся командой mkdir внутри /sys/fs/resctrl, и у неё сразу появляются файлы tasks, cpus, schemata и mode. Допустим, кеш содержит 32 порции, о чём говорит маска ffffffff в info/L3/cbm_mask. Следующие команды закрепляют за группой p0 двенадцать порций с битами с 8 по 19 и ставят потолок полосы в шестьдесят процентов:
mkdir /sys/fs/resctrl/p0
echo "L3:0=000fff00" > /sys/fs/resctrl/p0/schemata
echo "MB:0=60" > /sys/fs/resctrl/p0/schemata
echo 4311 > /sys/fs/resctrl/p0/tasks
Группу по умолчанию тоже стоит ограничить явно:
echo "L3:0=fff000ff" > /sys/fs/resctrl/schemata
Если чужие порции должны быть неприкосновенны, включается режим echo exclusive > p0/mode. При пересечении масок ядро вернёт Invalid argument, а файл info/last_cmd_status объяснит причину текстом schemata overlaps. Раскладку покажет bit_usage, где S означает разделяемую порцию, E эксклюзивную, а p0/size отдаёт размер в байтах, например 12582912 при двенадцати порциях по мегабайту. Привязка идёт двумя способами: номер процесса в tasks переносит задачу в группу, маска в cpus, например echo f0 > p0/cpus, переносит целые ядра вместе с потоками ядра; для устойчивости задачи закрепляют утилитой taskset.
Полоса задаётся в процентах, и точность зависит от числа дробных бит bwa_wd: шаг равен ста, делённому на два в степени bwa_wd с округлением вверх, при шести битах выходит 2 процента, при семи 1 процент. Железо хранит значение в фиксированной точке с двоичной запятой между битами 15 и 16, пересчёт драйвер выполняет сам. Разделение кода и данных подключается опцией cdp при монтировании: инструкции и данные уносятся с разными PARTID из полей PARTID_I и PARTID_D. Ядро предупреждает, что CDP может вызвать сбои MPAM, а число мониторинговых групп сокращается вдвое.
Мониторинг занятости кеша и потребления полосы
Число групп в info/L3_MON/num_rmids на MPAM считается по формуле (pmg_max + 1) × (partid_max + 1). Если прошивка сообщила PARTID_MAX равным 15 и PMG_MAX равным 15, получится 16 × 16, то есть 256 групп; включённый CDP делит результат пополам. Умножение не случайно: мониторинг привязан к паре PARTID и PMG.
Группы мониторинга заводятся внутри каталогов mon_groups:
mkdir /sys/fs/resctrl/mon_groups/m01
echo 5678 > /sys/fs/resctrl/mon_groups/m01/tasks
cat /sys/fs/resctrl/mon_groups/m01/mon_data/mon_L3_00/llc_occupancy
16234000
Число 16234000 означает занятость порядка 16 мегабайт в L3 домена 00. Событие mbm_total_bytes в соседнем файле копит суммарную полосу в байтах. А вот привычного по x86 события mbm_local_bytes здесь не будет: MPAM не отделяет локальный трафик от глобального, поэтому ядро экспонирует только суммарное событие. Внутри MSC мониторинг настраивается парами регистров MSMON_CFG_MBWU_FLT и MSMON_CFG_MBWU_CTL, число CSU-мониторов читается из поля NUM_MON MPAMF_CSUMON_IDR, а драйвер при выборе MB-ресурса предпочитает MSC, ближайший к L3, чтобы цифры соответствовали трафику, уходящему из кеша.
Мониторинг доступен и без разметки: если аппаратно есть только счётчики, resctrl смонтируется, но контролирующую группу создать не выйдет, зато mon_groups в корне позволит профилировать задачи заранее. Файл max_threshold_occupancy задаёт порог, выше которого освободившийся счётчик llc_occupancy переиспользуется. Для согласованных операций предусмотрен flock: команда flock -s /sys/fs/resctrl find /sys/fs/resctrl снимет непротиворечивый снимок каталогов.
Ловушки прошивки и виртуализации в работе MPAM
Самые коварные проблемы лежат ниже ядра. Платформа описывает MSC таблицей ACPI с подписью MPAM, и без неё драйверу нечего обнаруживать. В регистре MPAM3_EL3 живут бит MPAMEN и бит TRAPLOWER, чьё сбросовое значение равно единице: доступы к MPAM-регистрам из нижних уровней перехватывает EL3. Прошивка обязана сбросить этот бит либо эмулировать отсутствие MPAM, иначе ядро поверит ID-регистрам, полезет в регистры, поймает трапы в EL3 и загрузка рухнет. Для таких машин есть параметр командной строки arm64.nompam: ядро считает процессоры лишёнными MPAM и не трогает системные регистры.
Вторая ловушка: arm64.nompam не выключит MPAM на рабочей машине. Трапы в MPAM2_EL2 и MPAMHCR_EL2 остаются с непредсказуемыми сбросовыми значениями, гипервизор KVM по-прежнему прячет MPAM от гостей, но перестаёт включать блокирующие трапы, и гость может писать MPAM0_EL1, MPAM1_EL1 и MPAMSM_EL1. Ядро эти регистры не сохраняет, поэтому записанное одним гостем значение действует для следующего гостя и даже для хоста.
Гетерогенность тоже портит жизнь: если кластеры big.LITTLE несут кеши с разными возможностями CPOR, поддержка откатывается, ведь разметка зависела бы от размещения задачи. Контроль полосы на NUMA-узле без процессоров и без соответствующего L3 лишает систему схемы MB и события mbm_total_bytes. Часть MSC общается через почтовый канал PCC, на что намекает сообщение Failed to request MSC PCC channel. Ошибки устройств приходят прерываниями строкой вида error irq from msc:0, partid:5, pmg:2, ris:1, где ris различает экземпляры ресурса внутри MSC.
Практический сценарий выделения кеша под критичную нагрузку
Типовой рецепт для сервера, где база данных делит платформу с пакетной аналитикой, занимает минуту. Допустим, L3 имеет 32 порции, а критичной базе хочется двенадцать порций и шестьдесят процентов полосы:
mount -t resctrl resctrl /sys/fs/resctrl
mkdir /sys/fs/resctrl/db /sys/fs/resctrl/batch
echo "L3:0=000fff00" > /sys/fs/resctrl/db/schemata
echo "MB:0=60" > /sys/fs/resctrl/db/schemata
echo "L3:0=00000fff" > /sys/fs/resctrl/batch/schemata
echo "MB:0=30" > /sys/fs/resctrl/batch/schemata
echo exclusive > /sys/fs/resctrl/db/mode
Процессы базы переносятся в группу db записью номеров в tasks, аналитика уезжает в batch, критичные потоки закрепляются утилитой taskset. Понедельные снимки llc_occupancy показывают реальный рабочий набор базы, а mbm_total_bytes выявляет, кто давит на контроллер памяти. Разница до и после разметки бывает разительной: задержки хвостовых запросов перестают ходить за аналитическими заданиями, и шумные соседи переходят из категории жалоб в категорию настроек.
Перспективы обнадёживающие. resctrl пока мониторит только на уровне L3, поэтому часть MBWU-мониторов на контроллерах памяти остаётся за кадром, а режим назначаемых счётчиков полосы, знакомый по x86 под именем ABMC, для MPAM ещё не задействован. Оба направления обозначены в комментариях драйвера, так что будущие версии ядра обещают больше цифр.
Смысл затеянного короткий: MPAM переводит делёжку кеша и полосы из области уговоров nice в область железных гарантий. Проверка занимает минуты, разметка умещается в десяток команд, а выигрыш измеряется предсказуемостью хвостовых задержек. Кто однажды ограничил шумного соседа на уровне железа, к уговорам возвращается неохотно.