编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 计算机原理

    • README
    • 计算机组成结构原理
    • 计算器存储器的原理
    • 计算机基础CPU设计
    • 系统CPU缓存的设计
    • 计算机输入输出设备
    • 计算机总线系统设计
      • 01.工作案例引入
        • 1.1 双实例性能鸿沟
        • 1.2 排查思路与结论
        • 1.3 本文要回答的问题
      • 02.总线的基本概念
        • 2.1 什么是总线
        • 2.2 为什么需要总线
        • 2.3 总线的基本特征
      • 03.总线的分类
        • 3.1 按功能分类
        • 3.2 按层次分类
        • 3.3 按传输方式分类
      • 04.总线的关键指标
        • 4.1 总线宽度
        • 4.2 总线频率
        • 4.3 总线带宽
        • 4.4 总线性能瓶颈
      • 05.总线仲裁
        • 5.1 为什么需要仲裁
        • 5.2 集中仲裁
        • 5.3 分布仲裁
      • 06.总线标准演进
        • 6.1 ISA到PCI
        • 6.2 PCI到PCIe
        • 6.3 PCIe核心设计
        • 6.4 总线标准对比
      • 07.现代总线架构
        • 7.1 传统南北桥架构
        • 7.2 现代集成架构
        • 7.3 NUMA架构
        • 7.4 对编程的影响
      • 08.综合案例NVMe读取
        • 8.1 全景路径图
        • 8.2 10个步骤拆解
        • 8.3 知识对号入座
        • 8.4 回到开头案例
      • 09.思考题与作业
        • 9.1 基础理解题
        • 9.2 进阶思考题
        • 9.3 动手作业
    • 计算机指令编程原理
    • 计算机程序如何执行
    • 计算机内存设计原理
    • 计算机二进制和字节
    • 计算机异常处理机制
    • 计算机IO操作和原理
  • 网络协议

  • 操作系统

  • 数据库原理

  • 计算机
  • 计算机原理
杨充
2021-06-26
目录

计算机总线系统设计

# 06.计算机总线系统设计

# 目录介绍

  • 01.工作案例引入
    • 1.1 双实例性能鸿沟
    • 1.2 排查思路与结论
    • 1.3 本文要回答的问题
  • 02.总线的基本概念
    • 2.1 什么是总线
    • 2.2 为什么需要总线
    • 2.3 总线的基本特征
  • 03.总线的分类
    • 3.1 按功能分类
    • 3.2 按层次分类
    • 3.3 按传输方式分类
  • 04.总线的关键指标
    • 4.1 总线宽度
    • 4.2 总线频率
    • 4.3 总线带宽
    • 4.4 总线性能瓶颈
  • 05.总线仲裁
    • 5.1 为什么需要仲裁
    • 5.2 集中仲裁
    • 5.3 分布仲裁
  • 06.总线标准演进
    • 6.1 ISA到PCI
    • 6.2 PCI到PCIe
    • 6.3 PCIe核心设计
    • 6.4 总线标准对比
  • 07.现代总线架构
    • 7.1 传统南北桥架构
    • 7.2 现代集成架构
    • 7.3 NUMA架构
    • 7.4 对编程的影响
  • 08.综合案例NVMe读取
    • 8.1 全景路径图
    • 8.2 10个步骤拆解
    • 8.3 知识对号入座
    • 8.4 回到开头案例
  • 09.思考题与作业
    • 9.1 基础理解题
    • 9.2 进阶思考题
    • 9.3 动手作业

# 01.工作案例引入

# 1.1 双实例性能鸿沟

DBA 小刘接到线上告警:一台 2U 双路服务器上跑着两个 MySQL 实例 mysql-A 和 mysql-B,QPS 压力相同、配置相同、数据量相同,但 mysql-B 的 p99 延迟比 mysql-A 高了整整 30%。

小刘的第一反应是磁盘或网络的问题,但 iostat、netstat 的数据完全一致;CPU 利用率也相近(都在 70% 左右),甚至 mysql-B 的 CPU 利用率还略低一点。

继续排查,他注意到一个细节:

$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0-23   memory size: 128 GB
node 1 cpus: 24-47  memory size: 128 GB
node distances:
node   0   1
  0:  10  21
  1:  21  10    # 跨节点访问代价是本地的 2.1 倍

$ ps -o pid,psr,comm -p $(pidof mysqld)
 PID PSR COMMAND
1234  5   mysqld   # mysql-A:绑定在 node 0 的 CPU 5,内存也在 node 0
5678  32  mysqld   # mysql-B:绑定在 node 1 的 CPU 32,但内存却大多分配在 node 0!
1
2
3
4
5
6
7
8
9
10
11
12
13

原因找到了:mysql-B 启动时运维误操作把 CPU 亲和性设到了 node 1,但 numa_balancing 没及时迁移内存,大量缓冲池页还留在 node 0。每次查询都要跨 QPI/UPI 总线去 node 0 取内存,延迟从本地 ~80ns 飙到 ~150ns。

# 1.2 排查思路与结论

numactl --cpunodebind=0 --membind=0 mysqld ... 重启后,mysql-B 的 p99 延迟立刻回到与 A 一致的水平。

这个事故背后的关键认知:

  • 在现代多路服务器里,"内存"不是一块;CPU 访问不同内存的代价差很多。
  • "总线"也早就不是一条共享的铜线,而是由 CPU 内部总线、QPI/UPI、PCIe、DMI、SATA 等多层互联网络组成。理解哪条路径有多宽、多远,才能排查性能问题。

# 1.3 本文要回答的问题

  • 总线为什么要分层?数据总线、地址总线、控制总线各自的角色是什么?
  • 并行总线速度本应更快,为什么 PCIe、USB 3、SATA 都改用串行?
  • 多个设备同时请求总线,由谁决定先后顺序?
  • 南北桥去哪了?现代 CPU 为什么把总线控制器集成进来?
  • NUMA 架构对我们写代码有什么影响?为什么 mysql-B 会慢 30%?

带着这些问题往下读,文末我们会用一张"NVMe SSD 读 4KB"的完整路径图把总线全章串起来。

# 02.总线的基本概念

# 2.1 什么是总线

总线(Bus)是计算机各部件之间传输信息的公共通道。它是一组共享的导线,连接计算机的各个功能部件,用于在它们之间传输数据、地址和控制信号。

类比理解:如果计算机是一座城市,各个部件(CPU、内存、硬盘、显卡)是城市里的建筑,那总线就是连接这些建筑的公共马路。

没有总线的连接方式(点对点):
  CPU ←→ 内存
  CPU ←→ 硬盘
  CPU ←→ 显卡
  CPU ←→ 网卡
  内存 ←→ 硬盘
  ...
  N个设备需要 N×(N-1)/2 条连线!

有了总线:
  CPU ──┐
  内存 ──┤
  硬盘 ──┼── 总线 ── 
  显卡 ──┤
  网卡 ──┘
  所有设备共享一条"公路"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.2 为什么需要总线

疑惑:为什么不给每两个设备之间都拉一条专线?那样不是更快吗?

答疑:

方案 优点 缺点
点对点全连接 每对设备独占通道,不争抢 连线数量爆炸(N²级别),扩展性差
共享总线 连线少、易扩展、成本低 同一时刻只有一对设备能通信

实际上,现代计算机采用的是混合方案:

  • 核心高速通路(CPU↔内存)使用专用连接
  • 其他设备通过共享总线或交换网络连接

# 2.3 总线的基本特征

总线由三组信号线构成:

总线
 ├── 数据总线(Data Bus):传输数据
 │   宽度决定一次传输多少位数据(32位/64位)
 │
 ├── 地址总线(Address Bus):传输地址
 │   宽度决定最大寻址空间(32位→4GB,64位→16EB)
 │
 └── 控制总线(Control Bus):传输控制信号
     读/写信号、中断请求、总线请求等
1
2
3
4
5
6
7
8
9

一次总线数据传输的过程:

主设备(如CPU)发起一次内存读操作:

1. CPU 把内存地址放到地址总线上:  地址总线 = 0x0000_1000
2. CPU 发出"读"控制信号:          控制总线 = READ
3. 内存收到地址和读信号,取出数据
4. 内存把数据放到数据总线上:      数据总线 = 0x12345678
5. CPU 从数据总线上读取数据

整个过程称为一个"总线事务"(Bus Transaction)
1
2
3
4
5
6
7
8
9

# 03.总线的分类

# 3.1 按功能分类

总线类型 连接对象 特点
片内总线 CPU内部各部件 速度最快,在CPU芯片内部
系统总线 CPU、内存、I/O接口 计算机系统的"主干道"
I/O总线 I/O接口与外设 连接各种外部设备

# 3.2 按层次分类

flowchart TB
    subgraph CPU["CPU 芯片内部"]
        REG[寄存器总线<br/>速度最快]
        INT[内部总线<br/>ALU/控制器/Cache]
    end
    SYS[系统总线<br/>前端总线 FSB]
    MEM[内存总线<br/>DDR 连接 DRAM]
    IO[I/O 总线<br/>PCIe 连接显卡/网卡/SSD]
    EXT[外设接口<br/>USB/SATA/HDMI]

    REG --> INT
    INT --> SYS
    SYS --> MEM
    SYS --> IO
    IO --> EXT

    style CPU fill:#e3f2fd
    style MEM fill:#fff3e0
    style IO fill:#f3e5f5
    style EXT fill:#e8f5e9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

图注:越靠上层的总线越快但范围越窄,越靠下层总线覆盖面越大但速度越慢,这种"分层+分级"的设计是为了在性能和成本之间取得平衡。

现代计算机的总线层次:

  CPU 内部
  ┌──────────┐
  │ 寄存器总线 │ ← 速度最快,连接CPU内部寄存器
  │ 内部总线   │ ← 连接ALU、控制器、缓存
  └────┬─────┘
       │ 前端总线/系统总线
  ┌────┴─────┐
  │   内存总线  │ ← 连接CPU和内存
  └────┬─────┘
       │ PCIe总线
  ┌────┴─────┐
  │ I/O总线   │ ← 连接显卡、网卡、SSD等
  └────┬─────┘
       │ USB/SATA等
  ┌────┴─────┐
  │ 外设接口   │ ← 连接键盘、鼠标、外接硬盘等
  └──────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.3 按传输方式分类

并行总线 vs 串行总线:

疑惑:并行传输一次发多位数据,应该比串行快才对,为什么现代总线(PCIe、USB3)都改用串行了?

答疑:

并行总线(如旧式PCI):
  线1: ──0──1──0──1──
  线2: ──1──0──1──0──     同时传输多位
  线3: ──0──0──1──1──
  线4: ──1──1──0──0──
  
  问题:当频率很高时,多条线之间会产生电磁干扰(串扰),
  导致数据线之间的信号到达时间不一致(时序偏移),
  频率越高问题越严重,限制了并行总线的频率上限。

串行总线(如PCIe):
  线1: ──0──1──0──1──0──1──0──1──  只有一条线,没有串扰
  
  虽然一次只传1位,但频率可以做到非常高(GHz级别),
  总带宽 = 频率 × 通道数(多条串行线并行使用)
  反而比并行总线更快!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

技术演变:PCI(并行,133MB/s)→ PCIe(串行,每通道~2GB/s)

这是一个违反直觉但非常经典的"串行打败并行"的案例。

# 04.总线的关键指标

# 4.1 总线宽度

总线宽度指数据总线一次能传输的数据位数。

8位总线:一次传 1字节  (早期的ISA总线)
16位总线:一次传 2字节
32位总线:一次传 4字节  (PCI总线)
64位总线:一次传 8字节  (现代内存总线)
1
2
3
4

# 4.2 总线频率

总线频率指总线每秒钟的传输周期数。

内存总线频率:DDR4-3200 → 等效频率 3200MHz
PCIe 4.0:16 GT/s(每通道每秒传输160亿次)
PCIe 5.0:32 GT/s
1
2
3

# 4.3 总线带宽

总线带宽 = 总线宽度 × 总线频率,表示单位时间内的最大数据传输量。

PCIe 4.0 x16 带宽计算:
  每通道速率: 16 GT/s
  每次传输: 1 bit(串行)
  编码效率: 128b/130b ≈ 98.5%
  单通道带宽: 16 × 1 × 0.985 / 8 ≈ 1.97 GB/s
  x16: 1.97 × 16 ≈ 31.5 GB/s(双向各 31.5 GB/s)
1
2
3
4
5
6

# 4.4 总线性能瓶颈

疑惑:总线带宽足够了,为什么系统还是有时候感觉慢?

带宽 ≠ 性能

总线性能由两个因素决定:
  1. 带宽(Bandwidth):单位时间能传多少数据 → 影响大文件传输
  2. 延迟(Latency):发出请求到收到响应的时间 → 影响随机小数据访问

类比:
  带宽 = 公路的车道数(8车道 vs 2车道)
  延迟 = 从家到公司的距离(1km vs 100km)
  
  8车道但100km远的路,运大货很快(高带宽),
  但送一封信还是很慢(高延迟)。
  
  SSD随机读取快是因为"延迟低"(100μs vs 10ms),
  而不是因为"带宽大"。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 05.总线仲裁

# 5.1 为什么需要仲裁

总线是共享资源,同一时刻只能有一个设备作为主设备控制总线。当多个设备同时请求使用总线时,就需要仲裁(Arbitration)机制来决定谁先用。

场景:CPU和DMA控制器同时想用内存总线
  CPU: "我要读内存!"
  DMA: "我也要写内存!"
  总线仲裁器: "DMA优先级更高(因为DMA不能等),CPU你等一下"
1
2
3
4

# 5.2 集中仲裁

由一个中央仲裁器统一决定。

链式查询(菊花链):

flowchart LR
    ARB[仲裁器] -->|总线授权| D1[设备1<br/>优先级最高]
    D1 -->|未使用则传递| D2[设备2]
    D2 -->|未使用则传递| D3[设备3]
    D3 -->|未使用则传递| D4[设备4<br/>优先级最低]

    style ARB fill:#ffccbc
    style D1 fill:#c8e6c9
    style D4 fill:#ffcdd2
1
2
3
4
5
6
7
8
9

图注:授权信号沿链路依次传递,离仲裁器近的设备容易抢到总线,远端设备可能"饿死"。

仲裁器 → 设备1 → 设备2 → 设备3 → 设备4
  │                                    
  └── 总线授权信号沿链路依次传递
      离仲裁器越近的设备优先级越高
      
优点:简单,线路少
缺点:离得远的设备可能饿死
1
2
3
4
5
6
7

计数器查询:

仲裁器内有计数器,轮流给每个设备机会
  第1次:授权设备1
  第2次:授权设备2
  第3次:授权设备3
  ...循环

优点:公平
缺点:实现稍复杂
1
2
3
4
5
6
7
8

# 5.3 分布仲裁

没有中央仲裁器,每个设备都参与仲裁决策。

每个设备发出自己的优先级编号到仲裁总线
所有设备比较优先级
优先级最高的设备获得总线控制权

类似于多人同时出牌,点数最大的赢
1
2
3
4
5

# 06.总线标准演进

# 6.1 ISA到PCI

ISA (1981):
  16位数据总线,8MHz
  带宽:16MB/s
  IBM PC的标准总线

PCI (1992):
  32/64位数据总线,33/66MHz
  带宽:133/533 MB/s
  支持即插即用(Plug and Play)
  首次实现自动配置设备
1
2
3
4
5
6
7
8
9
10

# 6.2 PCI到PCIe

疑惑:PCI已经很成功了,为什么要发明PCIe?

PCI的问题:
  1. 共享总线 → 所有设备争抢同一条总线
  2. 并行传输 → 频率无法继续提升(串扰限制)
  3. 单向半双工 → 不能同时收发

PCIe的解决方案:
  1. 点对点连接 → 每个设备有专属通道
  2. 串行传输 → 频率可以做到GHz级别
  3. 全双工 → 可以同时收发
1
2
3
4
5
6
7
8
9

# 6.3 PCIe核心设计

PCIe 的分层架构(类似网络协议栈):

  ┌─────────────────┐
  │ 事务层(Transaction)│  ← 数据包的组装/拆解
  ├─────────────────┤
  │ 数据链路层(Data Link)│  ← 流控、纠错、重传
  ├─────────────────┤
  │ 物理层(Physical)    │  ← 实际的串行信号传输
  └─────────────────┘

PCIe 的通道(Lane)概念:
  x1:  1个通道,~2GB/s (PCIe 4.0)   → 网卡、声卡
  x4:  4个通道,~8GB/s              → NVMe SSD
  x8:  8个通道,~16GB/s
  x16: 16个通道,~32GB/s            → 高端显卡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6.4 总线标准对比

标准 类型 带宽 应用
ISA 并行共享 16MB/s 历史(PC)
PCI 并行共享 133MB/s 历史
PCIe 3.0 x16 串行点对点 16GB/s 显卡
PCIe 4.0 x16 串行点对点 32GB/s 显卡/SSD
PCIe 5.0 x16 串行点对点 64GB/s 最新
USB 3.2 串行 2.5GB/s 外接设备
SATA III 串行 600MB/s 硬盘
NVMe(PCIe) 串行点对点 ~7GB/s(PCIe4 x4) SSD

# 07.现代总线架构

# 7.1 传统南北桥架构

flowchart TB
    CPU[CPU]
    NB[北桥<br/>Northbridge<br/>高速总线枢纽]
    SB[南桥<br/>Southbridge<br/>低速总线枢纽]
    MEM[内存 DRAM]
    GPU[显卡]
    USB[USB/键鼠]
    SATA[SATA/硬盘]
    PCI[PCI 设备]

    CPU -->|前端总线 FSB| NB
    NB --> MEM
    NB --> GPU
    NB --> SB
    SB --> USB
    SB --> SATA
    SB --> PCI

    style CPU fill:#bbdefb
    style NB fill:#ffecb3
    style SB fill:#d1c4e9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

图注:北桥是整个系统的"咽喉"——CPU 访问内存或显卡都必须过北桥,当多个高速设备同时请求时,北桥成为性能瓶颈。

早期 PC 架构(~2008年之前):

       CPU
        │
  ┌─────┴─────┐
  │  北桥芯片   │ ← 高速总线枢纽
  │(Northbridge)│    连接CPU、内存、显卡
  └─────┬─────┘
        │
  ┌─────┴─────┐
  │  南桥芯片   │ ← 低速总线枢纽
  │(Southbridge)│    连接USB、SATA、PCI、音频等
  └───────────┘

北桥处理高速设备,南桥处理低速设备
北桥是系统的性能瓶颈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7.2 现代集成架构

现代架构(Intel 从 2008 年开始逐步集成):

       CPU(集成了北桥的功能)
  ┌────────────────────┐
  │ 内存控制器(原北桥)  │ ← 内存控制器直接集成到CPU
  │ PCIe控制器(原北桥)  │ ← PCIe也直接连CPU
  │ GPU(核显,可选)     │
  └────────┬───────────┘
           │ DMI(直接媒体接口)
  ┌────────┴───────────┐
  │    PCH(平台控制集线器)│ ← 相当于南桥
  │  USB / SATA / 网络   │
  │  PCIe低速设备         │
  └────────────────────┘

优势:
  CPU直接与内存通信,不再经过北桥,延迟大幅降低
  PCIe设备直连CPU,带宽更高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.3 NUMA架构

疑惑:在多CPU服务器中,总线是怎么设计的?

flowchart TB
    subgraph Node0["NUMA Node 0"]
        CPU0[CPU 0<br/>0-23核]
        MEM0[本地内存 128GB<br/>访问 ~80ns]
        CPU0 <-->|IMC/DDR| MEM0
    end
    subgraph Node1["NUMA Node 1"]
        CPU1[CPU 1<br/>24-47核]
        MEM1[本地内存 128GB<br/>访问 ~80ns]
        CPU1 <-->|IMC/DDR| MEM1
    end

    CPU0 <-.->|QPI/UPI 互联<br/>远程访问 ~150ns| CPU1
    CPU0 -.->|跨节点访问| MEM1
    CPU1 -.->|跨节点访问| MEM0

    style Node0 fill:#e3f2fd
    style Node1 fill:#fff3e0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

图注:每个节点都有自己的 CPU+内存"私域",跨节点访问需经 QPI/UPI 总线,延迟几乎翻倍,这就是本文开头 MySQL 30% 性能差距的根因。

UMA(统一内存访问):
  CPU0 ──┐
         ├── 共享总线 ── 内存
  CPU1 ──┘
  
  问题:多个CPU争抢同一条总线,带宽成为瓶颈

NUMA(非统一内存访问):
  CPU0 ── 本地内存0
    │
  互联通道(QPI/UPI)
    │
  CPU1 ── 本地内存1
  
  每个CPU优先访问本地内存(快),也可以访问远程内存(慢)
  
  本地访问延迟: ~80ns
  远程访问延迟: ~150ns(慢了近一倍)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.4 对编程的影响

疑惑:总线这么底层,对写代码有什么影响?

// NUMA感知编程示例
// 在NUMA系统上,线程应该访问本地内存以获得最佳性能

// Java中可以通过JVM参数启用NUMA感知
// -XX:+UseNUMA

// C/C++可以使用numa库
// #include <numa.h>
// void *ptr = numa_alloc_local(size);  // 在本地节点分配内存

// 错误做法:在CPU0上创建线程,但数据在CPU1的内存上
// 正确做法:让线程和它要访问的数据在同一个NUMA节点上
1
2
3
4
5
6
7
8
9
10
11
12
PCIe带宽对实际应用的影响:

  NVMe SSD (PCIe 4.0 x4):
    理论带宽: 8GB/s
    实际顺序读: ~7GB/s
    实际随机读: ~1GB/s(受到SSD内部并行度和延迟限制)

  显卡 (PCIe 4.0 x16):
    理论带宽: 32GB/s
    如果降级到 x8:带宽减半,但对大多数游戏影响<5%
    因为游戏的数据大部分在显存中,CPU-GPU数据交换不是瓶颈
    
    但对AI训练影响大:需要频繁在CPU和GPU之间传输大量数据
1
2
3
4
5
6
7
8
9
10
11
12
13

# 08.综合案例NVMe读取

现在我们把全章内容串成一个完整的故事:进程 A 调用 pread(fd, buf, 4096, offset) 从 NVMe SSD 读 4KB 数据,这 4KB 要穿越多少段总线?每一段是什么类型?谁来仲裁?

# 8.1 全景路径图

                ┌────────────────────────────────────┐
                │           CPU 芯片内部               │
                │                                     │
  寄存器 ←片内总线→ L1/L2/L3 Cache                     │
      ↑                                               │
      │ 内部 Ring/Mesh 总线                           │
      ↓                                               │
  集成内存控制器(IMC) ──DDR 总线──> DRAM (64位/3200MT/s)│
                                                      │
  集成 PCIe Root Complex                              │
                │                                     │
                │ PCIe 4.0 x4 (~8 GB/s 理论带宽)       │
                ↓                                     │
  ┌──────────────────┐                                │
  │  NVMe SSD 控制器  │── 闪存通道 ──> NAND Flash 芯片 │
  └──────────────────┘                                │
                                                      │
                │ DMI 4.0                              │
                ↓                                     │
           PCH (平台控制集线器)                        │
                │                                     │
                ├── SATA 总线 ──> 机械硬盘             │
                ├── USB 总线 ──> 键盘/鼠标             │
                └── LPC/SPI ──> BIOS/EC               │
                └────────────────────────────────────┘
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

# 8.2 10个步骤拆解

步骤 发生位置 涉及总线 关键动作
① 用户态调用 pread CPU 片内总线 指令走流水线,MMU 查 TLB
② 系统调用陷入内核 CPU 片内总线 特权级切换,切到内核栈
③ 内核构造 NVMe Submission Queue Entry CPU → 内存 IMC → DDR 总线 CPU 把命令写进 DRAM 中的 SQ,地址总线选行列,数据总线 64 位传命令
④ CPU 通过 MMIO 敲 NVMe SSD 的门铃寄存器 CPU → SSD PCIe 事务层包 (TLP) 控制总线发 Write 信号,地址总线定位 BAR,数据总线传入新 SQ tail 值
⑤ SSD 控制器从 SQ 取命令 SSD → 内存 PCIe DMA(SSD 当主设备) SSD 主动向内存发起 DMA 读,总线仲裁决定它与 CPU 谁先用 PCIe
⑥ SSD 内部从 NAND 读出 4KB SSD 闪存通道 与 CPU 解耦,延迟 ~50μs
⑦ SSD 通过 DMA 把 4KB 写入内存 buf 所在物理页 SSD → 内存 PCIe x4 + IMC + DDR PCIe 传 64 字节 / TLP,共 64 个 TLP;写入 DRAM 时 IMC 做行列选通
⑧ SSD 写完成,往 CQ 写一条完成条目 SSD → 内存 PCIe + DDR 再一次 DMA 写
⑨ SSD 发 MSI-X 中断给 CPU SSD → CPU PCIe + APIC 用控制总线的中断信号而不是拉低物理引脚
⑩ 内核唤醒进程 A,pread 返回 CPU 片内总线 切回用户态,进程看到 4KB 数据

# 8.3 知识对号入座

本章概念 在这次 I/O 中的体现
数据/地址/控制三总线 步骤 ③ 中 CPU 对 DRAM 的一次写,地址总线选行列,控制总线发 Write,数据总线搬 64 位
并行总线 DDR4 内存总线仍是 64 位并行(短距离、时序可控)
串行总线 PCIe 4.0 x4 是 4 条独立串行差分对,一次传 1 bit、但 16 GT/s
总线仲裁 步骤 ⑤ ⑦,CPU 和 SSD 都想用 PCIe 和内存,Root Complex 按优先级/轮询仲裁
带宽 vs 延迟 4KB 的 ⑦ 只要 ~1μs(带宽足),但整次 pread 要 ~60μs(瓶颈是 ⑥ NAND 延迟)
PCIe 分层 ④ ⑤ ⑦ ⑧ 都是事务层 TLP,数据链路层保证可靠传输,物理层负责串行 SerDes
南北桥 → PCH 因为 PCIe Root 直连 CPU,所以 NVMe 没走 PCH;但键鼠仍走 PCH
NUMA 如果 buf 所在物理页在 node 1 内存,而 SSD 的 PCIe 通道挂在 node 0 的 CPU 上,DMA 还要再经 UPI 跨节点,延迟多 ~70ns
位宽 × 频率 = 带宽 PCIe 4.0 x4 = 16 GT/s × 4 × 128b/130b / 8 ≈ 7.88 GB/s

# 8.4 回到开头案例

再看本文开头的 30% 性能鸿沟:mysql-B 每次查 buffer pool 都要从 node 1 的 CPU 跨 UPI 到 node 0 的内存控制器再到 DRAM,本来 80ns 的访问变成 150ns。对于一个 OLTP 场景,每条 SQL 要访问几十到几百个内存页,差距被成倍放大,最终体现为 p99 延迟 +30%。

理解了"总线是多层的、有远有近"这一点,numactl --cpunodebind --membind、UseNUMA、thread affinity 这些看起来玄学的配置就都讲得通了。

# 09.思考题与作业

# 9.1 基础理解题

  1. 一次总线事务为什么需要数据、地址、控制三种总线协同?缺了哪一种会怎样?
  2. 为什么并行总线频率很难超过几百 MHz,而串行总线能轻松做到 10+ GHz?
  3. 链式仲裁的"饿死"问题是什么?如何改造成不会饿死的仲裁方式?
  4. 为什么 PCIe 是点对点而不是共享总线?这对带宽计算有什么影响?
  5. 在 NUMA 系统上,"本地内存访问"和"远程内存访问"的延迟差大约是多少?差别主要来自哪段路径?

# 9.2 进阶思考题

  1. 一个 PCIe 4.0 x16 的显卡,如果插到只有 x8 电气通道的插槽上,你能通过什么命令或工具看到它"降级"到了 x8?
  2. 集成内存控制器(IMC)从南桥挪进 CPU,对延迟、带宽、可扩展性各产生了什么影响?有什么代价?
  3. 为什么 NVMe 走 PCIe 而 SATA SSD 已被淘汰出高端场景?差距主要在带宽还是协议栈?
  4. DMA 写内存时,CPU 的 Cache 怎么办?会不会读到旧数据?(提示:Cache Coherency)
  5. 在 numactl --interleave=all 下运行的程序,总线的使用模式和本地绑定有什么不同?适合什么场景?

# 9.3 动手作业

作业 1:观察本机的 NUMA 拓扑和总线层次

numactl --hardware          # 节点数、每节点 CPU 和内存
lscpu                        # 看 NUMA node(s)
lspci -tv                    # 以树形展示 PCIe 总线层次
lspci -vv -s <NVMe BDF> | grep -E "LnkCap|LnkSta"  # 看 NVMe 真实链路宽度
1
2
3
4

把输出画成一张"本机总线拓扑图",标出哪些是 PCIe、哪些是 DDR、哪些走 PCH。

作业 2:用 fio 验证 NVMe 的带宽 vs 延迟

写一个 fio 配置,分别做:

  • 顺序读 1MB(观察带宽是否接近 PCIe x4 理论值)
  • 随机读 4KB,iodepth=1(观察单次延迟)
  • 随机读 4KB,iodepth=32(观察吞吐量)

对比三组结果,解释为什么顺序读拉满带宽、但随机深度 1 却只能打到几百 MB/s。

作业 3:复现 NUMA 性能陷阱

在双路服务器上写一个简单的内存扫描程序(malloc 2GB 随机读),分别用:

numactl --cpunodebind=0 --membind=0 ./bench     # 本地
numactl --cpunodebind=0 --membind=1 ./bench     # 跨节点
1
2

记录两次的耗时,解释为什么会有差异。如果没有 NUMA 机器,可以用云厂商的 NUMA 裸金属实例(如火山/AWS Bare Metal)复现。

上次更新: 2026/06/07, 18:47:40
计算机输入输出设备
计算机指令编程原理

← 计算机输入输出设备 计算机指令编程原理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式