動機

算是packet在linux中走過的路的延伸

in

device init

module_init => 當設備驅動編譯時,MODULE_DEVICE_TABLE會導出一個 PCI 設備 ID 列表,驅動據此識別它可以控制的設備,內核也會依據這個列表對不同設備加載相應驅動。

通過 PCI ID 識別設備後,內核就會為它選擇合適的驅動。 probe()

  1. 啟用 PCI 設備
  2. 請求(requesting)內存範圍和 IO 端口
  3. 設置 DMA 掩碼
  4. 註冊設備驅動支持的 ethtool 方法(後面介紹)
  5. 註冊所需的 watchdog(例如,e1000e 有一個檢測設備是否freeze的 watchdog)
  6. 其他和具體設備相關的事情,例如一些 workaround,或者特定硬件的非常規處理
  7. 創建、初始化和註冊一個 struct net_device_ops 類型變量,這個變量包含了用於設 備相關的回調函數,例如打開設備、發送數據到網絡、設置 MAC 地址等
  8. 創建、初始化和註冊一個更高層的 struct net_device 類型變量(一個變量就代表了 一個設備)

驅動的硬中斷處理函數做的事情很少,但軟中斷將會在和硬中斷相同的 CPU 上執行。 這就是為什麼給每個 CPU 一個特定的硬中斷非常重要:這個 CPU 不僅處理這個硬中斷,而且通過 NAPI 處理接下來的軟中斷來收包。

可以設定cpu affinity,讓localty上升,Set the IRQ affinity for IRQ 8 to CPU 0 echo 1 > /proc/irq/8/smp_affinity

device bring up

igb_open =>

  • 分配 RX、TX 隊列內存
    • 有些網卡支援Receive Side Scaling(RSS,接收端擴展)或者多隊列( multiqueue)
  • 打開 NAPI 功能 (一次收多一點)
    • 在一個單獨的線程裡,調用驅動註冊的 poll 方法收包
    • 驅動禁止網卡產生新的硬件中斷。這樣做是為了 NAPI 能夠在收包的時候不會被新的中 斷打擾
    • 一旦沒有包需要收了(或是收滿了,weight滿了),NAPI 關閉,網卡的硬中斷重新開啟
  • 註冊中斷處理函數
    • /proc/softirqs
    • MSI-X
      • 因為每個 RX 隊列有獨 立的MSI-X 中斷,因此可以被不同的 CPU 處理(通過 irqbalance 方式,或者修改 /proc/irq/IRQ_NUMBER/smp_affinity)
    • MSI
    • legacy IRQ
  • 打開(enable)硬中斷
    • “Interrupt Throttling”(也叫 “Interrupt Coalescing”)的硬件 特性相關,這個特性可以平滑傳送到 CPU 的中斷數量
    • Interrupt coalescing 中斷合併會將多個中斷事件放到一起,累積到一定閾值後才向 CPU 發起中斷請求。
      • 這可以防止中斷風暴,提升吞吐,降低 CPU 使用量,但延遲也變大;中斷數量過多則相反。
      • ethtool -C eth0 adaptive-rx on: 自適應 RX IRQ 合併
    • cat /proc/interrupts
  • 網絡設備子系統的初始化 (net_dev_init)
    • struct softnet_data 變量初始化
      • 需要註冊到這個 CPU 的 NAPI 變量列表
      • 數據處理 backlog
      • 處理權重
      • receive offload 變量列表
      • receive packet steering 設置
    • 註冊SoftIRQ
      • NET_TX_SOFTIRQ => net_tx_action
      • NET_RX_SOFTIRQ => net_rx_action
  • monitor
    • ethtool -S
    • cat /sys/class/net/<NIC>/statistics/<item>
    • /proc/net/dev
    • 所有顯示的項目都是由driver定義的,所以不同driver同樣item的意思可能不同
  • tuning
    • 調整queue
      • RX queue的數量
      • RX queue的大小
      • RX queue的權重
      • RX queue的hash (讓pkt到不同的queue)
    • ntuple filtering
      • 在NIC上做過濾,steer到指定的queue

inet開始收資料

先irq(igb_msix_ring)去要求跑sortirq,之後會帶到對應的softirq net_rx_action 從包所在的內存開始處理,包是被設備通過 DMA 直接送到內存。

softirq透過NAPI(igb_poll)去讀,讀完就會解綁(unmap)這些內存,讀取數據,將數據送到napi_gro_receive,之後到下一層(tap或是ip,如果中間有RPS,會先過RPS)。

有趣的是這裡的NAPI的weight寫死成64

monitor: /proc/net/softnet_stat 沒有title,這要直接看kernel code, net/core/net-procfs.c 每一行代表一個 struct softnet_data 變量。因為每個 CPU 只有一個該變量,所以每行 其實代表一個 CPU 每列用空格隔開,數值用 16 進製表示 第一列 sd->processed,是處理的網絡幀的數量。如果你使用了 ethernet bonding, 那這個值會大於總的網絡幀的數量,因為 ethernet bonding 驅動有時會觸發網絡數據被 重新處理(re-processed) 第二列,sd->dropped,是因為處理不過來而 drop 的網絡幀數量。後面會展開這一話題 第三列,sd->time_squeeze,前面介紹過了,由於 budget 或 time limit 用完而退出 net_rx_action 循環的次數 接下來的 5 列全是 0 第九列,sd->cpu_collision,是為了發送包而獲取鎖的時候有衝突的次數 第十列,sd->received_rps,是這個 CPU 被其他 CPU 喚醒去收包的次數 最後一列,flow_limit_count,是達到 flow limit 的次數。 flow limit 是 RPS 的特性, 後面會稍微介紹一下 tune: 調整 net_rx_action budget * net.core.netdev_budget=600

GRO(Generic Receive Offloading)

Large Receive Offloading (LRO) 是一個硬件優化,GRO 是 LRO 的一種軟件實現。

如果用 tcpdump 抓包,有時會看到機器收到了看起來不現實的、非常大的包, 這很可能是你的系統開啟了 GRO。

tcpdump 的抓包點(捕獲包的 tap )在整個棧的更後面一些,在GRO 之後。 NIC(與linux的level2)到IP之間

dev_gro_receive 完成,napi_skb_finish 就會被調用,之後就是走 netif_receive_skb 或者繼續將包送到協議棧,或者交給 RPS,後者會轉交給其他 CPU 處理。

RPS (Receive Packet Steering)

RPS (Receive Packet Steering,接收包控制,接收包引導)是 RSS 的一種軟件實現

因為它是軟件實現的,這意味著 RPS 只能在 packet 通過 DMA 進入內存後,RPS 才能開始工 作。

RPS 並不會減少 CPU 處理硬件中斷和 NAPI poll(軟中斷最重要的一部分)的時 間,但是可以在 packet 到達內存後,將 packet 分到其他 CPU,從其他 CPU 進入協議棧。

RPS 的工作原理是對個 packet 做 hash,以此決定分到哪個 CPU 處理。然後 packet 放到每個 CPU 獨占的接收後備隊列(backlog)等待處理。這個 CPU 會觸發一個進程間中斷( IPI,Inter-processor Interrupt)向對端 CPU。如果當時對端 CPU 沒有在處理 backlog 隊列收包,這個進程間中斷會 觸發它開始從 backlog 收包。 /proc/net/softnet_stat 其中有一列是記錄 softnet_data 變量(也即這個 CPU)收到了多少 IPI(received_rps 列)。

tune: /sys/class/net/DEVICE_NAME/queues/QUEUE/rps_cpus

RFS (Receive Flow Steering)

RFS 将属于相同 flow 的包送到相同的 CPU 进行处理,可以提高缓存命中率。

echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt sysctl -w net.core.rps_sock_flow_entries=32768

aRFS (Hardware accelerated RFS)

RFS 可以用硬件加速,網卡和內核協同工作,判斷哪個 flow 應該在哪個 CPU 上處理。 這個要開RFS與kernel要編CONFIG_RFS_ACCEL,ntuple也要開,最後配置 IRQ(硬中斷)中每個 RX 和 CPU 的對應關係

到linux的level2 (协议栈)

從netif_receive_skb過來 先當成有RPS,pkt就會到cpu的backlog(enqueue_to_backlog),之後NAPI的poller會去拉(process_backlog),可以把cpu當成另一種網卡 另外有flow limit,避免cpu被打爆 最後做gro(napi_gro_complete)

等到了cpu的backlog queue,NAPI poller就會去拉pkt backlog NAPI 變量和設備驅動 NAPI 變量的不同之處在於,它的 weight 是可以調節的,而設備 驅動是 hardcode 64。

monitor: 由於 input_pkt_queue 打滿或 flow limit 導致的丟包 * /proc/net/softnet_stat 裡面的 dropped tune: * RX packet timestamping * net.core.netdev_tstamp_prequeue=0 * 調cpu backlog: * net.core.netdev_max_backlog=3000 * 讓NAPI能一次拿更多pkt(wieght) * net.core.dev_weight=600 * 調flow limit的table size * net.core.flow_limit_table_len=8192 * 打開flow limit * /proc/sys/net/core/flow_limit_cpu_bitmap

最後的最後,__netif_receive_skb_core把pkt送到抓包點(tap)或協議層

到目前為止的流程

NIC ->(DMA,irq) driver ->(NAPI,sortirq) gro -> RPS ->(NAPI) tap/IP

到IP

ip_rcv之後就是netfilter(NF_HOOK)的prerouting,ip_rcv_finish(early_demux) 就可以往上走了!!

early_demux 如果這個優化打開了,但是並沒有命中緩存(例如,這是第一個包),這個包就會被送到內 核的路由子系統,在那裡將會計算出一個 dst_entry 並賦給相應的字段 所以應該能猜,沒開就是每次都去查?

tune: 把early_demux關了(也許需要)

  • net.ipv4.ip_early_demux=0

如果是自己的pkt? ip_local_deliver處理,過netfilter,到ip_local_deliver_finish

monitor:

  • /proc/net/snmp

InReceives: ip_rcv收到多少pkt InHdrErrors: 多少ip header壞了 InAddrErrors: 多少pkt的addr是到不了的 ForwDatagrams: forwarded的ip pkt InUnknownProtos: 多少是protocol不明的 InDiscards: 多少pkt被丟了(也許是mem alloc失敗,也許是checksum error) InDelivers: 多少pkt成功往上送 InCsumErrors: 多少pkt是checksum error

到udp

udp_rcv

  • 這裡會看pkt與他的dst_entry(routing的結果),送到socket的backlog

tune:

  • Socket receive queue memory
    • net.core.rmem_max=8388608
    • net.core.rmem_default=8388608
    • 或是用setsockopt帶 monitor:
  • /proc/net/snmp
  • /proc/net/udp

/proc/net/snmp InDatagrams: 總共有多少udp pkt進入或流出 NoPorts: 總共有多少udp pkt的dport是沒有人在聽的 InErrors: 多少udp pkt有錯誤(也許是mem alloc失敗,也許是checksum error) OutDatagrams: 多少pkt成功往下送 RcvbufErrors: 有多少pkt是因為rcv buffer爆了而塞不進去 SndbufErrors: 有多少pkt是因為send buffer爆了而塞不進去 InCsumErrors: 多少pkt是checksum error

/proc/net/udp sl: Kernel hash slot for the socket local_address: local addr rem_address: remote addr st: socket的狀態 tx_queue: tx queue的大小 rx_queue: rx queue的大小 tr, tm->when, retrnsmt: 這些應該retry與retransmit uid: 創這個socket的uid timeout: 應該是tcp的timeout inode: socket的inode ref: reference count pointer: socket的address drops: 這個socket的drop

socket

sock_queue_rcv收pkt,sk_data_ready通知socket

out

socket

sentto,會走到sock_sendmsg,之後看socket是AF_INET,走道inet_sendmsg 之後就是走到udp_sendmsg

udp

拿dip,dport,處理unicast或mutilcast,之後就是routing??,生skb(ip_make_skb),丟給udp_send_skb

怎麼感覺有偷跑的感覺在??

ip

ip_send_skb收到pkt,ip_local_out,netfilter(local_out),routing(拿dst_entry),ip_output,netfilter(post_routing),ip_finish_output

ip_finish_output,通常是

  1. ip_fragment,之後ip_finish_output2
  2. 直接ip_finish_output2

ip_finish_output2會去調arp(鄰居系統)

Path MTU Discovery

此功能允許內核自動確定 路由的最大傳輸單元( MTU )。

調用 setsockopt 帶 SOL_IP 和 IP_MTU_DISCOVER

linux level2 (TC)

dev_queue_xmit,之後就是找TX queue的旅程(netdev_pick_tx),拿到TX queue找對應的qdisc,之後到dev_hard_start_xmit,會去跑qdisc,再到sch_direct_xmit->dev_hard_start_xmit

任何無法發送的 skb 都重新入隊,將在 NET_TX softirq 中進行 發送。 __netif_schedule發tx的irq

  1. sortirq: net_tx_action
    • completion queue: 待釋放 skb 隊列
    • output queue: 待發送 skb 隊列
      • 之後到dev_hard_start_xmit
        • 發送數據花費的總時間是下面二者之和
          • 系統調用的系統時間(sys time)
          • NET_TX 類型的 softirq 時間(softirq time)
  2. dev_hard_start_xmit
    • dev_queue_xmit_nit: copy skb到tap(pcap)
    • ops->ndo_start_xmit: 到device去

monitor: tc -s qdisc show dev eth1 bytes: driver總共傳了多少bytes pkt: driver總共傳了多少pkt dropped: qdisc drop多少pkt overlimits: 有多少pkt是因為這個qdisc的策略而被去掉的(queue爆了或是有pkt出queue時去清) requeues: 有多少pkt是重新入queue(像是driver丟不出去就會重新入queue) backlog: 現在queue多長

你可以調整前面看到的__qdisc_run 循環的權重(上面看到的 quota 變量),這將導致 __netif_schedule 更多的被調用執行。結果將是當前 qdisc 將被更多的添加到當前 CPU 的 output_queue,最終會使發包所佔的時間變多。

tune:

  • 調整__qdisc_run 處理權重
    • net.core.dev_weight=600
  • 增加發送隊列長度
    • ifconfig eth0 txqueuelen 10000

Transmit Packet Steering (XPS)

發送數據包控制(XPS)是一項功能,允許系統管理員配置哪些 CPU 可以處理網卡的哪些發送 隊列。 XPS 的主要目的是避免處理髮送請求時的鎖競爭。使用 XPS 還可以減少緩存驅逐, 避免NUMA機器上的遠程 內存訪問等。

tune: /sys/class/net/DEVICE_NAME/queues/QUEUE/xps_cpus

NIC

到了driver的igb_xmit_frame_ring,再到igb_tx_map將 skb 數據映射到 RAM 的 DMA 區域

完成傳送之後device送完pkt就會丟irq

對於 igb 驅動程序(及其關聯設備),發送完成和數據包接收所觸發的 IRQ 是相同的。這意味著 對於 igb 驅動程序,NET_RX 既用於處理髮送完成,又用於處理數據包接收。 如果是這種情況,則 NET_RX softirq 會被用於 處理數據包接收和發送完成兩種情況。

在NAPI做poll(igb_poll)時會去清已完成的pkt(igb_clean_tx_irq)

Dynamic Queue Limits (DQL)

DQL 內部算法判斷何時數據已足夠多,達到此閾值後,DQL 將暫時禁用 TX Queue,從而對網絡系統產生背壓。當足夠的數據已發送完後,DQL 再自動重新啟用該隊列。

monitor: /sys/class/net/<NIC>/queues/tx-<QUEUE_ID>/byte_queue_limits/* hold_time: 類似tcp的timeout,如果queue一直滿到一定時間(以HZ為單位)就去縮小queue的最大長度 inflight: number of packets queued - number of packets completed limit_max: (寫死) DQL_MAX_LIMIT (1879048192 on my x86_64 system) limit_min: (寫死) 0 limit: 介於limit_min與limit_max之間,代表queue的最大長度

Ref

[译] Linux 网络栈监控和调优:接收数据(2016) Illustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data Monitoring and Tuning the Linux Networking Stack: Receiving Data [译] Linux 网络栈监控和调优:发送数据(2017) Monitoring and Tuning the Linux Networking Stack: Sending Data linux网络实现分析(1)——数据包的接收(从网卡到协议栈) linux网络实现分析(3)——数据包的发送(IP层到链路层) The packet flow, from userspace to kernel driver in Linux network stack