дескриптор rte_eth_tx_burst ()/гарантии управления mbuf по сравнению со свободными порогами

#dpdk

Вопрос:

Эта rte_eth_tx_burst() функция задокументирована как:

  * It is the responsibility of the rte_eth_tx_burst() function to
 * transparently free the memory buffers of packets previously sent.
 * This feature is driven by the *tx_free_thresh* value supplied to the
 * rte_eth_dev_configure() function at device configuration time.
 * When the number of free TX descriptors drops below this threshold, the
 * rte_eth_tx_burst() function must [attempt to] free the *rte_mbuf*  buffers
 * of those packets whose transmission was effectively completed.
 

У меня есть небольшая тестовая программа, в которой это, похоже, не соответствует действительности (при использовании драйвера ixgbe на сетевой карте vfio X553 1GbE).

Поэтому моя программа устанавливает одну очередь передачи следующим образом:

 uint16_t tx_ring_size = 1024-32;
rte_eth_dev_configure(port_id, 0, 1, amp;port_conf);
r = rte_eth_dev_adjust_nb_rx_tx_desc(port_id, amp;rx_ring_size, amp;tx_ring_size);
struct rte_eth_txconf txconf = dev_info.default_txconf;
r = rte_eth_tx_queue_setup(port_id, 0, tx_ring_size,
        rte_eth_dev_socket_id(port_id), amp;txconf);
 

Пул пакетов передачи mbuf создается следующим образом:

 struct rte_mempool *pkt_pool = rte_pktmbuf_pool_create("pkt_pool", 1023, 341, 0,
        RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
 

Таким образом, при отправке пакетов у меня скорее заканчиваются дескрипторы TX, чем заканчиваются буферы пакетов. (программа генерирует пакеты только с одним сегментом)

Я ожидаю, что, когда я вызываю rte_eth_tx_burst() цикл (для отправки одного пакета за другим), он никогда не завершается сбоем, поскольку он прозрачно освобождает mbufs от уже отправленных пакетов.

Однако этого не происходит.

У меня в основном есть такой цикл передачи, как этот:

 for (unsigned i = 0; i < 2048;   i) {
    struct rte_mbuf *pkt = rte_pktmbuf_alloc(args.pkt_pool);
    // error check, prepare packet etc.

    uint16_t l = rte_eth_tx_burst(args.port_id, 0, amp;pkt, 1);
    // error check etc.
}
 

После 1086 переданных пакетов (по ~ 300 байт каждый) rte_eth_tx_burst() возвращает 0.

Я использую пороговые значения по умолчанию, т. Е. Запрашиваемые значения (от dev_info.default_txconf ):

 tx thresh   : 32
tx rs thresh: 32
wthresh     : 0
 

Итак, главный вопрос сейчас заключается в следующем: насколько сложно rte_eth_tx_burst() попытаться освободить буферы mbuf (и, следовательно, дескрипторы)?

Я имею в виду, что он может быть занят до тех пор, пока передача ранее предоставленных mbuf не будет завершена.

Или он может просто быстро проверить, свободны ли снова некоторые дескрипторы. Но если нет, просто сдавайся.

Связанный с этим вопрос: Подходят ли пороговые значения по умолчанию для данного варианта использования?


So I work around this like that:

 for (;;) {
    uint16_t l = rte_eth_tx_burst(args.port_id, 0, amp;pkt, 1);
    if (l == 1) {
        break;
    } else {
        RTE_LOG(ERR, USER1, "cannot send packetn");
        int r = rte_eth_tx_done_cleanup(args.port_id, 0, 256);
        if (r < 0) {
             rte_panic("%u. cannot cleanup tx descs: %sn", i, rte_strerror(-r));
        }
        RTE_LOG(WARNING, USER1, "%u. cleaned up %d descriptors ...n", i, r);
    }
}
 

With that I get output like this:

 USER1: cannot send packet
USER1: 1086. cleaned up 32 descriptors ...
USER1: cannot send packet
USER1: 1118. cleaned up 32 descriptors ...
USER1: cannot send packet
USER1: 1150. cleaned up 0 descriptors ...
USER1: cannot send packet
USER1: 1182. cleaned up 0 descriptors ...
[..]

USER1: cannot send packet
USER1: 1950. cleaned up 32 descriptors ...
USER1: cannot send packet
USER1: 1982. cleaned up 0 descriptors ...
USER1: cannot send packet
USER1: 2014. cleaned up 0 descriptors ...
USER1: cannot send packet
USER1: 2014. cleaned up 32 descriptors ...
USER1: cannot send packet
USER1: 2046. cleaned up 32 descriptors ...
 

Meaning that it frees at most 32 descriptors like this. And that it doesn’t always succeed, but then the next rte_eth_tx_burst() succeeds freeing some.

Side question: Is there a better more dpdk-idiomatic way to handle the recycling of mbufs?


When I change the code such that I run out of mbufs before I run out of transmit descriptors (i.e. tx ring created with 1024 descriptors, mbuf pool still has 1023 elements), I have to change the alloc part like this:

 struct rte_mbuf *pkt;
do {
    pkt = rte_pktmbuf_alloc(args.pkt_pool);
    if (!pkt) {
        r = rte_eth_tx_done_cleanup(args.port_id, 0, 256);
        if (r < 0) {
             rte_panic("%u. cannot cleanup tx descs: %sn", i, rte_strerror(-r));
        }
        RTE_LOG(WARNING, USER1, "%u. cleaned up %d descriptors ...n", i, r);
    }
} while (!pkt);
 

The output is similar, e.g.:

 USER1: 1023. cleaned up 95 descriptors ...
USER1: 1118. cleaned up 32 descriptors ...
USER1: 1150. cleaned up 32 descriptors ...
USER1: 1182. cleaned up 32 descriptors ...
USER1: 1214. cleaned up 0 descriptors ...
USER1: 1214. cleaned up 0 descriptors ...
USER1: 1214. cleaned up 32 descriptors ...
[..]
 

That means the freeing of descriptors/mbufs is so ‘slow’ that it has to busy loop up to 3 times.

Again, is this a valid approach, or are there better dpdk ways to solve this?


Since rte_eth_tx_done_cleanup() might return -ENOTSUP , this may point to the direction that my usage of it might not be the best solution.

Incidentally, even with the ixgbe driver it fails for me when I disable checksum offloads!

По-видимому, ixgbe_dev_tx_done_cleanup() затем вызывает ixgbe_tx_done_cleanup_vec() , вместо ixgbe_tx_done_cleanup_full() чего безоговорочно возвращает -ENOTSUP :

 static int
ixgbe_tx_done_cleanup_vec(struct ixgbe_tx_queue *txq __rte_unused,
                        uint32_t free_cnt __rte_unused)
{
        return -ENOTSUP;
}
 

Имеет ли это смысл?

Итак, тогда, возможно, лучшая стратегия состоит в том, чтобы убедиться, что дескрипторов меньше, чем элементов пула (например 1024-32 ,< 1023 ), и просто перезванивать rte_eth_tx_burst() , пока он не вернет один?

Это значит вот так:

 for (;;) {
    uint16_t l = rte_eth_tx_burst(args.port_id, 0, amp;pkt, 1);
    if (l == 1) {
        break;
    } else {
        RTE_LOG(ERR, USER1, "%u. cannot send packet - retryn", i);
    }
}
 

Это работает, и вывод снова показывает, что дескрипторы освобождаются по 32 за раз, например:

 USER1: 1951. cannot send packet - retry
USER1: 1951. cannot send packet - retry
USER1: 1983. cannot send packet - retry
USER1: 1983. cannot send packet - retry
USER1: 2015. cannot send packet - retry
USER1: 2015. cannot send packet - retry
USER1: 2047. cannot send packet - retry
USER1: 2047. cannot send packet - retry
 

Я знаю, что я также могу использовать rte_eth_tx_burst() для отправки больших пакетов. Но я хочу сначала разобраться в простых/крайних случаях и понять семантику dpdk.

Я нахожусь на Fedora 33 и DPDK 20.11.2.

Комментарии:

1. Это поведение отличается от NIC PF и VF PMD. Текущий формат содержит в общей сложности 4 вопроса, таких как 1) Насколько сложно rte_eth_tx_burst() должен пытаться освободить буферы mbuf (и, следовательно, дескрипторы)?, 2) Подходят ли пороговые значения по умолчанию для этого варианта использования? 3) Существует ли лучший, более идиоматичный dpdk способ обработки рециркуляции mbuf? и 4) освобождение дескрипторов/mbuf настолько «медленное», что оно должно быть занято циклом до 3 раз, это правильный подход или есть лучшие способы dpdk для решения этой проблемы? Это хорошее наблюдение, будет ли легче четко осветить вопрос?

Ответ №1:

Рекомендация/решение: после анализа причины проблемы действительно с дескриптором TX с помощью rte_mempool_list_dump или dpdk-procinfo, пожалуйста, используйте rte_eth_tx_buffer_flush или измените настройки для пороговых значений TX.

Объяснение:

Поведение mbuf_free варьируется в зависимости от PMD, и в пределах одной и той же сетевой карты PF и VF также различаются. Ниже приведены некоторые моменты, чтобы понять это правильно

  1. rte_mempool может быть создан с элементами кэша или без них.
  2. при создании с кэшированными элементами, в зависимости от доступных lcores (eal_options) и количества элементов кэша на базовый параметр, настроенные mbuf добавляются на базовый кэш.
  3. Когда разгрузка HW DEV_TX_OFFLOAD_MBUF_FAST_FREE доступна и включена, соглашение заключается в том, что mbuf будет иметь значение ref_cnt как 1.
  4. Поэтому, когда когда-либо вызывается tx_burst (успех или сбой), проверяются пороговые уровни, могут ли свободные сегменты mbuf/mbuf быть возвращены в пул.
  5. При включенной функции DEV_TX_OFFLOAD_MBUF_FAST_FREE драйвер вслепую помещает элементы в кэш lcore.
  6. в то время как в случае no DEV_TX_OFFLOAD_MBUF_FAST_FREE общего подхода проверки MBUF, обеспечивающего проверку nb_segments и ref_cnt, затем переносится в mempool.

Но всегда либо фиксированный (32, я полагаю, установлен по умолчанию для всех PMD), либо доступный бесплатный mbuf всегда помещается в кэш или пул.

Факты:

  1. В случае драйвера IXGBE VF эта опция DEV_TX_OFFLOAD_MBUF_FAST_FREE недоступна. Это означает, что каждый раз при достижении пороговых значений каждый отдельный mbuf проверяется и передается в mempool.
  2. согласно фрагменту кода rte_eth_dev_configure , он настроен только для TX и rte_pktmbuf_pool_create создан так, чтобы иметь 341 элемент в качестве кэша.
  3. Необходимо сделать предположение, что существует только 1 основанный на Lcore (который запускает цикл alloc и tx).

Фрагмент кода-1:

 for (unsigned i = 0; i < 2048;   i) {
    struct rte_mbuf *pkt = rte_pktmbuf_alloc(args.pkt_pool);
    // error check, prepare packet etc.

    uint16_t l = rte_eth_tx_burst(args.port_id, 0, amp;pkt, 1);
    // error check etc.
}

After 1086 transmitted packets (of ~ 300 bytes each), rte_eth_tx_burst() returns 0.
 

[Наблюдение] Если действительно mbuf был запущен, rte_pktmbuf_alloc он должен был выйти из строя раньше rte_eth_tx_burst . Но сбой в 1086 создает интересное явление, потому что общее количество созданных mbuf равно 1023, и сбой происходит за 2 итерации 32 mbuf_release в mempool. Анализируя код драйвера для ixgbe, можно обнаружить, что (только возвращаемое значение 0) в tx_xmit_pkts равно

         /* Only use descriptors that are available */
        nb_pkts = (uint16_t)RTE_MIN(txq->nb_tx_free, nb_pkts);
        if (unlikely(nb_pkts == 0))
                return 0;
 

Несмотря на то, что в конфигурации tx_ring_size установлено значение 992, внутренне rte_eth_dev_adjust_nb_desc установлено значение max of *nb_desc, desc_lim->nb_min . Основываясь на коде, это не потому, что нет свободного mbuf, но из-за того, что дескриптор TX низкий или недоступен.

в то время как во всех других случаях, всякий rte_eth_tx_done_cleanup раз, когда или rte_eth_tx_buffer_flush когда они фактически выталкивают любые ожидающие дескрипторы, чтобы быть DMA немедленно из SW PMD. Это внутренне высвобождает больше дескрипторов, что делает tx_burst намного более плавным.

Чтобы определить первопричину, всякий раз, когда DPDK API tx_burst возвращает либо

  1. призвать rte_mempool_list_dump или
  2. используйте дамп mempool через dpdk-procinfo

Примечание: большинство PMD работает над амортизацией стоимости записи дескриптора (полезной нагрузки PCIe) путем пакетной обработки и группирования не менее 4 (в случае SSE). Следовательно, один пакет, даже если DPDK tx_burst, возвращающий 1, не будет выталкивать пакет из сетевой карты. Следовательно, для обеспечения использования rte_eth_tx_buffer_flush .

Ответ №2:

Скажем, вы вызываете rte_eth_tx_burst() для отправки один небольшой пакет (один mbuf, без выгрузки). Предположим, что драйвер действительно отправляет пакет в HW. Это съедает один дескриптор в кольце: драйвер «запоминает», что этот пакет mbuf связан с этим дескриптором. Но пакет отправляется не мгновенно. HW обычно имеет некоторые средства для уведомления водителя о завершении. Только представьте: если бы водитель проверял наличие завершений на каждом rte_eth_tx_burst() вызов (таким образом, игнорируя любые пороговые значения), а затем вызов rte_eth_tx_burst() еще раз по замкнутому циклу для другого пакета, скорее всего, потребит еще один дескриптор, а не переработает первый. Поэтому, учитывая этот факт, я бы не стал использовать жесткий цикл при исследовании tx_free_thresh семантики. И не должно иметь значения, вызываете ли вы rte_eth_tx_burst() один раз за пакет или один раз за их пакет.

Сейчас. Скажем, у вас есть кольцо размера Tx N . Предположим, tx_free_thresh есть M . И у вас есть mempool определенного размера Z . Что вы делаете, так это выделяете пакет N - M - 1 небольших пакетов и вызываете rte_eth_tx_burst() отправку этого пакета (без разгрузки; предполагается, что каждый пакет поглощает один дескриптор Tx). Затем вы ждете некоторого сознательно достаточного (для завершения) количества времени и проверяете количество свободных объектов в пуле памяти. Эту цифру следует прочитать Z - (N - M - 1) . Затем вы выделяете и отправляете один дополнительный пакет. Затем снова подождите. На этот раз количество запасных объектов в пуле памяти должно быть указано Z - (N - M) . Наконец, вы выделяете и отправляете еще один пакет (снова!), Таким образом, пересекая пороговое значение (количество запасных дескрипторов Tx становится меньше M ). Во время этого вызова rte_eth_tx_burst() драйвер должен обнаружить пересечение порога и начать проверку на выполнение. Это должно освободить дескрипторы драйвера (N - M) (используемые двумя предыдущими rte_eth_tx_burst() вызовами), тем самым очистив все кольцо. Затем драйвер переходит к отправке нового пакета, о котором идет речь, в HW, таким образом, тратя один дескриптор. Затем вы проверяете mempool: он должен сообщать Z - 1 о свободных объектах.

Итак, короче говоря: никакого цикла, всего три rte_eth_tx_burst() вызова с достаточным временем ожидания между ними. И вы проверяете количество запасных объектов в пуле памяти после каждой операции отправки. Теоретически, таким образом, вы сможете понять семантику углового регистра. В этом-то и суть. Однако, пожалуйста, имейте в виду, что фактическое поведение может отличаться у разных поставщиков / PMD.

Ответ №3:

Полагаться на rte_eth_tx_done_cleanup() действительно не вариант, так как многие PMD не реализуют его. В основном это обеспечивают Intel PMD, но, например, SFC, MLX* и af_packet этого не делают.

Однако до сих пор неясно, почему ixgbe PMD не поддерживает очистку, когда разгрузки не включены.

Требования rte_eth_tx_burst() к освобождению действительно легкие — из документов API:

  * It is the responsibility of the rte_eth_tx_burst() function to
 * transparently free the memory buffers of packets previously sent.
 * This feature is driven by the *tx_free_thresh* value supplied to the
 * rte_eth_dev_configure() function at device configuration time.
 * When the number of free TX descriptors drops below this threshold, the
 * rte_eth_tx_burst() function must [attempt to] free the *rte_mbuf*  buffers
 * of those packets whose transmission was effectively completed.
[..]
 * @return
 *   The number of output packets actually stored in transmit descriptors of
 *   the transmit ring. The return value can be less than the value of the
 *   *tx_pkts* parameter when the transmit ring is full or has been filled up.
 

Таким образом, просто попытка освободить (но не ожидание результатов этой попытки) и возврат 0 (поскольку 0 меньше tx_pkts ) покрывается этим «контрактом».

FWIW, ни один пример, распространяемый с помощью dpdk rte_eth_tx_burst() , не повторяет отправку еще не отправленных пакетов. Однако есть несколько примеров использования rte_eth_tx_burst() и удаления неотправленных пакетов.

AFAICS, кроме rte_eth_tx_done_cleanup() того, и rte_eth_tx_burst() нет никакой другой функции для запроса выпуска mbuf, ранее представленных для передачи.

Таким образом, рекомендуется увеличить размер пула пакетов mbuf больше, чем настроенный размер кольца, чтобы избежать ситуаций, когда все mbuf находятся в полете и не могут быть восстановлены, потому что не осталось mbuf для повторного вызова rte_eth_tx_burst() .