面试中PCIe枚举与BAR典型问题 - 第1节
一、PCIe 总线架构与分层模型
说到 PCIe,很多刚入行的朋友总觉得它很神秘。我刚开始接触的时候也是一头雾水,脑子里全是“这玩意到底怎么工作的?”其实说白了,PCIe 就是一个高速串行总线,用来连接 CPU、内存和各种外设。但它的设计思路跟老一代的 PCI 总线完全不同——PCI 是并行总线,所有设备共享一根总线;而 PCIe 是点对点的串行链路,每个设备都有自己的专用通道。
这里有个关键点:PCIe 采用分层架构。为什么要分层?你想想看,如果所有功能都揉在一起,改一个地方就动全身,维护起来多痛苦。分层之后,每一层各司其职,接口清晰,也方便升级。
1.1 分层模型的三个核心层次
PCIe 协议栈从下往上分为三层:物理层(Physical Layer)、数据链路层(Data Link Layer) 和 事务层(Transaction Layer)。每层都有自己的职责,往下看。
| 层次 | 英文缩写 | 核心职责 | 我遇到过的问题 |
|---|---|---|---|
| 事务层 | TL | 组装/解析 TLP(事务层包),管理地址空间 | BAR 地址写错导致设备找不到 |
| 数据链路层 | DLL | 纠错、重传、流量控制(DLLP) | 丢包时重传机制救了我一命 |
| 物理层 | PHY | 串行化/解串、电气特性、链路训练 | 差分信号布线不规范直接掉 Gen1 |
1.2 事务层(Transaction Layer)
事务层是离软件最近的一层。CPU 发起的内存读写、配置读写、I/O 读写,最终都变成事务层包(TLP)。TLP 包含包头、数据和 CRC 校验。说白了,事务层就是“翻译官”——把软件请求转成标准化的包格式,发给对端设备。
我做过一个项目,写 BAR 空间映射的驱动时,发现读回来的数据全是 0。查了半天,结果是我在 TLP 的地址字段里填错了偏移量。这种坑,踩一次就记住了。
事务层还有几个重要概念:请求者(Requester)、完成者(Completer)。谁发起 Request,谁就是 Requester;谁响应,谁就是 Completer。还有一个叫标签(Tag) 的东西,用来区分不同的未完成请求——这跟 HTTP 的请求 ID 有点像。
1.3 数据链路层(Data Link Layer)
数据链路层在事务层下面,负责可靠传输。它把 TL 传下来的 TLP 加上序号(Sequence Number)和 CRC,然后发出去。同时,它还会处理重传——如果对端没收到或 CRC 不对,DLL 会重新发一遍。
这个重传机制叫 ACK/NAK 协议。我印象很深:有一次调试 FPGA 的 PCIe IP,链路总是不稳定。后来发现是 DLL 的重传计数器溢出了——因为我们把 Retry Buffer 设得太小。嗯,硬件设计的坑,软件有时候很难发现。
数据链路层还负责流量控制(Flow Control)。它通过 DLLP(数据链路层包)来告诉对端“我的缓冲区还有多空”。如果一端不通知,另一端使劲发,那缓冲区就炸了。这个机制在枚举阶段尤其重要,后面会细讲。
1.4 物理层(Physical Layer)
物理层是最底层,直接管电信号。它把 DLL 传下来的并行数据串行化,通过差分对发出去。反过来,把收到的串行信号解串成并行数据。物理层还负责链路训练(Link Training)——就是上电后两端握手,协商速率、宽度、极性等参数。
我记得第一次用逻辑分析仪抓 PCIe 物理层信号,看到那密密麻麻的比特流,头都大了。后来我用专门的 PCIe 分析仪,直接看协议层级,舒服多了。但话说回来,如果物理层有问题,分析仪也帮不了——你看不到链路状态机。
物理层还有个容易忽略的点:极性反转。差分对的 P/N 如果接反了,物理层会自动反转,保证通信正常。但我在一个项目中遇到过,因为 PCB 走线长度不匹配,链路只能跑到 Gen1,加个均衡器才解决。
二、PCIe 总线架构与设备拓扑
讲完分层,我们来聊聊总线架构。PCIe 系统里有一个 Root Complex(RC),它是 CPU 和 PCIe 之间的桥梁。RC 下面可以接交换机(Switch),交换机再分出多个端口接端点(EP)。还有桥(Bridge) 用来连接旧设备(比如 PCI 设备)。
你想想看:CPU 访问外设时,地址怎么映射?答案是通过 BAR(基地址寄存器)。每个 EP 都有 0~5 个 BAR,每个 BAR 描述一段内存或 I/O 空间。枚举时 BIOS 会给每个 BAR 分配地址。这块是面试高频考点,我后面会专门开一章讲 BAR 的坑。
关键点总结:
- RC 是根,但它不是 CPU 本身,而是集成在 CPU 内部或芯片组里。
- Switch 内部有多个虚拟 PCI-PCI 桥,负责路由 TLP。
- EP 可以是显卡、NVMe 硬盘、网卡等。
- 一个 PCIe 域最多支持 256 条总线,每条总线最多 32 个设备,每个设备最多 8 个功能。
三、PCIe 分层架构示意图
下面是我画的一张分层架构图。注意看各层之间的接口,以及包在不同层的形态变化。
3.1 数据流向与包封装
看这个图你就明白了:软件发起一次读操作,先到事务层封装成 TLP,交给数据链路层;数据链路层在 TLP 前后加上序列号和 CRC,变成 DLLP;再交给物理层串行化成比特流,通过差分线发出去。接收端反向解包,逐层上送。
如果发生错误,数据链路层会要求重传。这块我曾见过一个很隐蔽的 bug:发送端重传后忘了更新序列号,导致接收端认为收到重复包而丢弃——嗯,又是个协议实现的细节。
四、枚举与 BAR 的关联
说完分层,我提前给你埋个伏笔:后面课程会详细讲枚举过程。但你现在要明白,枚举是 BIOS/Kernel 扫描 PCIe 树的过程,而 BAR 是每个设备用来声明自己需要多少地址空间的寄存器。没有枚举,BAR 就是一堆无效值;没有 BAR,设备没法被 CPU 访问。两者就像锁和钥匙,缺一不可。
我个人建议你在学 BAR 之前,先把分层模型印在脑子里。因为很多 BAR 问题根子上出在事务层对地址映射的理解上——比如 64 位 BAR 的高 32 位没写对,或者其实是预取属性设错了。
本章核心概念清单(面试常问):
- PCIe 三层模型分别做什么?
- TLP、DLLP 区别?
- 物理层链路训练包括哪些步骤?
- RC、Switch、EP、Bridge 在拓扑中的角色?
- BAR 是什么?和分层模型的哪一层关系最紧密?