“tcp丟包分析”實驗解析(三)--驅動接收包過程
tcp丟包分析系列文章代碼來自謝寶友老師,由西郵陳莉君教授研一學生進行解析,本文由戴君毅整理,梁金榮編輯,薛曉雯校對。
tcp丟包分析系列文章:
“tcp丟包分析”實驗解析(一)--proc文件系統
“tcp丟包分析”實驗解析(二)--kprobe和tracepoint
繼續分析實驗,上回說到了 kprobe
機制,說完機制自然要再說說策略,也就是實驗裏對 pre_handler
的實驗,這纔是本質內容。本實驗總共對4個地方添加了 kprobe
鉤子:
爲了深刻理解實驗,我們要知道這些函數的作用以及所在層次。這四個函數的層次是自底向上遞增的,那麼先看第一個函數 eth_type_trans
:
當一個包中斷到來,驅動ISR響應,首先會調用 dev_alloc_skb
來生成一個SKB,隨後就要調用 eth_type_trans 來獲取包的協議填充到這個SKB中,內核註釋一句話解釋:
爲什麼要填充它的 protocol
呢?因爲驅動接收包後就中斷,隨後進入哪條處理路徑完全取決於 protocol
。根據上圖,完成 dev_alloc_skb
以及 eth_type_trans
隨後會調用 netif_rx
(或者 netif_rx_schedule
,隨後講解)觸發軟中斷, ISR
返回。之後軟中斷調用 process_backlog
中的 poll
函數,最終 netif_receive_skb
會調用 deliver_skb
將報文傳遞給相應的協議處理函數,即這就是協議棧的入口函數,實際會調用 __netif_receive_skb
,也是我們第三個添加 kprobe
鉤子的函數, -core
後綴表明它是在特定場景(忽略XDP等)使用的簡單版本。
那麼第二個函數 napi_gro_receive
是什麼?看過內核網絡的老闆們都應該聽說過 NAPI
,我這裏簡單說下方便大家理解。綜合整個收包邏輯,大致可以分爲以下兩種方式:
a. 每個數據包到來即中斷CPU,由CPU調度中斷處理程序進行收包處理,收包邏輯又分爲上半部和下半部,核心的協議棧處理邏輯在下半部完成。
b. 數據包到來,中斷CPU,CPU調度中斷處理程序並且關閉中斷響應,調度下半部不斷輪詢網卡,收包完畢或者達到一個閥值後,重新開啓中斷。
方式a就是上圖講述的過程,而方式b是 Linux NAPI
採用的方式。NAPI是中斷與輪詢的結合,可以想象,數據量很低與很高時,NAPI可以分別發揮中斷與輪詢方式的優點,性能較好。如果數據量不穩定,則NAPI則會在兩種方式切換上消耗不少時間,效率反而較低一些。下面這個圖顯示了NAPI過程和傳統過程的對比:
可以看到 napi_gro_receive
可以就是NAPI方式處理POLL後的事宜。GRO也是一種合併各種包一起接收的技術,這裏不展開了,參考https://lwn.net/Articles/358910/。
tcp_v4_rcv
則是比較上層的函數了,層次如圖所示 :
回到實驗,來看 pre_handler
實現,這裏截取兩個:
第一行函數其實看起來就有點懵,這其實是遵守了X86-64的ABI,來獲取函數的參數。什麼意思?在函數調用過程中,寄存器 pt_regs
保存了函數的參數列表,X86-64的約定如下:
當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
當參數爲7個以上時, 前 6 個與前面一樣, 但後面的依次從 “右向左” 放入棧中。
例如:
H(a, b, c, d, e, f, g, h);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
h->8(%esp)
g->(%esp)
call H
所以,第一個語句 structsk_buff*skb=(void*)regs->di;
其實就是拿到函數 eth_type_trans
的第一個參數,即一個skb。 eth_type_trans
前面說過是在驅動中完成的,所以它拿到SKB的data還是指在MAC報頭的,所以 pre_handler
需要將 skb->data
加一個以太網報頭長度 ETH_HLEN
(實際上是14)的長度變成IP報頭。而 kprobe_napi_gro_receive
的 pre_handler
則需要從rsi中取得SKB,因爲 napi_gro_receive
中第二個參數接收SKB。
最後的任務就是實現 trace_packet
了:
可以看到主要還是爲了根據報頭獲取源地址和目標地址的信息,也是本實驗的目的所在。函數重點在 find_alloc_desc
上,這是用戶態自己實現的不是內核函數,後續分析。