Ответ 1
Многие интерфейсы к ядру Linux плохо документированы. Даже если они выглядят хорошо документированными, они могут быть довольно сложными, и это может затруднить понимание того, что функциональные или, зачастую, даже более сложные, нефункциональные свойства интерфейса.
По этой причине мой совет любому, кто хочет твердо понимать API ядра или должен создавать высокопроизводительные приложения с использованием API-интерфейсов ядра, должен иметь возможность взаимодействовать с кодом ядра, чтобы быть успешным.
В этом случае вопросник хочет понять характеристики производительности отправки необработанных кадров через интерфейс общей памяти (пакетный mmap) в ядро.
Документация по linux здесь. Он имеет устаревшую ссылку на "как", который теперь можно найти здесь и включает в себя копию packet_mmap.c
(у меня есть немного доступна другая версия здесь.
Документация в значительной степени ориентирована на чтение, что является типичным прецедентом использования пакета mmap: эффективное считывание необработанных кадров из интерфейса для, например, эффективно получать захват пакетов с высокоскоростного интерфейса с небольшой потерей или без потерь.
Однако OP заинтересован в высокопроизводительной записи, которая является гораздо менее распространенным вариантом использования, но потенциально полезна для генератора трафика/симулятора, который, по-видимому, стремится к тому, что OP хочет сделать с ним. К счастью, "как" все о написании кадров.
Тем не менее, очень мало информации о том, как это работает, и ничего очевидного ответа на вопрос OP о том, почему использование пакета mmap, похоже, не быстрее, чем не использовать его, и вместо этого отправляет один кадр в время.
К счастью, исходный код ядра с открытым исходным кодом и хорошо проиндексирован, поэтому мы можем обратиться к источнику, чтобы помочь нам получить ответ на вопрос.
Чтобы найти соответствующий код ядра, есть несколько ключевых слов, которые вы могли бы искать, но PACKET_TX_RING
выделяется как опция сокета, уникальная для этой функции. Поиск в interwebs для "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!
W00t! Мы на что-то; 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("[email protected]/packet/af_packet.c:334") {
print_ts();
printf("SET(V1): %d (0x%.16x)\n", $status, $frame);
}
probe kernel.statement("[email protected]/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("[email protected]/packet/af_packet.c:358") {
print_ts();
printf("GET(V1): %d (0x%.16x)\n", $h->h1->tp_status, $frame);
}
probe kernel.statement("[email protected]/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("[email protected]/packet/af_packet.c:2140") {
print_ts();
printf("tpacket_snd:2140: current frame ph = 0x%.16x\n", $ph);
}
probe kernel.statement("[email protected]/packet/af_packet.c:2141") {
print_ts();
printf("tpacket_snd:2141: (ph==NULL) --> schedule()\n");
}
probe kernel.statement("[email protected]/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("[email protected]/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("[email protected]/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("[email protected]/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 script с помощью sudo stap status.stp
. Затем запустите sudo packet_mmap -c 2 <interface>
, чтобы отправить 2 кадра через интерфейс. Вот результат, который я получил из SystemTap script:
[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
(через код "loop until queue empty" ), но больше нет данных для отправки в tx-кольцо, и запрашивается неблокировка, поэтому он немедленно возвращает 0, поскольку никакие данные не были поставлены в очередь. Обратите внимание, что кадр, в котором он проверял статус, - это тот же самый кадр, который он проверил последним в предыдущем вызове, - он не начинался с первого кадра в кольце tx, он проверял head
(который недоступен в пользовательской области).
Асинхронно, деструктор вызывается сначала в первом кадре, устанавливая статус кадра на TP_STATUS_AVAILABLE
и уменьшая ожидающий счет, а затем на втором кадре. Обратите внимание, что если неблокирование не запрашивалось, тест в конце цикла do {...} while (...)
будет ждать, пока все возвращенные пакеты не будут перенесены в NIC (при условии, что он поддерживает разбросанные данные) перед возвратом. Вы можете посмотреть это, запустив packet_mmap
с опцией -t
для "threaded", которая использует блокировку ввода-вывода (до тех пор, пока она не завершится до "loop до очереди пуста" ).
Несколько замечаний. Во-первых, временные метки на выходе 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 с помощью этого пожарного шланга.
Но во-первых, не совсем понятный факт о том, как работают qdiscs. Они содержат ограниченный объем данных (как правило, подсчитывается по количеству кадров, но в некоторых случаях он может быть измерен в байтах), и если вы попытаетесь вставить рамку в полный 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 script 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("[email protected]/packet/af_packet.c:2213") {
print_ts();
printf("tpacket_snd: return(%d)\n", $err);
}
Запустите SystemTap script с помощью 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
WAT? Выбросил один из 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)
WAT? Потребовалось около 100 мс для обработки 8096 кадров в tpacket_snd
? Позвольте проверить, сколько времени потребуется на передачу; что 8096 кадров при 1500 байт/кадр при 1gigabit/s ~ = 97 мс. WAT? Пахнет, что что-то блокирует.
Давайте более подробно рассмотрим 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 max (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 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("[email protected]/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("[email protected]/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("[email protected]/packet/af_packet.c:2182") {
if ($err != 0) {
print_ts();
printf("tpacket_snd:2182: dev_queue_xmit err = %d\n", $err);
}
}
probe kernel.statement("[email protected]/packet/af_packet.c:2187") {
print_ts();
printf("tpacket_snd:2187: destructed: net_xmit_errno = %d\n", $err);
}
probe kernel.statement("[email protected]/packet/af_packet.c:2194") {
print_ts();
printf("tpacket_snd:2194: *NOT* destructed: net_xmit_errno = %d\n", $err);
}
probe kernel.statement("[email protected]/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. Простая целесообразность (обновленная в пакете_mmap.c) заключается в том, чтобы зацикливать на sendto до достижения успеха или ошибки, отличной от ENOBUFS
или EAGAIN
.
В любом случае, мы знаем, что более чем достаточно, чтобы ответить на вопрос OPs сейчас, даже если у нас нет полного решения для эффективного предотвращения сбоя сетевого адаптера.
Из того, что мы узнали, мы знаем, что когда OP вызывает sendto с tx-кольцом в режиме блокировки, tpacket_snd
запустит enqueuing skbs на qdisc до тех пор, пока предел sndbuf не будет превышен (а значение по умолчанию обычно невелико, 213K, и, кроме того, я обнаружил, что данные кадра, на которые ссылаются в общем tx-кольце, подсчитываются по этому), когда он будет блокироваться (при сохранении pg_vec_lock
). Поскольку skb освобождается, больше кадров будет выставлено в очередь, и, возможно, sndbuf будет снова превышен, и мы снова заблокируем. В конце концов все данные будут поставлены в очередь на qdisc, но tpacket_snd
будет продолжать блокироваться до тех пор, пока все кадры не будут переданы (вы не можете пометить кадр в кольце tx как доступно до тех пор, пока NIC не получит его, поскольку skb в кольце драйвера ссылается на кадр в кольце tx), сохраняя при этом pg_vec_lock
. На данный момент NIC голодает, и любые другие производители сокетов заблокированы блокировкой.
С другой стороны, когда OP публикует пакет за раз, он обрабатывается packet_snd
, который будет блокироваться, если в sndbuf нет места, а затем помещает кадр в qdisc и немедленно возвращается. Он не ждет передачи кадра. Когда qdisc сливается, дополнительные кадры могут быть выставлены в очередь. Если издатель может идти в ногу, сетевой адаптер никогда не будет голодать.
Кроме того, op копирует в tx-кольцо для каждого вызова sendto и сравнивает это с передачей буфера фиксированного кадра, когда не используется tx-кольцо. Вы не увидите ускорения от того, чтобы не копировать этот путь (хотя это не единственное преимущество использования кольца tx).