Адаптеры NVIDIA Mellanox ConnectX реализуют функцию разделения заголовков и полезной нагрузки на аппаратном уровне. Технология SHAMPO - Scalable Header And payload Multi-Packet Offload - позволяет сетевому контроллеру записывать заголовки принятых пакетов в отдельную область памяти, а данные - в страницы приёма. Такой подход снижает нагрузку на кеш процессора при обработке входящего трафика и улучшает локальность данных. В статье разобраны механизм работы, способы проверки поддержки и настройки через стандартные интерфейсы Linux.
Архитектура разделения заголовков и данных в mlx5
В драйвере mlx5 разделение заголовков и данных реализовано через механизм MPWQE - Multi-Packet Work Queue Elements. Каждая рабочая очередь приёма состоит из наборов stride, каждый из которых вмещает один пакет. При включённом SHAMPO аппарат выделяет первые байты пакета - заголовки Ethernet, IP, TCP или UDP - и помещает их в специальный буфер заголовков. Остаток пакета записывается в обычную страницу данных. Размер записи заголовка фиксирован и составляет 256 байт - значение константы MLX5E_SHAMPO_LOG_HEADER_ENTRY_SIZE равное 8 в степени двойки.
Структура завершения приёма CQE содержит объединение shampo с полями header_size, header_entry_index и data_offset. Поле header_size указывает фактический размер заголовка, header_entry_index - индекс записи в буфере заголовков, а data_offset - смещение начала полезной нагрузки внутри страницы данных. Драйвер использует эти значения для сборки skb: заголовок копируется из буфера заголовков через отображение страниц, данные - из страницы приёма по смещению.
Буфер заголовков выделяется как непрерывная область памяти и регистрируется через UMR - User Memory Registration - с созданием локального ключа mkey. Количество записей в буфере рассчитывается исходя из размера очереди приёма: hd_per_wq умножается на 256 байт, результат округляется до размера страницы. При этом флаг PP_FLAG_ALLOW_UNREADABLE_NETMEM разрешает page_pool работать с непрочитанной памятью, что устраняет лишние отображения.
Поле hds_ip_ext в CQE содержит флаги CQE_L3_OK и CQE_L4_OK, указывающие на корректность контрольных сумм IP и TCP/UDP. При наличии туннеля эти флаги применяются к внешнему и внутреннему заголовкам одновременно. Дополнительно поле l4_l3_hdr_type кодирует тип заголовков L3/L4, что позволяет драйверу быстро определить протокол без повторного разбора.
Проверка поддержки оборудования и драйвера
Поддержка SHAMPO определяется двумя флагами возможностей устройства. Первый - MLX5_CAP_GEN(mdev, shampo) - указывает на наличие базовой функциональности. Второй - MLX5_CAP_SHAMPO(mdev, shampo_header_split_data_merge) - подтверждает возможность объединения заголовков и данных в один пакет на стороне драйвера. Оба флага проверяются при инициализации очереди приёма в функции mlx5e_rq_shampo_alloc.
Для проверки на работающей системе используется утилита ethtool. Параметр tcp-data-split показывает текущее состояние:
ethtool -k eth0 | grep tcp-data-split
В выводе значение on означает включённое разделение, off - выключенное. Если параметр отсутствует, аппарат или драйвер не поддерживают функцию. На уровне netlink атрибут ETHTOOL_TCP_DATA_SPLIT принимает значения ETHTOOL_TCP_DATA_SPLIT_ENABLED (1) и ETHTOOL_TCP_DATA_SPLIT_DISABLED (0).
Дополнительно можно проверить флаг NETIF_F_GRO_HW в возможностях интерфейса. SHAMPO требует включённого аппаратного GRO - Generic Receive Offload - так как разделение заголовков работает в связке с агрегацией пакетов. При попытке включить tcp-data-split при выключенном HW-GRO драйвер вернёт ошибку и оставит функцию выключенной.
Информацию о возможностях адаптера можно получить через lspci с детальным выводом:
lspci -nn -d 15b3:
Вендор ID 15b3 соответствует Mellanox/NVIDIA. Device ID 1017 - ConnectX-5, 101b - ConnectX-6, 101d - ConnectX-6 Dx, 101f - ConnectX-7. Поддержка SHAMPO появилась в ConnectX-6 Dx (101d) и новее.
Параметры прошивки проверяются через mlxconfig:
mlxconfig -d /dev/mst/mt4125_pciconf0 q | grep -i shampo
В выводе могут присутствовать параметры SHAMPO_EN, SHAMPO_LOG_HEADER_ENTRY_SIZE, SHAMPO_MODE. Их значения определяют аппаратные ограничения разделения.
Настройка через ethtool и devlink
Включение и выключение производится командой:
ethtool -K eth0 tcp-data-split on
ethtool -K eth0 tcp-data-split off
Изменение вступает в силу немедленно, но может потребовать пересоздания очередей приёма. Драйвер отслеживает состояние через поле hds_config в структуре конфигурации привилегий устройства. При переключении с enabled на disabled вызывается netdev_update_features для пересчёта возможностей интерфейса.
Параметров модуля mlx5_core для управления SHAMPO нет - функция управляется исключительно через ethtool и netlink. Отладочная маска debug_mask и выбор профиля prof_sel не влияют на поведение разделения заголовков.
Для постоянного включения после перезагрузки правило добавляется в системные скрипты инициализации сети или через udev-правило:
ACTION=="add", SUBSYSTEM=="net", DRIVERS=="mlx5_core", RUN+="/usr/sbin/ethtool -K $name tcp-data-split on"
Настройка размера кольца приёма критична для работы SHAMPO. Рекомендуемое значение rx-pending - 4096 или больше:
ethtool -G eth0 rx 4096 tx 4096
Меньшие значения могут привести к ошибке "SHAMPO header buffer size < PAGE_SIZE" при инициализации.
Подсистема devlink предоставляет альтернативный интерфейс управления. Параметр hdp_enable (или header_data_split в новых версиях) позволяет переключать функцию без ethtool:
devlink dev param set pci/0000:81:00.0 name hdp_enable value true cmode runtime
devlink dev reload pci/0000:81:00.0
Режим cmode runtime применяет изменения немедленно, driverinit - при следующей загрузке драйвера. Список доступных параметров показывает:
devlink dev param show pci/0000:81:00.0
В выводе присутствуют hdp_enable, shampo_mode, shampo_log_header_entry_size. Последние два доступны только для чтения и отражают аппаратные ограничения.
Влияние на кеш-эффективность и числовые примеры выравнивания
Разделение заголовков и данных меняет паттерн доступа к памяти при обработке входящих пакетов. Без SHAMPO заголовок и данные лежат последовательно в одной странице. При обработке первые 64–128 байт - заголовки - загружаются в кеш L1 вместе с началом полезной нагрузки. Если приложение читает только заголовки (например, для маршрутизации или фильтрации), остаток страницы забирает место в кеше впустую.
С SHAMPO заголовки упакованы плотно в отдельный буфер. 256-байтовые записи позволяют разместить 16 заголовков на странице 4 КБ. При обработке потока пакетов процессор загружает в кеш только заголовки, данные остаются в страницах приёма и загружаются по требованию. Это особенно эффективно при малопакетном трафике - 64–128 байт полезной нагрузки - где накладные расходы на заголовки максимальны.
Статистика работы доступна через ethtool -S:
ethtool -S eth0 | grep hds
Счётчики:
- hds_nodata_packets - пакеты, где заголовок есть, но данных нет (чистые ACK, управляющие пакеты);
- hds_nodata_bytes - суммарный размер таких заголовков;
- hds_nosplit_packets - пакеты, не подвергшиеся разделению (фрагментированные, с опциями IP, не TCP/UDP);
- hds_nosplit_bytes - объём данных в неразделённых пакетах.
На тестовых нагрузках с пакетами 64 байта (заголовок 54 байта, данные 10 байт) наблюдается сокращение промахов кеша L1 на 15–20 процентов по сравнению с обычным приёмом. При MTU 1500 байт выигрыш менее заметен - данные занимают большую часть страницы, и разделение не даёт существенного преимущества.
Взаимодействие с CQE compression (сжатием структур завершения) важно учитывать. При включённом CQE compression драйвер может отключить SHAMPO, так как оба механизма конкурируют за биты в CQE. Проверка:
ethtool -k eth0 | grep rx-cqe-compress
Если rx-cqe-compress включен, SHAMPO может быть недоступен.
Размеры типичных заголовков: Ethernet II - 14 байт; VLAN тег (802.1Q) - +4 байта; IPv4 без опций - 20 байт; IPv6 базовый - 40 байт; TCP без опций - 20 байт; UDP - 8 байт.
Суммарно IPv4 + TCP + Ethernet = 58 байт, с VLAN = 62 байта. IPv6 + TCP + Ethernet = 74 байта. Все варианты помещаются в 256-байтовый слот заголовка с запасом. Выравнивание записей на 256 байт упрощает адресацию аппаратной единицей SHAMPO - индекс умножается на 256 без сложных вычислений.
Буфер заголовков для очереди 4096 stride (log_rq_stride = 6, страница 4 КБ, 64 stride на страницу) требует 4096 записей по 256 байт = 1 МБ. Это 256 страниц памяти. При 8 очередях приёма общий объём - 8 МБ, что приемлемо для современных серверов.
При включённом SHAMPO headroom в WQE устанавливается в ноль - данные начинаются сразу после заголовка в странице приёма. Без SHAMPO headroom равен MLX5_RX_HEADROOM (обычно NET_SKB_PAD = 128 байт) для размещения заголовка skb. Экономия 128 байт на каждом stride увеличивает плотность упаковки пакетов в память.
Логарифмические параметры очереди: log_rq_stride определяет размер stride (2^log_rq_stride байт), log_rq_size - количество stride (2^log_rq_size). Для SHAMPO минимальный log_rq_stride = 6 (64 байта), максимальный - 13 (8192 байта). Рекомендуемое значение для 100G - 10 (1024 байта на stride).
Особенности работы с туннелями и интеграция с RDMA
SHAMPO корректно обрабатывает инкапсулированные пакеты - VXLAN, GRE, GENEVE, MPLS over GRE/UDP. В CQE поле hds_ip_ext содержит флаги CQE_L3_OK и CQE_L4_OK для внешнего и внутреннего заголовков. При наличии туннеля драйвер устанавливает skb->encapsulation = 1 и skb->csum_level = 1, а контрольные суммы помечаются как CHECKSUM_UNNECESSARY для обоих уровней.
Разделение работает только для известных протоколов. Пакеты с неизвестными расширениями IPv6, фрагментированные датаграммы, пакеты с опциями IP передаются без разделения - счётчик hds_nosplit_packets растёт. Это ограничение аппаратного парсера, которое не устраняется настройками драйвера.
Для VXLAN трафика важно включить аппаратную разгрузку туннелей:
ethtool -K eth0 rx-vxlan-hw on
ethtool -K eth0 rx-geneve-hw on
Без этого внешние заголовки VXLAN/GENEVE не будут разобраны парсером, и разделение не сработает для внутреннего трафика.
Для приложений, использующих rdma-core и ibverbs напрямую, разделение заголовков работает на уровне Ethernet-драйвера mlx5e и не требует специальных флагов в ibv_create_qp. Однако при использовании Raw Packet QP или Raw Ethertype QP приложение получает пакет уже собранный драйвером из двух частей.
В пользовательском пространстве нет отдельного флага HDR_SPLIT в ibv_device_attr. Возможность определяется косвенно - через поддержку striding RQ (MLX5_IB_STRIDING_RQ) и наличие флага MLX5_IB_SUPPORT_EMPW в mlx5_ib_striding_rq_caps. При создании QP через ibv_create_qp_ex с max_recv_sge = 2 можно передать два SGE - для заголовка и данных, но это работает только для Raw QP и требует настройки в mlx5 DV (Direct Verbs) API.
Для стандартных RC/UC QP разделение происходит прозрачно внутри драйвера. Приложение видит обычный буфер приёма. Настройка производится только на уровне сетевого интерфейса через ethtool/devlink.
Диагностика проблем и взаимодействие с XDP
Если tcp-data-split не включается, проверяются: 1. Версия прошивки адаптера - SHAMPO требует ConnectX-6 Dx и новее с актуальной прошивкой; 2. Версия ядра - поддержка появилась в ядре 6.1, полная функциональность - в 6.4+; 3. Включён ли HW-GRO: ethtool -k eth0 | grep gro-hw; 4. Режим очереди приёма - SHAMPO работает только с MPWQE (striding RQ), не с обычным циклическим WQ.
Сообщения в dmesg при ошибках: "SHAMPO header buffer size < PAGE_SIZE" - очередь приёма слишком мала, увеличьте rx-pending через ethtool -G; "TCP-data-split is not supported when GRO HW is disabled" - включите gro-hw перед tcp-data-split.
Статистика hds_nosplit_packets растёт при большом количестве не-TCP/UDP трафика (ICMP, RAW сокеты, нестандартные протоколы). Это нормально - функция ориентирована на основной полезный трафик.
Для глубокой диагностики полезны tracepoints:
echo 1 > /sys/kernel/debug/tracing/events/mlx5/mlx5e_rx_shampo/enable
События показывают выделение буферов заголовков, создание mkey, ошибки UMR.
При использовании XDP с SHAMPO страница данных может быть недоступна для чтения процессором - флаг PP_FLAG_ALLOW_UNREADABLE_NETMEM разрешает такому страницам существовать в page_pool. XDP-программа получает указатель на заголовок в буфере заголовков и на данные в странице. Если программа перенаправляет пакет (XDP_REDIRECT, XDP_TX), драйвер собирает полный пакет из двух частей.
При XDP_DROP или XDP_ABORTED страница возвращается в пул без сборки skb, заголовок остаётся в буфере и перезаписывается при следующем приёме. Это исключает лишние копирования для отбрасываемых пакетов.
Для XDP_REDIRECT в другую сетевую карту заголовок копируется в новый пакет. При XDP_TX на ту же карту - используется zero-copy через пересылку указателей.
Функция разделения заголовков и данных в адаптерах ConnectX реализована через механизм SHAMPO в драйвере mlx5. Включение одной командой ethtool -K tcp-data-split on даёт выигрыш по кеш-промахам на малопакетном трафике без изменения логики приложений. Основные требования - поддержка оборудования ConnectX-6 Dx и новее, ядро 6.1+, включённый HW-GRO и режим striding RQ. Мониторинг эффективности ведётся через счётчики hds_* в ethtool -S.