长期以来,在笔记本上装 Linux 都会面临发热和电池续航下降(相比于在同一台笔记本上装 Windows)的问题。这个主要是驱动的电源管理不完善,很多设备的省电功能没有启动造成的。不过最近随着主要的厂商(Intel, AMD, Qualcomm 等)拥抱开源,主动编写开源驱动,大部分问题都得到了解决。但由于还是有 bug 存在,并且电源管理的 bug 很容易造成整个系统 hang up, 所以发行版一般都把策略配置得比较保守。现在一般在接入电源是都会停用大部分电源管理功能,使用电池供电时才会启用。这样插电运行的时候就还是发热和耗电很厉害;而如果电源管理有 bug, 就会出现插电运行一切正常,不插电就 hang up 的现象。
前段时间在一台 ThinkPad p15 gen2 上装了 Linux, 就遇到了这个问题,断断续续调了很久,才解决问题。感觉这里的调试方法有参考意义,这里记录一下调试问题的过程。
背景
ThinkPad p15 gen2 是一个使用 Intel Core CPU 和 Nvidia Quadro 专业显卡的图形工作站。作为图形工作站,它的显卡接口连接方式和一般的笔记本不同。一般的笔记本是核显连接显示输出,独显只能做计算渲染,渲染结果拷贝回核显再进行输出。现在大多数的显卡切换方案,也是按照这个设计来的。但这台笔记本是 intel 核显连接内置显示屏,nvidia 独显连接外置显示屏。这个配置很难配好能适应各种情况的切换方案,由于我主要是连接外置显示器工作的,就在 UEFI 里面把核显禁用了,只用独显。只有在长期出差,没有外置显示器的情况下,才会使用核显。
除了显卡的问题,另外一个更严重的问题就是,这台机器在不接电源的时候,就会在开机进入桌面环境后不久就 hang 住,连关机都关不了,只能按 alt + sysrq + reisub 重启。由于这个机器的网卡声卡什么全都是 intel 的,就只有显卡和 NVMe 硬盘是第三方品牌,我就直接怀疑这两个设备有问题,把它们的电源管理禁用。但问题并没有解决,最后只好把全部 PCIe 设备的电源管理关掉,才没有问题。不过这样就完全没有续航了,2 个小时就耗尽电池。
在这期间,我还遇到了 type-c 供电驱动程序的 bug, 表现也是系统 hang up. 多个问题交织到一起更加增大了调试难度。
x86 电源管理概况
x86 平台上的电源管理大概分为 3 部分:
- CPU 电源管理:包括 freqency scaling, C-state 这些
- 平台电源管理:包括待机、休眠、关机、唤醒、电池充放电管理这些
- 外设设备的电源管理:目前的 x86 上的外设设备主要就分为 PCIe 和 USB 两大类
其中,CPU 就只有 Intel AMD 两家,并且文档资料齐全,厂商也支持开源驱动开发,电源管理支持得非常好。平台这块则是 firmware 通过 ACPI 向系统提供接口。这里台式机一般没有问题,但笔记本由于多了合盖检测、背光、电池这些,更加复杂,出问题的可能性比较大,不过老牌厂商,如 ThinkPad, Dell 这些,都经过了内核驱动和 ACPI 固件的长期磨合,一般也问题不大。外设设备中,USB 的规范比较具体,比如 HID 键盘鼠标、摄像头、U 盘这些都有通用协议和驱动(比如通用的 Mass Storage 驱动就可以适用于所有品牌的 U 盘),也不容易出问题。问题最大的就是 PCIe 设备,这类设备都是显卡、网卡这类的复杂设备,固件和驱动都很复杂,如果有 bug, 就很容易造成内核错误。
PCIe 设备电源管理
PCIe 设备的电源管理有统一的规范和通信协议。一个设备的电源状态分为 D0, D1, D2, D3 4 个状态。其中 D0 是完全启动正常工作,D3 是最深的省电状态。这两个状态是所有设备都要支持的,D1 和 D2 是可选的。CPU 可以通过写入设备的配置寄存器来改变电源状态。PCIe 设备在进入省电状态时,会关闭一部分电路,缓存、寄存器之类的状态信息可能会丢失。这样,在唤醒设备时,就需要重新初始化这部分状态。这需要驱动的密切配合,如果这个过程有 bug, 设备就会进入一个非预期的状态,驱动无法正常和设备通信,就会造成 hang up 之类的问题。
Linux 下的配置
内核
Linux 下,每个 PCIe 的电源管理 RUNTIME_PM
可以通过 sysfs 中的开关 /sys/bus/pci/devices/<address>/power/control
单独开启或关闭。开关的值可以是
on
: 设备保持开启状态,即禁用电源管理auto
: 自动控制,即启用电源管理
用户空间
有个 tlp
命令可以根据 /etc/tlp.conf
中的配置来调整 sysfs 中的各个开关。在多数发行版中,这个命令会通过 udev 规则来触发,在启动过程中、插拔电源的情况下执行。在多数发行版的默认配置中,tlp 会在电池运行时打开更多省电功能。
另外有个命令是 Intel 开发的 powertop
. 这是一个 TUI 应用,可以监控 CPU 的电源、外设状态,以及调整 sysfs 中的电源管理开关。
tlp 适合保存长期、持久化的配置,而 powertop 适合在调试过程中来使用。
调试过程
这台笔记本最严重的问题就是拔掉电源、电池供电时,会随机 hang 住。例如,不插电源开机,一般是可以正常进入桌面环境,但进入桌面环境后,尝试启动一个应用,如 firefox, 就会启动不了。这个时候其它部分看起来都是正常的,但在终端里尝试 kill 掉 firefox, 也会卡住。尝试执行 sudo journcalctl -k
或 dmesg
来检查日志也会卡住。甚至执行 sudo reboot
也会卡住。
这种已经启动的程序运行正常,新程序无法启动的问题,让我地第一时间怀疑问题是硬盘 I/O 相关的,于是第一时间通过 tlp 禁用了 NVMe 的电源管理,但是没有效果。考虑到 N 卡是闭源驱动,也禁用了电源管理,也没有效果。
这个时候我还不太了解 powertop 这个工具,就开始对比 tlp 的配置在电源供电和电池供电的状态下有哪些区别。最明显的区别就是,插电状态的默认 PCIe 电源管理是 on
, 电池状态是 auto
. 这里我把电池状态也改成 on
后,问题基本解决。
但这只能算是个能用的方案。在没有开启设备省电的情况下,机器发热比较厉害,并且电池续航只有大概 2.5 小时。
为什么说是基本解决呢?
因为这里还穿插了一个 type-c 供电驱动的 bug, 这个 bug 和 PCIe 电源管理的 bug 混在一起,很难调试。但根据最后的结论,这里关闭 PCIe runtime power management 后,就没有因为 PCIe 设备导致 hang up 的问题了。
这个 type-c bug, 会导致 kernel oops. 在过了一个多月,这个 kernel oops 被修复以后,我才重新找时间去定位 PCIe 的问题。
到了这一步,我能想到的就是通过二分法去查找因为问题的设备,即每次禁用一半设备的电源管理,看看问题还是否复线。但看到除了 NVMe 和显卡,其它设备全都是 Intel 的,我都怀疑是否有多个设备同时有问题,用 tlp 来配置又还是比较麻烦的,这个问题就脱了很久。
直到两个月后,有几天比较闲,我尝试去 powertop 这个工具里去看一下。这就发现 powertop 可以通过 TUI 列出所有外设并 toggle 每个设备的电源管理开关。于是我决定在插电运行,默认关闭所有 PCIe 设备电源管理的情况下,一个个打开电源管理开关,看看什么时候会出故障。
于是一个终端运行 dmesg
看日志,另一个终端开 powertop. 首先把我没有用的设备打开,看了一下,好像完全没用的就只有有线网卡。按下 Enter 打开电源管理,系统立刻表现出异常,过了十几秒中,耳机里的音乐也停了。所以这就找到问题了?
重启电脑,在 tlp 里面把有线网卡的电源管理禁用,尝试一下拔掉电源,好像没有出现任何问题。再继续使用电脑,多开几个新应用,也都没有问题。所以引起问题的竟然是一个 Intel 的,我根本没有使用的有线网卡???因为有线网卡这种设备,在我用过的环境中,从来没有出现过问题;Intel 的设备,也是从来没有出现过问题。
后续配置
把有线网卡加入 tlp 黑名单后,在电池模式下开启省电,大概有 5.5 小时的续航,续航时间翻了一倍。后面我顺便把插电状态下的 PCIe runtime power management 也打开了。虽然插电时没有电池续航的问题,但打开电源管理能让空闲时的 CPU 温度降低大约 7 度。
想到这个有线网卡我也用不上,后面我干脆在 UEFI 里面把网卡禁用了。
总结
- 如果一个问题只在不插电的时候出现,那么它大概率和省电配置有关,这时应该去配置 tlp.conf.
- Intel 的驱动也可能出问题,不能完全信任。
- 发行版默认在插电状态下会禁用一下省电配置,如果想要省电/静音/冷却,应该去修改 tlp.conf 把它打开。(包括台式机)
powertop
是个调试省电配置的好工具。
最后修改于 2022-09-10