1. 程式人生 > >網絡卡驅動收發包過程

網絡卡驅動收發包過程

網絡卡 網絡卡工作在物理層和資料鏈路層,主要由PHY/MAC晶片、Tx/Rx FIFO、DMA等組成,其中網線通過變壓器接PHY晶片、PHY晶片通過MII接MAC晶片、MAC晶片接PCI匯流排

PHY晶片主要負責:CSMA/CD、模數轉換、編解碼、串並轉換

MAC晶片主要負責:

位元流和幀的轉換:7位元組的前導碼Preamble和1位元組的幀首定界符SFD CRC校驗 Packet Filtering:L2 Filtering、VLAN Filtering、Manageability / Host Filtering Intel的千兆網絡卡以82575/82576為代表、萬兆網絡卡以82598/82599為代表

收發包過程圖 ixgbe_adapter包含ixgbe_q_vector陣列(一個ixgbe_q_vector對應一箇中斷),ixgbe_q_vector包含napi_struct

硬中斷函式把napi_struct加入CPU的poll_list,軟中斷函式net_rx_action()遍歷poll_list,執行poll函式

發包過程

1、網絡卡驅動建立tx descriptor ring(一致性DMA記憶體),將tx descriptor ring的匯流排地址寫入網絡卡暫存器TDBA

2、協議棧通過dev_queue_xmit()將sk_buff下送網絡卡驅動

3、網絡卡驅動將sk_buff放入tx descriptor ring,更新TDT

4、DMA感知到TDT的改變後,找到tx descriptor ring中下一個將要使用的descriptor

5、DMA通過PCI匯流排將descriptor的資料快取區複製到Tx FIFO

6、複製完後,通過MAC晶片將資料包傳送出去

7、傳送完後,網絡卡更新TDH,啟動硬中斷通知CPU釋放資料快取區中的資料包

Tx Ring Buffer 收包過程

1、網絡卡驅動建立rx descriptor ring(一致性DMA記憶體),將rx descriptor ring的匯流排地址寫入網絡卡暫存器RDBA

2、網絡卡驅動為每個descriptor分配sk_buff和資料快取區,流式DMA對映資料快取區,將資料快取區的匯流排地址儲存到descriptor

3、網絡卡接收資料包,將資料包寫入Rx FIFO

4、DMA找到rx descriptor ring中下一個將要使用的descriptor

5、整個資料包寫入Rx FIFO後,DMA通過PCI匯流排將Rx FIFO中的資料包複製到descriptor的資料快取區

6、複製完後,網絡卡啟動硬中斷通知CPU資料快取區中已經有新的資料包了,CPU執行硬中斷函式:

NAPI(以e1000網絡卡為例):e1000_intr() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ) 非NAPI(以dm9000網絡卡為例):dm9000_interrupt() -> dm9000_rx() -> netif_rx() -> napi_schedule() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ) 7、ksoftirqd執行軟中斷函式net_rx_action():

NAPI(以e1000網絡卡為例):net_rx_action() -> e1000_clean() -> e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb() 非NAPI(以dm9000網絡卡為例):net_rx_action() -> process_backlog() -> netif_receive_skb() 8、網絡卡驅動通過netif_receive_skb()將sk_buff上送協議棧

Rx Ring Buffer SW向從next_to_use開始的N個descriptor補充sk_buff,next_to_use += N,tail = next_to_use - 1(設定網絡卡暫存器RDT)

HW向從head開始的M個descriptor的sk_buff複製資料包並設定DD,head += M

SW將從next_to_clean的開始的L個sk_buff移出Rx Ring Buffer交給協議棧,next_to_clean += L,向從next_to_use開始的L個descriptor補充sk_buff,next_to_use += L,tail = next_to_use - 1

注意:每次補充完sk_buff以後,tail、next_to_use、next_to_clean三者都是緊挨著的

中斷上下部

do_IRQ()是CPU處理硬中斷的總入口,在do_IRQ()中呼叫硬中斷函式

// 在e1000_request_irq()中註冊硬中斷,中斷函式為e1000_intr() irq_handler_t handler = e1000_intr; err = request_irq(adapter->pdev->irq, handler, irq_flags, netdev->name,                   netdev);

// 在dm9000_open()中註冊硬中斷,中斷函式為dm9000_interrupt() if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))     return -EAGAIN;

// 在net_dev_init()中註冊軟中斷,中斷函式為net_rx_action() open_softirq(NET_RX_SOFTIRQ, net_rx_action);

// 在e1000_probe()中註冊napi的poll函式為e1000_clean() netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);

// 在net_dev_init()中註冊非napi的poll函式為process_backlog() queue->backlog.poll = process_backlog; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 netif_rx() 在netif_rx()中把skb加入CPU的softnet_data

/**  * netif_rx   -  post buffer to the network code  * @skb: buffer to post  *  * This function receives a packet from a device driver and queues it for  * the upper (protocol) levels to process.  It always succeeds. The buffer  * may be dropped during processing for congestion control or by the  * protocol layers.  *  * return values:  * NET_RX_SUCCESS (no congestion)  * NET_RX_DROP     (packet was dropped)  *  */

int netif_rx(struct sk_buff *skb) {    struct softnet_data *queue;    unsigned long flags;

   /* if netpoll wants it, pretend we never saw it */    if (netpoll_rx(skb))       return NET_RX_DROP;

   if (!skb->tstamp.tv64)       net_timestamp(skb);

   /*     * The code is rearranged so that the path is the most     * short when CPU is congested, but is still operating.     */    local_irq_save(flags);    queue = &__get_cpu_var(softnet_data); // 得到CPU的softnet_data

   __get_cpu_var(netdev_rx_stat).total++;    if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { // 若佇列長度不大於netdev_max_backlog       if (queue->input_pkt_queue.qlen) { // 若佇列長度非0,表示queue->backlog已被加入poll_list enqueue:          __skb_queue_tail(&queue->input_pkt_queue, skb); // 將skb加入佇列尾部          local_irq_restore(flags);          return NET_RX_SUCCESS;       }

      napi_schedule(&queue->backlog); // 排程queue->backlog       goto enqueue; // 將skb加入佇列尾部    }

   __get_cpu_var(netdev_rx_stat).dropped++;    local_irq_restore(flags);

   kfree_skb(skb);    return NET_RX_DROP; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 RSS + FDIR FDIR(Flow Director)的優先順序高於RSS(Receive Side Scaling)

RSS通過計算包的五元組(sip、sport、dip、dport、protocol)的hash並取餘,得到佇列的index,然後將包放入這個佇列,實現了資料包在各個佇列之間的負載均衡,不過RSS不能保證回包也落在同一個佇列上

對稱hash(sip/sport和dip/dport交換後hash不變)可以部分解決該問題,但是對於一些需要做NAT的裝置(比如負載均衡)就失效了,FDIR可以完全解決該問題,參見https://tech.meituan.com/MGW.html

參考資料 82599:  https://www.intel.com/content/www/us/en/embedded/products/networking/82599-10-gbe-controller-datasheet.html  網絡卡:  http://blog.csdn.net/tao546377318/article/details/51602298  http://blog.csdn.net/Just_Do_IT_Ye/article/details/47000383  DMA:  http://www.wowotech.net/memory_management/DMA-Mapping-api.html  http://blog.csdn.net/phunxm/article/details/9452575  http://blog.chinaunix.net/uid-1858380-id-3261817.html  http://www.elecfans.com/book/232/  協議棧收發包過程:  https://segmentfault.com/a/1190000008836467  https://segmentfault.com/a/1190000008926093  NAPI:  http://blog.csdn.net/zhangskd/article/details/21627963

https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/  https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/  https://blog.packagecloud.io/eng/2016/10/11/monitoring-tuning-linux-networking-stack-receiving-data-illustrated/ ---------------------  作者:hz5034  來源:CSDN  原文:https://blog.csdn.net/hz5034/article/details/79794615?utm_source=copy  版權宣告:本文為博主原創文章,轉載請附上博文連結!