linux網絡數據包的接收過程

相關文章:linux網絡數據包的接收過程Ⅱ

本文將介紹在Linux系統中,數據包是如何一步一步從網卡傳到進程手中的。

本文只討論以太網的物理網卡,不涉及虛擬設備,並且以一個UDP包的接收過程作爲示例.

本示例裏列出的函數調用關係來自於kernel 3.13.0,如果你的內核不是這個版本,函數名稱和相關路徑可能不一樣,但背後的原理應該是一樣的(或者有細微差別)

網卡到內存

網卡需要有驅動才能工作,驅動是加載到內核中的模塊,負責銜接網卡和內核的網絡模塊,驅動在加載的時候將自己註冊進網絡模塊,當相應的網卡收到數據包時,網絡模塊會調用相應的驅動程序處理數據。

下圖展示了數據包(packet)如何進入內存,並被內核的網絡模塊開始處理:

                   +-----+
                   |     |                            Memroy
+--------+   1     |     |  2  DMA     +--------+--------+--------+--------+
| Packet |-------->| NIC |------------>| Packet | Packet | Packet | ...... |
+--------+         |     |             +--------+--------+--------+--------+
                   |     |<--------+
                   +-----+         |
                      |            +---------------+
                      |                            |
                    3 | Raise IRQ                  | Disable IRQ
                      |                          5 |
                      |                            ||
                   +-----+                   +------------+
                   |     |  Run IRQ handler  |            |
                   | CPU |------------------>| NIC Driver |
                   |     |       4           |            |
                   +-----+                   +------------+
                                                   |
                                                6  | Raise soft IRQ
                                                   |
  1. 數據包從外面的網絡進入物理網卡。如果目的地址不是該網卡,且該網卡沒有開啓混雜模式,該包會被網卡丟棄。
  2. 網卡將數據包通過DMA的方式寫入到指定的內存地址,該地址由網卡驅動分配並初始化。注: 老的網卡可能不支持DMA,不過新的網卡一般都支持。
  3. 網卡通過硬件中斷(IRQ)通知CPU,告訴它有數據來了
  4. CPU根據中斷表,調用已經註冊的中斷函數,這個中斷函數會調到驅動程序(NIC Driver)中相應的函數
  5. 驅動先禁用網卡的中斷,表示驅動程序已經知道內存中有數據了,告訴網卡下次再收到數據包直接寫內存就可以了,不要再通知CPU了,這樣可以提高效率,避免CPU不停的被中斷。
  6. 啓動軟中斷。這步結束後,硬件中斷處理函數就結束返回了。由於硬中斷處理程序執行的過程中不能被中斷,所以如果它執行時間過長,會導致CPU沒法響應其它硬件的中斷,於是內核引入軟中斷,這樣可以將硬中斷處理函數中耗時的部分移到軟中斷處理函數裏面來慢慢處理。

內核的網絡模塊

軟中斷會觸發內核網絡模塊中的軟中斷處理函數,後續流程如下

                                            +-----+
                                    17      |     |
                               +----------->| NIC |
                               |            |     |
                               |Enable IRQ  +-----+
                               |
                               |
                         +------------+                                      Memroy
                         |            |        Read           +--------+--------+--------+--------+
        +--------------->| NIC Driver |<--------------------- | Packet | Packet | Packet | ...... |
        |                |            |          9            +--------+--------+--------+--------+
        |                +------------+
        |                      |    |        skb
   Poll | 8      Raise softIRQ | 6  +-----------------+
        |                      |             10       |
        |                      ↓                      ↓
+---------------+  Call  +-----------+        +------------------+        +--------------------+  12  +---------------------+
| net_rx_action |<-------| ksoftirqd |        | napi_gro_receive |------->| enqueue_to_backlog |----->| CPU input_pkt_queue |
+---------------+   7    +-----------+        +------------------+   11   +--------------------+      +---------------------+
                                                      |                                                      | 13
                                                   14 |        + - - - - - - - - - - - - - - - - - - - - - - +
                                                      ↓        ↓
                                           +--------------------------+    15      +------------------------+
                                           | __netif_receive_skb_core |----------->| packet taps(AF_PACKET) |
                                           +--------------------------+            +------------------------+
                                                      |
                                                      | 16+-----------------+
                                             | protocol layers |
                                             +-----------------+
  1. 內核中的ksoftirqd進程專門負責軟中斷的處理,當它收到軟中斷後,就會調用相應軟中斷所對應的處理函數,對於上面第6步中是網卡驅動模塊拋出的軟中斷,ksoftirqd會調用網絡模塊的net_rx_action函數
  2. net_rx_action調用網卡驅動裏的poll函數來一個一個的處理數據包
  3. 在pool函數中,驅動會一個接一個的讀取網卡寫到內存中的數據包,內存中數據包的格式只有驅動知道
  4. 驅動程序將內存中的數據包轉換成內核網絡模塊能識別的skb格式,然後調用napi_gro_receive函數
  5. napi_gro_receive會處理GRO相關的內容,也就是將可以合併的數據包進行合併,這樣就只需要調用一次協議棧。然後判斷是否開啓了RPS,如果開啓了,將會調用enqueue_to_backlog
  6. 在enqueue_to_backlog函數中,會將數據包放入CPU的softnet_data結構體的input_pkt_queue中,然後返回,如果input_pkt_queue滿了的話,該數據包將會被丟棄,queue的大小可以通過net.core.netdev_max_backlog來配置
  7. CPU會接着在自己的軟中斷上下文中處理自己input_pkt_queue裏的網絡數據(調用__netif_receive_skb_core)
  8. 如果沒開啓RPS,napi_gro_receive會直接調用__netif_receive_skb_core
  9. 看是不是有AF_PACKET類型的socket(也就是我們常說的原始套接字),如果有的話,拷貝一份數據給它。tcpdump抓包就是抓的這裏的包。
  10. 調用協議棧相應的函數,將數據包交給協議棧處理。
  11. 待內存中的所有數據包被處理完成後(即poll函數執行完成),啓用網卡的硬中斷,這樣下次網卡再收到數據的時候就會通知CPU

enqueue_to_backlog函數也會被netif_rx函數調用,而netif_rx正是lo設備發送數據包時調用的函數

協議棧

IP層

由於是UDP包,所以第一步會進入IP層,然後一級一級的函數往下調:

          |
          |
          ↓         promiscuous mode &&
      +--------+    PACKET_OTHERHOST (set by driver)   +-----------------+
      | ip_rcv |-------------------------------------->| drop this packet|
      +--------+                                       +-----------------+
          |
          |+---------------------+
| NF_INET_PRE_ROUTING |
+---------------------+
          |
          |+---------+
      |         | enabled ip forword  +------------+        +----------------+
      | routing |-------------------->| ip_forward |------->| NF_INET_FORWARD |
      |         |                     +------------+        +----------------+
      +---------+                                                   |
          |                                                         |
          | destination IP is local                                 ↓
          ↓                                                 +---------------+
 +------------------+                                       | dst_output_sk |
 | ip_local_deliver |                                       +---------------+
 +------------------+
          |
          |+------------------+
 | NF_INET_LOCAL_IN |
 +------------------+
          |
          |+-----------+
    | UDP layer |
    +-----------+
  • ip_rcv:ip_rcv函數是IP模塊的入口函數,在該函數裏面,第一件事就是將垃圾數據包(目的mac地址不是當前網卡,但由於網卡設置了混雜模式而被接收進來)直接丟掉,然後調用註冊在NF_INET_PRE_ROUTING上的函數
  • NF_INET_PRE_ROUTING:netfilter放在協議棧中的鉤子,可以通過iptables來注入一些數據包處理函數,用來修改或者丟棄數據包,如果數據包沒被丟棄,將繼續往下走
  • routing: 進行路由,如果是目的IP不是本地IP,且沒有開啓ip forward功能,那麼數據包將被丟棄,如果開啓了ip forward功能,那將進入ip_forward函數
  • ip_forward:ip_forward會先調用netfilter註冊的NF_INET_FORWARD相關函數,如果數據包沒有被丟棄,那麼將繼續往後調用dst_output_sk函數
  • dst_output_sk: 該函數會調用IP層的相應函數將該數據包發送出去,同下一篇要介紹的數據包發送流程的後半部分一樣
  • ip_local_deliver:如果上面routing的時候發現目的IP是本地IP,那麼將會調用該函數,在該函數中,會先調用NF_INET_LOCAL_IN相關的鉤子程序,如果通過,數據包將會向下發送到UDP層

UDP層

          |
          |+---------+            +-----------------------+
      | udp_rcv |----------->| __udp4_lib_lookup_skb |
      +---------+            +-----------------------+
          |
          |+--------------------+      +-----------+
 | sock_queue_rcv_skb |----->| sk_filter |
 +--------------------+      +-----------+
          |
          |+------------------+
 | __skb_queue_tail |
 +------------------+
          |
          |+---------------+
  | sk_data_ready |
  +---------------+
  • udp_rcv:udp_rcv函數是UDP模塊的入口函數,它裏面會調用其它的函數,主要是做一些必要的檢查,其中一個重要的調用是__udp4_lib_lookup_skb,該函數會根據目的IP和端口找對應的socket,如果沒有找到相應的socket,那麼該數據包將會被丟棄,否則繼續
  • sock_queue_rcv_skb: 主要乾了兩件事,一是檢查這個socket的receive buffer是不是滿了,如果滿了的話,丟棄該數據包,然後就是調用sk_filter看這個包是否是滿足條件的包,如果當前socket上設置了filter,且該包不滿足條件的話,這個數據包也將被丟棄(在Linux裏面,每個socket上都可以像tcpdump裏面一樣定義filter,不滿足條件的數據包將會被丟棄)
  • __skb_queue_tail: 將數據包放入socket接收隊列的末尾
  • sk_data_ready: 通知socket數據包已經準備好

調用完sk_data_ready之後,一個數據包處理完成,等待應用層程序來讀取,上面所有函數的執行過程都在軟中斷的上下文中。

socket

應用層一般有兩種方式接收數據,一種是recvfrom函數阻塞在那裏等着數據來,這種情況下當socket收到通知後,recvfrom就會被喚醒,然後讀取接收隊列的數據;另一種是通過epoll或者select監聽相應的socket,當收到通知後,再調用recvfrom函數去讀取接收隊列的數據。兩種情況都能正常的接收到相應的數據包

原文鏈接:https://segmentfault.com/a/1190000008836467

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章