Высокопроизводительные VPN-концентраторы и узлы защищенной маршрутизации работают в условиях экстремальной динамики выделения памяти. При обслуживании десятков тысяч одновременных зашифрованных туннелей с суммарным трафиком от 40 до 100 Гбит/с сетевая подсистема Linux непрерывно выделяет и освобождает сокетные буферы (sk_buff), таблицы отслеживания соединений conntrack и криптографические очереди. При резких всплесках пакетов на полной скорости физическая память быстро исчерпывается, вынуждая ядро запускать процедуры высвобождения страниц памяти.
Более двадцати лет ядро Linux использовало классический алгоритм Least Recently Used (LRU) с двумя списками — Active и Inactive. Для серверов общего назначения эта модель была достаточной, однако на критичных к задержкам сетевых шлюзах она приводит к масштабным всплескам хвостовой задержки (tail latency). Когда объем свободной памяти опускается ниже порогового уровня (low watermark), ядро переходит в режим прямого высвобождения (direct reclamation): рабочие сетевые потоки зависают на десятки миллисекунд, проходя цепочки обратных отображений, что вызывает потери пакетов и срывы мультимедийных сессий.
Внедрение архитектуры Multi-Gen LRU (MGLRU, вошедшей в ядро Linux 6.1) в сочетании с механизмом Pressure Stall Information (PSI, появившимся в Linux 4.20) принципиально преобразило управление оперативной памятью на сетевых узлах. Эта связка обеспечивает поколенческое старение страниц, сканирование таблиц страниц без блокировок и мониторинг задержек на микросекундном уровне, гарантируя стабильную пропускную способность туннелей.
Проблемы классической схемы с двумя списками LRU
Чтобы понять причины задержек, необходимо рассмотреть устройство традиционного механизма с двумя списками:
Классический LRU с двумя списками:
[ Список Active (Горячие страницы) ] <--- Блокировка памяти (zone->lru_lock) ---> [ Список Inactive (Холодные страницы) ]
| |
Сканирование kswapd Сброс / Свопинг
(Проход по rmap O(N), нагрузка CPU) (Прямые задержки потоков)
Классическое разделение на списки Active и Inactive порождает четыре фундаментальные проблемы:
- Острая конкуренция за глобальную блокировку: Любые операции добавления, перемещения или вытеснения страниц требуют захвата общей блокировки
zone->lru_lock. Когда десятки процессорных ядер параллельно обрабатывают сетевые сокеты, потоки тратят такты процессора на ожидание освобождения мьютекса. - Накладные расходы обратного отображения (rmap): Для выяснения активности страницы в неактивном списке ядро проходит цепочки обратных указателей по всем адресным пространствам процессов. Этот процесс вызывает промахи кэша процессора и высокую утилизацию ядер.
- Грубая градация возраста: Страница считается либо горячей, либо холодной. У ядра нет градации по времени, чтобы отличить страницу, запрошенную 2 секунды назад, от страницы, простаивавшей 10 минут. В итоге активные структуры маршрутизации нередко преждевременно выгружаются.
- Потери пакетов из-за прямой очистки: Если демон
kswapdне успевает освобождать память при взрывном росте сетевого трафика, сетевые потоки принудительно переключаются на прямое высвобождение, блокируя пересылку пакетов ради дискового ввода-вывода.
Архитектура Multi-Gen LRU: Поколенческая сортировка
MGLRU заменяет примитивное деление на два списка структурированным многоуровневым конвейером поколений. Страницы распределяются по дискретным поколениям с инкрементным счетчиком:
Конвейер поколений MGLRU:
[ Поколение N (Новое) ] ---> [ Поколение N-1 ] ---> [ Поколение N-2 ] ---> [ Поколение N-3 (Старое) ]
^ |
|---------- Повышение поколения при обращении ----------------------------|
v
Упорядоченный сброс
(Без остановки пакетов)
Смена поколений и сканирование таблиц без блокировок
Вместо медленного перебора цепочек rmap алгоритм MGLRU применяет механизм page-table walker, считывающий аппаратные биты доступа (Accessed bits) непосредственно из записей страниц (PTE). Сканирование выполняется без глобальных блокировок с использованием механизма RCU (Read-Copy Update):
- Повышение статуса: При поступлении зашифрованного пакета обращение к данным переводит страницу в самое молодое поколение (
max_seq). Неиспользуемые страницы плавно смещаются к старым поколениям (min_seq). - Точечное вытеснение холодных страниц: Очистке подвергаются исключительно страницы самого старого поколения (
min_seq). Разница междуmax_seqиmin_seqотражает фактический возраст страниц, сохраняя активные сетевые буферы в памяти. - Сбалансированное вытеснение: Алгоритм рассчитывает соотношение между файловым кэшем и анонимной памятью без грубых эвристик swappiness, защищая адресные пространства криптографических демонов.
Мониторинг задержек через Pressure Stall Information (PSI)
Для предотвращения деградации сетевых служб операторам шлюзов необходим ранний сигнал о перегрузке памяти. Для этого применяется интерфейс PSI ядра Linux.
PSI измеряет долю времени, в течение которого процессы вынужденно простаивают в ожидании ресурсов памяти:
/proc/pressure/memory:
some avg10 = 0.05 avg60 = 0.02 avg300 = 0.00 total = 142083
full avg10 = 0.00 avg60 = 0.00 avg300 = 0.00 total = 0
- Метрика
some: Процент времени, когда хотя бы один поток заблокирован из-за нехватки памяти, пока остальные ядра продолжают работу. - Метрика
full: Процент времени, когда все активные потоки ядра заблокированы в ожидании памяти. На сетевом шлюзе любое ненулевое значениеfullозначает неизбежный риск потери пакетов.
Интерфейс PSI позволяет процессам регистрировать события через системные вызовы epoll(2) или select(2) непосредственно на файле /proc/pressure/memory. При превышении заданного порога задержки (например, 15 мс за 500 мс) ядро мгновенно пробуждает мониторинговый сервис.
Практическая настройка MGLRU и PSI на шлюзах
Для включения MGLRU в современных дистрибутивах с ядром 6.1 или новее убедитесь в наличии параметров компиляции:
# Проверка поддержки MGLRU в ядре
zcat /proc/config.gz | grep CONFIG_LRU_GEN
CONFIG_LRU_GEN = y
CONFIG_LRU_GEN_ENABLED = y
Динамическое управление параметрами через sysfs
Параметры работы подсистемы настраиваются через виртуальную файловую систему /sys/kernel/mm/lru_gen:
# Значения битовой маски:
# Бит 0 (1): Включение MGLRU для сканирования таблиц страниц
# Бит 1 (2): Включение многопоточного сканера таблиц
# Бит 2 (4): Включение раздельного учета поколений по NUMA-узлам
# Значение 7 (1 + 2 + 4): Полное аппаратное ускорение
echo 7 > /sys/kernel/mm/lru_gen/enabled
# Установка минимального времени жизни поколения (например, 2000 мс)
# Предотвращает вытеснение страниц при кратковременных всплесках пакетов
echo 2000 > /sys/kernel/mm/lru_gen/min_ttl_ms
Асинхронный перехват событий PSI на Python
Служебный демон VPN может перехватывать сигналы давления памяти для своевременной балансировки потоков или сброса второстепенных буферов:
import select, os
# Открытие дескриптора давления памяти в неблокирующем режиме
psi_fd = os.open("/proc/pressure/memory", os.O_RDWR | os.O_NONBLOCK)
# Регистрация триггера: уведомление при задержке более 15 мс за окно 500 мс
trigger = "some 15000 500000"
os.write(psi_fd, trigger.encode("ascii"))
epoll = select.epoll()
epoll.register(psi_fd, select.POLLPRI)
print("Мониторинг задержек памяти PSI запущен...")
try:
while True:
events = epoll.poll(timeout = 5)
for fd, event in events:
if event & select.POLLPRI:
print("[ТРЕВОГА] Порог PSI превышен! Зафиксировано давление на память.")
# Динамическая оптимизация сокетных буферов и сокращение conntrack
finally:
os.close(psi_fd)
Реализация в архитектуре RiceGuard VPN
Сетевой движок RiceGuard от VPNRice использует потенциал MGLRU и PSI на уровне инфраструктуры маршрутизации:
- Защита рабочих структур данных: Обработчики пакетов RiceGuard функционируют в срезах cgroup v2 с гарантированным резервированием
memory.min. Механизм MGLRU сохраняет дескрипторы активных сессий в старших поколениях, предотвращая сброс памяти при пиковой передаче файлов. - Упреждающее реагирование: Не дожидаясь срабатывания OOM-killer, управляющий сервис RiceGuard отслеживает показатели PSI. При росте метрики
someвыше 2% шлюз автоматически регулирует размеры буферов сокетов (SO_RCVBUF) и перенаправляет трафик по резервным каналам. - Исключение скачков хвостовой задержки: В стресс-тестах с нагрузкой 1,2 млн пакетов в секунду на 8 000 туннелей применение MGLRU позволило сократить вариацию задержки p99.9 с 48,2 мс до 1,4 мс по сравнению со старым ядром.
Часто задаваемые вопросы
Увеличивает ли MGLRU нагрузку на процессор маршрутизатора?
Нет. В тестах MGLRU снижает потребление CPU демоном kswapd на 15–40%, так как сканирование таблиц страниц без блокировок устраняет ожидание на мьютексах и ресурсоемкий перебор обратных отображений.
Совместим ли MGLRU с ограничениями памяти cgroup v2?
Да, архитектура MGLRU полностью интегрирована с иерархией cgroup v2 (memory.high, memory.max). Вытеснение памяти внутри группы изолировано и не затрагивает таблицы соседних сетевых пространств имен.
Чем PSI отличается от классических счетчиков vmstat?
Устаревшие метрики вроде pgscan лишь подсчитывают количество затронутых страниц, не отражая влияние на производительность. PSI измеряет потерянное процессорное время, напрямую коррелирующее с задержками передачи сетевых пакетов.



