Линия PCIe под устройством редко работает без пауз, и именно на паузах спецификация предлагает экономить. Механизм ASPM переводит соединение в одно из спящих состояний, когда по нему не идут пакеты, и возвращает его в активный режим по первому запросу. Плата за экономию всегда одна и та же: задержка на пробуждение линии, порой ощутимая для быстрого накопителя или сетевой карты. Ядро Linux управляет этой настройкой централизованно через понятие политики, а посмотреть и поменять ее можно прямо из sysfs, без перезагрузки. В статье разбираются состояния L0s и L1, поля LnkCap и LnkCtl из вывода lspci, числовая оценка цены L1 для задержек NVMe и практические сценарии переключения политики. Отдельное внимание уделено подпиткам L1.1 и L1.2 и проверке результата измерениями powertop, turbostat и fio.
Состояния линии L0s и L1 и обещания спецификации PCIe по задержкам
Полное название механизма звучит как Active State Power Management, сокращенно ASPM. Стандарт описывает лестницу состояний линии: L0 означает полностью активное соединение, L0s мелкую паузу, L1 глубокий сон, а L2 и L3 относятся уже к обесточиванию компонента целиком. В L0s приемник сохраняет генератор и фазировку, и на восстановление уходят наносекунды. В L1 питание основной части приемника снимается, при выходе линк заново проходит тренировку, и задержка измеряется уже микросекундами. Для честной игры спецификация требует от устройства задекларировать собственные задержки выхода в регистре возможностей LnkCap. Кодировка устроена ступенями: для L0s значения лежат от 64 нс до величин свыше 4 мкс, для L1 от 1 мкс до величин свыше 64 мкс. Программное обеспечение читает эти цифры и включает только те состояния, которые укладываются в заявленные пределы. На практике L0s дает скромную экономию и нередко отключен производителем, а основная прибыль сидит в L1, поэтому спор обычно идет вокруг него.
Состояние включается только при согласии обеих сторон: у порта и у устройства свои регистры LnkCap, и ядро ориентируется на более строгую из двух деклараций. Отсюда частый казус: накопитель поддерживает L1, а соседний корневой порт нет, и линия остается в L0, хотя со стороны устройства все выглядит исправным. Проверять поэтому надо оба конца линии, а не только сам накопитель.
Политика ASPM в ядре Linux и файл policy в sysfs
Ядро прячет ручное управление регистрами за одной политикой, видимой в файле /sys/module/pcie_aspm/parameters/policy.
cat /sys/module/pcie_aspm/parameters/policy
[default] performance powersave powersupersave
Режим default повторяет конфигурацию, которую прошивка выставила на старте, performance выключает ASPM на всех линиях под управлением ядра, powersave включает L0s и L1 там, где устройства их заявляют, а powersupersave добавляет к этому глубокие подпитки L1. Тот же выбор доступен параметром загрузки pcie_aspm.policy=powersave, а полное отключение записывается как pcie_aspm=off. Нюанс, о котором забывают: право управлять ASPM прошивка может оставить себе, и тогда записи в файлы ничего не изменят. Косвенный признак такой ситуации ищется в журнале загрузки:
sudo dmesg | grep -i aspm
Если ядро сообщает, что управление осталось у BIOS, политику сначала меняют в настройках прошивки, и только после этого ядро подхватит линии. После смены политики ядро заново пересчитывает конфигурацию всех линий, включая горячеподключаемые устройства, поэтому эффект виден немедленно. Для локальной проверки результата берут фильтр из следующего раздела и сравнивают LnkCtl у конкретного устройства до и после смены. В настройках прошивки нужные пункты обычно называются ASPM, Native ASPM или PCIe ASPM, и для работы переключателей из sysfs должно быть разрешено управление со стороны операционной системы.
Разбор полей LnkCap и LnkCtl в выводе lspci -vv
Диагностика начинается с листинга конфигурационного пространства. Утилита lspci с ключом -vv печатает подробный разбор, и за энергосбережение линии отвечают поля LnkCap и LnkCtl. Для накопителя на шине команда выглядит так:
sudo lspci -vv -s 0000:03:00.0 | grep -E "LnkCap|LnkCtl"
LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM L1, Exit Latency L0s unlimited, L1 <16us
LnkCtl: ASPM L1 Enabled; RCB 64 bytes Disabled- CommClk+
В LnkCap слово ASPM перечисляет поддерживаемые состояния, здесь заявлена только L1. Запись Exit Latency расшифровывает ступени из первого раздела: L1 <16us означает, что выход укладывается в 16 микросекунд, а unlimited для L0s читается как более 4 мкс. В LnkCtl видно фактическое состояние, и фраза ASPM L1 Enabled говорит, что линия засыпает между запросами. Обзор всей машины удобнее снимать агрегирующим фильтром:
sudo lspci -vv | grep "LnkCtl:" | sort | uniq -c
Строки с ASPM L1 Enabled показывают линии, которые уже засыпают, а ASPM Disabled у большинства устройств на сервере обычно норма. Дергать регистр вручную через setpci смысла мало: ядро перепрограммирует линк при каждом событии и требует симметричной настройки обоих концов, поэтому все правки лучше вести через его интерфейсы.
Рядом с ASPM в листинге виден и флаг общего тактирования CommClk: когда тактовая частота общая с обеих сторон, выход из L1 проходит быстрее, и ядро охотнее включает глубокие состояния. Само спящее состояние поле LnkSta не показывает, там только текущие скорость и ширина линии, поэтому факт сна проверяют косвенно, по потреблению и таймингам.
Числовая оценка цены L1 для задержек NVMe и других устройств
Оценку выгоды и издержек удобно свести к числам. Возьмем линк с заявленной задержкой выхода из L1 до 16 мкс. Если нагрузка будит линк 50 раз в секунду, накладка составит 800 мкс на каждую секунду работы, то есть 0,08 процента времени, и заметить ее невозможно. При случайном чтении с накопителя на 2000 операций в секунду расчет другой: 2000 умножить на 16 мкс дает 32 мс в секунду, или 3,2 процента времени, и это уже видно в хвостах задержек. Для типичного NVMe с медианной задержкой чтения порядка 30-60 мкс добавка к хвосту способна вырасти на десятки процентов, а для накопителей с откликом в 8-10 мкс, выпускаемых под базы данных, доля еще выше. Зато при редких обращениях эффект для человека нулевой: лишние 16 мкс против времени нажатия клавиши в десятки миллисекунд не различить. Со звуком и видео сложнее: период буфера аудиопотока занимает единицы миллисекунд, запас формально есть, но отдельные чипсеты в связке с подпитками L1 дают щелчки, и линию контроллера звука лечат точечным отключением. У Wi-Fi модулей и гигабитных контроллеров Realtek история с нестабильностью при активном ASPM давняя, и драйверы давно научились накладывать квирки автоматически. Стоит помнить и о разделении слоев: у NVMe есть собственный механизм APST со своими таймаутами в миллисекундах, ASPM лежит ниже и на него не влияет. На серверах с требованиями к постоянной низкой задержке линк обычно держат в L0, на ноутбуках экономия перевешивает. Тот же расчет применим к звуку в числах: при частоте 48 кГц и буфере в 128 кадров период равен 2,67 мс, и даже сто пробуждений линии в секунду дают лишь 1,6 мс накладки, меньше двух десятых процента, что для непрерывного потока незаметно. По питанию картина обратная: активная линия тянет десятки милливатт, линия в L1 укладывается в единицы милливатт, а с подпитками уходит почти в ноль, и на четырех-пяти линиях ноутбука разница складывается в десятую долю ватта и больше.
Переключение политики и точечное управление отдельными линиями
Разовое переключение на живой системе занимает минуту, а осмысленный порядок действий такой:
- убедиться, что у нужных устройств в LnkCap вообще заявлена поддержка L1 и что текущая политика не performance;
- прочитать файл /sys/module/pcie_aspm/parameters/policy и запомнить, какой вариант помечен квадратными скобками;
- записать выбранную политику и убедиться, что маркер в скобках переехал на новое слово;
- для капризной линии выключить только ее L1 через атрибут link/l1_aspm у родительского порта и повторить измерение.
echo powersave | sudo tee /sys/module/pcie_aspm/parameters/policy
echo 0 | sudo tee /sys/bus/pci/devices/0000:03:00.1/link/l1_aspm
Каталог link/ существует у портов и мостов и управляет линией вниз к устройству, поэтому за накопителем на 03:00.0 обычно стоит корневой порт или мост 00:1c.x. Единица в атрибуте включает состояние, ноль выключает, а соседние файлы l1_1_aspm и l1_2_aspm отвечают за подпитки. Чтобы выбор пережил перезагрузку, его закрепляют в параметрах загрузчика.
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash pcie_aspm.policy=powersave"
После правки /etc/default/grub выполняется update-grub, в семействе Red Hat используется grub2-mkconfig -o /boot/grub2/grub.cfg. Альтернатива без правки загрузчика лежит в systemd-tmpfiles: строка сохраняется в файле /etc/tmpfiles.d/aspm-policy.conf и применяется на раннем этапе загрузки.
w /sys/module/pcie_aspm/parameters/policy - - - - powersave
Переключение действует сразу и не требует перезагрузки или перезапуска служб, устройства подхватывают новую конфигурацию на лету. Для горячей замены действует то же правило: при подключении нового устройства ядро применяет к его линии текущую политику, поэтому глобальный режим надежнее набора ручных записей.
Состояния L1.1 и L1.2 и их роль в экономии заряда ноутбука
Базовая L1 снимает питание лишь частично, и для ноутбука интереснее ее продолжения. Спецификация описывает подпитки L1.1 и L1.2 через отдельную структуру L1 PM Substates: в L1.1 останавливается часть цепей, в L1.2 гасится и тактирование, а пробуждение требует сигнала CLKREQ#. Задержка выхода из L1.2 типично укладывается в десятки микросекунд, зато потребление линии падает на порядки, и именно эта глубина открывает процессору дорогу в глубокие пакетные состояния C8 и C10. Без перевода всех линий PCIe в L1 процессор не досидит до C8, и батарея тает быстрее при той же загрузке. Управление точечное: у каждого порта в каталоге link/ лежат файлы l1_1_aspm и l1_2_aspm, глобально те же состояния включает powersupersave. Подвох в исполнении производителей: часть устройств декларирует подпитки, но работает с ними нестабильно, особенно Wi-Fi модули и отдельные видеокарты, и жалобы видны в dmesg в виде переподключений и исправлений счетчиков. Выигрыш на типичном ноутбуке составляет до полутора ватт в простое, что на фоне потребления платформы в 3-5 ватт очень весомо. Вход в подпитки регулирует и механизм LTR: устройства сообщают, какая задержка для них терпима, а порты на основе этих сообщений выставляют пороги, видимые в поле LnkCtl2. Если накопитель заявляет терпимость в сотни микросекунд, линия уходит в L1.2 почти сразу после затишья, а устройство с жестким требованием в единицы микросекунд останется на поверхности, и насильно утапливать его не стоит.
Проверка результата измерениями powertop, turbostat и fio
Любая правка энергосбережения заканчивается измерением, иначе решения принимаются на веру. Первый инструмент, powertop, на вкладке tunables показывает устройства, для которых L1 еще не активна, и позволяет щелкать по ним в реальном времени. Второй, turbostat, отвечает на главный вопрос ноутбука, добирается ли платформа до глубоких пакетных состояний: столбец Pkg%pc8 после десяти минут простоя честно покажет процент времени.
sudo powertop
sudo turbostat sleep 10
Третий инструмент проверяет накопитель: fio с глубиной очереди 1 и блоком 4 КБ выдает распределение задержек, и его снимают дважды, с включенной и выключенной L1.
sudo fio --name=lat --filename=/dev/nvme0n1 --rw=randread --bs=4k --iodepth=1 --ioengine=libaio --direct=1 --runtime=30 --time_based --group_reporting
Характерный фрагмент отчета выглядит так:
clat percentiles (usec):
| 50.00th=[ 28], 90.00th=[ 34], 95.00th=[ 38],
| 99.00th=[ 62], 99.90th=[ 141]
В секции lat сравнивают 95-й и 99-й процентили: сдвиг хвоста на 10-20 мкс при выключенной L1 означает, что для рабочей станции отклик дороже сэкономленного ватта. На ноутбуке с резервом производительности правильным решением чаще оказывается powersave или powersupersave. Ток в простое проверяют чтением /sys/class/power_supply/BAT0/power_now после выдержки, сняв по три показания до и после смены политики. Такой связкой вопрос ASPM перестает быть лотереей: видно и цену в микросекундах, и прибыль в ваттах, и решение по каждому устройству принимается на глазах, а не по привычке.