PCIe ARI(Alternative Routing-ID Interpretation):为什么需要 & 如何工作

本文整理了 PCIe ARI 的背景动机与技术方法,以及 Linux 内核在 PCI 枚举扫描过程中对 ARI 的使用方式。
讨论主线:传统 PCI BDF 的 8-function 限制 → SR-IOV 大量 VF 的编址困境 → ARI 的解决方案 → 内核实现细节。

本文中所有代码引用均基于内核 v7.2-rc2-22-g0e35b9b6ec0f 的源码。【Claude-4.7-Opus】


0.1. 一、背景:为什么需要 ARI

0.1.1. 1.1 传统 PCI 的 BDF 编址

PCI 配置事务里,Routing ID(RID)总长 16 bit,用来在总线上唯一寻址一个 function:

1
2
3
4
5
6
 15                      8 7          3 2       0
┌────────────────────────┬────────────┬─────────┐
│ Bus Number │ Device # │ Func # │
(8 bits)(5 bits)(3 bits)
0..2550..310..7
└────────────────────────┴────────────┴─────────┘
  • Bus:8 bit,0..255;
  • Device:5 bit,一条 bus 最多 32 个 slot;
  • Function:3 bit,一个 Device 最多 8 个 function

这就是”一个 D 下最多 8 个 function”的硬性来源——协议字段就 3 bit,不可能表达超过 8 的 function 号。

0.1.2. 1.2 SR-IOV 的编址困境

SR-IOV(Single Root I/O Virtualization)允许一个物理设备(PF, Physical Function)虚拟出很多个 VF(Virtual Function),每个 VF 都是标准的 PCI Function

  • 有自己独立的 Configuration Space;
  • 有自己独立的 BAR;
  • 有自己独立的中断;
  • 可以被独立地 passthrough 给虚拟机。

问题来了:一块现代网卡(如 Mellanox ConnectX、Intel 82599 后继型号)常常需要 64/128/256 个 VF,而传统 3-bit function 字段最多只能编址 8 个 function,根本装不下

如果只靠”多条 Bus 号分散”来放 VF:128 个 VF 就得至少占 16 条 bus,效率低,且浪费宝贵的 Bus 号资源。

ARI 就是为了解决这个编址困境而设计的。


0.2. 二、方法:ARI 的核心思想

0.2.1. 2.1 重新解释 Routing ID

ARI 使能后,在支持 ARI 的 bus 上,Routing ID 低 8 bit 不再拆成 5 bit Device + 3 bit Function,而是整体作为 Function 号

1
2
3
4
5
6
7
8
      传统解释(Non-ARI)                    ARI 解释
15 8 7 3 2 0 15 8 7 0
┌───────────┬────────┬─────┐ ┌───────────┬─────────────┐
│ Bus │ Dev │ Fn │ │ Bus │ Function │
(8 bits)(5 bits)(3b) │ │ (8 bits)(8 bits)
└───────────┴────────┴─────┘ └───────────┴─────────────┘
32 dev × 8 fn 256 function
= 256 addressable (Device 概念消失)

关键变化

  • ARI bus 上,Device 号概念被”抹掉”,全部 8 bit 都当 function 号;
  • 一条 bus 上理论上最多 256 个 function(0..255);
  • 代价:一条 ARI bus 上只能有一个”逻辑设备”(因为 Device 域不再存在)。

0.2.2. 2.2 ARI 使能的两个必要条件

必须同时满足:

  1. 上游桥支持并使能 ARI Forwarding(PCIe Capability 里的 ARI Forwarding Enable bit);
  2. 端点设备支持 ARI(Extended Capability 里有 ARI Capability,报告 Next Function Number)。

只要上游桥或者端点任一方不支持,就退化为传统 Non-ARI 行为。

0.2.3. 2.3 SR-IOV 中 VF 的 Routing ID 计算

SR-IOV Extended Capability 里的关键寄存器:

字段 含义
NumVFs 当前使能的 VF 数量
First VF Offset 第一个 VF 的 RID 相对 PF 的偏移(16 bit)
VF Stride 相邻两个 VF 之间的 RID 步长(16 bit)

VF 的 BDF 用公式算:

1
2
3
4
5
6
7
PF_RID     = (PF_Bus << 8) | PF_Dev_Fn        // Non-ARI 解释
= (PF_Bus << 8) | PF_Fn // ARI 解释

VF_RID[i] = PF_RID + First_VF_Offset + i × VF_Stride

VF_Bus[i] = VF_RID[i] >> 8 // 高 8 bit
VF_low[i] = VF_RID[i] & 0xFF // 低 8 bit(ARI 下就是 Function 号)

Routing ID 一路加下去,会自然溢出到下一条 Bus。


0.3. 三、两种典型场景对照

0.3.1. 场景 A:Non-ARI(如 Intel 82599)

VF 挤在同一条 bus 的高 Device 号上:

1
2
3
4
5
6
7
8
9
PF_RID = 0x0400
First VF Offset = 0x80
VF Stride = 2

VF 0: 0x0400 + 0x80 + 0×2 = 0x0480 → 04:10.0 (Dev=0x10, Fn=0)
VF 1: 0x0400 + 0x80 + 1×2 = 0x0482 → 04:10.2 (Dev=0x10, Fn=2)
VF 2: 0x0400 + 0x80 + 2×2 = 0x0484 → 04:10.4
...
VF 7: 0x0400 + 0x80 + 7×2 = 0x048E → 04:11.6

对应 lspci 输出示例(下方内容为 AI 生成的模拟输出,非实测数据,仅用于说明 BDF 排列规律):

1
2
3
4
5
6
04:00.0  Ethernet controller: Intel 82599ES  ← PF0
04:00.1 Ethernet controller: Intel 82599ES ← PF1
04:10.0 Ethernet controller: Intel 82599 VF ← VF 0 of PF0
04:10.2 Ethernet controller: Intel 82599 VF ← VF 1 of PF0
04:10.4 Ethernet controller: Intel 82599 VF ← VF 2 of PF0
...

0.3.2. 场景 B:ARI 使能(如 Mellanox ConnectX)

VF 直接跨到下一条 bus,从 fn 0 起连续排列:

1
2
3
4
5
6
7
8
9
PF_RID = 0x0400
First VF Offset = 0x100 ← 直接跨一整条 bus
VF Stride = 1

VF 0: 0x0400 + 0x100 + 0 = 0x0500 → 05:00.0 (ARI: Bus=05, Fn=0x00)
VF 1: 0x0400 + 0x100 + 1 = 0x0501 → 05:00.1
VF 2: 0x0400 + 0x100 + 2 = 0x0502 → 05:00.2
...
VF 127: 0x0400 + 0x100 + 127 = 0x057F → 05:00.7f (ARI: Bus=05, Fn=0x7F)

规则:VF BDF = PF RID 加上 First VF Offset 再加上 i × Stride,然后按 ARI/Non-ARI 拆成 Bus + Dev/Fn,一步到位,不存在”先到 04:10.0 再跨到 05:00.0”这种中间态。


0.4. 四、Linux 内核中的 ARI 处理

0.4.1. 4.1 扫描器分支:next_fn

位置:drivers/pci/probe.c

1
2
3
4
5
6
7
8
9
10
11
12
13
2819 static int next_fn(struct pci_bus *bus, struct pci_dev *dev, int fn)
2820 {
2821 if (pci_ari_enabled(bus))
2822 return next_ari_fn(bus, dev, fn); // ← ARI 路径
2823
2824 if (fn >= 7)
2825 return -ENODEV; // ← 传统 3 bit 到 7 就到头
2826 /* only multifunction devices may have more functions */
2827 if (dev && !dev->multifunction)
2828 return -ENODEV;
2829
2830 return fn + 1;
2831 }

三条路径

  1. ARI 使能 → 走 next_ari_fn(利用 ARI Cap 的 Next Function Number 稀疏跳跃);
  2. Non-ARI,fn 0 是单 function 设备dev->multifunction == 0,直接 -ENODEV
  3. Non-ARI,多 function 设备fn + 1,直到 fn = 7 停止。

0.4.2. 4.2 ARI 的稀疏跳跃:next_ari_fn

位置:drivers/pci/probe.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2798 static int next_ari_fn(struct pci_bus *bus, struct pci_dev *dev, int fn)
2799 {
2800 int pos;
2801 u16 cap = 0;
2802 unsigned int next_fn;
2803
2804 if (!dev)
2805 return -ENODEV;
2806
2807 pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI);
2808 if (!pos)
2809 return -ENODEV;
2810
2811 pci_read_config_word(dev, pos + PCI_ARI_CAP, &cap);
2812 next_fn = PCI_ARI_CAP_NFN(cap); // ← 读 ARI Cap 的 "Next Function Number"
2813 if (next_fn <= fn)
2814 return -ENODEV; /* protect against malformed list */
2815
2816 return next_fn;
2817 }

优势:ARI Cap 直接告诉内核”下一个存在的 function 号”,跳过所有空 function,避免挨个尝试 256 次配置读。这是 SR-IOV 大量 VF 场景下的关键性能优化

0.4.3. 4.3 上限对比

场景 单 bus function 上限 由谁决定
Non-ARI 8(fn = 0..7) if (fn >= 7) return -ENODEV;
ARI 使能 256(fn = 0..255) ARI Cap 的 Next Function Number 字段

0.5. 五、验证方法(可在真机上执行)

说明:以下命令本身是通用的 Linux 验证套路,可以在支持 SR-IOV 的真机上直接执行。但下方所有 # 期望输出示例 中展示的字段和数值均为 AI 生成的模拟输出,仅用于说明字段结构与 BDF 排列规律,不代表任何一台真实机器的实测数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ① 查看某 PF 是否报告 ARI Capability
lspci -vvv -s 04:00.0 | grep -i ari
# 期望输出示例(AI 模拟,非实测):
# Capabilities: [xxx v1] Alternative Routing-ID Interpretation (ARI)
# ARICap: MFVC- ACS-, Next Function: 0
# ARICtl: MFVC- ACS-, Function Group: 0

# ② 查看 SR-IOV Cap 的关键字段
lspci -vvv -s 04:00.0 | grep -A2 "SR-IOV"
# 期望输出示例(AI 模拟,非实测):
# Capabilities: [xxx v1] Single Root I/O Virtualization (SR-IOV)
# IOVCap: Migration-, Interrupt Message Number: 000
# IOVCtl: Enable+ Migration- Interrupt- MSE+ ARIHierarchy+ ← ARI 使能标志
# IOVSta: Migration-
# Initial VFs: 64, Total VFs: 64, Number of VFs: 8, Function Dependency Link: 00
# VF offset: 128, stride: 1, Device ID: 10ed
# Supported Page Size: 00000553, System Page Size: 00000001

# ③ 使能 VF 并观察 BDF 分布
echo 8 > /sys/class/net/ens1f0/device/sriov_numvfs
lspci | grep "Virtual Function"
# 观察 VF BDF 是否符合 PF_RID + VF_offset + i × stride 的公式

# ④ 看拓扑树
lspci -tv

对照关系:若 lspci 报告 VF offset: 128,对应 First VF Offset = 0x80stride: 1 对应 VF Stride = 1。手动套公式算,应该与实机 lspci 输出一致。


0.6. 六、总结对比表

维度 传统 PCI Function SR-IOV VF (ARI)
本质 Function Function(就是 function)
Routing ID 结构 Bus:Dev(5):Fn(3) Bus:Fn(8),Dev 域消失
单 bus function 上限 8(3 bit) 256(8 bit)
单 PF 下 VF 数上限 由 SR-IOV Cap 的 TotalVFs 决定,规范最大 64K
配置空间 完整独立 完整独立(Type 0,1 KB / 4 KB)
BAR 自己有 自己有(从 SR-IOV Cap 的 VF BAR 派生)
依赖能力 需要 ARI Forwarding + SR-IOV Extended Cap
枚举方式 next_fnfn+1 next_ari_fn 走 ARI Cap 的 Next Function Number
典型 BDF 示例 04:00.0..04:00.7 PF 04:00.0; VFs 05:00.0..05:00.7f(128 VFs)

0.7. 七、几个易错点澄清

7.1 “VF 是不是 function?” —— 是。VF 是标准的 PCI Function,只是通过 ARI 摆脱了 3-bit 编号限制。

7.2 “一个 D 下最多 8 个 function” 什么时候成立?

  • 传统 PCI / Non-ARI PCIe:✅ 成立(Function 字段就 3 bit);
  • ARI PCIe(含 SR-IOV):❌ 不成立。“D 号” 概念在 ARI bus 上被抹掉了,整条 bus 就是一个 256-function 的扁平空间。

7.3 VF BDF 的中间态 —— 不存在。VF 的 BDF 一步算出PF_RID + offset + i × stride,再按 ARI/Non-ARI 拆 Bus/Dev/Fn),不要写成”先 04:10.0 再跨到 05:00.0”这种自相矛盾的中间态。


0.8. 八、一句话结论

ARI 通过把 BDF 中原本”5 bit Dev + 3 bit Fn”的划分改成”8 bit Fn”(Device 概念在该 bus 上消失),把单 bus function 数从 8 扩到 256;配合 SR-IOV Cap 的 First VF Offset / Stride,VF 可以进一步溢出到新 Bus 号,从而支撑 128 甚至几千个 VF。

Linux 内核在 next_fn 里通过 pci_ari_enabled(bus) 分支进入 next_ari_fn,读取 ARI Cap 的 Next Function Number 做稀疏跳跃,避免枚举时挨个尝试 256 次配置读,是现代 SR-IOV 场景不可或缺的性能优化。


PCIe ARI(Alternative Routing-ID Interpretation):为什么需要 & 如何工作
https://martinbj2008.github.io/2026/07/09/2026-07-09-pcie-ari-why-and-how.ai/
Author
Martinbj2008
Posted on
July 9, 2026
Licensed under