數據包從物理網卡流經 Open vSwitch 進入 OpenStack 云主機的流程

      網友投稿 1043 2025-04-03

      目錄

      文章目錄

      目錄

      前言

      數據包從物理網卡進入虛擬機的流程

      物理網卡處理

      如何將網卡收到的數據寫入到內核內存?

      中斷下半部分軟中斷處理

      數據包在內核態 OvS Bridge(Datapath)中的處理

      veth pair 的工作原理

      將數據包交給 Linux Bridge 處理

      將數據包送入虛擬機 tap 口

      tap 口的數據包處理流程

      vhost 線程的工作流程

      KVM 如何中斷觸發虛擬機的內核協議棧?

      最后

      前言

      數據包從物理網卡進入虛擬機的流程

      頂層視角

      中層視角

      微觀視角

      物理網卡處理

      中斷下半部分軟中斷處理

      將數據包交給內核的 OvS Bridge 處理

      將數據包交給 Linux Bridge 處理

      將數據包送入虛擬機 tap 口

      物理網卡處理

      NIC 收到數據包,會先將高低電信號轉換到網卡 FIFO 存儲器。NIC 首先申請 Ring buffer 的描述,根據描述找到具體的物理地址,NIC 會使用 DMA 將數據包從 FIFO 隊列寫到該物理地址指向的空間,其實就是 skb_buffer 中。

      數據包從物理網卡流經 Open vSwitch 進入 OpenStack 云主機的流程

      此時數據包已經被轉移到 skb_buffer 中,因為是 DMA 寫入,所以內核并沒有監控數據包寫入情況,這時候 NIC 觸發一個硬中斷,每一個硬件中斷會對應一個中斷號,且指定一個 vCPU 來處理。如:上圖 vcpu2 收到了該硬件中斷。

      硬件中斷的中斷處理程序,通過調用 NIC 驅動程序來完成。

      硬中斷觸發的驅動程序首先會禁用 NIC 硬中斷,意思是告訴 NIC 再來數據就先不要觸發硬中斷了,把數據包通過 DMA 拷入系統內存即可。

      硬中斷觸發的驅動程序繼續啟動軟中斷,啟用軟中斷目的是將數據包的后續處理流程交給軟中斷異步的慢慢處理。此時 NIC 驅動程序就退出硬件中斷了,其他的外部設備可以繼續調用硬件中斷。但網絡 I/O 相關的硬中斷,是需要等到軟中斷處理完成并再次開啟硬中斷后,才能被再次觸發。

      軟中斷喚醒 NAPI,觸發 napi() 系統調用。

      逐一消耗 Ring Buffer 指向的 skb_buffer 中的數據包。

      NAPI 循環處理 Ring Buffer 中的數據。

      開啟網絡 I/O 硬件中斷,有新數據到來時可以繼續觸發硬件中斷,繼續通知特定的 CPU 來消耗數據包。

      簡單總結一下上述流程:

      物理網卡接收到數據包首先將數據包緩存在設備控制器的 Ring Buffer 上。

      然后物理網卡 CPU 發送一個硬中斷,硬中斷調用網卡驅動程序,網卡驅動程序在觸發一個軟中斷。

      軟中斷處理程序輪詢的從 Ring Buffer 上讀取數據包并翻譯成標準的 skb 數據結構,并將 skb 掛到 CPU 的處理隊列中。

      CPU 從隊列中接收到 skb 之后就會執行指令,讓內核協議棧處理該 skb 對應的數據包。

      關鍵在于:網卡和內核協議棧其實是標準的生產者/消費者模型,網卡生產,內核協議棧消費,生產者需要通知消費者進行消費。所以,當生產/消費供給不平衡時,就會產生丟包。在高流量壓力情況下,單純的優化生產者是不行的,還要考量消費能力是否跟得上。由此,丟包未必就是網卡的問題,也可能是內核協議棧消費得太慢了。

      如何將網卡收到的數據寫入到內核內存?

      NIC 在接收到數據包之后,首先需要將數據同步到內核中,這中間的橋梁是 rx ring buffer。它是由 NIC 和驅動程序共享的一片區域,事實上,rx ring buffer 存儲的并不是實際的數據包,而是一個描述符,這個描述符指向了它真正的存儲地址,具體流程如下:

      NIC 驅動程序在內存中分配一片緩沖區用來接收數據包,叫做 skb_buffer。

      將 skb_buffer 的接收描述符(包含物理地址和大?。?,加入到 rx ring buffer,描述符中的緩沖區地址是 DMA 使用的物理地址。

      驅動通知網卡有一個新的描述符。

      網卡從 rx ring buffer 中取出描述符,從而獲知緩沖區的地址和大小。

      網卡收到新的數據包。

      網卡將新數據包通過 DMA 直接寫到 sk_buffer 中。

      當 NIC 驅動程序的處理速度跟不上網卡收包速度時,驅動來不及分配緩沖區,NIC 接收到的數據包無法及時寫到 sk_buffer,就會產生堆積,當 NIC 內部緩沖區寫滿后,就會丟棄部分數據,引起丟包。這部分丟包為 rx_fifo_errors,在 /proc/net/dev 中體現為 FIFO 字段增長,在 ifconfig 中體現為 overruns 指標增長。

      中斷下半部分軟中斷處理

      7.1 因為 igb_ckean_rx_irq 會循環消耗數據包,但存在循環有次數限制,否則 CPU 會一直停留在處理部分。循環次數限制對應的內核參數為 net.core.netdev_budget = 300。

      7.2 取出 skb,調用 napi_gro_receive,這個函數先做一些 GRO 包合并動作,然后根據是否開啟 RPS 執行如下流程。

      7.2.1 開啟了 RPS,將數據包放至 HASH 到的 vCPU 隊列中,數據包掛入這個隊列后,本次處理就結束了,繼續處理下一個包。隊列長度由內核參數 net.core.netdev_max_backlog = 1000 決定,也就是當數據包超過 1000 個時,就會被丟棄。所以用戶態消耗要跟上才行。后續就由隊列的 vCPU 取出數據包并調用 __netif_receive_skb_core 來處理。

      7.2.2 如果沒有開啟 RPS,則直接使用 vCPU2 繼續調用 __netif_receive_skb_core 來處理該數據包。

      NOTE:由上述描述可知,開啟 RPS 后,下半步軟中斷的數據包可以直接掛到其它 vCPU 隊列上,這樣就能減少 vCPU2 的壓力,vCPU2 就能處理更大的流量??梢姡琑PS 適合單網卡隊列,多 vCPU 的使用場景。

      NOTE:__netif_receive_skb_core 是內核協議棧處理數據包的入口函數,使用 tcpdump 抓包就是在此處起了作用,也就是說如果 tcpdump 能數據包就代表數據包已經到達內核協議棧入口了。

      數據包在內核態 OvS Bridge(Datapath)中的處理

      上圖所示是數據包在 Open vSwitch 內部轉發的流程,其中 netdev_frame_hook 是 OvS Bridge(Datapath)的入口,被 __netif_receive_skb_core 調用來處理接收到的數據包。

      數據流量進入 OvS Bridge 之后根據數據包的各類頭部信息(五元組)查找流表,并進行過濾或轉發處理。

      主要有下列兩種處理情況,而判斷的依據就是是否能夠根據五元組匹配到流表。

      Slow Path:流量轉發給在用戶態的控制器(ovs-vswitchd 和 ovs-db)。內核流量通過 netlink 將數據包傳給用戶態的 ovs-vswitchd 進程,ovs-vswitchd 會對流量解析,根據解析和 OpenFlow vSwitch 定義的 Proactive/Reactive 模式進行處理,一般是下發流表。

      Fast Path:流量在內核態直接轉發。根據流表的動作執行轉發動作,最終會調用 dev_hard_star_xmit、xmit_one,最后調用 ndo_start_xmit(skb, dev) 發送數據包。

      由于當前采用的是 Linux 虛擬 “網線” 設備(veth),所以 ndo_start_xmit(skb, dev) 調用了 veth_xmit,所以虛擬端口發包計數的統計工作也在 veth_xmit 完成。

      veth pair 的工作原理

      虛擬“網線”(veth pair):不是一個設備,而是一對設備,用于連接兩個虛擬以太端口。veth pair 的本質是反轉通訊數據的方向,需要發送的數據會被轉換成需要收到的數據重新送入內核協議棧進行處理,從而間接的完成數據的注入。veth pair 的工作原理就相當于當連接 Linux Bridge 的口收到了數據包,內核協議棧會反轉將該數據包重新發送給 Linux Bridge。

      繼續上述的流程:

      veth_xmit 將數據包發出。

      調用 dev_forward_sk,然后 dev_forward_sk 調用內核協議棧收包入口 netif_rx_internal,最終 netif_rx_internal 將數據包放入 CPU 收包隊列。如此,該數據包就被注入了內核協議棧。

      veth pair 設備的一端在軟中斷觸發時,會立刻處理該 CPU 隊列的數據包,并將數據包重新注入內核協議棧,最終數據包作為待接收數據被被 veth pair 設備的另一端接收。

      將數據包交給 Linux Bridge 處理

      在本文的網絡模型中。Linux Bridge 作為安全層,虛擬機的 tap 口通過 veth pair 虛擬設備接入 Linux Bridge。由上文可知,數據包會經過 veth pair 注入到內核協議棧,內核協議棧會再次調用 _netif_reveive_skb 處理該數據包。而因該函數是 veth 口收到的數據,該接口連接的是Linux Bridge,所以協議棧會調用 br_handle_frame 處理該數據包。Linux Bridge 處理數據包會經過以下幾個情況:

      協議棧收到該數據包會先經過 NF_BR_PRE_ROUTING 鉤子,該鉤子 處理的就是之前通過 iptables 添加的 PRE ROUTING 策略。

      經過 NF_BR_PRE_ROUTING 鉤子后,會判斷是將該數據包當成本機數據包處理,還是繼續轉發(后文以轉發為例進行說明)。

      若進行數據轉發,那么數據包轉發時會經過 NF_BR_FORWARD 鉤子函數,所有轉發的數據包都會經過該函數,安全組策略就是在此鉤子中進行的。

      轉發數據包處理完后,最后調用 br_forward_finish 將數據包發出去,NF_BR_POST_ROUTING 鉤子存在此處,作為數據包發出前最后的處理。

      最后會調用 dev_hard_start_xmit 函數將數據包發出。

      將數據包送入虛擬機 tap 口

      Linux Bridge 將數據包交給 tap 口,最終是將數據包將給用戶態進程 KVM-QEMU 的虛擬機進行消耗。所以需要將數據包從內核態轉發到用戶態,且觸發中斷告知虛擬機數據包來了。顯然,tap 口自己完成不了這樣的工作,tap 口僅能處理數據包,而內核態、用戶態的運行模式切換以及中斷觸發,就需要通過另一個層級的內核線程驅動了。本文場景中使用的是 vhost-net 技術。也就是說,實際上是通過 tap 口和 vhost 的配合來完成了將數據包交給虛擬機。

      tap 口的數據包處理流程

      上文提到,Linux Bridge 最后調用了 dev_hard_start_xmit 將數據包交給 tap 口,所以會調用 tap 口的 xmit_one 將數據包送到其入口函數。最后調用 tun_net_xmit 把將數據包發出,tun_net_xmit 這個函數很重要,承載了 tap 數據的邏輯:

      發包統計、drop 丟包統計都在此進行

      將數據包放入 socket.sk->sk_receive_queue,即 Socket 隊列。Socket 隊列有長度限制,默認為 tap 口的 tx_queuelen,通常是 1000 個數據包,如果長度大于 1000 則會丟棄數據包。遇到這種情況,說明虛擬機處理較慢,應該想辦法優化虛擬機的處理速度。

      數據包放入 Socket 隊列后,需要喚醒 vhost 線程開始工作。

      e.g. txqueuelen:1000 為 tap 口 Socket 隊列的長度,單位是包個數

      root@compute-001:~# ifconfig tapc3072bad-18 tapc3072bad-18 Link encap:Ethernet HWaddr fe:16:3e:09:6f:46 inet6 addr: fe80::fc16:3eff:fe09:6f46/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:3569 errors:0 dropped:0 overruns:0 frame:0 TX packets:372 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:294046 (294.0 KB) TX bytes:26034 (26.0 KB)

      1

      2

      3

      4

      5

      6

      7

      8

      vhost 線程的工作流程

      tap 口通過 tun_net_xmit 喚醒 vhost worker 之后,vhost 線程就會將數據包拷貝到虛擬機的 Ring Buffer 中,且觸發中斷告訴虛擬機數據包已到,虛擬機繼而消費這些數據包。

      vhost 線程,是創建虛擬機時一并啟動的線程,線程名稱 vhost-。vhost 線程是個死循環,它被喚醒后循環著干兩件事情:

      注入中斷觸發激活 GuestOS

      循環從 tap 口的 Socket 隊列中取出數據,直接將數據包拷用戶態虛擬機進程的 Ring Buffer 中,注意此處的 Ring Buffer 是 vhost 線程內核與用戶態共享的內存。

      KVM 如何中斷觸發虛擬機的內核協議棧?

      如圖,vhost 線程會調用 KVM 的接口來觸發中斷,主要做了兩件事情:

      判斷 pCPU 是否運行中,是,則讓 pCPU 先退出運行,為了有機會注入中斷。

      向目標 vCPU 添加一個請求,這個請求 vCPU 會死循環的監聽, 若發現,則真正注入中斷。

      也就是說,這兩件事情其實只是讓 pCPU 退出,方便注入中斷請求,和提前注入中斷標記。此外,還需要 vCPU 死循環來完成,在循環體中,會有檢查中斷注入標記的環節,如果發現該標記就調用 kvm_x86_ops->run 立即觸發中斷。該觸發會最終調用中斷處理函數 vp_interrupt -> vp_ring_interrupt-> 最終觸發內核協議棧 的運行,讓協議棧調用 __netif_receive_core 處理數據包。

      往后,虛擬機對數據包的處理流程就與宿主機大同小異了。

      最后

      Linux OpenStack

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:何繪制文檔中表格的斜線表頭?
      下一篇:WPS表格技巧—長表格打印時如何讓每頁都有表頭(wps打印表格的時候怎么讓表頭在每一頁都出現)
      相關文章
      亚洲高清视频一视频二视频三| 色老板亚洲视频免在线观| 亚洲AV无码一区二区一二区| 亚洲另类图片另类电影| 亚洲va在线va天堂va不卡下载| 亚洲AV无码国产丝袜在线观看 | 亚洲中文字幕无码爆乳av中文| 国产精品亚洲а∨无码播放麻豆 | 久久精品国产亚洲av天美18| 亚洲精品国产suv一区88| 亚洲综合av一区二区三区不卡| 精品国产日韩久久亚洲| 一本天堂ⅴ无码亚洲道久久| 亚洲日韩AV一区二区三区四区| 亚洲精品无码少妇30P| 18禁亚洲深夜福利人口| 亚洲成A人片在线观看中文| 亚洲乱码日产精品a级毛片久久 | 亚洲1234区乱码| 亚洲 日韩经典 中文字幕| 亚洲人成网站色7799| 亚洲av午夜国产精品无码中文字| 国产精品观看在线亚洲人成网| 久久久久久亚洲av无码蜜芽| 国产AV日韩A∨亚洲AV电影| 亚洲视频在线免费| 亚洲午夜久久久影院| 亚洲av无码一区二区三区网站| 亚洲国产人成网站在线电影动漫| 激情内射亚洲一区二区三区| 亚洲另类精品xxxx人妖| 亚洲色成人四虎在线观看| 国产成人亚洲综合无| 亚洲人成色7777在线观看不卡| 亚洲午夜久久久影院| 亚洲综合一区二区国产精品| 亚洲一区二区三区精品视频| 亚洲精品自偷自拍无码| 亚洲精品视频久久久| 亚洲综合无码AV一区二区 | 久久夜色精品国产噜噜亚洲AV|