网络子系统架构
章节概述
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: 优化方向:
- 增大Ring Buffer
- 启用RPS/RFS
- 使用多队列网卡
- 调整软中断预算
- 升级网卡/使用硬件卸载
Q3: 什么时候需要手动设置IRQ亲和性?
A:
- NUMA架构服务器
- 延迟敏感应用
- 高性能计算场景
- 关闭irqbalance后
Q4: Ring Buffer是越大越好吗?
A: 不一定:
- 太大:增加延迟
- 太小:容易丢包
- 建议:根据流量调整,监控丢包率
复习题
选择题
NAPI的主要作用是?
- A. 提高中断速度
- B. 减少中断次数
- C. 增加带宽
- D. 减少延迟
sk_buff是什么?
- A. Socket缓冲区
- B. 内核数据包结构
- C. 网卡驱动
- D. 协议栈
软中断在哪里执行?
- A. 硬中断上下文
- B. 用户进程
- C. ksoftirqd线程
- D. 网卡驱动
简答题
- 描述数据包从网卡到应用程序的完整路径。
- 什么是NAPI?它解决了什么问题?
- 硬中断和软中断的区别是什么?
- 如何诊断和解决网络丢包问题?
实战题
编写一个脚本,监控网络中断和软中断的变化,当软中断占比超过30%时发出告警。
下一章预告: TCP协议深度解析,包括三次握手、滑动窗口、拥塞控制等核心机制。