Linux內核的許多接口沒有很好的記錄。或者即使它們看起來有很好的文檔記錄,它們也可能非常複雜,並且很難理解界面的功能性,甚至更難以操作的特性。由於這個原因,任何想要深入瞭解內核API或需要使用內核API創建高性能應用程序的人都需要能夠與內核代碼聯繫才能成功。
在這種情況下,提問者想要了解通過共享內存接口(數據包mmap)將內核原始幀發送到內核的性能特徵。
linux文檔是here。它有一個陳舊的鏈接到「如何」,它現在可以發現here和包括packet_mmap.c
複印件(我有一個可用的版本略有不同here。
的文檔主要是面向對閱讀,這是典型的用於使用數據包mmap的使用案例:從接口高效讀取原始幀,例如:有效地從高速接口獲得數據包捕獲,幾乎沒有損失。
然而,OP對高性能編寫感興趣,這是一個非常不常見的用例,但對於似乎是OP想要處理的流量生成器/模擬器可能是有用的。謝天謝地,「如何做」就是寫幀。
即便如此,關於如何實際工作的信息卻很少,沒有什麼明顯的幫助來回答OPs關於爲什麼使用數據包mmap似乎不會比不使用它快的問題,而是發送一個幀一次。
謝天謝地,內核源代碼是開源的,編譯良好,所以我們可以找到源代碼來幫助我們得到問題的答案。
爲了找到相關的內核代碼,有幾個關鍵字可以搜索,但PACKET_TX_RING
脫穎而出作爲此功能特有的套接字選項。在interwebs上搜索「PACKET_TX_RING linux交叉引用」會出現少量引用,其中包括af_packet.c
,其中有一點檢查似乎是執行所有AF_PACKET
功能,包括數據包mmap。
翻閱af_packet.c
,看起來用分組mmap傳輸的核心工作發生在tpacket_snd()
。但這是正確的嗎?我們如何判斷這與我們的想法有什麼關係?
一個非常強大的工具,用於從內核中獲取這樣的信息是SystemTap。 (使用這需要你的內核安裝調試符號。我碰巧使用Ubuntu和this是獲得了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
在沒有優化的情況下構建。請參閱here瞭解一些指導。我會等。
好的,希望這不是太難,你已經成功啓動了一個內核,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析構函數添加一個探測器。
我們可以用sudo stap status.stp
啓動SystemTap腳本。然後運行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)
,這裏是網絡捕獲:
![network capture of first run of packet_mmap](https://i.stack.imgur.com/Ul5oH.png)
有很多在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 (...)
循環結束時的測試將等到所有未完成的數據包在返回之前已傳輸到NIC(假設它支持分散的數據)。您可以通過運行packet_mmap
並使用-t
選項來觀察此選項,該選項用於使用阻塞I/O的「線程」(直到它進入「循環直到隊列爲空」)。
有幾件事要注意。首先,SystemTap輸出上的時間戳不會增加:從SystemTap輸出推斷時間順序並不安全。其次,請注意網絡捕獲(本地完成)上的時間戳是不同的。 FWIW,這個界面在便宜的塔式電腦中是廉價的1G。
所以在這一點上,我想我們或多或少知道af_packet
是如何處理共享tx環的。接下來的內容是tx環中的幀如何到達網絡接口。查看linux網絡內核中的控制流的overview的this section(處理層2傳輸的方式)可能會有幫助。 OK,所以如果你對2層傳輸的處理有一個基本的瞭解,看起來這個包mmap接口應該是一個巨大的消防水帶;加載一個帶有數據包的共享tx環,調用sendto()
和MSG_DONTWAIT
,然後tpacket_snd
將遍歷創建skb的tx隊列並將它們排入qdisc。異步地,skb將從qdisc中出隊併發送到硬件tx環。 skb應該是non-linear,所以他們會引用tx環中的數據而不是複製,而一個不錯的現代NIC應該能夠處理分散的數據並引用tx環中的數據。當然,這些假設中的任何一個都可能是錯誤的,所以我們試着用這種消防水帶將大量的傷害轉移到qdisc上。
但首先,關於qdisc如何工作的一個不常見的事實。它們擁有有限的數據量(通常以幀的數量計算,但在某些情況下,它可以以字節爲單位),並且如果嘗試將幀排入完整的qdisc,幀通常會被丟棄(取決於enqueuer決定這麼做)。所以我會發出一個提示,我原來的假設是,OP使用數據包mmap將幀快速傳輸到qdisc,以至於很多被丟棄。但是不要太緊張,它會帶你走向一個方向,但始終保持開放的態度。讓我們試試看看會發生什麼。
這樣做的第一個問題是默認qdisc pfifo_fast
不保留統計信息。所以讓我們用qdisc pfifo
替換它。默認pfifo
將隊列限制爲TXQUEUELEN
幀(通常默認爲1000)。但是,因爲我們想展示出壓倒性一個隊列規定,讓我們明確地將其設置爲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("[email protected]/packet/af_packet.c:2213") {
print_ts();
printf("tpacket_snd: return(%d)\n", $err);
}
開始與sudo stap call-return.stp
的SystemTap的腳本,然後讓高爐8096 1500字節幀成隊列規定與微薄的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)
因此,讓我們檢查多少數據包是由隊列規定下降:
$ 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?在tpacket_snd
中處理8096幀花了將近100ms?讓我們來看看實際需要傳輸多長時間;在1千兆/秒〜= 97毫秒時,這是8096幀,1500字節/幀。 WAT?它聞起來有些阻塞。
讓我們仔細看看tpacket_snd
。 Groan:
skb = sock_alloc_send_skb(&po->sk,
hlen + tlen + sizeof(struct sockaddr_ll),
0, &err);
0
看起來非常無害,但實際上這是noblock參數。它應該是msg->msg_flags & MSG_DONTWAIT
(原來這是fixed in 4.1)。這裏發生的事情是qdisc的大小不是唯一的限制性資源。如果爲skb分配空間將超出套接字sndbuf限制的大小,則此調用將阻止等待skb被釋放,或者將-EAGAIN
返回給非阻塞調用方。在V4.1的修復中,如果請求非阻塞,它將返回寫入的字節數,如果非零,否則-EAGAIN
給調用者,這幾乎看起來像某人不希望你弄清楚如何使用這個(例如你填寫一個80MB數據的tx環,調用sendto與MSG_DONTWAIT
,你會得到一個結果,你發送150KB而不是EWOULDBLOCK
)。所以如果你運行4.1之前的內核(我相信OP運行> 4.1並且不受這個bug的影響),你需要打補丁af_packet.c
並構建一個新的內核或者升級到內核4.1或者更好。
我現在已經啓動了內核的修補版本,因爲我使用的機器運行的是3.13。雖然我們不會阻止如果sndbuf已滿,我們仍然會以-EAGAIN
返回。我對packet_mmap.c
進行了一些更改,以增加sndbuf的默認大小,並在必要時使用SO_SNDBUFFORCE
覆蓋每個套接字的系統最大值(它似乎需要大約750個字節+每個幀的幀大小)。我還對call-return.stp
進行了一些補充以記錄sbbuf最大大小(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("[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限制的bug,並且我們已經調整了sndbuf限制,以便它不應該是一個約束,現在我們看到來自tx環的幀被排隊到qdisc上,直到它滿了,在這一點我們得到返回ENOBUFS
。
接下來的問題是如何有效地保持發佈到qdisc以保持界面繁忙。請注意,在我們填寫qdisc並取回ENOBUFS
的情況下,packet_poll
的實現是無用的,因爲它只是查詢頭是否爲TP_STATUS_AVAILABLE
,在這種情況下將保持TP_STATUS_SEND_REQUEST
,直到後續調用sendto
成功排隊幀到qdisc。簡單的權宜之計(在packet_mmap.c中更新)是在sendto上循環,直到成功或ENOBUFS
或EAGAIN
以外的錯誤。
無論如何,即使我們沒有一個完整的解決方案來有效地防止NIC餓死,我們現在已經知道了足以回答OP問題的方法。根據我們所瞭解的情況,我們知道當OP在阻塞模式下用一個tx環呼叫sendto時,tpacket_snd
將啓動將skbs排入qdisc,直到超過sndbuf限制(並且默認通常很小,約213K ,並且進一步,我發現在共享tx環中引用的幀數據會被計數到這個時間),當它阻塞時(仍然持有pg_vec_lock
)。隨着skb的釋放,更多的幀將被排隊,也許sndbuf會再次超出,我們將再次阻止。最終,所有的數據都將被排隊等待到qdisc,但是tpacket_snd
將繼續阻塞,直到所有的幀都被髮送出去爲止(你不能將tx環中的幀標記爲可用,直到網卡已經收到它爲止驅動器環中的skb引用tx環中的幀),同時仍然保持pg_vec_lock
。在這一點上,NIC被餓死,其他任何套接字編寫器都被鎖定。另一方面,當OP每次發佈一個數據包時,它將被packet_snd
處理,如果在sndbuf中沒有空間,然後將該幀排入qdisc並立即返回,將會被阻止。它不等待幀被傳輸。當qdisc被排空時,可以將其他幀排入隊列。如果發行商能夠跟上,那麼NIC永遠不會餓死。
此外,對於每次sendto調用,操作將複製到tx環中,並將其與不使用tx環時傳遞固定幀緩衝區的情況進行比較。你不會看到從這種方式複製的速度加快(儘管這不是使用tx環的唯一好處)。
你的網絡有多快?你的尺寸有多大?你可能只是讓你的鏈接飽和?你有沒有檢查實際(自動協商)比特率? – maxy
幀的大小是1514個八位字節的頭文件,我將流量發送到回送接口lo,如輸出中所示。我正在將流量發送到回送接口,以消除網卡問題。 – jwbensley
我的理解是,因爲'packet_tx_mmap'函數應該與內核共享一個緩衝區,這意味着多個數據包在一個sendto()'系統調用中從userland複製到kernelland,所以向loopback接口發送流量意味着我們正在測試並且不用擔心將數據包DMA傳給NIC,這對於packet_tx和packet_tx_mmap都是相同的過程,因爲這是進一步在內核堆棧之下。 – jwbensley