Многие интерфейсы ядра 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)
А вот и сетевой захват:
В выходных данных 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
packet_tx_mmap
должна совместно использовать буфер с ядром, это означает, что несколько пакетов копируются из пространства пользователя в пространство ядра в одном системном вызовеsendto()
, поэтому отправка трафика на интерфейс обратной связи означает, что мы тестируем именно этот аспект и не беспокоимся о DMA. 'передача пакетов на сетевую карту, что будет одним и тем же процессом как дляpacket_tx
, так и дляpacket_tx_mmap
, потому что это находится ниже по стеку ядра. - person jwbensley   schedule 07.04.2017send_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.2017MSG_DONTWAIT
и без него я, может быть, заполняю очередь NIC? Или я неправильно понимаю? - person jwbensley   schedule 12.04.2017memcpy
в своей программе. - person kfx   schedule 13.04.2017socket()
иsendto()
, чтобы увидеть, где разветвляется вызовsendto
для сокета, созданного с помощью кольца TX сmmap()
и обычным сокетом буфера пакетов: github.com/jwbensley/EtherateMT/wiki/ - person jwbensley   schedule 15.04.2017sendto
, но я не вижу, куда отправляются пакеты. Вообще у меня проблемы с пониманиемPACKET_MMAP
. В частности, позволяет лиPACKET_MMAP
отправить пакет с помощьюTCP/UDP
обычным способом (например, с использованием общего сокетаAF_INET
,SOCK_STREAM
)? - person Gilgamesz   schedule 07.06.2018sendto()
— это системный вызов в Linux, который в конечном итоге вызоветtpacket_snd()
в af_packet.c. Я проследил путь этих вызовов из пользовательской программы в ядро здесь: github.com /jwbensley/EtherateMT/wiki/ - person jwbensley   schedule 09.06.2018