4. RPS 技术原理:Receive Packet Steering 工作机制
Receive Packet Steering (RPS) 是 Linux 内核在软件层面实现的一种中断负载均衡机制。它不修改硬件中断的分配方式,而是在中断处理完成后、数据包进入网络协议栈之前,通过软件方式将数据包的处理负载重新分发到多个 CPU 核心上。RPS 的核心思想是:让一个 CPU 处理硬件中断,让多个 CPU 共同处理软中断(SoftIRQ),从而避免单个 CPU 成为瓶颈。
4.1 软件中断分发机制
RPS 的工作流程分为三个阶段:硬件中断处理 → 数据包入队 → 软件中断分发。其核心在于将数据包的处理(而非中断接收)分散到不同的 CPU 上。
- 硬件中断阶段: NIC 触发硬件中断,被分配到某个 CPU(通常由硬件或 irqbalance 决定)。该 CPU 执行中断处理函数(如
ixgbe_msix_clean),从 NIC 的 RX 队列中取出数据包。 - 数据包入队阶段: 中断处理函数调用
napi_gro_receive或netif_receive_skb,将数据包送入目标 CPU 的 backlog 队列(即softnet_data结构体中的input_pkt_queue)。 - 软件中断分发阶段: 触发
NET_RX_SOFTIRQ软中断。RPS 根据哈希值将数据包重定向到其他 CPU 的 backlog 队列,由目标 CPU 的软中断处理函数执行后续协议栈处理。
4.2 哈希计算:数据包分发决策的核心
RPS 使用 哈希计算 来决定每个数据包应该被分发到哪个 CPU 的 backlog 队列。哈希计算的输入是数据包头部中的关键字段,输出是一个哈希值,该值对 CPU 数量取模后得到目标 CPU 编号。
4.2.1 哈希输入字段
RPS 支持多种哈希输入组合,通过 rps_hash_key 和 rps_hash_mask 控制。默认情况下,RPS 使用四元组(源 IP、目的 IP、源端口、目的端口)进行哈希计算。对于不同协议,哈希输入字段有所不同:
| 协议类型 | 哈希输入字段 | 说明 |
|---|---|---|
| TCP / UDP | 源 IP + 目的 IP + 源端口 + 目的端口 | 四元组哈希,保证同一流的数据包分发到同一 CPU |
| 其他 L4 协议 | 源 IP + 目的 IP | 仅使用 IP 地址,端口不可用时降级 |
| IPv6 扩展头 | 流标签(Flow Label)+ 源 IP + 目的 IP | 优先使用流标签,提高哈希均匀性 |
| GRE / IPIP 隧道 | 内层 IP 头字段 | 支持隧道协议的内层哈希 |
4.2.2 哈希计算过程
RPS 的哈希计算在内核函数 skb_get_hash 中实现,使用 Toeplitz 哈希算法(与硬件 RSS 相同的算法)。计算过程如下:
- 提取字段: 从 skb 中提取协议相关的字段(如 IP 地址、端口)。
- 构造输入: 将提取的字段按固定顺序拼接成一个字节数组。
- 计算哈希: 使用 Toeplitz 哈希函数,配合一个 40 字节的随机密钥(
rps_hash_key),计算得到 32 位哈希值。 - 取模映射: 将哈希值与
rps_cpus掩码进行运算,得到目标 CPU 编号。
skb->hash 有效),RPS 会直接复用该哈希值,避免重复计算。这要求 NIC 驱动支持 NETIF_F_RXHASH 特性。
4.3 RPS 数据结构与配置接口
RPS 的核心数据结构是 rps_map,它定义了每个 RX 队列对应的 CPU 集合。系统管理员通过 sysfs 接口配置 RPS 映射。
4.3.1 核心数据结构
| 数据结构 | 定义位置 | 作用 |
|---|---|---|
rps_map |
include/linux/netdevice.h |
存储每个 RX 队列对应的 CPU 位图,决定数据包可以分发到哪些 CPU |
rps_dev_flow |
include/linux/netdevice.h |
记录每个流(flow)当前使用的 CPU,用于 RFS(Receive Flow Steering)的后续优化 |
softnet_data |
net/core/dev.c |
每个 CPU 一个实例,包含 backlog 队列(input_pkt_queue)和 RPS 处理统计信息 |
4.3.2 配置接口
RPS 的配置通过 /sys/class/net/<interface>/queues/rx-<n>/rps_cpus 文件进行。该文件接受一个十六进制 CPU 位图,表示允许处理该 RX 队列数据包的 CPU 集合。
# 查看 eth0 的 RX 队列 0 的 RPS 配置
cat /sys/class/net/eth0/queues/rx-0/rps_cpus
# 将 RX 队列 0 的数据包分发到 CPU 0-3(位图:0x0f)
echo "0f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
# 将 RX 队列 1 的数据包分发到 CPU 4-7(位图:0xf0)
echo "f0" > /sys/class/net/eth0/queues/rx-1/rps_cpus
ff(位图 0xff)。
4.4 RPS 性能影响与适用场景
RPS 在特定场景下能显著提升网络性能,但也存在一定的开销。以下是 RPS 的优缺点对比:
| 维度 | 优势 | 劣势 |
|---|---|---|
| CPU 利用率 | 将软中断负载分散到多个 CPU,避免单核 100% 占用 | 增加 CPU 间数据包传递(跨 CPU 队列操作),引入缓存失效 |
| 吞吐量 | 在硬件队列数少于 CPU 数时,显著提升整体吞吐量 | 在硬件队列数等于 CPU 数时,RPS 可能成为瓶颈(软件分发开销) |
| 延迟 | 避免单核软中断堆积,降低尾延迟 | 增加数据包在 backlog 队列中的等待时间(入队 + 出队) |
| 适用场景 | 低端 NIC(2-4 队列)配合多核 CPU(16-64 核) | 高端 NIC(64+ 队列)且 CPU 核数较少时,建议关闭 RPS |
rps_cpus = 0),直接依赖硬件 RSS 进行负载均衡。RPS 更适合 1G/10G 网卡或虚拟化环境(如 KVM 虚拟网卡)。
4.5 RPS 与硬件 RSS 的协同工作
RPS 并不是硬件 RSS 的替代品,而是补充。两者可以协同工作,形成两级负载均衡:
- 第一级(硬件 RSS): NIC 根据哈希值将数据包分配到不同的硬件 RX 队列,每个队列触发独立的 MSI-X 中断。
- 第二级(软件 RPS): 在中断处理完成后,RPS 根据哈希值将数据包从硬件队列对应的 CPU 分发到其他 CPU 的 backlog 队列。
这种两级架构的优势在于:硬件 RSS 负责中断层面的负载均衡,RPS 负责软中断处理层面的负载均衡,两者互补,最大化 CPU 利用率。