Я пытаюсь максимизировать пропускную способность в одной RX-очереди моей сетевой карты. Моя текущая настройка использует функцию Shared Umem
, настроив несколько сокетов в одной очереди RX, каждая со ссылкой на один и тот же Umem.
Затем моя программа XDP-ядра назначает потоки пакетов для правильных розетка через BPF_MAP_TYPE_XSKMAP
. Все это прекрасно работает, но при скорости около 600 000 pps ksoftirqd/18
достигает 100% загрузки процессора (я переместил свое пользовательское приложение на другое ядро через taskset -c 1
, чтобы уменьшить нагрузку на Core 18). В моем пользовательском приложении загрузка процессора не превышает 14%, поэтому, к сожалению, причина, по которой я не могу обрабатывать больше пакетов, заключается в огромном количестве прерываний.
Затем я прочитал о привязке xdp -flag XDP_USE_NEED_WAKEUP
, который отправляет Umem Fill-Ring в спящий режим, тем самым уменьшая накладные расходы на прерывания (насколько я правильно понимаю, там не так много информации об этой топике c). Поскольку Umem Fill-Ring может находиться в спящем режиме, необходимо регулярно проверять:
if (xsk_ring_prod__needs_wakeup(&umem->fq)) {
const int ret = poll(fds, len, 10);
}
fds
- это struct pollfd
, содержащие файловый дескриптор каждого сокета. Я не совсем уверен, где добавить флаг XDP_USE_NEED_WAKEUP
, но вот как я его использую:
static struct xsk_socket_info *xsk_configure_socket(struct xsk_umem_info *umem, struct config *cfg,
const bool rx, const bool tx) {
struct xsk_socket_config xsk_socket_cfg;
struct xsk_socket_info *xsk;
struct xsk_ring_cons *rxr;
struct xsk_ring_prod *txr;
int ret;
xsk = calloc(1, sizeof(*xsk));
if (!xsk) {
fprintf(stderr, "xsk `calloc` failed: %s\n", strerror(errno));
exit(1);
}
xsk->umem = umem;
xsk_socket_cfg.rx_size = XSK_CONS_AMOUNT;
xsk_socket_cfg.tx_size = XSK_PROD_AMOUNT;
if (cfg->ip_addrs_len > 1) {
xsk_socket_cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD;
} else {
xsk_socket_cfg.libbpf_flags = 0;
}
xsk_socket_cfg.xdp_flags = cfg->xdp_flags;
xsk_socket_cfg.bind_flags = cfg->xsk_bind_flags | XDP_USE_NEED_WAKEUP;
rxr = rx ? &xsk->rx : NULL;
txr = tx ? &xsk->tx : NULL;
ret = xsk_socket__create(&xsk->xsk, cfg->ifname_buf, cfg->xsk_if_queue, umem->umem, rxr, txr, &xsk_socket_cfg);
if (ret) {
fprintf(stderr, "`xsk_socket__create` returned error: %s\n", strerror(errno));
exit(-ret);
}
return xsk;
}
Я заметил, что он оказал небольшое влияние на нагрузку ksoftirqd/18
, и я смог обработать на 50.000 pps больше, чем раньше (но это также может быть связано с изменениями общей нагрузки системы - я не уверен: /). Но я также заметил, что XDP_USE_NEED_WAKEUP
не работает для Shared Umem
, потому что libbpf имеет этот код в xsk.c
:
sxdp.sxdp_family = PF_XDP;
sxdp.sxdp_ifindex = xsk->ifindex;
sxdp.sxdp_queue_id = xsk->queue_id;
if (umem->refcount > 1) {
sxdp.sxdp_flags = XDP_SHARED_UMEM;
sxdp.sxdp_shared_umem_fd = umem->fd;
} else {
sxdp.sxdp_flags = xsk->config.bind_flags;
Как видите, bind_flags
используются только если Umem
имеет refcount
, равное 1 (оно не может быть меньше этого значения, поскольку оно увеличивается где-то выше в xsk_socket__create
). Но поскольку для каждого созданного сокета увеличивается refcount
- эти bind_flags
используются только для первого сокета (где refcount
по-прежнему <= 1
).
Я не совсем понимаю, почему XDP_USE_NEED_WAKEUP
можно использовать только для одной розетки? На самом деле, я не понимаю, почему этот флаг вообще связан с сокетом, если он действительно влияет на Umem?
Тем не менее, я ищу способ уменьшить издержки на прерывания - любые идеи, как это может быть достигнуты? Мне нужно как минимум 1.000.000 pps.