HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 系统底层修炼

    • 操作系统核心知识学习指南
    • CPU调度与上下文切换
    • CFS调度器原理与源码
    • 内存管理与虚拟内存
    • PageCache与内存回收
    • 文件系统与IO优化
    • 零拷贝与Direct I/O
    • 网络子系统架构
    • TCP协议深度解析
    • TCP问题排查实战
    • 网络性能优化
    • epoll与IO多路复用
    • 进程与线程管理
    • Go Runtime调度器GMP模型
    • 系统性能分析方法论
    • DPDK与用户态网络栈
    • eBPF与内核可观测性
    • 综合实战案例

网络子系统架构

章节概述

Linux网络子系统是一个复杂而精妙的系统,从网卡硬件到应用程序之间经过多个层次的处理。本章将深入剖析数据包的完整路径,帮助你理解网络性能的关键环节和优化点。

学习目标:

  • 理解从网卡到应用的完整数据路径
  • 掌握中断、软中断和NAPI机制
  • 理解Socket Buffer的作用
  • 学会使用工具观测网络子系统
  • 能够进行网络性能调优

核心概念

1. 网络栈全景图

应用层
  ↑ (read/write/recvfrom/sendto)
┌─┴──────────────────────────────────┐
│    Socket Layer                     │
│  - socket buffer管理                │
│  - 系统调用接口                      │
└─┬──────────────────────────────────┘
  ↑
┌─┴──────────────────────────────────┐
│    Transport Layer (L4)            │
│  - TCP/UDP协议处理                  │
│  - 端口管理                         │
│  - 流量控制/拥塞控制                │
└─┬──────────────────────────────────┘
  ↑
┌─┴──────────────────────────────────┐
│    Network Layer (L3)              │
│  - IP路由                           │
│  - IP分片/重组                      │
│  - netfilter/iptables              │
└─┬──────────────────────────────────┘
  ↑
┌─┴──────────────────────────────────┐
│    Data Link Layer (L2)            │
│  - 以太网帧处理                     │
│  - ARP协议                          │
│  - Bridge/VLAN                     │
└─┬──────────────────────────────────┘
  ↑
┌─┴──────────────────────────────────┐
│    Device Driver                   │
│  - 网卡驱动                         │
│  - DMA环形缓冲区                    │
│  - 中断处理                         │
└─┬──────────────────────────────────┘
  ↑
┌─┴──────────────────────────────────┐
│    NIC (Network Interface Card)    │
│  - 物理层收发                       │
└────────────────────────────────────┘

2. 接收数据包的完整流程

详细步骤:

步骤1: 网卡接收数据包
├─ 网卡通过DMA将数据包写入Ring Buffer
├─ Ring Buffer是内存中的环形队列
└─ 数据包暂存在这里

步骤2: 硬中断 (Hard IRQ)
├─ 网卡触发硬中断
├─ CPU响应中断,跳转到中断处理函数
├─ 驱动禁用网卡中断
├─ 调度软中断 (触发NET_RX_SOFTIRQ)
└─ 硬中断处理完成(极快,微秒级)

步骤3: 软中断 (Soft IRQ / ksoftirqd)
├─ 软中断处理网络数据包
├─ 从Ring Buffer取出数据包
├─ 分配sk_buff结构
└─ 向上层传递

步骤4: 协议栈处理
├─ L2: 以太网帧解析
├─ L3: IP层处理(路由、netfilter)
├─ L4: TCP/UDP处理
└─ 数据包放入Socket接收队列

步骤5: 应用程序读取
├─ read/recv系统调用
├─ 从Socket接收队列取数据
└─ 拷贝到用户空间

图示:

┌────────────┐
│   网卡     │
│   DMA      │ ──────┐
└────────────┘       │
                     ↓
              ┌──────────────┐
              │ Ring Buffer  │
              │ (环形队列)   │
              └──────┬───────┘
                     │
                     ↓ 触发硬中断
              ┌──────────────┐
              │  Hard IRQ    │
              │  (中断处理)  │ ──→ 禁用中断
              └──────┬───────┘     触发软中断
                     │
                     ↓
              ┌──────────────┐
              │  Soft IRQ    │
              │ (ksoftirqd)  │ ──→ NAPI轮询
              └──────┬───────┘
                     │
                     ↓ netif_receive_skb()
              ┌──────────────┐
              │  协议栈      │
              │ L2→L3→L4    │
              └──────┬───────┘
                     │
                     ↓
              ┌──────────────┐
              │ Socket Queue │
              └──────┬───────┘
                     │
                     ↓ read/recv
              ┌──────────────┐
              │  应用程序    │
              └──────────────┘

3. 中断与软中断

硬中断 (Hard IRQ):

特点:
- 响应速度快
- 不可被打断
- 执行时间短(几微秒)
- 会阻塞CPU

职责:
- 确认网卡中断
- 禁用网卡中断
- 调度软中断
- 快速返回

软中断 (Soft IRQ):

特点:
- 延迟执行
- 可被硬中断打断
- 执行时间较长
- 在ksoftirqd内核线程中执行

职责:
- 从Ring Buffer取数据包
- 协议栈处理
- 数据包分发到Socket

为什么分离硬中断和软中断?

如果在硬中断中处理所有工作:
- 硬中断时间过长
- CPU被长时间占用
- 影响系统实时性
- 丢失其他中断

分离后:
- 硬中断快速返回
- 软中断延迟处理
- 提高系统响应速度

4. NAPI (New API)

传统中断模式的问题:

高流量场景:
- 大量数据包到达
- 频繁触发中断
- CPU大量时间处理中断
- 中断风暴 (Interrupt Storm)

NAPI解决方案:

中断 + 轮询混合模式

低流量:
- 使用中断模式
- 响应及时

高流量:
- 第一个包触发中断
- 之后禁用中断,进入轮询模式
- 处理完所有数据包后重新启用中断

好处:
- 减少中断次数
- 提高吞吐量
- 降低CPU开销

NAPI流程:

1. 数据包到达,触发中断
   ↓
2. 硬中断处理:
   - 禁用网卡中断
   - 调度NAPI轮询
   ↓
3. NAPI轮询 (在软中断中):
   - poll()函数持续取包
   - 每次最多处理64个包 (可调)
   - 如果没有新包,退出轮询
   ↓
4. 重新启用网卡中断
   ↓
5. 等待下一个数据包

5. sk_buff结构

sk_buff (Socket Buffer):

  • 内核网络栈的核心数据结构
  • 贯穿整个网络栈
  • 包含数据包的元数据

结构示意:

struct sk_buff {
    // 数据指针
    unsigned char  *head;     // buffer起始位置
    unsigned char  *data;     // 数据起始位置
    unsigned char  *tail;     // 数据结束位置
    unsigned char  *end;      // buffer结束位置
    
    // 网络相关
    struct net_device  *dev;  // 网络设备
    struct sock        *sk;   // 关联的socket
    
    // 协议头指针
    struct tcphdr  *th;       // TCP头
    struct iphdr   *iph;      // IP头
    struct ethhdr  *mac;      // 以太网头
    
    // 元数据
    __u32          len;       // 数据长度
    __u16          protocol;  // 协议类型
    __u8           pkt_type;  // 包类型
    
    // 队列管理
    struct sk_buff *next;
    struct sk_buff *prev;
    
    // ...更多字段
};

sk_buff操作:

添加协议头 (headroom):
  head    data         tail      end
  |        |            |         |
  [      ][  payload   ][       ]
  
添加TCP头:
  skb_push(skb, sizeof(struct tcphdr));
  
  head  data       tail      end
  |      |          |         |
  [  ][TCP][payload][       ]

添加IP头:
  skb_push(skb, sizeof(struct iphdr));
  
  head data     tail      end
  |    |        |         |
  [  ][IP][TCP][payload][       ]

性能观测

1. 查看中断统计

# 查看中断统计
cat /proc/interrupts

# 输出示例:
#            CPU0       CPU1       CPU2       CPU3
#   0:         34          0          0          0   IO-APIC   2-edge      timer
# ...
#  25:    1234567    1234567    1234567    1234567   PCI-MSI 524288-edge      eth0

# 实时监控
watch -n 1 'cat /proc/interrupts | grep eth0'

关键指标:

  • 中断次数增长速度
  • 中断在CPU间的分布
  • 是否某个CPU中断过多

2. 查看软中断统计

# 查看软中断
cat /proc/softirqs

# 输出示例:
#                    CPU0       CPU2       CPU3
#          HI:          0          0          0
#       TIMER:    1234567    1234567    1234567
#      NET_TX:     123456     123456     123456
#      NET_RX:    9876543    9876543    9876543
# ...

# 实时监控
watch -n 1 'cat /proc/softirqs | grep NET'

3. 网络统计

# 网络接口统计
netstat -i

# 详细统计
cat /proc/net/dev

# 实时监控
sar -n DEV 1

# 输出:
# IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s
# eth0   50000.00  50000.00  30000.00  35000.00

4. 丢包统计

# 查看丢包
netstat -s | grep -i drop

# 或
cat /proc/net/softnet_stat

# 每列含义:
# 第1列:processed - 处理的包数
# 第2列:dropped - 丢弃的包数
# 第3列:time_squeeze - 软中断预算耗尽次数

常见丢包原因:

1. Ring Buffer满
   - 增大rx/tx ring size
   
2. Socket Buffer满
   - 增大net.core.rmem_max/wmem_max

3. 软中断处理不过来
   - 调整net.core.netdev_budget
   - 启用RPS/RFS

4. 队列溢出
   - 增大net.core.netdev_max_backlog

️ 性能优化

1. 中断亲和性 (IRQ Affinity)

绑定中断到特定CPU:

# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 假设中断号是25

# 查看当前亲和性
cat /proc/irq/25/smp_affinity

# 绑定到CPU 0和1 (掩码: 11 = 0x3)
echo 3 | sudo tee /proc/irq/25/smp_affinity

# 或使用irqbalance自动平衡
sudo systemctl start irqbalance

好处:

  • 减少CPU cache miss
  • 提高L1/L2 cache命中率
  • 避免跨NUMA节点访问

2. RPS/RFS (Receive Packet Steering / Flow Steering)

RPS - 软件实现的接收包分发:

# 启用RPS
echo f | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
# f = 1111 (使用CPU 0-3)

# 设置flow限制
echo 32768 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

RFS - 基于流的智能分发:

# 启用RFS
echo 32768 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
echo 2048 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

适用场景:

  • 单队列网卡
  • 低端服务器
  • 需要负载均衡

3. 多队列网卡

查看网卡队列:

ethtool -l eth0

# 输出:
# Channel parameters for eth0:
# Pre-set maximums:
# RX:     4
# TX:     4
# Combined:   8
# Current hardware settings:
# RX:     4
# TX:     4
# Combined:   8

调整队列数:

# 设置为8个队列
sudo ethtool -L eth0 combined 8

好处:

  • 每个队列有独立的中断
  • 可以绑定到不同CPU
  • 提高并行处理能力

4. Ring Buffer调优

查看Ring Buffer大小:

ethtool -g eth0

# 输出:
# Ring parameters for eth0:
# Pre-set maximums:
# RX:     4096
# TX:     4096
# Current hardware settings:
# RX:     256
# TX:     256

调整大小:

# 增大到4096
sudo ethtool -G eth0 rx 4096 tx 4096

建议:

  • 高流量场景:增大ring buffer
  • 延迟敏感场景:适当减小
  • 监控丢包情况调整

5. 内核参数调优

# Socket Buffer大小
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216
sudo sysctl -w net.core.rmem_default=262144
sudo sysctl -w net.core.wmem_default=262144

# TCP Buffer自动调整
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sudo sysctl -w net.ipv4.tcp_wmem="4096 16384 16777216"

# 接收队列长度
sudo sysctl -w net.core.netdev_max_backlog=16384

# 软中断预算
sudo sysctl -w net.core.netdev_budget=600
sudo sysctl -w net.core.netdev_budget_usecs=8000

# SYN队列
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sudo sysctl -w net.core.somaxconn=1024

实践示例

监控脚本

#!/bin/bash
# network_monitor.sh - 网络子系统监控

echo "网络子系统监控"
echo "=============="

echo ""
echo "1. 中断统计:"
cat /proc/interrupts | grep -E "CPU0|eth0" | head -2

echo ""
echo "2. 软中断统计 (NET_RX/NET_TX):"
cat /proc/softirqs | grep -E "CPU0|NET_RX|NET_TX" | head -3

echo ""
echo "3. 网络接口统计:"
cat /proc/net/dev | grep -E "Inter|eth0"

echo ""
echo "4. 丢包统计:"
netstat -s | grep -i "drop\|discard" | head -5

echo ""
echo "5. Socket Buffer使用:"
ss -m | grep -A 1 "skmem"| head -10

echo ""
echo "6. Ring Buffer状态:"
ethtool -S eth0 2>/dev/null | grep -E "rx.*queue|tx.*queue" | head -5

常见问题

Q1: 如何判断网络是否有瓶颈?

A: 检查以下指标:

  • netstat -s中的丢包
  • cat /proc/net/softnet_stat第2列
  • CPU的软中断占比 (top中的%si)
  • 网卡是否达到带宽上限

Q2: ksoftirqd占用CPU很高怎么办?

A: 优化方向:

  1. 增大Ring Buffer
  2. 启用RPS/RFS
  3. 使用多队列网卡
  4. 调整软中断预算
  5. 升级网卡/使用硬件卸载

Q3: 什么时候需要手动设置IRQ亲和性?

A:

  • NUMA架构服务器
  • 延迟敏感应用
  • 高性能计算场景
  • 关闭irqbalance后

Q4: Ring Buffer是越大越好吗?

A: 不一定:

  • 太大:增加延迟
  • 太小:容易丢包
  • 建议:根据流量调整,监控丢包率

复习题

选择题

  1. NAPI的主要作用是?

    • A. 提高中断速度
    • B. 减少中断次数
    • C. 增加带宽
    • D. 减少延迟
  2. sk_buff是什么?

    • A. Socket缓冲区
    • B. 内核数据包结构
    • C. 网卡驱动
    • D. 协议栈
  3. 软中断在哪里执行?

    • A. 硬中断上下文
    • B. 用户进程
    • C. ksoftirqd线程
    • D. 网卡驱动

简答题

  1. 描述数据包从网卡到应用程序的完整路径。
  2. 什么是NAPI?它解决了什么问题?
  3. 硬中断和软中断的区别是什么?
  4. 如何诊断和解决网络丢包问题?

实战题

编写一个脚本,监控网络中断和软中断的变化,当软中断占比超过30%时发出告警。


下一章预告: TCP协议深度解析,包括三次握手、滑动窗口、拥塞控制等核心机制。

Prev
零拷贝与Direct I/O
Next
TCP协议深度解析