AF-XDP - Как работает `XDP_USE_NEED_WAKEUP`, например,« Как уменьшить нагрузку ksoftirqd »? - PullRequest
0 голосов
/ 24 марта 2020

Я пытаюсь максимизировать пропускную способность в одной 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.

...