天天看點

Linux ixgbe 10G intel 網卡資料包處理流程

Linux ixgbe 10G intel 網卡資料包處理流程

ixgbe_adapter

/* board specific private data structure */

struct ixgbe_adapter {

//資料量太多,摘錄部分看過比較有用的

//發送的rings

struct ixgbe_ring *tx_ring[MAX_TX_QUEUES] ____cacheline_aligned_in_smp;

//接收的rings

struct ixgbe_ring *rx_ring[MAX_RX_QUEUES];

//這個vector裡面包含了napi結構,不知道如何下定義這個q_vector

//應該是跟下面的entries一一對應起來做為是一個中斷向量的東西吧

struct ixgbe_q_vector *q_vector[MAX_Q_VECTORS];

//這個裡面估計是MSIX的多個中斷對應的響應接口

struct msix_entry *msix_entries;

}

ixgbe_q_vector

struct ixgbe_q_vector {

        struct ixgbe_adapter *adapter;

ifdef CONFIG_IXGBE_DCA

        int cpu;            /* CPU for DCA */

#endif

        u16 v_idx;              /* index of q_vector within array, also used for

                                 * finding the bit in EICR and friends that

                                 * represents the vector for this ring */

        u16 itr;                /* Interrupt throttle rate written to EITR */

        struct ixgbe_ring_container rx, tx;

        struct napi_struct napi;//這個poll的接口實作是ixgbe_poll

       cpumask_t affinity_mask;

        int numa_node;

        struct rcu_head rcu;    /* to avoid race with update stats on free */

        char name[IFNAMSIZ + 9];

        /* for dynamic allocation of rings associated with this q_vector */

        struct ixgbe_ring ring[0] ____cacheline_internodealigned_in_smp;

};

softnet_data

/*

 * Incoming packets are placed on per-cpu queues

 */

struct softnet_data {

    struct Qdisc        *output_queue;

    struct Qdisc        **output_queue_tailp;

    struct list_head    poll_list;

    struct sk_buff      *completion_queue;

    struct sk_buff_head process_queue;

    /* stats */

    unsigned int        processed;

    unsigned int        time_squeeze;

    unsigned int        cpu_collision;

    unsigned int        received_rps;

#ifdef CONFIG_RPS

    struct softnet_data *rps_ipi_list;

    /* Elements below can be accessed between CPUs for RPS */

    struct call_single_data csd ____cacheline_aligned_in_smp;

    struct softnet_data *rps_ipi_next;

    unsigned int        cpu;    

    unsigned int        input_queue_head;

    unsigned int        input_queue_tail;

    unsigned int        dropped;

    struct sk_buff_head input_pkt_queue;

    struct napi_struct  backlog;//cpu softnet_data的poll接口是process_backlog

napi_struct

 * Structure for NAPI scheduling similar to tasklet but with weighting

struct napi_struct {

    /* The poll_list must only be managed by the entity which

     * changes the state of the NAPI_STATE_SCHED bit.  This means

     * whoever atomically sets that bit can add this napi_struct

     * to the per-cpu poll_list, and whoever clears that bit

     * can remove from the list right before clearing the bit.

     */

    unsigned long       state;

    int         weight;

    unsigned int        gro_count;

    int         (*poll)(struct napi_struct *, int);//poll的接口實作

#ifdef CONFIG_NETPOLL

    spinlock_t      poll_lock;

    int         poll_owner;

    struct net_device   *dev;

    struct sk_buff      *gro_list;

    struct sk_buff      *skb;

    struct list_head    dev_list;

enum {

    NAPI_STATE_SCHED,   /* Poll is scheduled */

    NAPI_STATE_DISABLE, /* Disable pending */

    NAPI_STATE_NPSVC,   /* Netpoll - don't dequeue from poll_list */

1.......................................................

檔案 ixgbe_main.c

ixgbe_init_module注冊一個ixgbe_driver的pci結構,其中我們關注ixgbe_probe接口,這個是網卡probe時的實作

2.......................................................

ixgbe_probe這裡關注3個事情

1.建立ixgbe_adapter的adapter結構,這個結果是網卡的一個執行個體,包含了網卡的所有資料及接口,包含了下面要

   建立的netdev結構,還包含了每個中斷号的響應資料結構接口(網卡是支援MSIX的),裡面有一個叫q_vector的

   數組,是ixbge_q_vector資料類型的(這個應該是一個中斷向量的資料類型),每個元素是一個ixgbe_q_vector類型,

   這個類似的資料結構包含了幾樣重要的東西,其中一樣是napi_struct類型的成員napi,這個就包含了包含了ixgbe_poll

   接口,主要是上層軟中斷調用的輪詢接口;除此adapter還包含了一個重要的成員變量,struct msix_entry *msix_entries;

   這個應該是MSIX沒個通道對應的中斷向量結構,下面的步驟4會描述

2.建立一個netdev結構net_device,net_device在linux裡面代表是一個網絡裝置,然後綁定裡面的netdev_ops接口

   對應ixgbe_netdev_ops,這個結構有個響應網卡open的回調實作,ixgbe_open,按照open接口的描述

   "Called when a network interface is made active" 網卡激活的時候被調用的,之前的probe接口是檢測時被調用

3.ixgbe_init_interrupt_scheme這個函數主要是設定網卡的napi_struct結構的poll接口,這個poll接口實作是ixgbe_poll,

  這個接口是面向核心層的裝置poll包裝,這裡我們要區厘清楚,cpu的softnet_data的napi結構poll的接口實作是

   process_backlog,然而網卡ixgbe_q_vector裡面napi結構poll的接口實作是ixgbe_poll,這個最終會在下面是否是

   NAPI的調用方式中展現出來,參考步驟9

3.......................................................

ixgbe_open這裡主要有兩個任務

1.根據queue初始化對應的ring buffer

   ixgbe_setup_all_tx_resources(adapter); //設定發送隊列的rings等等

   ixgbe_setup_all_rx_resources(adapter); //設定接收隊列的rings等等

2. ixgbe_request_irq根據中斷類型設定中斷響應機制(MSI/MSIX或者其它)一般好點的網卡都是支援MSIX的,

   是以我們看裡面的ixgbe_request_msix_irqs這個函數的實作

4.......................................................

ixgbe_request_msix_irqs函數,因為MSIX是一個隊列對應一個中斷号,這裡主要是對每個隊列設定對應的中斷響應接口,

(這裡主要還是對adapter的q_vector進行設定,參考上面步驟2的第一點)

對應的接口是ixgbe_msix_clean_rings,這個函數會調用request_irq設定每個通道的硬中斷實作為ixgbe_msix_clean_rings,

具體部分代碼節選如下

for (vector = 0; vector < adapter->num_q_vectors; vector++) {

  ...........

  struct ixgbe_q_vector *q_vector = adapter->q_vector[vector];

  struct msix_entry *entry = &adapter->msix_entries[vector];

  err = request_irq(entry->vector, &ixgbe_msix_clean_rings, 0,q_vector->name, q_vector);

主要是把q_vector的中斷向量實作接口與MSIX的一一對應起來

除了設定中斷響應接口,這個函數還有設定IRQ的CPU affinity

5.......................................................

ixgbe_msix_clean_rings這個函數就是網卡接收,發送資料時的中斷響應接口,裡面沒做太多東西調用napi_schedule接口,

這裡面的napi schedule有點折騰,要慢慢追蹤下去,

static irqreturn_t ixgbe_msix_clean_rings(int irq, void *data)

{

struct ixgbe_q_vector *q_vector = data;

if (q_vector->rx.ring || q_vector->tx.ring)

  napi_schedule(&q_vector->napi);//這裡要注意,傳進去的是qvector的napi結構,到時候調用的poll接口是ixgbe_poll

static inline void napi_schedule(struct napi_struct *n)

if (napi_schedule_prep(n))

  __napi_schedule(n);

void __napi_schedule(struct napi_struct *n)

unsigned long flags;

local_irq_save(flags);

____napi_schedule(&__get_cpu_var(softnet_data), n); 

//這裡我們看到,把adapter的q_vector的napi結構放到目前運作cpu的napi結構的poll list隊列去

//__get_cpu_var這個宏按照網上找到的資訊,這個是擷取目前運作CPU的softnet_data

//最終會有下面raise一個NET_RX_SOFTIRQ軟中斷叫CPU去處理自己的softnet_data裡面的napi

//隊列

local_irq_restore(flags);

____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);

當軟中斷響應後,資料包就從驅動層上升到核心層面的邏輯了,(注意前面的下劃線個數,因為個數都有好幾個的)

6.......................................................

檔案 net/core/dev.c 

最終napi_schedule會調用__raise_softirq_irqoff去觸發一個軟中斷NET_RX_SOFTIRQ,然後又對應的軟中斷接口去實作往

上的協定棧邏輯

NET_RX_SOFTIRQ是收到資料包的軟中斷信号對應的接口是net_rx_action

NET_TX_SOFTIRQ是發送完資料包後的軟中斷信号對應的接口是net_tx_action

7.......................................................

檔案 net/core/dev.c

net_rx_action主要是擷取到cpu的softnet_data結構,然後開始疊代對softnet_Data裡面的queue隊列中的網絡資料包進行處理,

這裡會調用對應裝置的napi_struct資料結構裡面的poll接口。這個接口在ixgbe_probe裡面設定好了,對應是ixgbe_poll函數。

net_rx_action函數會對目前裝置是否已經處于NAPI_STATE_SCHED才會調用ixgbe_poll接口。

具體看看net_rx_action的代碼節選

static void net_rx_action(struct softirq_action *h)

//這個是NAPI的工作方式函數,NAPI工作模式下關閉硬中斷,在2個時鐘周期内poll收到預期資料包budget個數

//否則重新開啟硬中斷,跳出poll輪詢

unsigned long time_limit = jiffies + 2;

int budget = netdev_budget;

struct softnet_data *sd = &__get_cpu_var(softnet_data);

local_irq_disable();

while (!list_empty(&sd->poll_list)) {

    struct napi_struct *n;

    int work, weight;

    ......

/* If softirq window is exhuasted then punt.

    * Allow this to run for 2 jiffies since which will allow

    * an average latency of 1.5/HZ.

    */

//在指定2個時鐘周期内收到budget個資料包,否則跳出poll輪詢,這就是NAPI的工作方式

//減少中斷調用次數擷取資料包

if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit)))

    goto softnet_break;

local_irq_enable();

    //擷取目前CPU的softnet_data中napi的poll list清單

    n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

    if (test_bit(NAPI_STATE_SCHED, &n->state)) {

        work = n->poll(n, weight);//然後就開始調用napi_struct結構的poll接口,這個接口的實作就是上面步驟2的第三小點描述的

        //其實這裡的poll調用就是調用了ixgbe_poll

    }

8.......................................................

ixgbe_poll函數主要做3個事情

1.對tx發送資料包隊列進行處理 ixgbe_clean_tx_irq

2.對rx接收資料包隊列進行處理 ixgbe_clean_rx_irq

3.最後通知napi_complete

9.......................................................

ixgbe_clean_rx_irq函數把ring buffer的内容取出來轉成sk_buff包,然後送出到ixgbe_rx_skb,ixgbe_rx_skb會根據

目前是否是NETPOLL工作模式來區分調用接口,NETPOLL是一種用于調試的工作模式,我們暫不深入研究

napi_gro_receive會往上調用napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb),然而這個傳進來的napi

結構正是qvector的結構,poll接口是ixgbe_poll實作的

部分代碼節選如下

static void ixgbe_rx_skb(struct ixgbe_q_vector *q_vector, struct sk_buff *skb)

        struct ixgbe_adapter *adapter = q_vector->adapter;

        if (!(adapter->flags & IXGBE_FLAG_IN_NETPOLL))

                napi_gro_receive(&q_vector->napi, skb); 

        else

                netif_rx(skb); //這裡是NETPOLL的方式,

10.......................................................

netif_receive_skb接口在新版核心裡面做了兩件事情

1.google的RPS機制會在這裡排程CPU

2.往上層調用接口,從__netif_receive_skb_core再調用到deliver_skb接口,這個deliver_skb接口會調用一個叫packet_type資料

結構裡面的func接口,這個是資料包接收下層送出到協定棧的接口,資料也就由此進入了TCP/IP協定棧了

11.......................................................

檔案 net/ipv4/af_inet.c ip_input.c

這個是ipv4的AF_INET協定子產品,在inet_init初始化時會注冊一個ip_packet_type的資料結構,這個結果就是packet_type類型的,

裡面指定了資料接收的函數func回調接口的實作ip_rcv。除此之外還注冊了2個重要的net_protocol的協定接口tcp_protocol,udp_protocol,這個後面的ip_local_deliver會用到

ip_rcv的函數就開始了TCP/IP協定棧的工作流程,這裡主要是做一些IP包的分析,最後調用netfilter的PRE_ROUTING hook,

通知完netfilter的hook之後,假如這個包ACCEPT的話會調用ip_rcv_finish

12.......................................................

檔案 net/ipv4/ip_input.c route.c

這個ip_rcv_finish函數它會調用ip_route_input_noref,最後就會調用一個dst_input,然後這個dst_input其實是調用一個input的

函數指針,這個資料包的input函數指針則是接下來往上層的路徑入口,這個input函數指針在那裡設定,就是在之前

ip_route_input_noref調用裡面設定好了。如果是本機資料則input接口為ip_local_deliver

13.......................................................

檔案 net/ipv4/ip_input.c af_inet.c

ip_local_deliver首先會調用netfilter的NF_INET_LOCAL_IN, 如果沒問題則調用下一個 ip_local_deliver_finish,這裡會擷取該IP

資料包的協定結構net_protocol,然後調用net_protocol裡面的handler接口

net_protocol在之前的af_inet.c裡面的inet_init已經注冊好了

tcp協定對應的handler為tcp_v4_rcv

udp協定對應的handler為udp_rcv

14.......................................................

檔案 net/ipv4/udp.c

udp_rcv将UDP包放到sock結構sk裡面的sk_receive_queue的接收隊列裡面

15.......................................................

檔案 net/ipv4/udp.c net/core/datagram.c

UDP為應用層提供了一個叫udp_prto的資料結構,類型是proto,裡面提供了各種接口,如發送,接收等等recvmsg接口對應

UDP的實作是udp_recvmsg,這個函數會調用__skb_recv_datagram接口,這個函數就是從sock結構sk裡面的

sk->sk_receive_queue取資料

繼續閱讀