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_receivepre_handler 則需要從rsi中取得SKB,因爲 napi_gro_receive 中第二個參數接收SKB。

最後的任務就是實現 trace_packet 了:

可以看到主要還是爲了根據報頭獲取源地址和目標地址的信息,也是本實驗的目的所在。函數重點在 find_alloc_desc 上,這是用戶態自己實現的不是內核函數,後續分析。

相關文章