Подробный гайд по оптимизации малой памяти в embedded-системахz: swap + zram
Тема, является одной из самых интересных и сложных в области оптимизации embedded-систем, IoT-устройств и специализированных серверов. Когда у нас есть дефицит оперативной памяти (RAM), а энергонезависимая память (eMMC, SD-карта, NAND) либо отсутствует, либо крайне медленна и ограничена по ресурсу перезаписи (TBW), классический swap на диск неприменим.
Связка zswap + zram позволяет создать двухуровневую иерархию подкачки исключительно в быстрой оперативной памяти, максимизируя эффективность использования каждого мегабайта RAM и минимизируя нагрузку на CPU.
Ниже представлен подробный технический гайд по настройке, тюнингу и мониторингу этой связки.
1. Архитектурная концепция: Почему именно zswap + zram?
Чтобы понять, как это настраивать, нужно понять, как эти компоненты взаимодействуют:
1. zram (Второй уровень / Бэкинг-девайс):
Создает в RAM виртуальный блочный устройство, которое сжимает данные. В нашем случае оно выступает в роли физического раздела подкачки.
2. zswap (Первый уровень / Фронтенд-кэш):
Это легковесный кэш для страниц подкачки. Он перехватывает страницы, которые ядро хочет отправить в swap, сжимает их и кладет в динамический пул памяти (zpool).
3. Синергия:
Когда динамический пул zswap переполняется, он вытесняет (evict) наименее используемые (LRU) страницы на бэкинг-девайс — то есть на zram.
Преимущества перед просто zram:
zswapиспользует более умные алгоритмы вытеснения (LRU) и пулы памяти, распределяемые на каждый CPU (per-CPU pools), что снижает блокировки (lock contention) в многопоточных средах.zswapможет отбрасывать страницы, которые сжимаются плохо (не достигают заданного коэффициента), отправляя их напрямую вzramили вообще отказываясь от их кэширования, экономя CPU.
2. Требования к ядру (Kernel Configuration)
Для работы связки в ядре Linux должны быть включены следующие опции:
CONFIG_ZRAM=m (или =y)
CONFIG_ZSWAP=y
CONFIG_ZPOOL=y
CONFIG_ZSMALLOC=y
# Необходимы алгоритмы сжатия (выберите нужные):
CONFIG_CRYPTO_LZ4=y (или =m)
CONFIG_CRYPTO_ZSTD=y (или =m)
CONFIG_CRYPTO_LZO=y (или =m)
Примечание:
В современных ядрах (5.x+) frontswap интегрирован в zswap, поэтому отдельная опция CONFIG_FRONTSWAP может отсутствовать.
3. Пошаговая настройка
В embedded-системах обычно нет systemd в полном объеме, поэтому настройка часто делается через init-скрипты (OpenRC, procd, busybox init) или udev-правила. Ниже приведен универсальный shell-скрипт, который можно адаптировать.
Шаг 1: Настройка zram (Бэкинг-девайс)
Создаем устройство zram, выделяем под него память и активируем как swap.
#!/bin/sh
# Параметры для zram
ZRAM_DEV="/dev/zram0"
# Размер zram. Для embedded обычно ставят 50%-100% от объема физической RAM.
# Пусть у нас 512 МБ RAM, зададим zram на 256 МБ.
ZRAM_SIZE="256M"
# Алгоритм сжатия для zram.
# ВАЖНО: см. раздел про "Двойное сжатие" ниже.
ZRAM_ALG="lz4"
# Загружаем модуль (если не вкомпилирован в ядро)
modprobe zram num_devices=1
# Настраиваем алгоритм и размер
echo $ZRAM_ALG > /sys/block/zram0/comp_algorithm
echo $ZRAM_SIZE > /sys/block/zram0/disksize
# Форматируем и активируем swap с высоким приоритетом
mkswap $ZRAM_DEV
swapon -p 100 $ZRAM_DEV
Шаг 2: Настройка zswap (Фронтенд-кэш)
Теперь включаем zswap и указываем ему параметры. zswap автоматически подхватит активный swap-девайс (zram0) в качестве бэкинга.
# Включаем zswap
echo 1 > /sys/module/zswap/parameters/enabled
# Алгоритм сжатия для zswap (должен быть быстрым)
echo lz4 > /sys/module/zswap/parameters/compressor
# Используемый пул памяти (zsmalloc оптимален для сжатых страниц)
echo zsmalloc > /sys/module/zswap/parameters/zpool
# Максимальный размер пула zswap в % от общей RAM.
# Для малой памяти ставим 20-30%, чтобы оставить место для приложений.
echo 20 > /sys/module/zswap/parameters/max_pool_percent
4. Глубокий тюнинг (Sysctl и Embedded-специфика)
Для устройств с малой памятью стандартные настройки ядра не подходят.
Добавьте следующие параметры в /etc/sysctl.conf или примените их через sysctl -w:
# 1. Swappiness
# В классической системе 60. В embedded с zswap+zram можно оставить 60
# или даже увеличить до 80-100, так как swap теперь "быстрый" (в RAM).
# Но если CPU очень слабый, лучше снизить до 10-20, чтобы избежать лишнего сжатия.
vm.swappiness = 60
# 2. Page Cluster
# Определяет, сколько страниц читать за раз при swap-in (2^page-cluster).
# На SD-картах ставили 0. В RAM (zram) последовательное чтение не дает выигрыша,
# а случайное чтение из сжатой памяти быстрее. Ставим 0 или 1.
vm.page-cluster = 0
# 3. Водяные знаки (Watermarks)
# В embedded часто не хватает highmem. Увеличиваем min_free_kbytes,
# чтобы ядро раньше начинало освобождать память, не доводя до прямого reclaim.
vm.min_free_kbytes = 8192 # (8 МБ, корректировать под объем RAM)
# 4. OOM Killer
# В embedded лучше убить один процесс, чем получить kernel panic.
vm.oom_kill_allocating_task = 1
5. Критический нюанс: Проблема "Двойного сжатия"
Когда zswap вытесняет страницу в zram, эта страница уже сжата. Если zram настроен на сжатие, он попытается сжать уже сжатые данные.
Это приводит к двум проблемам:
- Лишняя трата CPU (процессор пытается сжать энтропийные данные).
- Увеличение фрагментации памяти внутри
zram.
Решения:
1. Использовать один алгоритм:
Установить в zswap и zram один и тот же алгоритм (например, lz4). Ядро zram достаточно умно, чтобы распознать, что данные уже сжаты алгоритмом lz4, и просто скопировать их (или сжать с коэффициентом 1:1, потратив минимум CPU).
2. Отключить сжатие в zram (если ядро позволяет):
В некоторых кастомных ядрах для embedded можно передать данные в zram без сжатия, но в стандартном mainline zram всегда сжимает. Поэтому рекомендация: используйте lz4 или lzo в обоих компонентах. Не используйте zstd в zram при наличии zswap, так как zstd очень прожорлив до CPU при декомпрессии.
6. Мониторинг и отладка
В embedded-системах важно понимать, что происходит с памятью, чтобы избежать OOM.
Статистика zswap:
cat /sys/kernel/debug/zswap/*
Обратите внимание на:
stored_pages— сколько страниц сейчас в кэше zswap.pool_total_size— реальный объем RAM, занятый пулом zswap (в байтах).reject_alloc_fail— если это значение растет, значит, пул zswap переполнен, и ядро не может выделить память даже для метаданных. Уменьшитеmax_pool_percentили увеличьте RAM.reject_compress_poor— страницы, которые плохо сжимаются и были отправлены напрямую в zram. Если значение огромное, смените алгоритм сжатия.
Статистика zram:
zramctl
# или
cat /sys/block/zram0/mm_stat
Смотрите на orig_data_size и compr_data_size. Коэффициент сжатия должен быть адекватным (обычно 2x - 3x для текстовых/бинарных данных).
Общая статистика swap:
cat /proc/vmstat | grep -E "pswp|zswap"
7. Рекомендации по выбору алгоритмов сжатия
Для embedded-систем выбор алгоритма — это баланс между CPU и RAM:
| Алгоритм | Скорость CPU | Коэффициент сжатия | Рекомендация для embedded |
|---|---|---|---|
| lzo / lzo-rle | Очень высокая | Средний (1.5x - 2x) | Идеально для очень слабых CPU (ARM Cortex-A5/A7, MIPS). Минимальная задержка. |
| lz4 | Высокая | Средний/Хороший (2x) | Золотой стандарт. Отличный баланс. Рекомендуется для zswap и zram. |
| zstd | Средняя/Низкая | Отличный (2.5x - 3x+) | Использовать только если CPU достаточно мощный (Cortex-A53 и выше), а RAM критически не хватает. |
8. важно
- [ ] Убедиться, что в ядре есть
zswap,zramи нужные крипто-модули. - [ ] Создать
zram, задать размер (50-100% от RAM) и алгоритм (например,lz4). - [ ] Активировать
zramкак swap с высоким приоритетом. - [ ] Включить
zswap, задать алгоритм (lz4), пул (zsmalloc) и лимит памяти (20-30%). - [ ] Настроить
sysctl(vm.page-cluster=0, адекватныйswappiness). - [ ] Провести стресс-тест (например,
stress-ng --vm 4 --vm-bytes 80% --vm-method all) и проверитьdmesgна наличие OOM и/sys/kernel/debug/zswap/на наличие ошибок аллокации.
Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.