第2章 PCIe电源管理架构:L0、L0s、L1、L1.1、L1.2状态的定义与转换
好,咱们直接进入正题。上一章我们聊了为什么ASPM重要,这一章就来拆解它的核心——那五个电源状态。
很多人刚接触PCIe电源管理时,都会被L0、L0s、L1这些代号搞得头大。其实说白了,它们就是一套标准化的省电方案,从“全速跑”到“几乎关机”,分成了几个档位。
2.1 状态概览:五个档位
我习惯把PCIe链路的状态想象成开车:
- L0 — 高速巡航,油门踩到底。
- L0s — 短暂松油门滑行,随时准备再加速。
- L1 — 靠边停车,但发动机不熄火。
- L1.1 — 熄火,但车门没锁。
- L1.2 — 熄火,锁车,甚至把电瓶线都拆了。
下面这张图能帮你快速建立起整体认知:
2.2 L0 状态 — 全速运行
L0 是链路的正常工作状态。在这个状态下,发送端和接收端的时钟、PLL(锁相环)全部开启,随时可以收发TLP(事务层包)和数据链路层包。
延迟?几乎没有。功耗?当然也是最高的。
我记得有个项目,板卡插在服务器上一直跑不满带宽。排查了半天,发现是ASPM没关,链路被降到了L0s。你想想看,驱动里一个配置没到位,性能直接少了一截。
2.3 L0s 状态 — 快速待命
L0s 是链路层的快速低功耗状态。它的退出延迟极短,大约只有几十纳秒。
它的设计思路很有意思:链路进入 L0s 后,发送端的时钟和 PLL 会被关闭,但接收端依然保持工作。这意味着只有发送方向进入了省电模式,接收方向还是全开的。
关键点:L0s 是单方向的状态。一个方向进入 L0s,另一个方向可能还在 L0。这在某些不对称流量场景下非常有用。
比如你的PCIe设备主要是读操作(大量数据从设备到CPU),那设备端发送方向可以进入 L0s,而CPU端发送方向仍保持 L0,减少写操作的延迟。
2.4 L1 状态 — 浅度睡眠
L1 比 L0s 更进一步。进入 L1 后,发送端和接收端的时钟都会被关闭,PLL 也可能被关闭。
退出 L1 的延迟比 L0s 大得多,一般在几微秒的量级。为什么会这样?
因为重新锁定时钟和PLL需要时间。你想想看,一个PLL从关闭到稳定输出,通常需要几个微秒。
这里有个我踩过的坑:曾经调试一块NVMe SSD,发现写性能忽高忽低。用逻辑分析仪抓了链路信号,发现每次写入间隔超过1毫秒时,链路就会进入L1。等下一次写入请求到来,链路要从L1恢复到L0,这期间几十微秒的时间内,数据只能等。这就是典型的问题。
2.5 L1.1 与 L1.2 — 深度睡眠
L1.1 和 L1.2 是在 L1 基础上进一步优化的子状态。这两个状态主要是 PCIe 3.0 及以后版本引入的。
L1.1:在 L1 的基础上,主参考时钟(Refclk)被关闭。这意味着链路两端的设备几乎完全与时钟断开了。
L1.2:更进一步,不仅主时钟关闭,链路的公共参考时钟也被关闭,甚至链路的电源都可能被切断。我见过一些服务器方案,在 L1.2 下链路的功耗可以降到几乎可以忽略。
注意:L1.1 和 L1.2 的退出延迟非常大,通常在几十微秒到几百微秒之间。如果你的设备需要频繁唤醒,不建议使用 L1.2。
2.6 状态转换条件与延迟对比
下面这个表格是我个人总结的,帮你快速对比几个关键参数:
| 状态 | 进入条件 | 退出延迟(典型) | 功耗降低 | 时钟状态 |
|---|---|---|---|---|
| L0 | 正常数据传输 | — | 基准 | 全开 |
| L0s | 单方向空闲 > 阈值 | ~40 ns | 中 | 发送端关闭 |
| L1 | 双方空闲 > 阈值 | ~3-5 μs | 大 | 全部关闭 |
| L1.1 | L1 基础上无活动 | ~10-20 μs | 更大 | 主参考关闭 |
| L1.2 | L1 基础上无活动 | ~50-200 μs | 极大 | 公共参考关闭 |
你可能注意到了,从 L0 到 L1,中间没有直接跳过的可能。系统需要先进入 L0s(如果允许),然后再进入 L1。这是硬件设计上的限制。
2.7 驱动层面的控制点
聊了这么多状态,咱们要回到驱动开发上。这些状态转换的触发条件,很多都可以通过寄存器和驱动代码来控制。
最常见的是以下几个控制点:
- ASPM enable bit:每个端口都有一个寄存器位,控制是否允许进入 L0s 和 L1。
- L1.1 和 L1.2 使能位:这些通常是更细粒度的控制,在设备的扩展能力寄存器里。
- 空闲超时阈值:链路空闲多长时间后才进入低功耗状态。这个值可以在驱动中修改。
实践建议:在我的项目中,我通常优先关闭 L1.2,因为它对延迟的影响太大了。L0s 和 L1 根据实际场景酌情使用。如果你在调试性能问题,第一步就是确认 ASPM 是否被意外打开。