У памяти CXL есть тихая внутренняя работа, о которой администратор обычно вспоминает, только когда модуль уже забарахлил и держит данные в кулаках. Речь о патрульном скрабе. Контроллер устройства день и ночь перечитывает собственные ячейки, вылавливает биты, которые начали портиться, и чинит их кодом коррекции до того, как прикладная задача наткнётся на невидимый сбой. Логика "сломалось, отремонтировали" здесь не годится: скраб нужен ровно для того, чтобы слово "сломалось" никогда не прозвучало. С ядер 6.15 и 6.16 этим механизмом можно управлять прямо из Linux через sysfs подсистемы EDAC, а осматривать устройства удобнее всего утилитой cxl из пакета ndctl. Неожиданность поджидает сразу: команды cxl scrub в составе ndctl не существует, и это не недоработка. Управление скрабом живёт в sysfs, и по ходу статьи будет видно, какие атрибуты за это отвечают и как их трогать руками.

Патрульный скраб и его место в механизмах RAS у CXL памяти

Спецификация CXL описывает эту возможность как Device Patrol Scrub Control в разделе 8.2.10.9.11.1, таблицы 8-222 и 8-223. Идентификатор задан фиксированным UUID 96dad7d6-fde8-482b-a733-75774e06db8a, прошивка каждого устройства перечисляет его среди прочих фич и честно сообщает, доступна она только для чтения или менять её разрешено. Суть проста. Устройство в свободные от полезной нагрузки моменты времени читает собственную память последовательно, область за областью, и прогоняет каждое чтение через ECC. Одиночный сбой исправляется на месте, растущий кластер ошибок попадает в счётчики и в события для хоста. Похожую работу выполняет и DDR5 ECS, Error Check and Scrub, только там за проверку отвечают сами чипы DRAM по боковой шине, а у CXL-контроллера это отдельная фича со своим UUID e5b13f22-2328-4a14-b8ba-b9691e893386 и собственными настройками.

Главный настраиваемый параметр патрульного скраба в CXL задан в часах. Устройство обязано завершать полный проход по объёму памяти за заданное число часов. Минимум для конкретного модуля прошивка отдаёт в старших восьми битах поля настроек, максимум упирается в размер однобайтового регистра, то есть 255 часов. Компромисс тут классический. Быстрый цикл ловит ошибки раньше, медленный меньше мешает потокам ввода-вывода и экономит питание. Платформа сама решает, что для неё важнее, и ради этого баланса разработчикам ядра понадобился способ менять параметр на лету, не лезя в прошивку.

Шина cxl в sysfs и атрибуты memdev для осмотра состояния

Каждый CXL-модуль памяти ядро показывает как устройство memX на шине cxl, путь выглядит как /sys/bus/cxl/devices/mem0. Базовые атрибуты закрепились с ядра 5.12: ram/size и pmem/size отдают объёмы энергозависимой и постоянной частей, firmware_version выдаёт строку прошивки из ответа на команду Identify Memory Device, payload_max показывает максимум полезной нагрузки почтового ящика, сериал и привязка к NUMA-узлу живут в serial и numa_node. С ядра 6.4 рядом появился файл trigger_poison_list, запись "true" в который заставляет ядро вытащить из модуля список отравленных адресов. С 6.5 в memdev переехали операции безопасности в каталоге security, а файл security/state умеет отвечать значениями "disabled" и "sanitize" и поддерживает poll, так что скрипт дожидается конца затяжной операции без пустых циклов. С 6.7 доступен счётчик decoders_committed: пока он больше нуля, устройство активно декодирует физические адреса, и деструктивные операции вроде sanitize ядро вежливо отклонит. Быстрый осмотр всего этого выглядит так:

cat /sys/bus/cxl/devices/mem0/ram/size
cat /sys/bus/cxl/devices/mem0/firmware_version
cat /sys/bus/cxl/devices/mem0/security/state
echo true > /sys/bus/cxl/devices/mem0/trigger_poison_list
dmesg | grep -iE "cxl"

В dmesg за эту сторону жизни отвечают модули cxl_acpi, cxl_pci и cxl_mem, корневой мост платформы объявляется через устройство ACPI0017, а порты через ACPI0016.

Ядра 6.15 и 6.16 передали управление скрабом подсистеме EDAC

Долгое время патрульный скраб CXL был вещью в себе: прошивка сканировала память по собственному усмотрению, и хост мог лишь догадываться, что происходит. Всё изменили два соседних релиза. В ядре 6.15, выпущенном в мае 2025 года, появилась базовая обвязка EDAC для скраба, файл drivers/edac/scrub.c и каталог sysfs /sys/bus/edac/devices, атрибуты которого закреплены в ABI документации в марте того же года. В ядре 6.16, летом 2025 года, CXL-драйверы подтянули обвязку к реальным устройствам: инженеры HiSilicon добавили файл drivers/cxl/core/edac.c на 519 строк с коммитом 23 мая 2025 года, и memdev стал регистрироваться в EDAC с набором RAS-возможностей, куда вошли патрульный скраб, DDR5 ECS, post-package repair и варианты memory sparing. Настройка уходит в устройство почтовыми командами Get Feature и Set Feature с уже знакомым UUID и помечается флагом сохранения после перезагрузки.

Имя EDAC-устройства собирается как префикс cxl_ плюс обычное имя объекта, поэтому на реальной машине каталоги называются cxl_mem0, cxl_mem1 и, что важно, cxl_region0 для скраба на уровне региона. За всё это отвечают три конфигурационные опции ядра, CONFIG_CXL_EDAC_SCRUB, CONFIG_CXL_EDAC_ECS и CONFIG_CXL_EDAC_MEM_REPAIR. Для записи атрибутов процессу нужны права CAP_SYS_RAWIO, то есть root. И приятная честность самой системы: если прошивка пометила скраб как неизменяемый, драйвер пропустит регистрацию, каталога scrub0 просто не появится, и это штатная ситуация, а не поломка.

Есть и дорога в обход EDAC. С июня 2025 года пакет ndctl v82 перечисляет FWCTL-символьные устройства CXL Features, и тем же ioctl можно отправлять модулю те же Get Feature и Set Feature, то есть управление фичами не заперто в одной подсистеме. EDAC при этом остаётся главным входом для администратора, потому что каталог /sys/bus/edac/devices собрал под одной крышей скраб CXL-модулей, скраб обычной памяти и все прочие RAS-ручки, которые EDAC-драйверы перечисляют одинаково.

Атрибуты scrub0 и команды для фонового и точечного режима

Практика начинается с осмотра каталога /sys/bus/edac/devices/cxl_mem0/scrub0. В нём живут шесть рабочих атрибутов. enable_background включает и выключает фоновый патруль. current_cycle_duration показывает текущую длительность цикла в секундах, min_cycle_duration и max_cycle_duration ограничивают диапазон, который готов принять конкретный модуль. addr и size обслуживают второй режим, точечный скраб произвольного диапазона. Типичные команды выглядят так:

cat /sys/bus/edac/devices/cxl_mem0/scrub0/min_cycle_duration
cat /sys/bus/edac/devices/cxl_mem0/scrub0/current_cycle_duration
echo 43200 > /sys/bus/edac/devices/cxl_mem0/scrub0/current_cycle_duration
echo 1 > /sys/bus/edac/devices/cxl_mem0/scrub0/enable_background

Здесь 43200 секунд это ровно 12 часов. Точечный прогон по диапазону укладывается в три шага:

  1. записать длину диапазона в size, например echo 0x20000000 > /sys/bus/edac/devices/cxl_mem0/scrub0/size, это 512 МиБ;
  2. записать базовый адрес в addr той же командой echo, например 0x40000000, запись адреса запускает проход;
  3. опрашивать addr, нулевой ответ при чтении сообщит о завершении, ненулевой что проход ещё идёт.

На уровне региона cxl_region0 картина иная. Минимум цикла берётся как самый большой из минимумов устройств-участников, максимум как самый маленький из максимумов, а одна запись в атрибут раскладывается на все модули интерлива сразу. Конфликты настроек ядро не прячет: в dmesg падает сообщение вида Device scrub rate(24 hours) set by region0 rate overwritten with device local scrub rate(4 hours), и след всегда достаточно полон для разбирательства.

Осмотр топологии здоровья и ошибок через cxl list

Утилита cxl из состава ndctl читает всё это в JSON, без ручного хождения по sysfs. Основа, список устройств памяти, выглядит как cxl list -M, а фильтры принимают имена, идентификаторы и PCI-адреса вида 0000:35:00.0, например cxl list -m mem0. Флаг -H добавляет блок health со счётчиками, в примере из документации это life_used_percent 15, temperature 25, dirty_shutdowns 10, volatile_errors 20, pmem_errors 30, плюс флаги вроде media_data_lost и performance_degraded. Флаг -I добавляет разбиение на партиции с выравниванием 268435456 байт, то есть 256 МиБ, флаг -A показывает пороги предупреждений, а -R перечисляет регионы с размерами и интерливом, как в образце size 549755813888 при interleave_ways 2 и interleave_granularity 256. Команда cxl list -vv выдаёт всё разом: устройства, шины, порты, декодеры, цели, здоровье, партиции и ошибки носителя.

Для отравленных адресов с ndctl v80 работает cxl list -m mem9 -L, в выводе появляются записи media_errors с полями offset, length 64 и source, а значения источника заданы спецификацией CXL 3.1 в таблице 8-140: External, Internal, Injected, Vendor Specific, Unknown. Опция требует libtracefs, и с ndctl v83 эта библиотека снова опциональна, без неё --media-errors и cxl monitor просто недоступны. Поле poison_injectable в выводе cxl list -u -m mem0 подсказывает, примет ли модуль внедрение отравления, у тестовых сборок там true рядом со строкой прошивки BWFW VERSION 00. Актуальный релиз ndctl, v85 лета 2026 года, уже догоняет ветку ядра 7.1 и показывает в списке статус locked у регионов.

Скраб, sanitize и путь ARS в командах ndctl

Три операции тут легко спутать, а зря. Патрульный скраб, о котором шла речь, это вечный фоновый уход за носителем, он ничего не стирает. Sanitize, почтовая команда с тем же именем, это разовое уничтожение данных перед выводом модуля из эксплуатации: запись "true" в /sys/bus/cxl/devices/mem0/security/sanitize запускает её, но ядро разрешает это только устройству с decoders_committed равным нулю, то есть не декодирующему адреса. Операция долгая, состояние видно в security/state, а ждать конца удобнее командой cxl wait-sanitize mem0, появившейся в ndctl v79, с опцией -t она сдаётся по таймауту в миллисекундах. Для проверки восстановления после ошибок в ndctl v84 добавили команды cxl inject-media-poison mem0 -a 0x1000 и cxl clear-media-poison mem0 -a 0x1000, обе идут через debugfs /sys/kernel/debug/cxl, а поддержка маркируется полем poison_injectable в выводе cxl list.

Отдельная ветка скраба живёт в мире NVDIMM. Address Range Scrub, перечисление ошибок силами платформенной прошивки по ACPI, запускается командой ndctl start-scrub, а завершение отслеживается командой ndctl wait-scrub: poll-совместимый файл scrub в sysfs шины nd во время работы выдаёт плюс поверх счётчика прогонов. За фоновое наблюдение за событиями CXL в ndctl отвечает отдельная служба cxl-monitor, к ней в комплекте прилагается юнит-файл cxl-monitor.service для systemd, и она превращает поток событий ядра в журналы, которые уже удобно собирать централизованно.

Математика пропускной способности и выбор длительности цикла

Осталась арифметика: сколько стоит управляемый скраб. Цикл задаётся в часах, а sysfs показывает секунды, поэтому сначала пересчёт: 24 часа это 86400 секунд, максимум 255 часов это 918000 секунд. Для примера возьмём регион на 512 ГиБ, то есть 549755813888 байт, размер из примера cxl list. Полный проход за 24 часа требует средней скорости чтения 6363543 байта в секунду, около 6,4 МБ/с, а это меньше одной сотой процента от пропускной способности линии PCIe 5.0 x16, которая для CXL-устройств достигает 64 ГБ/с. При цикле в 4 часа расход вырастает до 38 МБ/с, всё ещё 0,06% канала. Для модуля на 1 ТиБ при 12 часах получится 25 МБ/с. Ещё нагляднее разбивка по шагам: 512 ГиБ это 2147483648 участков по 256 байт, и за 24-часовой цикл контроллер трогает один участок каждые 40 микросекунд. Даже верхняя граница остаётся незаметной для приложений, поэтому смысл ручной настройки не в экономии трафика, а в балансе между свежестью данных и питанием: цикл 255 часов оставляет контроллеру больше тишины, быстрый держит счётчики ошибок всегда актуальными.

Отдельный сценарий у точечного режима. Перед вводом сервера в работу диапазон 512 МиБ прогоняют адресом и размером из шагов выше, ловя заводские дефекты до того, как поверх появятся файловые системы. К этому добавляется настройка порогов DDR5 ECS, на выбор 256, 1024 или 4096 кодовых слов с ошибками на гигабит ячеек, и контур ухода за CXL-памятью замыкается. sysfs EDAC крутит циклы и точечные прогоны, cxl list показывает истину об устройствах и регионах, dmesg и трассировка событий докладывают, когда патруль нашёл что-то, о чём стоит знать заранее. В Linux про CXL-память не нужно гадать, у неё есть понятные ручки и цифры.