https://zhaozhanxu.com/archives/page/7/
Linux版本: 3.10.103
網卡驅動: ixgbe
報文收發簡單流程
網卡驅動默認採用的是NAPI的報文處理方式。即中斷+輪詢的方式,網卡收到一個報文之後會產生接收中斷,並且屏蔽中斷,直到收夠了netdev_max_backlog個報文(默認300)或者收完網卡上的所有報文之後,重新打開中斷。
網卡數據處理
網卡初始化
- 內核啓動時會調用
do_initcalls
,從而調用註冊的初始化接口net_dev_init
,net_dev_init
註冊軟中斷的回調函數,分別爲接收和發送的:NET_RX_SOFTIRQ = net_rx_action
,NET_TX_SOFTIRQ = net_tx_action
。 - 網卡驅動加載時調用
ixgbe_init_module
註冊一個PCI驅動。 - 接着調用probe對應的
ixgbe_probe
做一些準備工作如下:- 創建
ixgbe_adapter
數據結構,這是網卡的一個實例,其中包含了網卡的所有數據和接口(netdev)。 - netdev註冊了網卡的所有操作:
ixgbe_netdev_ops
和網卡的features。 ixgbe_init_interrupt_scheme
主要是設置網卡的NAPI對應的poll接口,這裏主要是ixgbe_poll。
- 創建
- 網卡激活時會調用netdev上的open函數
ixgbe_open
,完成工作如下:- 設置接收和發送隊列,通過DMA講PCI網卡地址和隊列建立映射。
- 給網卡註冊硬中斷:
ixgbe_open-->ixgbe_request_irq-->ixgbe_request_msix_irqs
註冊中斷回調ixgbe_msix_clean_rings
。(MSIX中斷時)
ixgbe_close
函數所做的事情:- 關閉中斷並且釋放中斷向量號。
- 釋放申請的相應空間。
網卡收發數據
- 當網卡接收到數據幀時,DMA將數據搬運到對應的
rx_ring
,然後產生一個硬中斷,調用接口ixgbe_msix_clean_rings
。 - 硬中斷回調函數
ixgbe_msix_clean_rings
會調用當前CPU對應的NET_RX_SOFTIRQ
,即net_rx_action
。其中的主要實現NAPI操作如下:- 關閉硬中斷。
- 調用該CPU的
poll_list
中的所有poll函數,即ixgbe_poll
。 - 開啓硬中斷。
- 輪詢函數
ixgbe_poll
主要是處理接收發送任務:- 循環處理每個ring中的數據,
ixgbe_clean_tx_irq
,此處發送tx_work_limit個報文(128)或者沒有報文後完成,並且調用netif_wake_subqueue
觸發發送軟中斷NET_TX_SOFTIRQ
,即net_tx_action
。 - 循環處理每個ring中的數據,
ixgbe_clean_rx_irq
,此處接收weight個報文(64),總共收取netdev_budget(300)個,並且調用ixgbe_rx_skb
進行下一步的處理。
- 循環處理每個ring中的數據,
ixgbe_rx_skb
最終會處理過GRO之後調用到netif_receive_skb
,此處忽略rps。netif_receive_skb
最終調用__netif_receivve_skb_core
進入協議棧處理各種協議。- 首先根據下面介紹的rx_handler類型調用ovs或者橋處理函數進入L2的處理。
- 然後根據pttype_base處理相關的L3協議處理。
備註
- ixgbe擁有64個rx_ring,每個ring默認有512個buffer,最小64,最大4096。
- 由以上可知網卡默認可以緩存32768個報文,當網卡接收報文的速度過快,接收軟中斷無法處理的時候,系統進入扼流(throttle)狀態,所有後續的報文都被丟棄,直到rx_ring有空間了。
- 通過DMA地址映射的方式,網卡接收數據相當於零拷貝。
TUN虛擬網卡
- 網卡驅動加載時調用
tun_init
註冊一個MISC驅動,生成/dev/net/tun
的設備文件。 - 接着調用open對應的
tun_chr_open
做主要創建socket。 - 隨後調用ioctl對應的
tun_chr_ioctl
,完成一些需要的配置和信息獲取,包括校驗、連接狀態、offload、mac等。 - 通過調用write發送報文,主要是調用
tun_chr_aio_write
生成skb格式報文,最終通過調用netif_rx
進入協議棧,通過三層轉發到對應的端口發送。 - 通過調用read接收報文,主要是調用
tun_chr_aio_read
,從內核對應的sock隊列上拿到報文,然後將數據上傳到用戶層。
注:
因爲網卡接收報文後最終經過四層的時候,會將報文根據協議幾元組找到對應的sock結構,放入隊列。上層調用read或者recv的時候都是從socket對應的sock結構隊列上取數據。
VETH虛擬網卡
- 網卡驅動加載時調用
veth_init
註冊一個netlink驅動。 - 接着調用newlink對應的
veth_newlink
,創建兩個網絡設備,兩者互爲對方的peer。 - 發送報文時會調用
ndo_start_xmit
對應的veth_xmit
,實際操作爲將skb的dev設置爲自己的peer,調用到netif_rx
接收到協議棧。
QEMU虛擬機網絡通信
- 主機vhost驅動加載時調用
vhost_net_init
註冊一個MISC驅動,生成/dev/vhost-net
的設備文件。 - 主機qemu-kvm啓動時調用open對應的
vhost_net_open
做主要創建隊列和收發函數的掛載,接着調用ioctl啓動內核線程vhost,做收發包的處理。 - 主機qemu通過ioctl配置kvm模塊,主要設置通信方式,因爲主機vhost和virtio只進行報文的傳輸,kvm進行提醒。
- 虛擬機virtio模塊註冊,生成虛擬機的網絡設備,配置中斷和NAPI。
- 虛擬機發包流程如下:
- 直接從應用層走協議棧最後調用發送接口
ndo_start_xmit
對應的start_xmit
,將報文放入發送隊列,vp_notify
通知kvm。 - kvm通過
vmx_handle_exit
一系列調用到wake_up_process
喚醒vhost線程。 - vhost模塊的線程激活並且拿到報文,在通過之前綁定的發送接口
handle_tx_kick
進行發送,調用虛擬網卡的tun_sendmsg
最終到netif_rx
接口進入主機內核協議棧。
- 直接從應用層走協議棧最後調用發送接口
- 虛擬機收包流程如下:
- tap設備的
ndo_start_xmit
對應的tun_net_xmit
最終調用到wake_up_process
激活vhost線程,調用handle_rx_kick
,將報文放入接收隊列。 - 通過一系列的調用到kvm模塊的接口
kvm_vcpu_kick
,向qemu虛擬機注入中斷。 - 虛擬機virtio模塊中斷調用接口
vp_interrupt
,調用virtnet_poll
,再調用到netif_receive_skb
進入虛擬機的協議棧。
- tap設備的
Offload技術
發送數據
- TSO(TCP Segmentation Offload)使得網絡協議棧能夠將大塊buffer推送至網卡,然後網卡執行分片工作,這樣減輕了CPU 的負荷,但TSO需要硬件來實現分片功能,也叫LSO。
- UFO(UDP Fragmentation Offload)類似。
- GSO(Generic Segmentation Offload),它比TSO更通用,基本思想就是儘可能的推遲數據分片直至發送到網卡驅動之前,此時會檢查網卡是否支持分片功能(如TSO、UFO),如果支持直接發送到網卡,如果不支持就進行回調分片後再發往網卡。這樣大數據包只需走一次協議棧,而不是被分割成幾個數據包分別走,這就提高了效率。
接收數據
- LRO(Large Receive Offload)通過將接收到的多個TCP數據聚合成一個大的數據包,然後傳遞給網絡協議棧處理,以減少上層協議棧處理開銷,提高系統接收TCP數據包的能力。
- GRO(Generic Receive Offloading),LRO使用發送方和目的地IP地址,IP封包ID,L4協議三者來區分段,對於從同一個SNAT域的兩個機器發向同一目的IP的兩個封包可能會存在相同的IP封包ID(因爲不是全局分配的ID),這樣會有所謂的卷繞的bug。GRO採用發送方和目的地IP地址,源/目的端口,L4協議三者來區分作爲改進。所以對於後續的驅動都應該使用GRO的接口,而不是LRO。另外,GRO也支持多協議。
注意:所有文章非特別說明皆爲原創。爲保證信息與源同步,轉載時請務必註明文章出處!謝謝合作 :-)
原始鏈接:http://zhaozhanxu.com/2016/07/12/Linux/2016-07-12-Linux-Kernel-Pkts_Processing1/
許可協議: "署名-非商用-相同方式共享 4.0" 轉載請保留原文鏈接及作者。
另一些文章:
https://blog.csdn.net/meituantech/article/details/80062289 Redis 高負載下的中斷優化
軟中斷部分有幾個地方會有類似if (static_key_false(&rps_needed))這樣的判斷,會進入前文所述有丟包風險的enqueue_to_backlog函數。 這裏的邏輯爲判斷是否啓用了RPS機制,RPS是早期單隊列網卡上將軟中斷負載均衡到多個CPU Core的技術,它對數據流進行hash並分配到對應的CPU Core上,發揮多核的性能。不過現在基本都是多隊列網卡,不會開啓這個機制,因此走不到這裏,static_key_false是針對默認爲false的static key 的優化判斷方式。這段調用的最後,deliver_skb會將接收的數據傳入一個IP層的數據結構中,至此完成二層的全部處理.
關於ixgbe的rx ring,tx ring,msi,legacy對比 https://www.slideshare.net/suselab/ixgbe-internals:
ixgbe 網卡初始化及收發數據概覽 https://www.jianshu.com/p/9a78390df5b9