PCIe基础学习4:事务类型与 Posted 语义

📅 发布时间:2026/8/14 17:14:54
PCIe基础学习4:事务类型与 Posted 语义
事务类型与 Posted 语义读慢写快的根PCIe 协议特性精讲 · 第 04 篇 | 基准PCIe 5.0Base Spec r5.0| 内核Linux v7.0.0系列主线39 篇一篇一个协议特性——是什么、为什么、怎么工作、怎么测、坑在哪03 篇把 TLP 的头字段拆完了Fmt/Type 怎么判类型、Cpl 头里 Status 放哪、Tag 和 Requester ID 怎么配对全讲透了。这一篇接着回答抓包时第一个冒出来的问题这条 TLP 发出去该不该等 Completion完成报文该等的话等回来的状态对不对这个问题想明白了你就能解释一个日常现象——为什么 PCIe 写比读快还能在排障时一眼分清设备拒绝了你和设备压根没搭理你这两条完全不同的故障路径。一、四个地址空间事务往哪去先立个总纲。PCIe 的事务按目标分成四个地址空间数据出处Base Spec r5.0Memory、I/O、Configuration、Message。每一种事务都属于其中一个空间往对应的地方去。四个空间的定位差别很大Memory内存空间数据搬运主力。读写都允许目标是映射到内存地址的位置memory-mapped location。DMA、MMIO 寄存器访问全走这里日常抓包九成以上是它。I/OIO 空间老平台遗留物。x86 的 I/O 端口那套目标是 I/O 映射位置。新设计基本不碰规范对它的限制也最严I/O 请求的 Length 恒为 1 DW、TC 必须是 0想多传点数据都不行。说白了它就是给古董设备留的口子。Configuration配置空间控制面。设备枚举、BAR 分配、能力结构配置全靠它承载 CfgRd/CfgWrType0/Type1 两种。软件世界的开机点名就发生在这里。Message消息空间信令面。从事件通知机制到通用消息规范原文语义中断、电源管理、错误上报、LTR 这类边带信号全并进 TLP 走03 篇讲过的 INTx/PM/ERR 消息都归这。它不走地址路由Vendor-Defined 消息例外最常见的是发给根复合体或广播是四个空间里最特别的一个。这个设计省掉了一大把引脚——PCI 时代中断、电源管理、错误上报都是独立的边带信号线PCIe 全收编进消息 TLP 在链路上走插槽的引脚数才压得下来。承载关系一句话Memory 和 I/O 空间由地址路由的请求MRd/MWr、IORd/IOWr承载Configuration 由 ID 路由的配置请求承载Message 由消息请求Msg/MsgD承载。类型、路由、头长这些 03 篇都拆过这篇不重复往后就看一件事——每种请求怎么处理要不要等 Cpl。四个空间记熟了抓包第一眼就能答两个问题这条包去哪个空间、什么类型Fmt/Type 判03 篇讲过剩下的就是本篇的主题要不要等 Cpl、等回来什么状态。二、三分类等不等 Completion一眼定规范把事务按是否需要 Completion 回报分成三类Posted发完就算完。请求离开 Requester 那一刻事务就视为完成永不收 Cpl。成员就两个Memory WriteMWr和全部消息Msg/MsgD。Non-Posted非 Posted必须收 Cpl 确认状态。所有读请求MRd/IORd/CfgRd0/1、I/O 写IOWr、配置写CfgWr0/1、原子操作AtomicOp全在这边。Completion完成报文Cpl/CplD 本身。它不是请求是 Non-Posted 请求的答复跟请求配成一对。归属关系用一张表钉死请求类型怎么处理归属MWr内存写无 Cpl发完即走PostedMsg / MsgD消息无 Cpl路由方式看消息类型PostedMRd内存读必回 Cpl成功回一个或多个带数据的 CplD出错回不带数据的 CplNon-PostedAtomicOp原子操作必回 CplCpl 带目标位置的原始值Non-PostedIORd / IOWrIO 读/写必回 Cpl读成功带数据写和失败的读不带数据Non-PostedCfgRd / CfgWr配置读/写必回 Cpl读成功带数据写不带数据Non-Posted示意图数据出处Base Spec r5.0几个容易记混的点单独拎出来读一定 Non-Posted写不一定。Memory Write 是 Posted但 I/O 写和配置写是 Non-Posted——同样是写待遇完全不同。AtomicOp 是写语义却归 Non-Posted。FetchAdd/Swap/CAS 三型编码 0 1100 / 0 1101 / 0 1110都要改内存但完成报文要带回操作前的原始值软件拿它做无锁同步必须等。消息全 Posted。中断、电源管理这些消息发完即走谁都不等谁。记住一句话口诀读必等Memory 写不等IO/Config 写必等消息不等。三、为什么这么分Posted 免往返就是写快的根分类规则看着简单背后的设计逻辑值得掰开讲因为这直接回答为什么 PCIe 写比读快。先看读。PCIe 用的是Split Transaction拆分事务协议请求和完成彻底分离目标设备收到请求后等自己就绪了再单独回一个 Completion。这比 PCI 总线时代强多了——老 PCI 处理慢目标用的是 wait-state 和延迟事务retry请求方得反复试探目标好了没PCIe 不用试探目标好了自己回。作为代价一次读至少两个 TLP 在路上请求往上游走完成往下游回。而且一个读请求可能拆成多个 CplD回来载荷上限 4 KB设备实际常用更小的 MPS读的延迟就更明显了。这个模型还带来一个附带好处目标不用一次只伺候一个请求可以同时收多个请求、分别响应Requester 那边也能同时挂多个 outstanding 读靠 Tag 区分谁是谁——这正是读吞吐的来源也是 03 篇讲 Tag 宽度寄存器时说的延迟带宽积的出处。示意图数据出处Base Spec r5.0再看写。Memory Write 被设计成 Posted意思是请求离开 Requester 的那一刻写就视为完成了。讲 Posted 的语义业界常用一个邮局比喻很贴切寄信就是把信投进邮筒投进去你就当它送到了不会站在邮筒边等回执。MWr 发完即走不占往返时间也不占返回方向的带宽——返回方向全留给读的 CplD。这就是写快的根读是一来一回 可能拆包写是单程票。代价呢错误不可见。信可能寄丢MWr 也可能根本没到、或者到了但写失败Requester 一概不知道——没有 Cpl 可等。错误只能靠 AER 消息兜底对端发现异常发一条 ERR_ 消息上来软件再查。这个发完即走、错了后补的取舍规范认为值得写失败的概率小为它牺牲每次写都要等确认的时延不划算。这坑我见得多了——抓包看到 MWr 后面没有 Cpl先别当故障报那是设计如此。那为什么 I/O 写和配置写非要 Non-Posted因为这两类写影响设备行为改一个配置寄存器、踢一次 I/O 端口设备接下来的行为就变了。软件必须确认到了、没报错才能放心走下一步。这层意思说白了内存写可以接受数据迟早会到I/O 写不行——下一步动作必须等确认到达之后才能做。配置写错了设备行为全乱I/O 写丢了外设状态错位后面所有读写全跟着错。控制面的事慢一点也要确认。这是控制面的纪律快留给数据面确认留给控制面。这么设计是有原因的。还有一条很多人不知道的义务在接收端Posted Request Acceptance Limit——正常工况下Endpoint 收到 Posted 请求后必须在 10 μs 内接受并返还对应的流控信用。Posted 快不是发送方单方面快接收端也不能赖着不收两边都有规矩。四、Completion 语义回来的完成报文怎么读Non-Posted 请求发出去了Cpl 回来怎么判断对不对三个层次状态对不对、是不是回给我的、回齐了没有。第一层Status 状态值。03 篇讲过 Cpl 头里 Status 字段的位置这里讲语义——什么场景该回什么值SC成功正常完成。读带数据CplD写不带数据。UR不支持请求地址没命中 BAR、请求类型不支持、路由出错。抓包看到 Cpl(UR)先查地址归属和类型支持这是被明确拒绝。CRS配置重试目标设备忙让软件过会儿再读。条文上它只允许出现在配置请求的完成里实践中基本只见于配置读CfgRd——CRS 出现在非配置请求的响应里属于非法接收方可选上报 Malformed。CACompleter Abort被拒请求被 Completer 终止比如目标功能不可用跟 UR 的区别在语义上抓包判读看 AER 报的是哪一类。第二层是不是回给我的。完成报文的关联键是 Requester ID TagCompleter 回 Cpl 时原样带回请求里的这对值Requester 拿它找自己挂起的未完成请求。找得到正常找不到对应请求的 Cpl就是 Unexpected Completion 错误——多半是 Tag 冲突、对端重复回包或者完成方发错。第三层回齐了没有。拆几个 CplD 回来由 Completer 决定它按自己的 MPS 把请求的数据切成若干块每块一个 CplD读 4 KB 数据、MPS 256 字节的设备就是 16 个 CplD 排着队回来。规则是同 Tag 的多个 CplD 必须按地址顺序返回中间不能乱同 Tag 保序第一个 CplD 的 Lower Address 由请求的 First DW BE 算出来后续拆分 CplD 的 Lower Address 恒为 0——这是正常设计抓包看到拆分包 Lower Address 全 0 别当 bug唯一例外是 RC 的 RCB64B 时 bit6 按 64B 对齐翻转出错即终止拆包任何一个 Cpl 出错Status 非 SC这个 Cpl 就是该请求的最后一个完成报文Completer 不得再发后续 CplD。抓包看到 Cpl 数量少于请求折算的预期数先看 Status——出错少包是正常没出错少包才是真丢收齐才算完成多 Cpl 的请求必须收齐所有 Cpl收了一半就判定成功是软件 bug这里必须把两条故障路径分清楚排障全靠它现象含义AER 上报等不到任何 Cpl超时目标没收到 / 无响应 / Cpl 丢了 →CTO完成超时AER CTO 位收到 Cpl 且 Status UR目标收到了明确拒绝 →UR不支持请求AER UR 位这是两个不同的 AER 位、两条不同的故障路径CTO 是没搭理你UR 是拒绝了你还告诉你为什么。驱动日志里看到 Unsupported Request 和 completion timeout先分清是哪个再决定往哪查。五、版本差异6.4 加了三个新东西本系列基准钉在 5.0。6.4 在事务分类上动了三处维度r5.0基准r6.4验证含义请求类型MRd/MWr/IORd/IOWr/CfgRd0/1/CfgWr0/1/Msg/MsgD/Cpl/CplD/AtomicOp新增DMWrDeferrable Memory Write可延迟内存写DMWr 是 Non-Posted 请求每次都回 CompletionSC/RRS/UR/CA——共享工作队列场景UIO 请求无新增 UIOMWr/UIOMRd仅 Flit Mode 下使用需要新的流控配置验证用例增加Posted 集合MWr Msg/MsgD同左Flit Mode 下NOP TLP 也属 Posted抓包新增 NOP 包识别过滤规则更新CRS 命名Configuration Request Retry Status改名 Request Retry StatusRRS抓包/驱动日志关键字更换语义不变另外 6.4 在 Flit Mode 下完成关联规则也有扩展完成带 TC 参与匹配、AtomicOp 头格式变化做 6.0 产品时完成关联的测试逻辑和抓包匹配器要跟着升级。7.0 在事务分类上和 6.4 没有功能变化数据出处Base r7.06.4 128 GT/s 编辑性修订。所以 5.0 产品的知识直接能用做 6.0 产品时把上表当升级清单逐行对照。六、三表故障、测试、判断故障模式Posted/Non-Posted 相关的五类典型现象症状可能根因可观测证据Posted 写后无任何确认正常——MWr 无 Cpl错误只能走 AER 消息抓包只见 MWr 无 Cpl出错时对端发 ERR_ 消息易误判为故障Non-Posted 请求无 Cpl超时Completer 未收到 / 无响应 / Cpl 丢失dmesg completion timeoutAER CTO 上报Cpl 状态 UR地址未命中 BAR / 请求类型不支持 / 路由错抓包 Cpl(UR)dmesg Unsupported Request读请求多个 Cpl 数据错序多 CplD 拆包顺序错违反同 Tag 保序抓包同 Tag 多 CplD 地址顺序乱数据校验失败Unexpected CompletionTag 冲突 / 重复 Cpl / 完成方错发抓包 Cpl 无对应请求AER Unexpected Completion测试方法四招够用手段命令/工具预期Posted/Non-Posted 行为对比分析仪分别抓 MWr 与 MRdMWr 无 CplMRd 必有 CplSC 或错误状态Cpl 状态注入对 BAR 外地址发起 MRddevmem2 越界返回 Cpl(UR) 而非 CTO——区分拒绝与无响应CTO 与 UR 判别停目标响应 vs 目标回 UR无 Cpl CTOAER CTO 位有 Cpl UR AER UR 位读拆包顺序验证大读请求MRRS 上限 分析仪同 Tag 的 CplD 按地址顺序返回乱序 违例判断依据抓包判读五把尺子判据期望值/特征反例特征Posted 收 Cpl永不发生MWr/Msg 无 Cpl收到 Cpl 对端违反 Posted 规则Malformed/UR 风险Non-Posted 必有 CplMRd/IORd/CfgRd/AtomicOp 等全部有 Cpl无 Cpl 超时 CTO 故障路径Cpl Status 合法值SC / UR / CRS仅 CfgRd 可回/ CACRS 出现在非配置请求响应 MalformedCompletion 与请求关联Requester ID Tag 唯一匹配无匹配请求的 Cpl Unexpected Completion 错误读 CplD 保序同 Tag 多 CplD 按地址顺序返回地址乱序 违例数据可能错七、观测命令把 Posted 和 Non-Posted 抓出来1. MWr vs MRd 行为对比最直观的一课分析仪挂在 Root Port 口上驱动分别做一次内存写和一次内存读写 BAR 一个寄存器、再读回来MWr只看到上行一条包没有下行 Cpl。写完成。MRd看到上行 MRd然后下行一个或多个 CplD。读完成。两趟抓完Posted 和 Non-Posted 的差别就刻在脑子里了写是单程票读是往返票。2. 越界读让目标回 URdevmem2 0xffffffff# 对 BAR 外地址发起读设备枚举正常、BAR 已配的前提下对 BAR 外地址发 MRd目标会回 Cpl(UR)。动手前先lspci -vvv看 BAR 范围把越界地址算准再打。这一条的意义在于验证拒绝和无响应是两回事有 Cpl(UR) 回来说明地址解码链路是活的只是这个地址没归属。3. 停目标复现 CTO把目标设备的响应停掉拔卡、把目标功能禁掉、或者用支持错误注入的测试设备再发 MRd。这次什么都等不到超时后 dmesg 报 completion timeoutAER 里是 CTO 位。和上一条对比着做CTO 与 UR 的判别就牢了有 Cpl 看状态无 Cpl 看超时。4. 大读拆包验证保序把 MRRS 配大DevCtl 里配置默认 512 字节03 篇讲过发一个大块读。分析仪上能看到一个 MRd 对应多个 CplD同 Tag 的 CplD 按地址顺序返回后续拆分的 Lower Address 是 0。看到这些说明拆分和保序都正常。系列首发CSDN「PCIe 协议特性精讲」| 基准 PCIe 5.0Base Spec r5.0| 内核 Linux v7.0.0数字均以 Base Spec r5.0 为准6.4/7.0 差异单列错误欢迎评论区指出勘误会更新在文末。