1. 程式人生 > >Linux系統下網絡數據包的處理流程

Linux系統下網絡數據包的處理流程

clean ××× 函數實現 vector important datasheet 內容 six 信號

本文主要探討linux環境下,數據包從網卡接收到協議棧處理的處理流程和對應的代碼邏輯。

分析的內核代碼版本為4.17.6,涉及到的網卡硬件功能特性和邏輯均以intel的82599以太網控制器為例,驅動為ixgbe。本文僅討論physical function的驅動代碼邏輯。


數據包從網卡接收開始,其總體處理流程如下:

  1. 網卡接收光/電信號,將其轉換為數據幀內容,如果幀符合以太網地址等過濾條件,則保存到FIFO緩存中。82599控制器中共有8個FIFO緩存隊列。

  2. 網卡解析FIFO中數據幀的2/3/4層信息,進行流過濾、流定向、RSS隊列分流,計算出幀對應的分流隊列號。82599控制器支持最多16個RSS分流隊列。

  3. 網卡將數據幀內容通過DMA方式寫入驅動程序指定的內存空間,並將幀的基本信息寫入報文描述信息隊列的寄存器(descriptor ring)中。

  4. 網卡發起硬件中斷,系統響應硬件中斷,進入驅動中的頂半部處理流程。

  5. 頂半部處理流程通過NAPI調度接口(napi_schedule)發起軟件中斷後就結束了,幀的具體處理邏輯在響應軟中斷的底半部流程中完成。

  6. 底半部流程中,驅動從DMA內存空間和網卡寄存器中獲取幀信息和內容。之後重新分配新的DMA內存空間並更新網卡寄存器,使網卡能夠繼續處理並寫入數據幀。

  7. 對於每個數據幀,驅動根據報文類型調用協議棧註冊的處理接口函數進行協議棧解析處理。


下面具體介紹一下每一步的具體代碼邏輯。

  1. 網卡接收光/電信號,將其轉換為數據幀內容,如果幀符合以太網地址等過濾條件,則保存到FIFO緩存中。82599控制器中共有8個FIFO緩存隊列。

    這一步是完全由網卡硬件完成的。但是L2的報文過濾可以通過驅動修改過濾地址的方式加以控制。一般情況下,只有以太網地址符合本地網卡以太網地址時幀才能通過過濾,82599控制器支持最多設置128個以太網地址。此外,可以設置打開網卡的混雜模式(promisc mode)來接收所有MAC地址幀,這個操作可以通過ixgbe_set_rx_mode函數實現,該函數修改了IXGBE_FCTRL_UPE 和 IXGBE_FCTRL_MPE寄存器來跳過L2報文過濾。


  2. 網卡解析F

    IFO中數據幀的2/3/4層信息,進行流過濾、流定向、RSS隊列分流,計算出幀對應的分流隊列號。82599控制器支持最多16個RSS分流隊列。

    這一步也是由網卡硬件實現的,驅動可以設置流過濾、流定向的規則,具體的方式可參見82599 datasheet第7.1.2節。RSS隊列分流的具體邏輯參見82599 datasheet的7.1.2.8節,驅動在啟動網卡時,需要修改MRQC、R×××K、RETA等寄存器來打開RSS分流功能、設置分流算法、哈希種子、分流映射表等。相關的代碼可在ixgbe_setup_mrqc中找到。

    PS:由於第1步和第2步完全由硬件實現,無從驗證,其功能步驟執行的具體順序不一定完全與本文相符。


  3. 網卡將數據幀內容通過DMA方式寫入驅動程序指定的內存空間,並將幀的基本信息寫入接收報文描述信息隊列(receive descriptor ring)中。

    這一步同樣由網卡硬件實現。但網卡寄存器的初始化,以及可DMA訪問的內存緩存空間申請是由網卡驅動在啟動網卡時完成的。每個隊列的具體初始化代碼在ixgbe_configure_rx_ring中。該函數首先初始化IXGBE_RDH、IXGBE_RDT等緩存隊列寄存器,然後調用ixgbe_alloc_rx_buffers分配緩存隊列的內存空間,並進行DMA映射。接收報文描述信息隊列(descriptor ring)同樣是一系列DMA映射的內存空間,每個隊列的ring空間是連續的,這個空間地址在ixgbe_setup_rx_resources函數中分配,在ixgbe_configure_rx_ring的起始部分寫入到IXGBE_RDBA寄存器中。

    receive descriptor有兩種格式,Legacy Receive Descriptor和Advanced Receive Descriptor,一般使用後者。數據結構的定義在ixgbe_type.h的ixgbe_adv_rx_desc函數中,字段解釋可以參見datasheet的7.1.6節。需要註意的是這個數據結構是網卡和驅動公用的接口數據結構,因此其結構定義是不能在驅動中修改的。這個結構分成兩個部分:read部分由驅動負責寫入,網卡負責讀取,用於向網卡傳遞每個報文的DMA緩存空間地址;wb(write-back)部分由網卡寫入,驅動讀取,用於網卡寫入與報文相關的信息,例如報文長度等。


  4. 網卡發起硬件中斷,系統響應硬件中斷,進入驅動中的頂半部處理流程。

    在驅動打開網卡的函數ixgbe_open過程中,會在ixgbe_request_msix_irqs函數中調用request_irq(entry->vector, &ixgbe_msix_clean_rings, 0,
    q_vector->name, q_vector)函數註冊硬件中斷號和中斷處理函數ixgbe_msix_clean_rings。這裏的中斷號在ixgbe_acquire_msix_vectors函數中使用pci_enable_msix_range函數分配。

    網卡發起硬件中斷後,系統調用中斷處理函數ixgbe_msix_clean_rings進行處理


  5. 頂半部處理流程通過NAPI調度接口(napi_schedule)發起軟件中斷後就結束了,幀的具體處理邏輯在響應軟中斷的底半部流程中完成。

    ixgbe_msix_clean_rings函數的流程非常簡單,函數判斷一下這個中斷是否有對應的rx或tx隊列,如果有則調用napi_schedule_irqoff發起napi調度,將具體的處理工作交給napi的底半部處理函數。


  6. 底半部流程中,驅動從DMA內存空間和網卡寄存器中獲取幀信息和內容。之後重新分配新的DMA內存空間並更新網卡寄存器,使網卡能夠繼續處理並寫入數據幀。

    napi的底半部處理函數為ixgbe_poll,在ixgbe_alloc_q_vector函數中使用netif_napi_add接口註冊。ixgbe_poll主要調用ixgbe_clean_rx_irq和ixgbe_clean_tx_irq來處理網卡收到和發送的報文。這裏主要分析ixgbe_clean_rx_irq。clean_rx_irq函數會從緩存隊列中獲取若幹個報文信息,並調用ixgbe_alloc_rx_buffers向隊列補充緩存空間資源,最後調用ixgbe_rx_skb函數,這個函數直接調用napi_gro_receive函數,之後的流程就與網卡和網卡驅動無關了。


  7. 對於每個數據幀,驅動根據報文類型調用協議棧註冊的處理接口函數進行協議棧解析處理。

    napi_gro_receive函數的邏輯較復雜,一般最終會調用__netif_receive_skb_core函數。該函數調用deliver_skb,最終調用註冊的packet_type->func函數對skb數據進行解析處理。例如IPv4協議的packet_type中的func函數就是ip_rcv。



由於內核代碼結構復雜,上述流程中仍有一些不明或不確之處,歡迎指正。

Linux系統下網絡數據包的處理流程