Отправка данных с PACKET_MMAP и PACKET_TX_RING происходит медленнее, чем обычно (без)

Я пишу генератор трафика на C, используя параметр сокета PACKET_MMAP для создания кольцевого буфера для отправки данных через необработанный сокет. Кольцевой буфер заполняется кадрами Ethernet для отправки, и вызывается sendto. Все содержимое кольцевого буфера отправляется через сокет, что должно обеспечить более высокую производительность, чем наличие буфера в памяти и повторный вызов sendto для каждого кадра в буфере, который необходимо отправить.

Если PACKET_MMAP не используется, при вызове sendto один кадр копируется из буфера в пользовательской памяти в буфер SK в памяти ядра, затем ядро ​​должно скопировать пакет в память, к которой обращается сетевой адаптер для прямого доступа к памяти, и сигнализировать сетевому адаптеру, чтобы DMA кадра в его собственные аппаратные буферы и поставить его в очередь для передачи. При использовании параметра сокета PACKET_MMAP память, отображаемая через mmapped, выделяется приложением и связывается с необработанным сокетом. Приложение помещает пакеты в mmapped-буфер, вызывает sendto, и вместо того, чтобы ядро ​​копировало пакеты в SK-буфер, оно может считывать их напрямую из mmapped-буфера. Также из кольцевого буфера можно считывать «блоки» пакетов вместо отдельных пакетов/кадров. Таким образом, повышение производительности достигается за счет одного системного вызова для копирования нескольких кадров и уменьшения количества копирующих действий для каждого кадра, чтобы поместить его в аппаратные буферы сетевой карты.

Когда я сравниваю производительность сокета, использующего PACKET_MMAP, с «обычным» сокетом (символьный буфер с одним пакетом в нем), я не вижу никакого выигрыша в производительности. Почему это так? При использовании PACKET_MMAP в режиме Tx в каждый кольцевой блок можно поместить только один кадр (а не несколько кадров в кольцевой блок, как в режиме Rx), однако я создаю 256 блоков, поэтому мы должны отправлять 256 кадры в одном вызове sendto правильно?

Производительность с PACKET_MMAP, main() вызовов packet_tx_mmap():

bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0   Tx Gbps 17.65 (2206128128) pps 1457152
2. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.08 (2385579520) pps 1575680
3. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.28 (2409609728) pps 1591552
4. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.31 (2414260736) pps 1594624
5. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.30 (2411935232) pps 1593088

Производительность без PACKET_MMAP, main() звонков packet_tx():

bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0   Tx Gbps 18.44 (2305001412) pps 1522458
2. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.30 (2537520018) pps 1676037
3. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.29 (2535744096) pps 1674864
4. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.26 (2533014354) pps 1673061
5. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.32 (2539476106) pps 1677329

Функция packet_tx() кажется немного быстрее, чем функция packet_tx_mmap(), но она также немного короче, поэтому я думаю, что минимальное увеличение производительности — это просто немного меньшее количество строк кода, присутствующих в packet_tx. Итак, мне кажется, что обе функции имеют практически одинаковую производительность, почему? Почему PACKET_MMAP не намного быстрее, как я понимаю, системных вызовов и копий должно быть гораздо меньше?

void *packet_tx_mmap(void* thd_opt_p) {

    struct thd_opt *thd_opt = thd_opt_p;
    int32_t sock_fd = setup_socket_mmap(thd_opt_p);
    if (sock_fd == EXIT_FAILURE) exit(EXIT_FAILURE);

    struct tpacket2_hdr *hdr;
    uint8_t *data;
    int32_t send_ret = 0;
    uint16_t i;

    while(1) {

        for (i = 0; i < thd_opt->tpacket_req.tp_frame_nr; i += 1) {

            hdr = (void*)(thd_opt->mmap_buf + (thd_opt->tpacket_req.tp_frame_size * i));
            data = (uint8_t*)(hdr + TPACKET_ALIGN(TPACKET2_HDRLEN));

            memcpy(data, thd_opt->tx_buffer, thd_opt->frame_size);
            hdr->tp_len = thd_opt->frame_size;
            hdr->tp_status = TP_STATUS_SEND_REQUEST;

        }

        send_ret = sendto(sock_fd, NULL, 0, 0, NULL, 0);
        if (send_ret == -1) {
            perror("sendto error");
            exit(EXIT_FAILURE);
        }

        thd_opt->tx_pkts  += thd_opt->tpacket_req.tp_frame_nr;
        thd_opt->tx_bytes += send_ret;

    }

    return NULL;

}

Обратите внимание, что функция ниже вызывает setup_socket(), а не setup_socket_mmap():

void *packet_tx(void* thd_opt_p) {

    struct thd_opt *thd_opt = thd_opt_p;

    int32_t sock_fd = setup_socket(thd_opt_p); 

    if (sock_fd == EXIT_FAILURE) {
        printf("Can't create socket!\n");
        exit(EXIT_FAILURE);
    }

    while(1) {

        thd_opt->tx_bytes += sendto(sock_fd, thd_opt->tx_buffer,
                                    thd_opt->frame_size, 0,
                                    (struct sockaddr*)&thd_opt->bind_addr,
                                    sizeof(thd_opt->bind_addr));
        thd_opt->tx_pkts += 1;

    }

}

Единственная разница в функциях настройки сокета указана ниже, но по сути это требования для настройки SOCKET_RX_RING или SOCKET_TX_RING:

// Set the TPACKET version, v2 for Tx and v3 for Rx
// (v2 supports packet level send(), v3 supports block level read())
int32_t sock_pkt_ver = -1;

if(thd_opt->sk_mode == SKT_TX) {
    static const int32_t sock_ver = TPACKET_V2;
    sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver));
} else {
    static const int32_t sock_ver = TPACKET_V3;
    sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver));
}

if (sock_pkt_ver < 0) {
    perror("Can't set socket packet version");
    return EXIT_FAILURE;
}


memset(&thd_opt->tpacket_req, 0, sizeof(struct tpacket_req));
memset(&thd_opt->tpacket_req3, 0, sizeof(struct tpacket_req3));

//thd_opt->block_sz = 4096; // These are set else where
//thd_opt->block_nr = 256;
//thd_opt->block_frame_sz = 4096;

int32_t sock_mmap_ring = -1;
if (thd_opt->sk_mode == SKT_TX) {

    thd_opt->tpacket_req.tp_block_size = thd_opt->block_sz;
    thd_opt->tpacket_req.tp_frame_size = thd_opt->block_sz;
    thd_opt->tpacket_req.tp_block_nr = thd_opt->block_nr;
    // Allocate per-frame blocks in Tx mode (TPACKET_V2)
    thd_opt->tpacket_req.tp_frame_nr = thd_opt->block_nr;

    sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_TX_RING , (void*)&thd_opt->tpacket_req , sizeof(struct tpacket_req));

} else {

    thd_opt->tpacket_req3.tp_block_size = thd_opt->block_sz;
    thd_opt->tpacket_req3.tp_frame_size = thd_opt->block_frame_sz;
    thd_opt->tpacket_req3.tp_block_nr = thd_opt->block_nr;
    thd_opt->tpacket_req3.tp_frame_nr = (thd_opt->block_sz * thd_opt->block_nr) / thd_opt->block_frame_sz;
    thd_opt->tpacket_req3.tp_retire_blk_tov   = 1;
    thd_opt->tpacket_req3.tp_feature_req_word = 0;

    sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_RX_RING , (void*)&thd_opt->tpacket_req3 , sizeof(thd_opt->tpacket_req3));
}

if (sock_mmap_ring == -1) {
    perror("Can't enable Tx/Rx ring for socket");
    return EXIT_FAILURE;
}


thd_opt->mmap_buf = NULL;
thd_opt->rd = NULL;

if (thd_opt->sk_mode == SKT_TX) {

    thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0);

    if (thd_opt->mmap_buf == MAP_FAILED) {
        perror("mmap failed");
        return EXIT_FAILURE;
    }


} else {

    thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0);

    if (thd_opt->mmap_buf == MAP_FAILED) {
        perror("mmap failed");
        return EXIT_FAILURE;
    }

    // Per bock rings in Rx mode (TPACKET_V3)
    thd_opt->rd = (struct iovec*)calloc(thd_opt->tpacket_req3.tp_block_nr * sizeof(struct iovec), 1);

    for (uint16_t i = 0; i < thd_opt->tpacket_req3.tp_block_nr; ++i) {
        thd_opt->rd[i].iov_base = thd_opt->mmap_buf + (i * thd_opt->tpacket_req3.tp_block_size);
        thd_opt->rd[i].iov_len  = thd_opt->tpacket_req3.tp_block_size;
    }


}

Обновление 1: результат для физического интерфейса(ов). Было упомянуто, что одна из причин, по которой я мог не видеть разницы в производительности при использовании PACKET_MMAP, заключалась в том, что я отправлял трафик на петлевой интерфейс (который, во-первых, , не имеет QDISC). Поскольку запуск любой из подпрограмм packet_tx_mmap() или packet_tx() может генерировать более 10 Гбит/с, а в моем распоряжении есть только интерфейсы 10 Гбит/с, я соединил два вместе, и это результаты, которые показывают почти то же самое, что и выше, разница в скорости между две функции:

packet_tx() до 20G облигация0

  • 1 поток: в среднем 10,77 Гбит/с~ / 889 тыс. кадров в секунду~
  • 2 потока: в среднем 19,19 Гбит/с~ / 1,58 Мбит/с~
  • 3 потока: в среднем 19,67 Гбит/с~ / 1,62 Мбит/с~ (это настолько быстро, насколько хватит связи)

packet_tx_mmap() до 20G Bond0:

  • 1 поток: в среднем 11,08 Гбит/с~ / 913 тыс. кадров в секунду~
  • 2 потока: в среднем 19,0 Гбит/с~ / 1,57 Мбит/с~
  • 3 потока: в среднем 19,66 Гбит/с~ / 1,62 Мбит/с~ (это настолько быстро, насколько хватит связи)

Это было с кадрами размером 1514 байт (чтобы сохранить его таким же, как исходные тесты обратной связи выше).

Во всех приведенных выше тестах количество программных IRQ было примерно одинаковым (измерено с помощью этого скрипта) . При работе одного потока packet_tx() на ядре ЦП было около 40 тыс. прерываний в секунду. С 2 и 3 потоками работает 40k на 2 и 3 ядрах соответственно. Результаты при использовании packet_tx_mmap() там же. Около 40 тыс. программных IRQ для одного потока на одном ядре ЦП. 40k на ядро ​​при работе с 2 и 3 потоками.

Обновление 2: полный исходный код

Я загрузил полный исходный код сейчас, я все еще пишу это приложение, поэтому у него, вероятно, много недостатков, но они выходят за рамки этого вопроса: https://github.com/jwbensley/EtherateMT


person jwbensley    schedule 03.04.2017    source источник
comment
Насколько быстро работает ваша сеть? Насколько велик размер вашей рамы? Возможно, вы просто насыщаете свою ссылку? Вы проверили фактический (автосогласованный) битрейт?   -  person maxy    schedule 07.04.2017
comment
Размер кадра составляет 1514 октетов с заголовками, я отправляю трафик на петлевой интерфейс lo, как показано в выводе. Я отправляю трафик на петлевой интерфейс, чтобы устранить сетевую карту как источник проблем.   -  person jwbensley    schedule 07.04.2017
comment
Насколько я понимаю, поскольку функция packet_tx_mmap должна совместно использовать буфер с ядром, это означает, что несколько пакетов копируются из пространства пользователя в пространство ядра в одном системном вызове sendto(), поэтому отправка трафика на интерфейс обратной связи означает, что мы тестируем именно этот аспект и не беспокоимся о DMA. 'передача пакетов на сетевую карту, что будет одним и тем же процессом как для packet_tx, так и для packet_tx_mmap, потому что это находится ниже по стеку ядра.   -  person jwbensley    schedule 07.04.2017
comment
Для send_ret = sendto(sock_fd, NULL, 0, 0, NULL, 0); в функции packet_tx_mmap() я изменил флаг с 0 на MSG_DONTWAIT, и это не имело никакого значения. MSG_DONTWAIT должно быть неблокирующим, как вы говорите, но я предполагаю, что причина, по которой я не увидел изменений в производительности, заключается в том, что на следующей итерации цикла повторный вызов sendto() будет означать, что данные больше не отправляются из сетевой карты, если в очереди сетевой карты нет места конечно? Если мы будем массово передавать данные и заполнять очередь NIC без блокировки, не имеет значения, что вызов sendto() является неблокирующим, если очередь заполнена? ...   -  person jwbensley    schedule 12.04.2017
comment
... Так что мне кажется, что с флагом MSG_DONTWAIT и без него я, может быть, заполняю очередь NIC? Или я неправильно понимаю?   -  person jwbensley    schedule 12.04.2017
comment
на одно действие копирования меньше для каждого кадра - мне кажется, что действие копирования просто перемещается из ядра в пространство пользователя, как вы делаете memcpy в своей программе.   -  person kfx    schedule 13.04.2017
comment
Вы меня заинтересовали этим; Я провел немного времени, ковыряясь еще немного. Если мы хотим использовать MSG_DONTWAIT, нам нужно понять, как синхронизировать доступ к общему кольцевому буферу между пользователем и пространством ядра. В ядре для установки и получения статуса пакета используются барьеры записи и чтения соответственно (см. это), поэтому мы должны быть так же осторожны в пользовательском пространстве, чтобы сделать это. Барьеры раньше определялись в ‹asm/system.h›, но теперь это не так. Я использую liburcu. Все на сегодня.   -  person Jim D.    schedule 15.04.2017
comment
Я использовал совершенно другой подход (который, вероятно, связан с моими крайне низкими знаниями о ядре Linux и способах его отладки). Я только что начал отслеживать код вызовов socket() и sendto(), чтобы увидеть, где разветвляется вызов sendto для сокета, созданного с помощью кольца TX с mmap() и обычным сокетом буфера пакетов: github.com/jwbensley/EtherateMT/wiki/   -  person jwbensley    schedule 15.04.2017
comment
@ Джим Д. спасибо за все ваши отзывы, я прочитаю различные ссылки. Я уезжаю на несколько дней, так что не могу найти время до следующей недели.   -  person jwbensley    schedule 15.04.2017
comment
Глядя в af_packet.h, вы найдете определение прототипа, в котором определение прототипа sendmsg указывает на package_sendmsg(), а затем на tpacket_snd(); lxr.free-electrons.com/source/net/packet/ af_packet.c#L4373 — поэтому я думаю, что мне нужно выяснить, когда/где в исходном коде ядра для proto def сокета для sendmsg установлено значение proto def, на которое я ссылался, я предполагаю, что мой сокет в настоящее время указывает на Определение исходного прототипа сокета здесь: lxr.free-electrons.com/ источник/сеть/ipv4/raw.c#L939   -  person jwbensley    schedule 15.04.2017
comment
@ Джим Д. Я обновил вопрос, указав некоторую статистику производительности, используя реальный интерфейс, а не петлевой интерфейс. Я получу полный код онлайн на следующей неделе, когда вернусь домой (сейчас путешествую). Мне кажется, что код в af_packet.h либо не используется, либо обычный путь пакета через необработанный сокет, должно быть, был улучшен за эти годы до такой степени, что он примерно такой же быстрый, как метод PACKET_MMAP.   -  person jwbensley    schedule 16.04.2017
comment
Спасибо! Я дам ему прочитать. Я загрузил код (пока что) на GitHub и обновил вопросы ссылкой.   -  person jwbensley    schedule 18.04.2017
comment
Я вижу, что вы используете функцию sendto, но я не вижу, куда отправляются пакеты. Вообще у меня проблемы с пониманием PACKET_MMAP. В частности, позволяет ли PACKET_MMAP отправить пакет с помощью TCP/UDP обычным способом (например, с использованием общего сокета AF_INET, SOCK_STREAM)?   -  person Gilgamesz    schedule 07.06.2018
comment
@Gilgamesz Вам следует задать для этого отдельный вопрос, но да, вы можете отправить TCP/UDP, создав сокет как SOCK_DRAM, я использовал SOCK_RAW. sendto() — это системный вызов в Linux, который в конечном итоге вызовет tpacket_snd() в af_packet.c. Я проследил путь этих вызовов из пользовательской программы в ядро ​​здесь: github.com /jwbensley/EtherateMT/wiki/   -  person jwbensley    schedule 09.06.2018
comment
@Gilgamesz Это более глубокое погружение (но на самом деле вам просто нужно погрузиться и прочитать исходный код ядра для себя или задать новый вопрос на SO): github.com/jwbensley/EtherateMT/wiki/   -  person jwbensley    schedule 09.06.2018
comment
@jwbensley, спасибо за ответ. Действительно, мне удалось отправить пакет UDP с помощью (AF_PACKET, SOCK_DGRAM). Каковы ваши наблюдения, когда дело доходит до выступления?   -  person Gilgamesz    schedule 10.06.2018
comment
@Gilgamesz - это действительно отдельный вопрос, но я получаю стабильную скорость 1 млн пакетов в секунду на ядро ​​ЦП.   -  person jwbensley    schedule 17.06.2018


Ответы (1)


Многие интерфейсы ядра Linux плохо документированы. Или даже если они кажутся хорошо задокументированными, они могут быть довольно сложными, и это может затруднить понимание функциональных или, что еще сложнее, нефункциональных свойств интерфейса.

По этой причине мой совет всем, кто хочет хорошо разбираться в API-интерфейсах ядра или создавать высокопроизводительные приложения с использованием API-интерфейсов ядра, должен уметь взаимодействовать с кодом ядра, чтобы добиться успеха.

В этом случае спрашивающий хочет понять характеристики производительности отправки необработанных кадров через интерфейс общей памяти (пакет mmap) в ядро.

Документация по Linux находится здесь. В нем есть устаревшая ссылка на руководство, которое теперь можно найти здесь и включает копия packet_mmap.c (у меня есть немного другая версия здесь).

Документация в значительной степени ориентирована на чтение, что является типичным случаем использования mmap пакетов: эффективное чтение необработанных кадров из интерфейса для, например, эффективного получения захвата пакета из высокоскоростного интерфейса с небольшими затратами или вообще без них. потеря.

Однако OP заинтересован в высокопроизводительном письме, что является гораздо менее распространенным вариантом использования, но потенциально полезным для генератора/симулятора трафика, который, по-видимому, является тем, что OP хочет с ним делать. К счастью, все «как это сделать» касается написания фреймов.

Тем не менее, очень мало информации о том, как это работает на самом деле, и нет никакой очевидной помощи, чтобы ответить на вопрос ОП о том, почему использование пакетного mmap не кажется более быстрым, чем его неиспользование и вместо этого отправка одного кадра за раз.

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

Чтобы найти соответствующий код ядра, вы можете выполнить поиск по нескольким ключевым словам, но PACKET_TX_RING выделяется как опция сокета, уникальная для этой функции. Поиск в Интернете по запросу "PACKET_TX_RING linux cross reference" выдает небольшое количество ссылок, в том числе af_packet.c, который при небольшом рассмотрении оказывается реализацией всех функций AF_PACKET, включая mmap пакетов.

Просматривая af_packet.c, оказывается, что основная работа по передаче с помощью mmap пакета происходит в tpacket_snd(). Но правильно ли это? Как мы можем определить, имеет ли это какое-то отношение к тому, что, по нашему мнению, он делает?

Очень мощным инструментом для получения такой информации из ядра является SystemTap. (Для этого требуется установить символы отладки для вашего ядра. Я использую Ubuntu, и это рецепт, как заставить SystemTap работать на Ubuntu.)

После того, как SystemTap заработает, вы можете использовать SystemTap в сочетании с packet_mmap.c, чтобы увидеть, вызывается ли вообще tpacket_snd(), установив зонд для функции ядра tpacket_snd, а затем запустив packet_mmap для отправки кадра через общее кольцо TX:

$ sudo stap -e 'probe kernel.function("tpacket_snd") { printf("W00T!\n"); }' &
[1] 19961
$ sudo ./packet_mmap -c 1 eth0
[...]
STARTING TEST:
data offset = 32 bytes
start fill() thread
send 1 packets (+150 bytes)
end of task fill()
Loop until queue empty (0)
END (number of error:0)
W00T!
W00T!

В00Т! Мы на что-то; tpacket_snd на самом деле вызывается. Но наша победа будет недолгой. Если мы продолжим пытаться получить больше информации из стандартной сборки ядра, SystemTap будет жаловаться, что не может найти переменные, которые мы хотим проверить, и аргументы функции будут выведены со значениями как ? или ERROR. Это связано с тем, что ядро ​​скомпилировано с оптимизацией, и вся функциональность для AF_PACKET определена в одной единице трансляции af_packet.c; многие функции встраиваются компилятором, эффективно теряя локальные переменные и аргументы.

Чтобы получить больше информации из af_packet.c, нам нужно собрать версию ядра, в которой af_packet.c собрано без оптимизации. Посмотрите здесь, чтобы получить некоторые рекомендации. Я буду ждать.

Хорошо, надеюсь, это было не слишком сложно, и вы успешно загрузили ядро, из которого SystemTap может получить много полезной информации. Имейте в виду, что эта версия ядра предназначена только для того, чтобы помочь нам понять, как работает пакет mmap. Мы не можем получить прямую информацию о производительности от этого ядра, потому что af_packet.c было собрано без оптимизации. Если выяснится, что нам нужно получить информацию о том, как будет вести себя оптимизированная версия, мы можем собрать другое ядро ​​с af_packet.c, скомпилированным с оптимизацией, но с добавлением некоторого инструментального кода, который предоставляет информацию через переменные, которые не будут оптимизированы, так что SystemTap их можно увидеть.

Так что давайте использовать его, чтобы получить некоторую информацию. Взгляните на status.stp:

# This is specific to net/packet/af_packet.c 3.13.0-116

function print_ts() {
  ts = gettimeofday_us();
  printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}

#  325 static void __packet_set_status(struct packet_sock *po, void *frame, int status)
#  326 {
#  327  union tpacket_uhdr h;
#  328 
#  329  h.raw = frame;
#  330  switch (po->tp_version) {
#  331  case TPACKET_V1:
#  332      h.h1->tp_status = status;
#  333      flush_dcache_page(pgv_to_page(&h.h1->tp_status));
#  334      break;
#  335  case TPACKET_V2:
#  336      h.h2->tp_status = status;
#  337      flush_dcache_page(pgv_to_page(&h.h2->tp_status));
#  338      break;
#  339  case TPACKET_V3:
#  340  default:
#  341      WARN(1, "TPACKET version not supported.\n");
#  342      BUG();
#  343  }
#  344 
#  345  smp_wmb();
#  346 }

probe kernel.statement("__packet_set_status@net/packet/af_packet.c:334") {
  print_ts();
  printf("SET(V1): %d (0x%.16x)\n", $status, $frame);
}

probe kernel.statement("__packet_set_status@net/packet/af_packet.c:338") {
  print_ts();
  printf("SET(V2): %d\n", $status);
}

#  348 static int __packet_get_status(struct packet_sock *po, void *frame)
#  349 {
#  350  union tpacket_uhdr h;
#  351 
#  352  smp_rmb();
#  353 
#  354  h.raw = frame;
#  355  switch (po->tp_version) {
#  356  case TPACKET_V1:
#  357      flush_dcache_page(pgv_to_page(&h.h1->tp_status));
#  358      return h.h1->tp_status;
#  359  case TPACKET_V2:
#  360      flush_dcache_page(pgv_to_page(&h.h2->tp_status));
#  361      return h.h2->tp_status;
#  362  case TPACKET_V3:
#  363  default:
#  364      WARN(1, "TPACKET version not supported.\n");
#  365      BUG();
#  366      return 0;
#  367  }
#  368 }

probe kernel.statement("__packet_get_status@net/packet/af_packet.c:358") { 
  print_ts();
  printf("GET(V1): %d (0x%.16x)\n", $h->h1->tp_status, $frame); 
}

probe kernel.statement("__packet_get_status@net/packet/af_packet.c:361") { 
  print_ts();
  printf("GET(V2): %d\n", $h->h2->tp_status); 
}

# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2136  do {
# 2137      ph = packet_current_frame(po, &po->tx_ring,
# 2138              TP_STATUS_SEND_REQUEST);
# 2139 
# 2140      if (unlikely(ph == NULL)) {
# 2141          schedule();
# 2142          continue;
# 2143      }
# 2144 
# 2145      status = TP_STATUS_SEND_REQUEST;
# 2146      hlen = LL_RESERVED_SPACE(dev);
# 2147      tlen = dev->needed_tailroom;
# 2148      skb = sock_alloc_send_skb(&po->sk,
# 2149              hlen + tlen + sizeof(struct sockaddr_ll),
# 2150              0, &err);
# 2151 
# 2152      if (unlikely(skb == NULL))
# 2153          goto out_status;
# 2154 
# 2155      tp_len = tpacket_fill_skb(po, skb, ph, dev, size_max, proto,
# 2156                    addr, hlen);
# [...]
# 2176      skb->destructor = tpacket_destruct_skb;
# 2177      __packet_set_status(po, ph, TP_STATUS_SENDING);
# 2178      atomic_inc(&po->tx_ring.pending);
# 2179 
# 2180      status = TP_STATUS_SEND_REQUEST;
# 2181      err = dev_queue_xmit(skb);
# 2182      if (unlikely(err > 0)) {
# [...]
# 2195      }
# 2196      packet_increment_head(&po->tx_ring);
# 2197      len_sum += tp_len;
# 2198  } while (likely((ph != NULL) ||
# 2199          ((!(msg->msg_flags & MSG_DONTWAIT)) &&
# 2200           (atomic_read(&po->tx_ring.pending))))
# 2201      );
# 2202 
# [...]
# 2213  return err;
# 2214 }

probe kernel.function("tpacket_snd") {
  print_ts();
  printf("tpacket_snd: args(%s)\n", $$parms);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2140") {
  print_ts();
  printf("tpacket_snd:2140: current frame ph = 0x%.16x\n", $ph);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2141") {
  print_ts();
  printf("tpacket_snd:2141: (ph==NULL) --> schedule()\n");
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2142") {
  print_ts();
  printf("tpacket_snd:2142: flags 0x%x, pending %d\n", 
     $msg->msg_flags, $po->tx_ring->pending->counter);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2197") {
  print_ts();
  printf("tpacket_snd:2197: flags 0x%x, pending %d\n", 
     $msg->msg_flags, $po->tx_ring->pending->counter);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
  print_ts();
  printf("tpacket_snd: return(%d)\n", $err);
}

# 1946 static void tpacket_destruct_skb(struct sk_buff *skb)
# 1947 {
# 1948  struct packet_sock *po = pkt_sk(skb->sk);
# 1949  void *ph;
# 1950 
# 1951  if (likely(po->tx_ring.pg_vec)) {
# 1952      __u32 ts;
# 1953 
# 1954      ph = skb_shinfo(skb)->destructor_arg;
# 1955      BUG_ON(atomic_read(&po->tx_ring.pending) == 0);
# 1956      atomic_dec(&po->tx_ring.pending);
# 1957 
# 1958      ts = __packet_set_timestamp(po, ph, skb);
# 1959      __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts);
# 1960  }
# 1961 
# 1962  sock_wfree(skb);
# 1963 }

probe kernel.statement("tpacket_destruct_skb@net/packet/af_packet.c:1959") {
  print_ts();
  printf("tpacket_destruct_skb:1959: ph = 0x%.16x, ts = 0x%x, pending %d\n",
     $ph, $ts, $po->tx_ring->pending->counter);
}

Это определяет функцию (print_ts для вывода времени эпохи unix с разрешением в микросекунды) и количество зондов.

Сначала мы определяем зонды для вывода информации, когда пакеты в tx_ring имеют установленный или прочитанный статус. Затем мы определяем зонды для вызова и возврата tpacket_snd и в точках внутри цикла do {...} while (...), обрабатывающих пакеты в tx_ring. Наконец, мы добавляем зонд в деструктор skb.

Мы можем запустить скрипт SystemTap с sudo stap status.stp. Затем запустите sudo packet_mmap -c 2 <interface>, чтобы отправить 2 кадра через интерфейс. Вот результат, который я получил от скрипта SystemTap:

[1492581245.839850] tpacket_snd: args(po=0xffff88016720ee38 msg=0x14)
[1492581245.839865] GET(V1): 1 (0xffff880241202000)
[1492581245.839873] tpacket_snd:2140: current frame ph = 0xffff880241202000
[1492581245.839887] SET(V1): 2 (0xffff880241202000)
[1492581245.839918] tpacket_snd:2197: flags 0x40, pending 1
[1492581245.839923] GET(V1): 1 (0xffff88013499c000)
[1492581245.839929] tpacket_snd:2140: current frame ph = 0xffff88013499c000
[1492581245.839935] SET(V1): 2 (0xffff88013499c000)
[1492581245.839946] tpacket_snd:2197: flags 0x40, pending 2
[1492581245.839951] GET(V1): 0 (0xffff88013499e000)
[1492581245.839957] tpacket_snd:2140: current frame ph = 0x0000000000000000
[1492581245.839961] tpacket_snd:2141: (ph==NULL) --> schedule()
[1492581245.839977] tpacket_snd:2142: flags 0x40, pending 2
[1492581245.839984] tpacket_snd: return(300)
[1492581245.840077] tpacket_snd: args(po=0x0 msg=0x14)
[1492581245.840089] GET(V1): 0 (0xffff88013499e000)
[1492581245.840098] tpacket_snd:2140: current frame ph = 0x0000000000000000
[1492581245.840093] tpacket_destruct_skb:1959: ph = 0xffff880241202000, ts = 0x0, pending 1
[1492581245.840102] tpacket_snd:2141: (ph==NULL) --> schedule()
[1492581245.840104] SET(V1): 0 (0xffff880241202000)
[1492581245.840112] tpacket_snd:2142: flags 0x40, pending 1
[1492581245.840116] tpacket_destruct_skb:1959: ph = 0xffff88013499c000, ts = 0x0, pending 0
[1492581245.840119] tpacket_snd: return(0)
[1492581245.840123] SET(V1): 0 (0xffff88013499c000)

А вот и сетевой захват:

сетевой захват первого запуска package_mmap

В выходных данных SystemTap содержится много полезной информации. Мы видим, что tpacket_snd получает статус первого кадра в кольце (TP_STATUS_SEND_REQUEST равно 1), а затем установите его на TP_STATUS_SENDING (2). То же самое проделывает со вторым. Следующий кадр имеет статус TP_STATUS_AVAILABLE (0), который не является запросом на отправку, поэтому он вызывает schedule() для возврата и продолжает цикл. Поскольку больше нет кадров для отправки (ph==NULL) и запрошена неблокировка (msg->msg_flags ==MSG_DONTWAIT) цикл do {...} while (...) завершается, и tpacket_snd возвращает 300, число байтов, поставленных в очередь на передачу.

Затем packet_mmap снова вызывает sendto (через код «зацикливания, пока очередь не станет пустой»), но в кольце tx больше нет данных для отправки, и запрошена неблокировка, поэтому он немедленно возвращает 0, так как данные не были поставлены в очередь. . Обратите внимание, что кадр, состояние которого он проверял, является тем же кадром, который он проверял последним в предыдущем вызове --- он не начинался с первого кадра в кольце tx, он проверял head (который недоступен в пространстве пользователя).

Асинхронно вызывается деструктор, сначала для первого кадра, устанавливающий статус кадра в TP_STATUS_AVAILABLE и уменьшающий количество ожидающих выполнения, а затем для второго кадра. Обратите внимание, что если не была запрошена неблокировка, тест в конце цикла do {...} while (...) будет ждать, пока все ожидающие пакеты не будут переданы на сетевую карту (при условии, что она поддерживает разрозненные данные), прежде чем вернуться. Вы можете наблюдать это, запустив packet_mmap с параметром -t для «threaded», который использует блокирующий ввод-вывод (пока не дойдет до «зацикливания, пока очередь не станет пустой»).

Несколько замечаний. Во-первых, временные метки в выходных данных SystemTap не увеличиваются: небезопасно делать вывод о временном порядке из выходных данных SystemTap. Во-вторых, обратите внимание, что временные метки в сетевом захвате (сделанном локально) отличаются. FWIW, интерфейс - дешевый 1G в дешевом компьютере башни.

Итак, на данный момент, я думаю, мы более или менее знаем, как af_packet обрабатывает общий tx-кольцо. Далее следует, как кадры в кольце tx находят свой путь к сетевому интерфейсу. Возможно, будет полезно ознакомиться с этим разделом (о том, как обрабатывается передача уровня 2) обзора потока управления в сетевом ядре linux.

Итак, если у вас есть базовое понимание того, как обрабатывается передача уровня 2, может показаться, что этот интерфейс mmap пакетов должен быть огромным пожарным шлангом; загрузите общее кольцо tx с пакетами, вызовите sendto() с MSG_DONTWAIT, а затем tpacket_snd будет перебирать очередь tx, создавая skb и помещая их в очередь на qdisc. Асинхронно skb будут удалены из очереди с qdisc и отправлены в аппаратное кольцо tx. skb должны быть нелинейными, чтобы они скорее ссылались на данные в кольце tx. чем копировать, и хорошая современная сетевая карта должна иметь возможность обрабатывать разбросанные данные и также ссылаться на данные в кольцах tx. Конечно, любое из этих предположений может быть неверным, так что давайте попробуем вылить на qdisc много боли с помощью этого пожарного шланга.

Но сначала, малоизвестный факт о том, как работают qdisc. Они содержат ограниченный объем данных (обычно подсчитываемый в количестве кадров, но в некоторых случаях он может измеряться в байтах), и если вы попытаетесь поставить кадр в очередь на полный qdisc, кадр, как правило, будет удален (в зависимости от того, что enqueuer решает сделать). Итак, я намекну, что моя первоначальная гипотеза заключалась в том, что OP использовала mmap пакетов для передачи кадров на qdisc так быстро, что многие из них были отброшены. Но не держитесь слишком твердо за эту идею; это ведет вас в направлении, но всегда держите ум открытым. Давайте попробуем выяснить, что происходит.

Первая проблема при попытке это сделать, это то, что qdisc по умолчанию pfifo_fast не ведет статистику. Итак, давайте заменим его на qdisc pfifo, который подходит. По умолчанию pfifo ограничивает очередь до TXQUEUELEN кадров (обычно по умолчанию 1000). Но так как мы хотим продемонстрировать переполнение qdisc, давайте явно установим его равным 50:

$ sudo tc qdisc add dev eth0 root pfifo limit 50
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8004: root refcnt 2 limit 50p
 Sent 42 bytes 1 pkt (dropped 0, overlimits 0 requeues 0) 
 backlog 0b 0p requeues 0 

Давайте также измерим, сколько времени требуется для обработки кадров в tpacket_snd скриптом SystemTap call-return.stp:

# This is specific to net/packet/af_packet.c 3.13.0-116

function print_ts() {
  ts = gettimeofday_us();
  printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}

# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2213  return err;
# 2214 }

probe kernel.function("tpacket_snd") {
  print_ts();
  printf("tpacket_snd: args(%s)\n", $$parms);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
  print_ts();
  printf("tpacket_snd: return(%d)\n", $err);
}

Запустите сценарий SystemTap с sudo stap call-return.stp, а затем запустите 8096 кадров по 1500 байт на этот qdisc со скудной емкостью 50 кадров:

$ sudo ./packet_mmap -c 8096 -s 1500 eth0
[...]
STARTING TEST:
data offset = 32 bytes
start fill() thread
send 8096 packets (+12144000 bytes)
end of task fill()
Loop until queue empty (0)
END (number of error:0)

Итак, давайте проверим, сколько пакетов было отброшено qdisc:

$ tc -s -d qdisc show dev eth0
qdisc pfifo 8004: root refcnt 2 limit 50p
 Sent 25755333 bytes 8606 pkt (dropped 1, overlimits 0 requeues 265) 
 backlog 0b 0p requeues 265 

ВАТ? Сбросил один из 8096 кадров, сброшенных на 50-кадровый qdisc? Давайте проверим вывод SystemTap:

[1492603552.938414] tpacket_snd: args(po=0xffff8801673ba338 msg=0x14)
[1492603553.036601] tpacket_snd: return(12144000)
[1492603553.036706] tpacket_snd: args(po=0x0 msg=0x14)
[1492603553.036716] tpacket_snd: return(0)

ЧТО? Обработка 8096 кадров в tpacket_snd? заняла почти 100 мс. Давайте проверим, сколько времени на самом деле потребуется для передачи; это 8096 кадров по 1500 байт/кадр при 1 гигабит/с ~= 97 мс. ВАТ? Пахнет, будто что-то блокирует.

Давайте поближе посмотрим на tpacket_snd. Стон:

skb = sock_alloc_send_skb(&po->sk,
                 hlen + tlen + sizeof(struct sockaddr_ll),
                 0, &err);

Это 0 выглядит довольно безобидно, но на самом деле это аргумент noblock. Должно быть msg->msg_flags & MSG_DONTWAIT (оказывается, это исправлено в 4.1). Здесь происходит то, что размер qdisc не является единственным ограничивающим ресурсом. Если выделение пространства для skb превысит размер ограничения sndbuf сокета, то этот вызов либо заблокирует ожидание освобождения skb, либо вернет -EAGAIN неблокирующему вызывающему объекту. В исправлении в V4.1, если запрашивается неблокировка, он возвращает количество записанных байтов, если оно не равно нулю, иначе -EAGAIN вызывающей стороне, что почти похоже на то, что кто-то не хочет, чтобы вы выяснили, как это использовать (например, вы заполняете кольцо tx 80 МБ данных, вызываете sendto с MSG_DONTWAIT и получаете результат, что вы отправили 150 КБ, а не EWOULDBLOCK).

Поэтому, если вы используете ядро ​​до версии 4.1 (я полагаю, что OP работает >4.1 и не подвержена этой ошибке), вам нужно будет исправить af_packet.c и собрать новое ядро ​​или обновить его до ядра 4.1 или выше.

Теперь я загрузил исправленную версию своего ядра, так как машина, которую я использую, работает под управлением 3.13. Хотя мы не будем блокироваться, если sndbuf заполнен, мы все равно вернемся с -EAGAIN. Я внес некоторые изменения в packet_mmap.c, чтобы увеличить размер sndbuf по умолчанию и использовать SO_SNDBUFFORCE для переопределения системного максимального значения на сокет, если это необходимо (кажется, что для этого требуется около 750 байт + размер кадра для каждого кадра). Я также сделал некоторые дополнения к call-return.stp, чтобы регистрировать максимальный размер sndbuf (sk_sndbuf), использованное количество (sk_wmem_alloc), любые ошибки, возвращаемые sock_alloc_send_skb, и любые ошибки, возвращаемые dev_queue_xmit при постановке skb в очередь на qdisc. Вот новая версия:

# This is specific to net/packet/af_packet.c 3.13.0-116

function print_ts() {
  ts = gettimeofday_us();
  printf("[%10d.%06d] ", ts/1000000, ts%1000000);
}

# 2088 static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
# 2089 {
# [...]
# 2133  if (size_max > dev->mtu + reserve + VLAN_HLEN)
# 2134      size_max = dev->mtu + reserve + VLAN_HLEN;
# 2135 
# 2136  do {
# [...]
# 2148      skb = sock_alloc_send_skb(&po->sk,
# 2149              hlen + tlen + sizeof(struct sockaddr_ll),
# 2150              msg->msg_flags & MSG_DONTWAIT, &err);
# 2151 
# 2152      if (unlikely(skb == NULL))
# 2153          goto out_status;
# [...]
# 2181      err = dev_queue_xmit(skb);
# 2182      if (unlikely(err > 0)) {
# 2183          err = net_xmit_errno(err);
# 2184          if (err && __packet_get_status(po, ph) ==
# 2185                 TP_STATUS_AVAILABLE) {
# 2186              /* skb was destructed already */
# 2187              skb = NULL;
# 2188              goto out_status;
# 2189          }
# 2190          /*
# 2191           * skb was dropped but not destructed yet;
# 2192           * let's treat it like congestion or err < 0
# 2193           */
# 2194          err = 0;
# 2195      }
# 2196      packet_increment_head(&po->tx_ring);
# 2197      len_sum += tp_len;
# 2198  } while (likely((ph != NULL) ||
# 2199          ((!(msg->msg_flags & MSG_DONTWAIT)) &&
# 2200           (atomic_read(&po->tx_ring.pending))))
# 2201      );
# [...]
# 2213  return err;
# 2214 }

probe kernel.function("tpacket_snd") {
  print_ts();
  printf("tpacket_snd: args(%s)\n", $$parms);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2133") {
  print_ts();
  printf("tpacket_snd:2133: sk_sndbuf =  %d sk_wmem_alloc = %d\n", 
     $po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2153") {
  print_ts();
  printf("tpacket_snd:2153: sock_alloc_send_skb err = %d, sk_sndbuf =  %d sk_wmem_alloc = %d\n", 
     $err, $po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2182") {
  if ($err != 0) {
    print_ts();
    printf("tpacket_snd:2182: dev_queue_xmit err = %d\n", $err);
  }
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2187") {
  print_ts();
  printf("tpacket_snd:2187: destructed: net_xmit_errno = %d\n", $err);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2194") {
  print_ts();
  printf("tpacket_snd:2194: *NOT* destructed: net_xmit_errno = %d\n", $err);
}

probe kernel.statement("tpacket_snd@net/packet/af_packet.c:2213") {
  print_ts();
  printf("tpacket_snd: return(%d) sk_sndbuf =  %d sk_wmem_alloc = %d\n", 
     $err, $po->sk->sk_sndbuf, $po->sk->sk_wmem_alloc->counter);
}

Давай еще раз попробуем:

$ sudo tc qdisc add dev eth0 root pfifo limit 50
$ tc -s -d qdisc show dev eth0
qdisc pfifo 8001: root refcnt 2 limit 50p
 Sent 2154 bytes 21 pkt (dropped 0, overlimits 0 requeues 0) 
 backlog 0b 0p requeues 0 
$ sudo ./packet_mmap -c 200 -s 1500 eth0
[...]
c_sndbuf_sz:       1228800
[...]
STARTING TEST:
data offset = 32 bytes
send buff size = 1228800
got buff size = 425984
buff size smaller than desired, trying to force...
got buff size = 2457600
start fill() thread
send: No buffer space available
end of task fill()
send: No buffer space available
Loop until queue empty (-1)
[repeated another 17 times]
send 3 packets (+4500 bytes)
Loop until queue empty (4500)
Loop until queue empty (0)
END (number of error:0)
$  tc -s -d qdisc show dev eth0
qdisc pfifo 8001: root refcnt 2 limit 50p
 Sent 452850 bytes 335 pkt (dropped 19, overlimits 0 requeues 3) 
 backlog 0b 0p requeues 3 

А вот вывод SystemTap:

[1492759330.907151] tpacket_snd: args(po=0xffff880393246c38 msg=0x14)
[1492759330.907162] tpacket_snd:2133: sk_sndbuf =  2457600 sk_wmem_alloc = 1
[1492759330.907491] tpacket_snd:2182: dev_queue_xmit err = 1
[1492759330.907494] tpacket_snd:2187: destructed: net_xmit_errno = -105
[1492759330.907500] tpacket_snd: return(-105) sk_sndbuf =  2457600 sk_wmem_alloc = 218639
[1492759330.907646] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.907653] tpacket_snd:2133: sk_sndbuf =  2457600 sk_wmem_alloc = 189337
[1492759330.907688] tpacket_snd:2182: dev_queue_xmit err = 1
[1492759330.907691] tpacket_snd:2187: destructed: net_xmit_errno = -105
[1492759330.907694] tpacket_snd: return(-105) sk_sndbuf =  2457600 sk_wmem_alloc = 189337
[repeated 17 times]
[1492759330.908541] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.908543] tpacket_snd:2133: sk_sndbuf =  2457600 sk_wmem_alloc = 189337
[1492759330.908554] tpacket_snd: return(4500) sk_sndbuf =  2457600 sk_wmem_alloc = 196099
[1492759330.908570] tpacket_snd: args(po=0x0 msg=0x14)
[1492759330.908572] tpacket_snd:2133: sk_sndbuf =  2457600 sk_wmem_alloc = 196099
[1492759330.908576] tpacket_snd: return(0) sk_sndbuf =  2457600 sk_wmem_alloc = 196099

Теперь все работает как положено; мы исправили ошибку, из-за которой мы блокировали превышение лимита sndbuf, и мы скорректировали лимит sndbuf так, чтобы он не был ограничением, и теперь мы видим, что кадры из кольца tx помещаются в очередь на qdisc, пока он не будет заполнен , после чего мы получаем ENOBUFS.

Теперь следующая проблема заключается в том, как эффективно продолжать публикацию на qdisc, чтобы интерфейс оставался загруженным. Обратите внимание, что реализация packet_poll бесполезна в случае, когда мы заполняем qdisc и возвращаем ENOBUFS, потому что он просто запрашивает, является ли голова TP_STATUS_AVAILABLE, которая в этом случае останется TP_STATUS_SEND_REQUEST до тех пор, пока последующий вызов sendto не поставит в очередь кадр на qdisc. Простая целесообразность (обновленная в package_mmap.c) состоит в том, чтобы зацикливаться на sendto до тех пор, пока не произойдет успех или ошибка, отличная от ENOBUFS или EAGAIN.

В любом случае, мы знаем более чем достаточно, чтобы ответить на вопрос OP, даже если у нас нет полного решения, позволяющего эффективно уберечь сетевую карту от голодания.

Из того, что мы узнали, мы знаем, что когда OP вызывает sendto с кольцом tx в режиме блокировки, tpacket_snd начнет ставить skbs в очередь на qdisc до тех пор, пока не будет превышен предел sndbuf (и по умолчанию обычно довольно мало, около 213 КБ, и далее, Я обнаружил, что данные кадра, на которые ссылаются в общем кольце tx, учитываются при блокировке (при сохранении pg_vec_lock). По мере освобождения skb в очереди будет ставиться больше кадров, и, возможно, sndbuf снова будет превышен, и мы снова заблокируемся. В конце концов, все данные будут помещены в очередь на qdisc, но tpacket_snd будут продолжать блокироваться до тех пор, пока все кадры не будут переданы (вы не можете пометить кадр в кольце tx как доступный до тех пор, пока NIC получил его, так как skb в кольце драйверов ссылается на кадр в кольце tx), все еще удерживая pg_vec_lock. В этот момент сетевая карта отключена, а любые другие устройства записи сокетов заблокированы блокировкой.

С другой стороны, когда OP публикует пакет за раз, он будет обрабатываться packet_snd, который блокируется, если в sndbuf нет места, а затем ставит кадр в очередь на qdisc и немедленно возвращается. Он не ждет передачи кадра. По мере опустошения qdisc в очередь могут ставиться дополнительные кадры. Если издатель сможет идти в ногу со временем, сетевая карта никогда не будет голодать.

Кроме того, оператор копирует в кольцо tx для каждого вызова sendto и сравнивает это с передачей фиксированного буфера кадров, когда кольцо tx не используется. Вы не увидите ускорения, если не будете копировать таким образом (хотя это не единственное преимущество использования tx-кольца).

person Jim D.    schedule 15.04.2017
comment
Спасибо за вашу помощь, сейчас я собираю ядро ​​с символами отладки и неоптимизированной версией af_packet.c. Пока ждем, просто пища для размышлений. Я не использую флаг MSG_DONTWAIT в своем приложении. Я не пытаюсь использовать неблокирующий вызов, поэтому приведенный выше фрагмент кода с sock_alloc_send_skb(x,x,0,x) - даже несмотря на то, что мы обнаружили ошибку, связанную с неправильной передачей этого третьего параметра, он все равно должен быть равен нулю, верно? - person jwbensley; 19.04.2017
comment
Также у меня есть ядро ​​​​4.4.x, но пока у вас есть ›= 3.14, если вы посмотрите на код, который я разместил на Github, есть опция сокета, чтобы обойти уровень QDISC и перейти прямо к передаче. Как только я соберу отладочное ядро, я добавлю ту же опцию сокета в тестовую программу packet_mmap.c, чтобы увидеть ее эффект: int bypass = 1; int ret = setsockopt(sock_fd, SOL_PACKET, PACKET_QDISC_BYPASS, &bypass, sizeof(bypass)); - person jwbensley; 19.04.2017
comment
Мое ядро ​​все еще компилируется, но после быстрого сканирования af_packet.c я ожидаю, что L2695 err = po->xmit(skb); будет указывать на packet_direct_xmit() вместо dev_queue_xmit(), см. L3751 в af_packet.c. Я думаю, что это указывает на это, которое указывает на это, которое указывает на это. - person jwbensley; 19.04.2017
comment
@jwbensley есть опция сокета, чтобы обойти уровень QDISC и перейти прямо к передаче Независимо от того, публикуете ли вы на qdisc или напрямую на кольцо сетевых карт, следующая проблема заключается в том, чтобы управлять скоростью, с которой вы это делаете (у вас есть неожиданно полагался на блокировку для этого до сих пор). Я бы начал с qdsic, так как легко управлять его размером (TXQUEUELEN) и легко увидеть статистику, такую ​​как сбросы, если вы публикуете слишком быстро (по крайней мере, если вы не используете pfifo_fast по умолчанию). Я думаю, что размер кольца драйвера динамически адаптируется, но вы можете контролировать его размер. Это займет некоторое исследование. - person Jim D.; 20.04.2017
comment
@jwbensley здесь есть потенциально интересная информация о очередь водителей. - person Jim D.; 20.04.2017
comment
@jwbensley хотя мы обнаружили ошибку с неправильной передачей третьего параметра, он все равно должен быть равен нулю, верно? Я думаю, что вы используете версию ядра с этим исправленным, и да, я теперь поймите, что он должен быть равен нулю, если вы хотите заблокировать, потому что это блокирует ожидание свободного места, если превышен предел sndbuf. - person Jim D.; 20.04.2017
comment
@jwbensley В конце этого ответа я кратко изложил, почему я думаю, что блокировка tx-кольца медленнее, чем блокировка без tx-кольца. Мне не хватает места (30к символов). Самый эффективный способ сделать это, вероятно, не блокировать (как я и писал в ответе). - person Jim D.; 21.04.2017
comment
Спасибо! - Я все еще читаю все это и ссылки и пытаюсь воспроизвести это на своей стороне и т. Д., Чтобы понять все это. У меня Systemtap работает плохо, у меня ядро ​​4.4.0, поэтому я думаю, что некоторые вещи изменились, ваш пример WOOT работает, но ни один из вызовов probe kernel.statement() не работает. Это, вероятно, отдельный пост StackOverflow сам по себе, поэтому я просто пытаюсь прочитать всю документацию, которую вы связали, и отладить, насколько это возможно. Я скоро вернусь. - person jwbensley; 21.04.2017
comment
@jwbensley зонды kenel.statement связаны с определенными номерами строк в исходном коде. Поскольку у вас другая версия af_packet.c, чем у меня, вам нужно будет изменить номера строк и, возможно, имена переменных. - person Jim D.; 22.04.2017
comment
D - Да, я изменил номера строк, но Стап все еще не был доволен. Возможно, я неправильно скомпилировал ядро, для этого подмодуля af_packet я скомпилировал его с -O0, но, возможно, мне следовало также добавить -g. Другие операторы stap работают, поэтому я справляюсь. Я только что закончил читать все ссылки, которые вы предоставили, и воспроизвел их на виртуальной машине и т. д., поэтому я дал вам вознаграждение, поскольку оно очень заслужено, я бы дал больше, если бы мог, но 500 — это максимум. Мне просто нужно больше времени, чтобы внести некоторые изменения в код и протестировать его на основе ваших выводов. - person jwbensley; 24.04.2017
comment
@jwbensley Спасибо за награду. Нужно понять гораздо больше, если вы хотите делать то, что, как я думаю, вы хотите делать. StackOverflow не обязательно является платформой для совместной работы над решением этой проблемы. Хотя может чаты? - person Jim D.; 24.04.2017
comment
Извините за задержку, я думал, что уже отметил этот ответ как правильный! - person jwbensley; 06.05.2017