Сбойный сетевой адаптер или NVMe накопитель традиционно означал останов сервера: перезагрузка, простой сервисов, ночной вызов. Стандарт PCI Express давно решил эту проблему на уровне железа, разрешив подключение и изъятие карт на работающей машине. В Linux эту способность обслуживает драйвер pciehp, а центр управления вынесен в каталог sysfs, где каждый слот превращается в набор обычных файлов. В статье разобрана настройка нативной обработки событий, управление питанием и индикаторами слота, поведение при внезапном изъятии, горячие операции в QEMU и измерение времени операций. Разобранные приемы превращают горячую замену из лотереи в штатную процедуру с предсказуемым результатом.
Драйвер pciehp и нативная обработка событий слота при параметре pcie_ports=native
В стандарте PCI Express горячей заменой ведает сам порт: контроллер слота следит за кнопкой, датчиком защелки и линией питания, а события до системы доходят через отдельное прерывание. Ядро Linux работает с таким портом двумя способами: нативная схема отдает управление драйверу pciehp, запасная перекладывает его на прошивку через методы ACPI. На серверах чаще выбирают нативную схему: она быстрее реагирует и дает полный набор файлов управления.
Первый шаг настройки - убедиться, что порт умеет горячую замену. Возможности корневого порта показывает lspci:
lspci -nn -s 00:1c.0 -vvv | grep -A5 SltCap
lspci -nn -s 00:1c.0 -vvv | grep SltSta
Примерный вывод на платформе с поддержкой замены:
SltCap: Attention Button Present
Power Controller Present
Attention Indicator Present
Power Indicator Present
Hot-Plug Surprise
Hot-Plug Capable
SltSta: Status: AttnBtn- PowerFault- MRL- AttnInd- PwrInd- HotPlug+ Surprise+
Строки SltCap и SltSta расшифровывают регистр Slot Capabilities по смещению 14 внутри Express capability порта; сырое значение вида 0010007b читается командой setpci -s 00:1c.0 CAP_EXP+14.l. Бит 0x40 объявляет Hot-Plug Capable, бит 0x20 разрешает внезапное изъятие, старшие биты хранят номер слота, сдвинутый на 19 позиций влево. Лимит питания типичного слота равен 25 Вт, активный NVMe накопитель потребляет 5-10 Вт.
Если прошивка оставила управление слотами себе, каталог слотов останется пустым. Помогает параметр pcie_ports=native, забирающий PCIe сервисы у прошивки, и параметр pciehp_poll=on, переводящий драйвер на опрос вместо прерываний. Опрос выручает там, где события теряются, в старых ветках ядра для него служили pciehp_poll_mode и pciehp_poll_time. Строку параметров добавляют в конфигурацию загрузчика:
GRUB_CMDLINE_LINUX_DEFAULT="quiet pcie_ports=native pciehp_poll=on"
Конфигурацию пересобирают командой grub2-mkconfig -o /boot/grub2/grub.cfg, в семействе Debian сработает update-grub, и машину перезагружают. На старте драйвер печатает карту возможностей:
pciehp 0000:00:1c.0: Slot(2): AttnBtn+ PwrCtrl+ MRL- AttnInd+ PwrInd+ HotPlug+ Surprise+ Interlock- NoComma- PM+
Кнопка и контроллер питания на месте, датчик защелки отсутствует, внезапное изъятие разрешено.
Слоты sysfs с файлами power и attention как пульт управления заменой платы
Каждый обнаруженный слот драйвер публикует в каталоге /sys/bus/pci/slots под собственным номером. Быстрый обзор машины:
ls /sys/bus/pci/slots/
for s in /sys/bus/pci/slots/*; do
printf "%s %s %s\n" "$(basename $s)" "$(cat $s/address)" "$(cat $s/adapter)"
done
На узле с тремя слотами и двумя занятыми вывод короткий:
2 0000:00:1c.0 1
5 0000:00:1c.1 1
7 0000:00:1c.2 0
Файл address связывает номер слота с адресом моста в формате домен:шина:устройство.функция, файл adapter показывает наличие карты: единица занятый слот, ноль свободный. Файл power управляет питанием, attention двигает светодиодом, latch отдает состояние защелки, а в свежих ядрах у моста есть reset_subordinate для сброса всех устройств под мостом.
Практический цикл замены сетевой карты укладывается в четыре команды:
echo 0 > /sys/bus/pci/slots/2/power
lspci -s 02:00.0
echo 1 > /sys/bus/pci/slots/2/power
echo 1 > /sys/bus/pci/rescan
После записи нуля устройство исчезает из вывода lspci, индикатор питания гаснет, карта свободно вынимается. Новая карта занимает слот, запись единицы подает питание, ядро заново опрашивает шину и загружает драйвер. Команда rescan остается страховкой, подхватывающей потерянные устройства. Убрать устройство без полного цикла питания, например перед передачей слота виртуальной машине, можно записью единицы в файл /sys/bus/pci/devices/0000:02:00.0/remove, выполняющей горячее удаление устройства и его дочерних элементов. За событиями шины удобно следить слушателем udevadm monitor --kernel --subsystem-match=pci: в его окне видна цепочка unbind и remove, а после установки новой карты обратная цепочка add и bind.
Кнопка Attention с окном отмены на 5 секунд и проверка индикаторов слота
Физическая кнопка на лицевой панели остается честным интерфейсом замены, и драйвер отрабатывает ее по стандартной схеме. Одно нажатие запускает отключение: индикатор питания мигает, и у инженера есть окно около 5 секунд, чтобы передумать. Второе нажатие отменяет запрос, в журнале остаются парные строки:
pciehp 0000:00:1c.0: Slot(2): Attention button pressed
pciehp 0000:00:1c.0: Slot(2): Attention button cancel
Окно отмены придумано не для красоты: в плотном шасси легко задеть соседнюю кнопку, и повторное нажатие экономит лишний цикл питания.
Программный путь дает те же индикаторы без кнопки. Единица в файле attention зажигает светодиод ровным светом, двойка запускает мигание с частотой около двух герц, ноль гасит, последовательность echo 2, sleep 5 и echo 0 в /sys/bus/pci/slots/2/attention отрабатывает включение и выключение. В серверной с десятками одинаковых узлов это главный способ указать коллеге конкретный слот: попросил мигать, подошел, нашел, сделал.
Внезапное изъятие устройства и события surprise removal в журнале ядра
Бит Hot-Plug Surprise в возможностях порта разрешает самый жесткий сценарий: карту вынимают без кнопки, команд и снятия питания. Стандарт обязывает платформу выдержать изъятие, а ядро разобрать последствия. Драйвер ловит событие смены состояния линии, видит пропажу связи и сворачивает устройство: отвязывает драйвер, освобождает прерывания, удаляет узлы из дерева. В журнале это выглядит сухо:
pciehp 0000:00:1c.0: Slot(2): Link Down
pciehp 0000:00:1c.0: Slot(2): Card not present
Внезапным изъятие остается только для приложений: файловая система на исчезнувшем диске отдает ошибки ввода-вывода, сетевой интерфейс теряет носитель, массив программного RAID помечает диск сбойным и запускает перестроение. Сюрпризом это должно остаться только для железа, не для администратора.
Штатный порядок перед изъятием NVMe накопителя выглядит так:
- размонтировать файловые системы командой umount /mnt/nvme1 и проверить, что ни один процесс не держит устройство, командой lsof /dev/nvme1n1;
- вывести накопитель из массива командами mdadm /dev/md0 --fail /dev/nvme1n1 и mdadm /dev/md0 --remove /dev/nvme1n1;
- при необходимости отвязать драйвер, записав адрес 0000:02:00.0 в файл /sys/bus/pci/drivers/nvme/unbind;
- удалить устройство из дерева PCI командой echo 1 > /sys/bus/pci/devices/0000:02:00.0/remove;
- снять питание слота командой echo 0 > /sys/bus/pci/slots/2/power и дожидаться в журнале строки Card not present, прежде чем тянуть карту.
На платформах с битом Surprise шаги 4 и 5 формально избыточны, но пропускать их стоит только там, где схема опробована на стенде: аккуратный порядок дает чистое размонтирование и предсказуемое перестроение массива.
Горячая замена устройств в QEMU через pcie-root-port и команды монитора
Машина q35 в QEMU строит шину PCIe честно, и это меняет правила горячей замены у гостя. Сетевые и дисковые устройства нельзя просто добавить на корневую шину, для них нужен корневой порт, который внутри гостя выглядит мостом со слотом:
qemu-system-x86_64 -machine q35 -m 4096 -enable-kvm \
-device pcie-root-port,id=rp2,chassis=2,slot=2 \
-netdev tap,id=n1,ifname=tap2,script=no,downscript=no \
-device virtio-net-pci,netdev=n1,bus=rp2
Параметр slot=2 превращается в госте в номер слота, внутри виртуальной машины появляется знакомый каталог /sys/bus/pci/slots/2 со всеми файлами из первой половины статьи. Гостевое ядро подключает тот же драйвер pciehp, и пульт управления слотом работает без изменений.
Подключение нового устройства выполняется из монитора QEMU, где сетевой бэкенд n2 заранее описан:
(qemu) device_add virtio-net-pci,netdev=n2,bus=rp2,id=nic2
(qemu) device_del nic2
Первая команда вставляет карту в порт, гостевое ядро подает питание на слот и загружает драйвер. Вторая запускает отключение, и здесь важна асинхронность: QEMU передает гостю запрос, гость гасит драйвер и подтверждает завершение, после чего событие DEVICE_DELETED в протоколе QMP сообщает об успехе:
{"event": "DEVICE_DELETED", "data": {"device": "nic2", "path": "/machine/peripheral/nic2"}}
Если гость не справился, свежие сборки QEMU присылают событие DEVICE_UNPLUG_GUEST_ERROR. Для простых virtio устройств цикл занимает доли секунды, для занятого дискового контроллера с очередями ввода-вывода счет идет на секунды. С миграцией правило одно: пока виртуальная машина летит между узлами, горячие операции откладывают, QEMU такие команды отклоняет, а на старых сборках не гарантирует результат. Добавленное до миграции устройство переезжает со снимком памяти.
Измерение времени операций по меткам dmesg и событиям QMP
Время каждой операции горячей замены измеряется без специальных средств: метки журнала ядра дают точность до микросекунд при точном формате вывода:
journalctl -k -o short-precise | grep pciehp | tail -n 20
Типичная пара строк при отключении по кнопке:
[ 8.412317] pciehp 0000:00:1c.0: Slot(2): Powering off due to button press
[ 8.923104] pciehp 0000:00:1c.0: Slot(2): Card not present
Разница меток 8923104 минус 8412317 составляет 510787 микросекунд, то есть примерно 0,5 секунды: столько занимает гашение питания контроллера слота по требованиям стандарта. Полный цикл с новой картой длиннее: добавляются опрос шины и загрузка драйвера, для сетевого контроллера обычно 1-3 секунды, для NVMe накопителя меньше секунды. Систематический замер удобно встроить в скрипт:
start=$(date +%s%N)
echo 1 > /sys/bus/pci/devices/0000:02:00.0/remove
echo "удаление заняло $(( ($(date +%s%N)-start)/1000000 )) мс"
На стороне гипервизора часы другие: отсчет начинается от device_del и заканчивается событием DEVICE_DELETED, задержка складывается из гашения драйвера в госте, завершения очередей и подтверждения ядра. Опрос pciehp_poll=on добавляет издержки: событие фиксируется не мгновенно, а на границе интервала порядка двух секунд. Когда тайминги критичны, опрос держат выключенным и заранее проверяют, что прерывания от порта доходят до ядра.
Диагностика молчащих слотов и типичные ошибки при настройке hotplug
Типовых отказов немного, и почти у каждого есть короткое лечение. Каталог /sys/bus/pci/slots пуст на плате, которая вроде бы умеет замену: порт не заявляет Hot-Plug Capable, либо прошивка не отдала управление. Проверка идет через lspci и регистр Slot Capabilities из первого раздела, лечение через pcie_ports=native.
Запись в файл power проходит, а устройство остается на месте: старое ядро не удаляет устройства при снятии питания, выходом служит горячее удаление через файл remove. Физическая кнопка игнорируется: события теряются на пути от прошивки, тут приходит очередь опроса, pciehp_poll=on в свежих ядрах или pciehp_poll_mode у старых. Запись отклоняется отказом в доступе: SELinux или AppArmor запрещают запись в sysfs от имени обычного пользователя, операцию выполняют с полными правами администратора. Карта вставлена, питание подано, а устройства нет: читают файл adapter, повторяют rescan и после этого подозревают прошивку.
Живой контроль по ходу работ открывает одна команда:
journalctl -kf | grep -E "pciehp|pci "
Она показывает весь поток событий слота в реальном времени, перед визитом в серверную ее держат открытой в отдельном окне. Замыкает подготовку репетиция: лабораторная машина с парой корневых портов, полный прогон подключения, мигания индикатора и удаления, записанные тайминги. После такой репетиции замена сбойной карты превращается из стресс-теста в рутину с известными числами, а ночной вызов заканчивается чашкой кофе, а не разбором упавшего массива.