基于 Linux 內(nèi)核 6.0、64 位系統(tǒng)和 Intel 網(wǎng)卡驅(qū)動(dòng) igb。由于篇幅過(guò)長(zhǎng)切分多篇,前一篇地址 《十年碼農(nóng)內(nèi)功:收包(一)》 ,下一篇是 《十年碼農(nóng)內(nèi)功:收包(三)》 ,所有參考內(nèi)容在最后一篇。
整體流程圖 三、網(wǎng)絡(luò)接口層
3.1 概述
數(shù)據(jù)包在本層主要處理流程有五:
網(wǎng)卡收到數(shù)據(jù)包,DMA 方式寫(xiě)入 Ring Buffer ,發(fā)出硬中斷;
內(nèi)核收到硬中斷,NAPI 加入本 CPU 的輪詢列表,發(fā)出軟中斷;
內(nèi)核收到軟中斷,輪詢 NAPI 并執(zhí)行 poll 函數(shù)從 Ring Buffer 取數(shù)據(jù);
GRO 操作(默認(rèn)開(kāi)啟),合并多個(gè)數(shù)據(jù)包為一個(gè)數(shù)據(jù)包,如果 RPS 關(guān)閉,則把數(shù)據(jù)包遞交到協(xié)議棧;
RPS 操作(默認(rèn)關(guān)閉),如果開(kāi)啟,使數(shù)據(jù)包通過(guò)別的(也可能是當(dāng)前的) CPU 遞交到協(xié)議棧;
圖4 L1流程圖
圖5 L1調(diào)用鏈 3.1.1 Ring Buffer
這一層里有一個(gè)大名鼎鼎的數(shù)據(jù)結(jié)構(gòu) Ring Buffer ,后面統(tǒng)稱它的中文名字叫 環(huán)形緩沖區(qū) 。
接收 Ring Buffer 實(shí)際上有兩個(gè)環(huán)形隊(duì)列,一個(gè)是 CPU 使用的 rx_buffer_info 數(shù)組,數(shù)組元素是 rx_buffer ,另一個(gè)是網(wǎng)卡硬件使用的 desc 數(shù)組,元素是 rx_desc ,它們都存儲(chǔ)在主內(nèi)存上的內(nèi)核空間。 igb 網(wǎng)卡驅(qū)動(dòng)中 rx_buffer 是 igb_rx_buffer , rx_desc 是 e1000_adv_rx_desc 。
igb_rx_buffer 結(jié)構(gòu)體代碼如下,結(jié)構(gòu)圖如圖2:
struct igb_rx_buffer { /* DMA 地址 */ dma_addr_t dma; /* 物理頁(yè),與 dma 指向同一個(gè)內(nèi)存區(qū)域 */ struct page * page ; # if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536) __u32 page_offset; # else __u16 page_offset; # endif __u16 pagecnt_bias; } ;
圖6 e1000_adv_rx_desc 結(jié)構(gòu)體代碼如下,結(jié)構(gòu)圖如圖3:
union e1000_adv_rx_desc { struct { __le64 pkt_addr; /* Packet buffer address */ __le64 hdr_addr; /* Header buffer address */ } read ; struct { struct { struct { __le16 pkt_info; /* RSS type, Packet type */ __le16 hdr_info; /* Split Head, buf len */ } lo_dword ; union { __le32 rss; /* RSS Hash */ struct { __le16 ip_id; /* IP id */ __le16 csum; /* Packet Checksum */ } csum_ip ; } hi_dword ; } lower ; struct { __le32 status_error; /* ext status/error */ __le16 length; /* Packet length */ __le16 vlan; /* VLAN tag */ } upper ; } wb ; /* writeback */ } ;
圖7 3.2 網(wǎng)卡收到數(shù)據(jù)包
網(wǎng)卡收到數(shù)據(jù)包后,通過(guò) DMA 寫(xiě)入 Ring Buffer ( rx_ring )內(nèi) rx_buffer_info 數(shù)組的下一個(gè)可用元素( igb_rx_buffer )的 dma 指向的內(nèi)核內(nèi)存, dma 實(shí)際是網(wǎng)卡可以使用的總線地址,一個(gè) 網(wǎng)絡(luò)幀 可能占用多個(gè) igb_rx_buffer 。
這是第一次復(fù)制,從網(wǎng)卡到 Ring Buffer 的復(fù)制。
3.3 內(nèi)核收到硬中斷
復(fù)制完后,如果硬中斷沒(méi)有被關(guān)閉,則網(wǎng)卡發(fā)出硬中斷。假設(shè)啟動(dòng)時(shí)硬中斷類型選擇的是 MSI-X ,那么硬中斷注冊(cè)的函數(shù)就 igb_msix_ring ,從下面的代碼可以看出,硬中斷處理函數(shù)邏輯非常簡(jiǎn)單,僅僅調(diào)用了 igb_write_itr 和 napi_schedule 兩個(gè)函數(shù), igb_msix_ring 函數(shù)代碼如下:
static irqreturn_t igb_msix_ring ( int irq , void * data ) { struct igb_q_vector * q_vector = data ; /* Write the ITR value calculated from the previous interrupt. */ igb_write_itr ( q_vector ) ; napi_schedule ( & q_vector -> napi ) ; return IRQ_HANDLED ; }
igb_write_itr 負(fù)責(zé)更新特定的硬件中的寄存器,而 napi_schedule 才是重點(diǎn)工作,負(fù)責(zé)調(diào)度 NAPI, napi_schedule 調(diào)用 __napi_schedule 再調(diào)用 ____napi_schedule 函數(shù),后者部分代碼如下:
static inline void ____napi_schedule ( struct softnet_data * sd , struct napi_struct * napi ) { list_add_tail ( & napi -> poll_list , & sd -> poll_list ) ; __raise_softirq_irqoff ( NET_RX_SOFTIRQ ) ; }
其主要邏輯有二:
把 napi_struct 結(jié)構(gòu)體的 poll_list 添加到當(dāng)前 CPU 所關(guān)聯(lián)的 softnet_data 結(jié)構(gòu)體的 poll_list 鏈表尾部;
然后調(diào)用 __raise_softirq_irqoff 函數(shù)觸發(fā) NET_RX_SOFTIRQ 軟中斷,從而內(nèi)核執(zhí)行網(wǎng)絡(luò)子系統(tǒng)初始化時(shí)注冊(cè)的 net_rx_action 軟中斷處理函數(shù)。
3.4 內(nèi)核收到軟中斷
處理硬中斷的 CPU 同樣也會(huì)執(zhí)行該硬中斷觸發(fā)的軟中斷注冊(cè)的處理函數(shù),軟中斷函數(shù) net_rx_action 在 ksoftirqd 內(nèi)核進(jìn)程執(zhí)行。 net_rx_action 遍歷當(dāng)前 CPU 隊(duì)列中的 NAPI 列表,依次取出列表中的 NAPI 結(jié)構(gòu)對(duì)其進(jìn)行 napi_poll 操作。 net_rx_action 函數(shù)非常重要。
3.4.1 net_rx_action 函數(shù)
// 部分代碼 static __latent_entropy void net_rx_action ( struct softirq_action * h ) { struct softnet_data * sd = this_cpu_ptr ( & softnet_data ) ; unsigned long time_limit = jiffies + usecs_to_jiffies ( READ_ONCE ( netdev_budget_usecs ) ) ; int budget = READ_ONCE ( netdev_budget ) ; LIST_HEAD ( list ) ; LIST_HEAD ( repoll ) ; local_irq_disable ( ) ; list_splice_init ( & sd -> poll_list , & list ) ; local_irq_enable ( ) ; for ( ; ; ) { struct napi_struct * n ; skb_defer_free_flush ( sd ) ; if ( list_empty ( & list ) ) { if ( ! sd_has_rps_ipi_waiting ( sd ) && list_empty ( & repoll ) ) goto end ; break ; } n = list_first_entry ( & list , struct napi_struct , poll_list ) ; budget -= napi_poll ( n , & repoll ) ; /* If softirq window is exhausted then punt. * Allow this to run for 2 jiffies since which will allow * an average latency of 1.5/HZ. */ if ( unlikely ( budget <= 0 || time_after_eq ( jiffies , time_limit ) ) ) { sd-> time_squeeze ++ ; break ; } } local_irq_disable ( ) ; list_splice_tail_init ( & sd -> poll_list , & list ) ; list_splice_tail ( & repoll , & list ) ; list_splice ( & list , & sd -> poll_list ) ; if ( ! list_empty ( & sd -> poll_list ) ) __raise_softirq_irqoff ( NET_RX_SOFTIRQ ) ; /* 通過(guò) smp_call_function_single_async 遠(yuǎn)程激活 sd->rps_ipi_list 中的其他 CPU 的軟中斷, * 使其他 CPU 執(zhí)行初始化時(shí)注冊(cè)的軟中斷函數(shù) csd = rps_trigger_softirq 來(lái)處理數(shù)據(jù)包 */ net_rps_action_and_irq_enable ( sd ) ; end: ; }
napi_poll 函數(shù)調(diào)用 __napi_poll 函數(shù)對(duì) napi_struct 進(jìn)行操作,然后判斷 napi_struct 是否加回 poll_list 列表尾部, budget 是控制消費(fèi) rx_buffer 的數(shù)量,避免 CPU 一直被軟中斷占用。
3.4.2 __napi_poll 函數(shù)
// 部分代碼 static int __napi_poll ( struct napi_struct * n , bool * repoll ) { int work , weight ; weight = n -> weight ; work = 0 ; if ( test_bit ( NAPI_STATE_SCHED , & n -> state ) ) { work = n -> poll ( n , weight ) ; trace_napi_poll ( n , work , weight ) ; } if ( likely ( work < weight ) ) return work ; if ( unlikely ( napi_disable_pending ( n ) ) ) { napi_complete ( n ) ; return work ; } * repoll = true ; return work ; }
上面代碼中 weight 代表 RX 隊(duì)列的處理優(yōu)先級(jí)(網(wǎng)卡驅(qū)動(dòng)對(duì)應(yīng)權(quán)重是固定的 64), napi_struct 里的 poll 函數(shù)被調(diào)用, igb 驅(qū)動(dòng)對(duì)應(yīng)的是先前注冊(cè)的 igb_poll 函數(shù)。
3.4.3 igb_poll 函數(shù)
// 部分代碼 static int igb_poll ( struct napi_struct * napi , int budget ) { struct igb_q_vector * q_vector = container_of ( napi , struct igb_q_vector , napi ) ; bool clean_complete = true ; int work_done = 0 ; # ifdef CONFIG_IGB_DCA if ( q_vector -> adapter -> flags & IGB_FLAG_DCA_ENABLED ) igb_update_dca ( q_vector ) ; # endif if ( q_vector -> tx . ring ) clean_complete = igb_clean_tx_irq ( q_vector , budget ) ; if ( q_vector -> rx . ring ) { int cleaned = igb_clean_rx_irq ( q_vector , budget ) ; work_done += cleaned ; if ( cleaned >= budget ) clean_complete = false ; } /* If all work not completed, return budget and keep polling */ if ( ! clean_complete ) return budget ; /* Exit the polling mode, but don't re-enable interrupts if stack might * poll us due to busy-polling */ if ( likely ( napi_complete_done ( napi , work_done ) ) ) igb_ring_irq_enable ( q_vector ) ; return work_done ; }
其主要邏輯如下:
如果內(nèi)核支持 DCA(Direct Cache Access),CPU 緩存命中率將會(huì)提升;
調(diào)用 igb_clean_rx_irq 循環(huán)處理數(shù)據(jù)包,直到處理完畢或者 budget 耗盡,下面詳細(xì)解讀;
檢查 clean_complete 判斷是否所有的工作已經(jīng)完成;
如果不是,返回剩下的 budget 值;
否則調(diào)用 napi_complete_done 函數(shù)繼續(xù)處理。
調(diào)用 gro_normal_list 函數(shù),因?yàn)閿?shù)據(jù)包處理完了,及時(shí)把 igb_clean_rx_irq 處理完的多個(gè)包一次性送到協(xié)議棧;
然后檢查 NAPI 的 poll_list 是否都處理完,如果是則關(guān)閉 NAPI,并通過(guò) igb_ring_irq_enable 重新打開(kāi)硬中斷,以保證下次中斷會(huì)重新打開(kāi) NAPI。
3.4.4 igb_clean_rx_irq 函數(shù)
//部分代碼 static int igb_clean_rx_irq ( struct igb_q_vector * q_vector , const int budget ) { struct igb_adapter * adapter = q_vector -> adapter ; struct igb_ring * rx_ring = q_vector -> rx . ring ; struct sk_buff * skb = rx_ring -> skb ; unsigned int total_bytes = 0 , total_packets = 0 ; u16 cleaned_count = igb_desc_unused ( rx_ring ) ; int rx_buf_pgcnt ; while ( likely ( total_packets < budget ) ) { union e1000_adv_rx_desc * rx_desc ; struct igb_rx_buffer * rx_buffer ; ktime_t timestamp = 0 ; unsigned int size ; /* return some buffers to hardware, one at a time is too slow */ if ( cleaned_count >= IGB_RX_BUFFER_WRITE ) { /* 1 */ igb_alloc_rx_buffers ( rx_ring , cleaned_count ) ; cleaned_count = 0 ; } /* 2 */ rx_desc = IGB_RX_DESC ( rx_ring , rx_ring -> next_to_clean ) ; size = le16_to_cpu ( rx_desc -> wb . upper . length ) ; /* 3 */ rx_buffer = igb_get_rx_buffer ( rx_ring , size , & rx_buf_pgcnt ) ; /* 4 retrieve a buffer from the ring */ if ( ! skb ) { unsigned char * hard_start = pktbuf - igb_rx_offset ( rx_ring ) ; unsigned int offset = pkt_offset + igb_rx_offset ( rx_ring ) ; xdp_prepare_buff ( & xdp , hard_start , offset , size , true ) ; xdp_buff_clear_frags_flag ( & xdp ) ; # if (PAGE_SIZE > 4096) /* At larger PAGE_SIZE, frame_sz depend on len size */ xdp. frame_sz = igb_rx_frame_truesize ( rx_ring , size ) ; # endif skb = igb_run_xdp ( adapter , rx_ring , & xdp ) ; } /* 5 retrieve a buffer from the ring */ if ( skb ) igb_add_rx_frag ( rx_ring , rx_buffer , skb , size ) ; else if ( ring_uses_build_skb ( rx_ring ) ) skb = igb_build_skb ( rx_ring , rx_buffer , & xdp , timestamp ) ; else skb = igb_construct_skb ( rx_ring , rx_buffer , & xdp , timestamp ) ; /* 6 */ igb_put_rx_buffer ( rx_ring , rx_buffer , rx_buf_pgcnt ) ; cleaned_count++ ; /* 7 fetch next buffer in frame if non-eop */ if ( igb_is_non_eop ( rx_ring , rx_desc ) ) continue ; /* 8 verify the packet layout is correct */ if ( igb_cleanup_headers ( rx_ring , rx_desc , skb ) ) { skb = NULL ; continue ; } /* 9 probably a little skewed due to removing CRC */ total_bytes += skb -> len ; /* 10 populate checksum, timestamp, VLAN, and protocol */ igb_process_skb_fields ( rx_ring , rx_desc , skb ) ; /* 11 GRO,合并數(shù)據(jù)包 */ napi_gro_receive ( & q_vector -> napi , skb ) ; /* reset skb pointer */ skb = NULL ; /* 12 update budget accounting */ total_packets++ ; } /* place incomplete frames back on ring for completion */ rx_ring-> skb = skb ; if ( cleaned_count ) igb_alloc_rx_buffers ( rx_ring , cleaned_count ) ; return total_packets ; }
igb_clean_rx_irq 函數(shù)中的 while 循環(huán)完成下面操作:
首先申請(qǐng)一批 rx_buffer 和 rx_desc,通常 IGB_RX_BUFFER_WRITE(16)個(gè),避免一個(gè)個(gè)申請(qǐng),效率低,操作由 igb_alloc_rx_buffers 函數(shù)完成:使用 dev_alloc_pages 申請(qǐng)新的物理頁(yè)保存到 rx_buffer->page,然后通過(guò) dma_map_page_attrs 將 page 映射結(jié)果保存到 rx_buffer->dma ;修改 rx_desc->read.pkt_addr(rx_buffer->dma + rx_buffer->page_offset),rx_desc->wb.upper.length = 0,方便網(wǎng)卡將收到的數(shù)據(jù)包 DMA 到 rx_desc->read.pkt_addr 地址,這是第一次復(fù)制,從網(wǎng)卡到 Ring Buffer 的復(fù)制;
從 Ring Buffer 中取出下一個(gè)可讀位置(next_to_clean)的 rx_desc,檢查它狀態(tài)是否正常,然后從 rx_desc 獲取接收的數(shù)據(jù) buffer 大小(wb.upper.length);
通過(guò) igb_get_rx_buffer 函數(shù)將下一個(gè)可讀位置(next_to_clean)的 rx_buffer 獲取到;
計(jì)算數(shù)據(jù)包開(kāi)始地址, page_address ( rx_buffer -> page ) + rx_buffer -> page_offset ,轉(zhuǎn)換成 xdp_buff 地址,然后交給 BPF 的 xdp 處理;
內(nèi)核把 rx_buffer 的 page(物理頁(yè))對(duì)應(yīng)的 buffer 數(shù)據(jù)拷貝到 Ring Buffer 的 skb(sk_buff)中,然后把 skb 直接傳給協(xié)議棧,這是第二次復(fù)制,從 Ring Buffer 到網(wǎng)絡(luò)協(xié)議棧的復(fù)制。為了減少?gòu)?fù)制次數(shù),skb 直到上層處理完以后才會(huì)被 __kfree_skb 釋放;
通過(guò) igb_put_rx_buffer 函數(shù)將 rx_buffer->page=NULL,如果可以重用,將 page、dma 等數(shù)據(jù)移動(dòng)到rx_ring->next_to_alloc 位置的 rx_buffer;反之,解除 DMA 映射,回收內(nèi)存;
通過(guò) igb_is_non_eop 函數(shù)檢查 rx_desc 是不是包含 eop(End of Packet),如果包含,說(shuō)明 skb 中已經(jīng)收錄一個(gè)完整的網(wǎng)絡(luò)包(幀);反之,需要獲取下一個(gè) rx_buffer 里的數(shù)據(jù)繼續(xù)復(fù)制到 skb 中直到 rx_desc 包含 eop;也就是說(shuō)一個(gè)網(wǎng)絡(luò)包(存儲(chǔ)在 skb 中)可能包含 1 個(gè)或多個(gè) rx_buffer 中的 buffer 數(shù)據(jù),也可以說(shuō) 1 個(gè) skb 對(duì)應(yīng) 1 個(gè)或多個(gè) Ring Buffer 隊(duì)列里連續(xù)的元素;
通過(guò) igb_cleanup_headers 檢查網(wǎng)絡(luò)包(skb)的頭部等信息是否正確;
把 skb 的長(zhǎng)度累計(jì)到 total_bytes,用于統(tǒng)計(jì)數(shù)據(jù);
調(diào)用 igb_process_skb_fields 設(shè)置skb 的 checksum、timestamp、VLAN 和 protocol 等信息,這些信息由硬件提供;
將構(gòu)建好的 skb 通過(guò) napi_gro_receive 函數(shù)上交到網(wǎng)絡(luò)協(xié)議棧,具體細(xì)節(jié)移步 2.4 章節(jié);
累加處理數(shù)據(jù)包個(gè)數(shù) total_packets,用于消耗 budget;
如果沒(méi)數(shù)據(jù)或者 budget 耗盡就退出循環(huán),否則回到 1;
上面第 5 步中,skb 的創(chuàng)建有兩種情況,當(dāng)網(wǎng)卡配置了 legacy 模式,使用 igb_build_skb ( napi_build_skb )創(chuàng)建 skb 并復(fù)制 rx_buffer->page 數(shù)據(jù);否則使用 igb_construct_skb ( napi_alloc_skb ) 創(chuàng)建 skb 并復(fù)制 rx_buffer->page 數(shù)據(jù)。當(dāng) skb 不為空時(shí),就是前一個(gè)包被 GRO 合并了,使用 igb_add_rx_frag 復(fù)制數(shù)據(jù)。
budget 的大小會(huì)影響到 CPU 的利用率,當(dāng)數(shù)據(jù)包特別多的情況下,budget 越大可以減少數(shù)據(jù)包的延時(shí),但是會(huì)影響 CPU 處理其他任務(wù)。budget 默認(rèn) 300,可以調(diào)整使用下面命令修改:
$ sysctl -w net.core.netdev_budget=500
前面收包過(guò)程都是內(nèi)核跟網(wǎng)卡硬件和驅(qū)動(dòng)配合來(lái)完成的,也就是說(shuō)不同網(wǎng)卡收包的具體實(shí)現(xiàn)可能不同(同一家廠商的網(wǎng)卡的實(shí)現(xiàn)基本相同),但是大體實(shí)現(xiàn)思路上是一樣的,都是用到了 Ring Buffer、DMA、硬中斷和軟中斷等操作。
后面就是由內(nèi)核和用戶程序來(lái)完成了,與網(wǎng)卡沒(méi)有關(guān)系了。
3.5 GRO
3.5.1 概述
GRO (Generic Receive Offloading)是 LGO(Large Receive Offload,多數(shù)是在 NIC 上實(shí)現(xiàn)的一種硬件優(yōu)化機(jī)制)的一種 軟件實(shí)現(xiàn) ,從而能讓所有 NIC 都支持這個(gè)功能。網(wǎng)絡(luò)上大部分 MTU 都是 1500 字節(jié),開(kāi)啟 Jumbo Frame 后能到 9000 字節(jié),如果發(fā)送的數(shù)據(jù)超過(guò) MTU 就需要切割成多個(gè)數(shù)據(jù)包。通過(guò)合并「足夠類似」的包來(lái)減少傳送給網(wǎng)絡(luò)協(xié)議棧的包數(shù),有助于減少 CPU 的使用量。GRO 使協(xié)議層只需處理一個(gè) header,而將包含大量數(shù)據(jù)的整個(gè)大包送到用戶程序。如果用 tcpdump 抓包看到機(jī)器收到了不現(xiàn)實(shí)的、非常大的包,這很可能是系統(tǒng)開(kāi)啟了 GRO。
GRO 和 硬中斷合并 的思想類似,不過(guò)階段不同。 硬中斷合并 是在中斷發(fā)起之前,而 GRO 已經(jīng)在軟中斷處理中了。
查看 GRO 是否開(kāi)啟命令:
$ ethtool -k eth0 | grep generic-receive-offload generic-receive-offload: on
開(kāi)啟 GRO 命令:
napi_gro_receive 就是實(shí)現(xiàn) GRO 機(jī)制的入口函數(shù)之一。
3.5.2 napi_gro_receive 函數(shù)
// 部分代碼 gro_result_t napi_gro_receive ( struct napi_struct * napi , struct sk_buff * skb ) { gro_result_t ret; skb_mark_napi_id ( skb , napi ) ; trace_napi_gro_receive_entry ( skb ) ; skb_gro_reset_offset ( skb , 0 ) ; ret = napi_skb_finish ( napi , skb , dev_gro_receive ( napi , skb ) ) ; trace_napi_gro_receive_exit ( ret ) ; return ret ; }
其主要邏輯有二:
調(diào)用 dev_gro_receive 函數(shù)具體完成多個(gè)數(shù)據(jù)包的合并,即把 skb 加入到 NAPI 中,這個(gè)操作調(diào)用鏈很長(zhǎng),根據(jù)包類型 TCP/UDP 分別判斷數(shù)據(jù)包的完整性和判斷需不需要合并;
把上步的返回結(jié)果傳入 napi_skb_finish 函數(shù)繼續(xù)處理。
3.5.3 napi_skb_finish 函數(shù)
static gro_result_t napi_skb_finish ( struct napi_struct * napi , struct sk_buff * skb , gro_result_t ret ) { switch ( ret ) { case GRO_NORMAL : gro_normal_one ( napi , skb , 1 ) ; break ; case GRO_MERGED_FREE : if ( NAPI_GRO_CB ( skb ) -> free == NAPI_GRO_FREE_STOLEN_HEAD ) napi_skb_free_stolen_head ( skb ) ; else if ( skb -> fclone != SKB_FCLONE_UNAVAILABLE ) __kfree_skb ( skb ) ; else __kfree_skb_defer ( skb ) ; break ; case GRO_HELD : case GRO_MERGED : case GRO_CONSUMED : break ; } return ret ; }
如果是 ret 是 GRO_MERGED_FREE,說(shuō)明 skb 已經(jīng)被合并,釋放 skb;
如果是 ret 是 GRO_NORMAL,會(huì)調(diào)用 gro_normal_one ,它會(huì)更新當(dāng)前 napi->rx_count 計(jì)數(shù), 當(dāng)數(shù)量足夠多時(shí),將調(diào)用 gro_normal_list 函數(shù),將多個(gè)包一次性送到協(xié)議棧。
3.5.4 gro_normal_one 函數(shù)
static inline void gro_normal_one ( struct napi_struct * napi , struct sk_buff * skb , int segs ) { list_add_tail ( & skb -> list , & napi -> rx_list ) ; napi-> rx_count += segs ; if ( napi -> rx_count >= READ_ONCE ( gro_normal_batch ) ) gro_normal_list ( napi ) ; }
這里的閾值 gro_normal_batch 默認(rèn)是 8,即攢夠 8 個(gè)數(shù)據(jù)包一起送到協(xié)議棧,可以通過(guò) sysctl 修改,命令如下:
$ sysctl net.core.gro_normal_batch net.core.gro_normal_batch = 8
3.5.5 gro_normal_list 函數(shù)
/* Pass the currently batched GRO_NORMAL SKBs up to the stack. */ static inline void gro_normal_list ( struct napi_struct * napi ) { if ( ! napi -> rx_count ) // 沒(méi)有包直接返回 return ; netif_receive_skb_list_internal ( & napi -> rx_list ) ; INIT_LIST_HEAD ( & napi -> rx_list ) ; // 初始化 napi->rx_list napi-> rx_count = 0 ; // 計(jì)數(shù)清零 }
到這里 GRO 的工作就完成了,然后經(jīng)過(guò) netif_receive_skb_list_internal 函數(shù)多層調(diào)用, 最終調(diào)用 __netif_receive_skb_core 函數(shù)把數(shù)據(jù)包遞交網(wǎng)絡(luò)協(xié)議棧 。
3.5.6 napi_complete_done 函數(shù)
// 部分代碼 bool napi_complete_done ( struct napi_struct * n , int work_done ) { unsigned long flags , val , new , timeout = 0 ; bool ret = true ; if ( unlikely ( n -> state & ( NAPIF_STATE_NPSVC | NAPIF_STATE_IN_BUSY_POLL ) ) ) return false ; if ( work_done ) { if ( n -> gro_bitmask ) timeout = READ_ONCE ( n -> dev -> gro_flush_timeout ) ; n-> defer_hard_irqs_count = READ_ONCE ( n -> dev -> napi_defer_hard_irqs ) ; } if ( n -> defer_hard_irqs_count > 0 ) { n-> defer_hard_irqs_count -- ; timeout = READ_ONCE ( n -> dev -> gro_flush_timeout ) ; if ( timeout ) ret = false ; } if ( n -> gro_bitmask ) { napi_gro_flush ( n , ! ! timeout ) ; } gro_normal_list ( n ) ; . . . }
3.4.4 中提到過(guò), poll 函數(shù)( igb_poll )在檢查是否已經(jīng)將現(xiàn)有的所有數(shù)據(jù)包合并完成,如果完成了,則調(diào)用 napi_complete_done 函數(shù)直接調(diào)用 gro_normal_list 函數(shù),及時(shí)把 dev_gro_receive 處理完的多個(gè)包一次性送到協(xié)議棧;
3.6 RPS
3.6.1 概述
RPS(Receive Packet Steering)是 RSS 的一種軟件實(shí)現(xiàn)。
因?yàn)槭擒浖?shí)現(xiàn)的,所以任何網(wǎng)卡都可以使用 RPS,單隊(duì)列和多隊(duì)列網(wǎng)卡都可以使用;
RPS 在數(shù)據(jù)包從 Ring Buffer 中取出來(lái)后開(kāi)始工作,將 Packet hash 到對(duì)應(yīng) CPU 的 backlog 中,并觸發(fā) IPI(Inter-processorInterrupt,進(jìn)程間中斷)告知目標(biāo) CPU 來(lái)處理 backlog。該 Packet 將被目標(biāo) CPU 交到協(xié)議棧。從而實(shí)現(xiàn)將負(fù)載分散到多個(gè) CPU 的目的;
單隊(duì)列網(wǎng)卡使用 RPS 可以提升傳輸效率,多隊(duì)列網(wǎng)卡在硬中斷不均勻時(shí)同樣可以使用來(lái)提升效率;
IPI 既像軟件中斷又像硬件中斷,它的產(chǎn)生像軟件中斷,是在程序中用代碼發(fā)送的,而它的處理像硬件中斷
3.6.2 GRO 后執(zhí)行 RPS
GRO 機(jī)制的最后一個(gè)函數(shù) gro_normal_list 調(diào)用了 netif_receive_skb_list_internal 函數(shù),后者和 netif_receive_skb_internal 函數(shù)均有下面類似的代碼:
void netif_receive_skb_list_internal ( struct list_head * head ) { # ifdef CONFIG_RPS if ( static_branch_unlikely ( & rps_needed ) ) { list_for_each_entry_safe ( skb , next , head , list ) { struct rps_dev_flow voidflow , * rflow = & voidflow ; /* 目標(biāo) CPU 的 id */ int cpu = get_rps_cpu ( skb -> dev , skb , & rflow ) ; if ( cpu >= 0 ) { /* Will be handled, remove from list */ skb_list_del_init ( skb ) ; enqueue_to_backlog ( skb , cpu , & rflow -> last_qtail ) ; } } } # endif }
上面代碼判斷是否設(shè)置了 RPS 對(duì)數(shù)據(jù)包進(jìn)行不同的處理:
如果沒(méi)有配置 RPS, netif_receive_skb* 將數(shù)據(jù)包交到網(wǎng)絡(luò)協(xié)議棧;
如果配置了 RPS, netif_receive_skb* 調(diào)用 get_rps_cpu 來(lái)計(jì)算網(wǎng)絡(luò)包的 hash 并決定壓入哪個(gè) CPU 的 backlog,具體壓入操作由 enqueue_to_backlog 函數(shù)完成。
3.6.3 壓入 backlog 隊(duì)列
enqueue_to_backlog 函數(shù)如下:
// 部分代碼 static int enqueue_to_backlog ( struct sk_buff * skb , int cpu , unsigned int * qtail ) { enum skb_drop_reason reason ; struct softnet_data * sd ; unsigned long flags ; unsigned int qlen ; reason = SKB_DROP_REASON_NOT_SPECIFIED ; sd = & per_cpu ( softnet_data , cpu ) ; rps_lock_irqsave ( sd , & flags ) ; if ( ! netif_running ( skb -> dev ) ) goto drop ; qlen = skb_queue_len ( & sd -> input_pkt_queue ) ; if ( qlen <= READ_ONCE ( netdev_max_backlog ) && ! skb_flow_limit ( skb , qlen ) ) { if ( qlen ) { enqueue: __skb_queue_tail ( & sd -> input_pkt_queue , skb ) ; input_queue_tail_incr_save ( sd , qtail ) ; rps_unlock_irq_restore ( sd , & flags ) ; return NET_RX_SUCCESS ; } if ( ! __test_and_set_bit ( NAPI_STATE_SCHED , & sd -> backlog . state ) ) // 將目標(biāo) CPU 的 sd 掛到當(dāng)前 CPU 的 sd 的 rps_ipi_list 便于后續(xù)向目標(biāo) CPU 發(fā)送 IPI 信號(hào)。 napi_schedule_rps ( sd ) ; goto enqueue ; } reason = SKB_DROP_REASON_CPU_BACKLOG ; drop: sd-> dropped ++ ; rps_unlock_irq_restore ( sd , & flags ) ; dev_core_stats_rx_dropped_inc ( skb -> dev ) ; kfree_skb_reason ( skb , reason ) ; return NET_RX_DROP ; }
當(dāng)目標(biāo) CPU 的 sd (softnet_data )中 input_pkt_queue 隊(duì)列長(zhǎng)度同時(shí)不超過(guò) netdev_max_backlog 和 flow limit 的值,將 skb 數(shù)據(jù)包壓入 input_pkt_queue ,否則將會(huì)被丟棄。
調(diào)用 napi_schedule_rps ,將目標(biāo) CPU 的 sd 掛到本 CPU 的 sd 的 rps_ipi_list 便于后續(xù)向目標(biāo) CPU 發(fā)送 IPI 信號(hào);
當(dāng)返回到 net_rx_action 函數(shù)中,最后一步經(jīng)過(guò)調(diào)用鏈 net_rps_action_and_irq_enable -> net_rps_send_ipi -> smp_call_function_single_async 遠(yuǎn)程激活 sd->rps_ipi_list 中的其他 CPU 的軟中斷,使其他 CPU 執(zhí)行初始化時(shí)注冊(cè)的軟中斷函數(shù) csd = rps_trigger_softirq 來(lái)處理數(shù)據(jù)包;
rps_trigger_softirq 函數(shù)將 backlog(napi)加入 poll_list 里,然后發(fā)出軟中斷信號(hào) NET_RX_SOFTIRQ ;
當(dāng)處理軟中斷函數(shù) net_rx_action 處理 poll_list 時(shí), backlog 的 poll 是 process_backlog 函數(shù), process_backlog 函數(shù)消費(fèi) CPU 的 input_pkt_queue 隊(duì)列數(shù)據(jù)包,經(jīng)過(guò) __netif_receive_skb 函數(shù)多層調(diào)用, 最終也調(diào)用 __netif_receive_skb_core 函數(shù)把數(shù)據(jù)包遞交網(wǎng)絡(luò)協(xié)議棧 。
偽文件 /proc/net/softnet_stat 的第 10 列記錄了每個(gè) CPU 收到了多少次 IPI。
圖8 上面的命令最終會(huì)調(diào)用下面的 softnet_seq_show 函數(shù):
static int softnet_seq_show ( struct seq_file * seq , void * v ) { struct softnet_data * sd = v ; unsigned int flow_limit_count = 0 ; # ifdef CONFIG_NET_FLOW_LIMIT struct sd_flow_limit * fl ; rcu_read_lock ( ) ; fl = rcu_dereference ( sd -> flow_limit ) ; if ( fl ) flow_limit_count = fl -> count ; rcu_read_unlock ( ) ; # endif seq_printf ( seq , '%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n' , sd-> processed , sd -> dropped , sd -> time_squeeze , 0 , 0 , 0 , 0 , 0 , /* was fastroute */ 0 , /* was cpu_collision */ sd-> received_rps , flow_limit_count , softnet_backlog_len ( sd ) , ( int ) seq -> index ) ; return 0 ; }
從代碼可以看出 IPI 的次數(shù)記錄在了 softnet_data 的 received_rps 里,除了 IPI 還有第 12 列的 backlog 隊(duì)列長(zhǎng)度。
3.7 遞交協(xié)議棧
如圖4 調(diào)用鏈所示, netif_receive_skb_list_internal 函數(shù)經(jīng)過(guò)多層調(diào)用,最后會(huì)執(zhí)行 __netif_receive_skb_core 函數(shù),這是網(wǎng)絡(luò)數(shù)據(jù)包接收的核心函數(shù),負(fù)責(zé)處理接收到的數(shù)據(jù)包并決定如何傳遞給上層協(xié)議處理。這里面做的事情非常多, 按順序包括:
準(zhǔn)備工作;
XDP 處理;
VLAN 標(biāo)記;
TAP 處理;
TC 處理;
Netfilter 處理;
遞交協(xié)議棧。
有的網(wǎng)卡會(huì)在 poll 函數(shù)里調(diào)用 netif_receive_skb 將數(shù)據(jù)包交到上層網(wǎng)絡(luò)棧繼續(xù)處理。最后發(fā)現(xiàn)同樣會(huì)調(diào)用到 __netif_receive_skb_core 函數(shù)。
__netif_receive_skb_core 函數(shù)代碼如下:
static int __netif_receive_skb_core ( struct sk_buff * * pskb , bool pfmemalloc , struct packet_type * * ppt_prev ) { struct packet_type * ptype , * pt_prev ; rx_handler_func_t * rx_handler ; struct sk_buff * skb = * pskb ; struct net_device * orig_dev ; bool deliver_exact = false ; int ret = NET_RX_DROP ; __be16 type; // 檢查網(wǎng)絡(luò)包的時(shí)間戳。 net_timestamp_check ( ! READ_ONCE ( netdev_tstamp_prequeue ) , skb ) ; // 跟蹤網(wǎng)絡(luò)數(shù)據(jù)包的接收過(guò)程,用于調(diào)試和性能分析。 trace_netif_receive_skb ( skb ) ; // 將接收到的數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備指針保存到 orig_dev 變量中,以備后續(xù)使用。 orig_dev = skb -> dev ; // 重置網(wǎng)絡(luò)頭部的偏移量,使其指向正確的位置。 skb_reset_network_header ( skb ) ; if ( ! skb_transport_header_was_set ( skb ) ) //如果傳輸頭部未設(shè)置,則重置傳輸層頭部的偏移量,使其指向正確的位置。 skb_reset_transport_header ( skb ) ; // 重置數(shù)據(jù)包的 MAC 長(zhǎng)度。 skb_reset_mac_len ( skb ) ; // 將 pt_prev 變量初始化為空,用于存儲(chǔ)上一個(gè)處理函數(shù)。 pt_prev = NULL ; another_round: //這是一個(gè)標(biāo)簽,用于在處理過(guò)程中跳轉(zhuǎn)到此處重新執(zhí)行一輪處理。 // 設(shè)置數(shù)據(jù)包的 skb_iif 字段,表示skb 是從哪個(gè)網(wǎng)絡(luò)設(shè)備接收的。 skb-> skb_iif = skb -> dev -> ifindex ; // 增加當(dāng)前 CPU 上的 softnet_data.processed 字段的計(jì)數(shù)。 __this_cpu_inc ( softnet_data . processed ) ; // 如果啟用了 Generic XDP(軟件實(shí)現(xiàn) XDP 功能),則調(diào)用do_xdp_generic()函數(shù)執(zhí)行 XDP 通用程序的處理。 if ( static_branch_unlikely ( & generic_xdp_needed_key ) ) { int ret2 ; migrate_disable ( ) ; ret2 = do_xdp_generic ( rcu_dereference ( skb -> dev -> xdp_prog ) , skb ) ; migrate_enable ( ) ; // 如果返回結(jié)果不是XDP_PASS,則將返回值設(shè)置為NET_RX_DROP并跳轉(zhuǎn)到標(biāo)簽out處。 if ( ret2 != XDP_PASS ) { ret = NET_RX_DROP ; goto out ; } } // 如果數(shù)據(jù)包是以太網(wǎng) VLAN 數(shù)據(jù)包,則調(diào)用skb_vlan_untag()函數(shù)將 VLAN 標(biāo)簽從數(shù)據(jù)包中移除。 if ( eth_type_vlan ( skb -> protocol ) ) { skb = skb_vlan_untag ( skb ) ; // 如果 skb 為空,則跳轉(zhuǎn)到 out 標(biāo)簽 if ( unlikely ( ! skb ) ) goto out ; } // 如果需要跳過(guò) TC 分類,則直接跳轉(zhuǎn)到 skip_classify 標(biāo)簽。 if ( skb_skip_tc_classify ( skb ) ) goto skip_classify ; // 如果 pfmemalloc 為 true,則跳轉(zhuǎn)到 skip_taps 標(biāo)簽。 if ( pfmemalloc ) goto skip_taps ; // 這個(gè)循環(huán)遍歷全局的注冊(cè)的協(xié)議處理函數(shù) ptype_all 鏈表,依次調(diào)用 deliver_skb 函數(shù)傳遞數(shù)據(jù)包給每個(gè)注冊(cè)的協(xié)議處理程序。 list_for_each_entry_rcu ( ptype , & ptype_all , list ) { if ( pt_prev ) ret = deliver_skb ( skb , pt_prev , orig_dev ) ; // 抓包:dev_add_pack(&po->prot_hook) 注冊(cè)的鉤子函數(shù) pt_prev = ptype ; } // 這個(gè)循環(huán)遍歷接收數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備的協(xié)議處理函數(shù) ptype_all 鏈表,同樣依次調(diào)用 deliver_skb 函數(shù)傳遞數(shù)據(jù)包給每個(gè)注冊(cè)的協(xié)議處理程序。 list_for_each_entry_rcu ( ptype , & skb -> dev -> ptype_all , list ) { if ( pt_prev ) ret = deliver_skb ( skb , pt_prev , orig_dev ) ; // 抓包:dev_add_pack(&po->prot_hook) 注冊(cè)的鉤子函數(shù) pt_prev = ptype ; } skip_taps: // 如果是使用 goto 跳轉(zhuǎn)過(guò)來(lái)的,那跳過(guò)了抓包邏輯(libpcap、tcpdump 等) # ifdef CONFIG_NET_INGRESS // 這部分代碼用于處理網(wǎng)絡(luò)數(shù)據(jù)包的入口(ingress)功能,即在數(shù)據(jù)包進(jìn)入網(wǎng)絡(luò)協(xié)議棧之前進(jìn)行處理。 // 如果需要進(jìn)行 TC ingress 處理 if ( static_branch_unlikely ( & ingress_needed_key ) ) { bool another = false ; // 跳過(guò) egress nf_skip_egress ( skb , true ) ; // 處理 ingress skb = sch_handle_ingress ( skb , & pt_prev , & ret , orig_dev , & another ) ; // 如果還需要進(jìn)行下一輪處理,則跳轉(zhuǎn)到 another_round 標(biāo)簽 if ( another ) //TC BPF 優(yōu)化,通過(guò) another round 將包從宿主機(jī)網(wǎng)卡直接送到容器 netns 內(nèi)網(wǎng)卡 ? goto another_round ; if ( ! skb ) goto out ; // 跳過(guò) egress nf_skip_egress ( skb , false ) ; // 處理 Netfilter ingress if ( nf_ingress ( skb , & pt_prev , & ret , orig_dev ) < 0 ) goto out ; } # endif // 重置數(shù)據(jù)包的重定向標(biāo)志 skb_reset_redirect ( skb ) ; skip_classify: // 如果是使用 goto 跳轉(zhuǎn)過(guò)來(lái)的,那跳過(guò)了抓包、TC、Netfilter 邏輯 // 如果 pfmemalloc 為 true,并且 skb 沒(méi)有設(shè)置 pfmemalloc 協(xié)議,則跳轉(zhuǎn)到 drop 標(biāo)簽 if ( pfmemalloc && ! skb_pfmemalloc_protocol ( skb ) ) goto drop ; if ( skb_vlan_tag_present ( skb ) ) { // 如果數(shù)據(jù)包中存在 VLAN 標(biāo)簽,則調(diào)用 deliver_skb() 函數(shù)將數(shù)據(jù)包傳遞給之前注冊(cè)的協(xié)議處理函數(shù)進(jìn)行處理 if ( pt_prev ) { ret = deliver_skb ( skb , pt_prev , orig_dev ) ; pt_prev = NULL ; } // 調(diào)用 vlan_do_receive() 函數(shù)處理 VLAN 相關(guān)操作 if ( vlan_do_receive ( & skb ) ) goto another_round ; else if ( unlikely ( ! skb ) ) goto out ; } // 獲取接收該數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備的接收處理函數(shù)(rx_handler) rx_handler = rcu_dereference ( skb -> dev -> rx_handler ) ; if ( rx_handler ) { // 如果接收處理函數(shù)存在,則調(diào)用 deliver_skb() 函數(shù)將數(shù)據(jù)包傳遞給接收處理函數(shù)進(jìn)行處理 if ( pt_prev ) { ret = deliver_skb ( skb , pt_prev , orig_dev ) ; pt_prev = NULL ; } // 根據(jù)接收處理函數(shù)的返回值,有不同的處理邏輯 switch ( rx_handler ( & skb ) ) { case RX_HANDLER_CONSUMED : ret = NET_RX_SUCCESS ; goto out ; case RX_HANDLER_ANOTHER : goto another_round ; case RX_HANDLER_EXACT : deliver_exact = true ; break ; case RX_HANDLER_PASS : break ; default : BUG ( ) ; } } // 如果存在 VLAN 標(biāo)簽,并且網(wǎng)絡(luò)設(shè)備不使用 DSA(Distributed Switch Architecture) if ( unlikely ( skb_vlan_tag_present ( skb ) ) && ! netdev_uses_dsa ( skb -> dev ) ) { check_vlan_id: if ( skb_vlan_tag_get_id ( skb ) ) { // VLAN ID 非 0,并且無(wú)法找到 VLAN 設(shè)備 skb-> pkt_type = PACKET_OTHERHOST ; } else if ( eth_type_vlan ( skb -> protocol ) ) { // 外部頭部是 802.1P 帶有 VLAN 0,內(nèi)部頭部是 802.1Q 或 802.1AD,并且無(wú)法找到 VLAN ID 0 對(duì)應(yīng)的 VLAN 設(shè)備 __vlan_hwaccel_clear_tag ( skb ) ; skb = skb_vlan_untag ( skb ) ; if ( unlikely ( ! skb ) ) goto out ; if ( vlan_do_receive ( & skb ) ) goto another_round ; else if ( unlikely ( ! skb ) ) goto out ; else goto check_vlan_id ; } __vlan_hwaccel_clear_tag ( skb ) ; } // 獲取數(shù)據(jù)包的協(xié)議類型 type = skb -> protocol ; if ( likely ( ! deliver_exact ) ) // 如果沒(méi)有設(shè)置精確匹配,將調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的注冊(cè)的協(xié)議處理函數(shù)處理。 deliver_ptype_list_skb ( skb , & pt_prev , orig_dev , type , & ptype_base [ ntohs ( type ) & PTYPE_HASH_MASK ] ) ; // 調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的協(xié)議處理函數(shù)處理 deliver_ptype_list_skb ( skb , & pt_prev , orig_dev , type , & orig_dev -> ptype_specific ) ; if ( unlikely ( skb -> dev != orig_dev ) ) // 如果數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備與接收時(shí)的網(wǎng)絡(luò)設(shè)備不一致,將調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的協(xié)議處理函數(shù)處理。 deliver_ptype_list_skb ( skb , & pt_prev , orig_dev , type , & skb -> dev -> ptype_specific ) ; if ( pt_prev ) { // 如果存在上一個(gè)協(xié)議處理函數(shù),將調(diào)用該處理函數(shù)來(lái)處理數(shù)據(jù)包。說(shuō)明數(shù)據(jù)包有未處理的分片數(shù)據(jù),調(diào)用 skb_orphan_frags_rx 函數(shù)處理剩余的分片數(shù)據(jù)。 if ( unlikely ( skb_orphan_frags_rx ( skb , GFP_ATOMIC ) ) ) goto drop ; * ppt_prev = pt_prev ; } else { // 如果不存在上一個(gè)協(xié)議處理函數(shù),表示沒(méi)有合適的處理函數(shù)來(lái)處理數(shù)據(jù)包,將丟棄數(shù)據(jù)包并增加接收丟棄計(jì)數(shù)。 drop: if ( ! deliver_exact ) // 更新網(wǎng)卡的 rx_dropped 統(tǒng)計(jì) dev_core_stats_rx_dropped_inc ( skb -> dev ) ; else // 更新網(wǎng)卡的 rx_nohandler 統(tǒng)計(jì) dev_core_stats_rx_nohandler_inc ( skb -> dev ) ; kfree_skb_reason ( skb , SKB_DROP_REASON_UNHANDLED_PROTO ) ; ret = NET_RX_DROP ; } out: //將處理完的 skb 賦值回 pskb 指針 * pskb = skb ; return ret ; }
3.7.1 準(zhǔn)備工作
函數(shù)開(kāi)始時(shí),對(duì)傳入的數(shù)據(jù)包進(jìn)行一些準(zhǔn)備工作,如:
處理 skb 時(shí)間戳;
重置網(wǎng)絡(luò)頭;
重置傳輸頭;
重置 MAC 長(zhǎng)度;
設(shè)置數(shù)據(jù)包的接收接口索引。
3.7.2 XDP(eXpress Data Path)處理
這里的 XDP 是軟件層面的實(shí)現(xiàn),當(dāng)硬件網(wǎng)卡不支持 offload 模式的 XDP,可以選擇 Generic 模式的 XDP。
3.7.3 VLAN 處理
如果數(shù)據(jù)包使用了 VLAN 標(biāo)記,首先去除 VLAN 標(biāo)記,并判斷是否成功。
3.7.4 TAP 處理
根據(jù)數(shù)據(jù)包的 packet_type ,按照 ptype_all 鏈表中的順序遍歷所有的 packet_type ,逐個(gè)嘗試將數(shù)據(jù)包交給相應(yīng)的處理函數(shù)進(jìn)行處理。比如交給前面初始化注冊(cè)的函數(shù),一般通過(guò) libpcap 庫(kù)埋的探測(cè)點(diǎn)(TAP),用于 tcpdump 抓包。
net/packet/af_packet.c: dev_add_pack(&po->prot_hook); //用于抓包 net/packet/af_packet.c: dev_add_pack(&f->prot_hook); //用于抓包
3.7.5 TC 處理
TC(Traffic Control)是 Linux 的 流量控制子系統(tǒng) ,通過(guò)調(diào)用 sch_handle_ingress 函數(shù)進(jìn)入 TC ingress 處理。
3.7.6 Netfilter 處理
Netfilter 是 Linux 的 包過(guò)濾子系統(tǒng) ,iptables 是其用戶空間的客戶端。通過(guò)調(diào)用 nf_ingress 函數(shù)進(jìn)入 Netfilter ingress 處理。
3.7.7 遞交協(xié)議棧
根據(jù)協(xié)議類型 packet_type.type 在 ptype_base 哈希表中找到對(duì)應(yīng)函數(shù)保存在 packet_type.func ,最終通過(guò) deliver_skb 函數(shù)調(diào)用 packet_type.func 把 skb 交到對(duì)應(yīng)的處理函數(shù)處理。
static inline int deliver_skb ( struct sk_buff * skb , struct packet_type * pt_prev , struct net_device * orig_dev ) { if ( unlikely ( skb_orphan_frags_rx ( skb , GFP_ATOMIC ) ) ) return - ENOMEM ; refcount_inc ( & skb -> users ) ; return pt_prev -> func ( skb , skb -> dev , pt_prev , orig_dev ) ; }