12.13.电源和资源管理

以一种有效的方式利用硬件资源是很重要的。电源和资源管理允许操作系统监控系统极限,并在系统温度意外升高时可能提供警报。提供电源管理的一个早期规范是高级电源管理(APM)设施。APM 根据系统的活动来控制其电源的使用。然而,对于操作系统来说,管理一个系统的电源使用和热属性是困难的,也不灵活。硬件是由 BIOS 管理的,用户对电源管理设置的可配置性和可见性有限。APMBIOS 是由供应商提供的,是针对硬件平台的。操作系统中的 APM 驱动程序介导对 APM 软件接口的访问,该接口允许对功率水平进行管理。

APM 有四个主要问题。首先,电源管理是由供应商特定的 BIOS 完成的,与操作系统分开。例如,用户可以在 APMBIOS 中为硬盘设置空闲时间值,这样一来,如果超过了空闲时间,BIOS 就会在未经操作系统同意的情况下将硬盘旋转下来。第二,APM 逻辑被嵌入到 BIOS 中,它在操作系统的范围之外运行。这意味着用户只能通过将新的 APMBIOS 闪入 ROM 来解决 APMBIOS 的问题,这是一个危险的程序,一旦失败,有可能使系统处于无法恢复的状态。第三,APM 是一项针对供应商的技术,这意味着有很多重复的工作,在一个供应商的 BIOS 中发现的错误可能无法在其他供应商中得到解决。最后,APMBIOS 没有足够的空间来实现复杂的电源策略或能够很好地适应机器的用途。

在很多情况下即插即用 BIOS(PNPBIOS)都不可靠。PNPBIOS 是 16 位的技术,所以操作系统必须使用 16 位的仿真技术才能与 PNPBIOS 方法对接。FreeBSD 提供了一个 APM 驱动程序,因为在 2000 年或之前制造的系统仍应使用 APM。这个驱动在 apm(4) 中做了说明。

APM 的后续版本是高级配置和电源接口(ACPI)。ACPI 是一个由供应商联盟编写的标准,为硬件资源和电源管理提供一个接口。它是操作系统引导的配置和电源管理的一个关键因素,因为它为操作系统提供了更多的控制和灵活性。

本章演示了如何在 FreeBSD 上配置 ACPI。然后,它提供了一些关于如何调试 ACPI 的提示,以及如何提交包含调试信息的问题报告,以便开发人员能够诊断和修复 ACPI 问题。

12.13.1. 配置 ACPI

在 FreeBSD 中,acpi(4) 驱动在系统启动时被默认加载,不应该被编译到内核中。这个驱动在启动后不能被卸载,因为系统总线使用它进行各种硬件交互。然而,如果系统遇到问题,可以通过在 /boot/loader.conf 中设置 hint.acpi.0.disabled="1 " 后重启,或者通过在加载器提示下设置这个变量来完全禁用 ACPI,如“第三阶段”所述。

注意

ACPI 和 APM 不能共存,应该分开使用。如果驱动程序注意到另一个正在运行,最后加载的那个将终止。

ACPI可以用 acpiconf-s 标志和 1 到 5 的数字来使系统进入睡眠模式。大多数用户只需要 1(快速挂起到 RAM)或 3(挂起到 RAM)。选项 5 执行软关闭,与运行 halt -p 相同。

acpi_video(4) 驱动程序使用 ACPI 视频扩展来控制显示切换和背光亮度。它必须在任何 DRM 内核模块之后加载。在加载该驱动后,Fn 亮度键将改变屏幕的亮度。可以通过检查 /var/run/devd.pipe 来检查 ACPI 事件:

...
# cat /var/run/devd.pipe
!system=ACPI subsystem=Video type=brightness notify=62
!system=ACPI subsystem=Video type=brightness notify=63
!system=ACPI subsystem=Video type=brightness notify=64
...

其他选项可以使用 sysctl。更多信息请参考 acpi(4)acpiconf(8) 的手册。

12.13.2. 常见错误

ACPI 存在于所有符合 ia32(x86)和 amd64(AMD)架构的现代计算机中。完整的标准有许多功能,包括 CPU 性能管理、电源平面控制、热区(thermal zones)、各种电池系统、嵌入式控制器和总线枚举。大多数系统实现的功能比完整标准要少。例如,台式机系统通常只实现总线枚举,而笔记本电脑可能也有冷却和电池管理支持。笔记本电脑也有挂起和恢复功能,并有其相关的复杂性。

一个符合 ACPI 标准的系统有各种组件。BIOS 和芯片组供应商在内存中提供各种固定表,如 FADT,指定 APIC 地图(用于 SMP)、配置寄存器和简单的配置值等。此外,一个字节码表,即差异化系统描述表 DSDT,指定了一个树状的设备和方法的名称空间。

ACPI 驱动程序必须解析这些固定的表格,实现字节码的解释器,并修改设备驱动程序和内核以接受来自 ACPI 子系统的信息。对于FreeBSD,Intel® 提供了一个解释器(ACPI-CA),与 Linux® 和 NetBSD 共享。ACPI-CA 源代码的路径是 src/sys/contrib/dev/acpica。允许 ACPI-CA 在 FreeBSD 上工作的胶合代码是在 src/sys/dev/acpica/Osd。最后,实现各种 ACPI 设备的驱动在 src/sys/dev/acpica 中找到。

要使 ACPI 正确工作,所有的部分都必须正确工作。下面是一些常见的问题,按照出现的频率排列,以及一些可能的解决方法或修复方法。如果修复方法不能解决问题,请参阅“获取和提交调试信息”,了解如何提交错误报告的说明。

12.13.2.1. 鼠标问题

在某些情况下,从暂停操作中恢复会导致鼠标失败。一个已知的解决方法是在 /boot/loader.conf 中添加 hint.psm.0.flags="0x3000"

12.13.2.2. 暂停/恢复

ACPI 有三种暂停到 RAM(STR)的状态,S1-S3,和一种暂停到磁盘的状态(STD),称为 S4。STD可以用两种不同的方式实现。S4BIOS 是一个由 BIOS 协助的挂起到磁盘的状态,S4OS 则完全由操作系统实现。当插上电源但不开机时,系统所处的正常状态是 "软关闭"(S5)。

使用 sysctl hw.acpi 来检查与挂起有关的项目。这些例子的结果来自 Thinkpad:

hw.acpi.supported_sleep_state: S3 S4 S5
hw.acpi.s4bios: 0

使用 acpiconf -s 来测试 S3S4S5s4bios 为一(1)表示S4BIOS的支持而不是 S4操作系统的支持。

当测试挂起/恢复时,如果支持,从S1开始。这个状态最有可能工作,因为它不需要太多的驱动支持。没有人实现S2,它与S1类似。接下来,尝试S3。这是最深的 STR 状态,需要大量的驱动支持来正确地重新初始化硬件。

暂停/恢复的一个常见问题是,许多设备驱动程序不能正确地保存、恢复或重新初始化其固件、寄存器或设备内存。作为调试问题的第一次尝试,请尝试:

# sysctl debug.bootverbose=1
# sysctl debug.acpi.suspend_bounce=1
# acpiconf -s 3

这个测试模拟了所有设备驱动程序的暂停/恢复周期,而没有实际进入 S3 状态。在某些情况下,诸如丢失固件状态、设备看门狗超时、永远重试等问题,都可以用这种方法捕捉到。注意,系统不会真正进入 S3 状态,这意味着设备可能不会失去电源,即使暂停/恢复方法完全消失,许多设备也能正常工作,这与真正的 S3 状态不同。

如果前面的测试有效,在笔记本电脑上可以配置系统在关闭盖子时暂停进入 S3 ,并在再次打开时恢复:

# sysctl hw.acpi.lid_switch_state=S3

这一变化可以在重启后持续进行:

# echo 'hw.acpi.lid_switch_state=S3' >> /etc/sysctl.conf

更困难的情况需要额外的硬件,例如通过串行控制台进行调试的串行端口和电缆,使用 dcons(4) 的火线端口(Firewire port)和电缆,以及内核调试技能。

为了帮助隔离问题,尽可能多地卸载驱动程序。如果它能工作,通过加载驱动程序来缩小问题所在,直到它再次失败。通常,像 nvidia.ko 这样的二进制驱动、显示驱动和 USB 会有最多的问题,而以太网接口通常工作良好。如果驱动程序可以被正确加载和卸载,可以通过在 /etc/rc.suspend/etc/rc.resume 中添加适当的命令来实现自动化。如果恢复后显示混乱,试着将 hw.acpi.reset_video 设置为1。尝试为 hw.acpi.sleep_delay 设置更长或更短的值,看看是否有帮助。

试着加载一个最新的 Linux® 发行版,看看挂起/恢复是否在相同的硬件上工作。如果它在 Linux® 上有效,那就很可能是 FreeBSD 的驱动问题。缩小哪个驱动导致问题的范围将有助于开发人员解决这个问题。由于 ACPI 的维护者很少维护其他的驱动,例如声音或 ATA,任何驱动问题都应该发布到 FreeBSD-CURRENT 邮件列表中并邮寄给驱动维护者。高级用户可以在有问题的驱动程序中加入调试 printf(3) 的功能,以追踪它在恢复功能的哪个地方挂掉。

最后,尝试禁用 ACPI,启用 APM。如果挂起/恢复功能在 APM 下有效,那就坚持使用 APM,特别是在旧硬件上(2000 年以前)。供应商花了一些时间来纠正对 ACPI 的支持,而且旧硬件更有可能在 ACPI 上出现 BIOS 问题。

12.13.2.3. 系统挂起

大多数系统挂起是由于中断丢失或中断风暴造成的。芯片组可能会根据启动、BIOS如何在 APIC(MADT)表的正确性之前配置中断,以及系统控制中断(SCI)的路由而出现问题。

中断风暴可以通过检查 vmstat -i 的输出并查看有 acpi0 的那一行来区别于丢失的中断。如果计数器的增加速度超过每秒几次,就有一个中断风暴。如果系统出现挂起的情况,尝试中断到 DDB(控制台的 CTRL + ALT + ESC)并输入show interrupts

当处理中断问题时,尝试在 /boot/loader.conf中用 hint.apic.0.disabled="1 " 禁用 APIC 支持。

12.13.2.4. Panic

Panic 对于 ACPI 来说是比较少见的,而且是最优先修复的。第一步是隔离再现恐慌的步骤,如果可能的话,并得到一个回溯。按照”从串行线进入 DDB 调试器“中关于启用选项DDB 和设置串行控制台的建议,或者设置一个转储分区。要在 DDB 中获得一个回溯,使用 tr. 当手写回溯时,至少要获得回溯中的最后五行和前五行。

然后,尝试通过禁用 ACPI 启动来隔离问题。如果成功了,通过使用 debug.acpi.disable 的不同值来隔离 ACPI 子系统。参见 acpi(4) 的命令手册以了解一些例子。

12.13.2.5. 系统在暂停和关机之后启动

首先,尝试在 /boot/loader.conf 中设置 hw.acpi.disable_on_poweroff="0"。这可以防止 ACPI 在关机过程中禁用各种事件,一些系统出于同样的原因需要将这个值设置为 1(默认值)。这通常可以解决系统在挂起或关机后自发开机的问题。

12.13.2.6. BIOS 含有带 bug 的字节码

一些BIOS供应商提供了不正确或有错误的字节码。这通常表现为像这样的内核控制台信息:

ACPI-1287: *** Error: Method execution failed [\\_SB_.PCI0.LPC0.FIGD._STA] \\
(Node 0xc3f6d160), AE_NOT_FOUND

通常,这些问题可以通过更新 BIOS 到最新版本来解决。大多数控制台信息是无害的,但如果有其他问题,如电池状态不工作,这些信息是开始寻找问题的一个好地方。

12.13.3. 覆盖默认的 AML

被称为 ACPI 机器语言(AML)的 BIOS 字节码是由称为 ACPI 源语言(ASL)的源语言编译而成。AML 可以在被称为差异化系统描述表(DSDT)的表格中找到。

FreeBSD 的目标是让每个人都有工作的 ACPI,而不需要任何用户干预。对于 BIOS 供应商所犯的常见错误,目前仍在开发解决方法。Microsoft® 解释器 ( acpi.sysacpiec.sys ) 并没有严格检查是否符合标准,因此许多只在 Windows® 下测试 ACPI 的 BIOS 供应商从未修复他们的 ASL。FreeBSD 的开发人员继续识别并记录哪些非标准的行为是 Microsoft® 的解释器所允许的,并对其进行复制,这样 FreeBSD 就可以在不强迫用户修正 ASL 的情况下工作。

为了帮助识别有缺陷的行为,并有可能进行手动修复,可以对系统的 ASL 进行复制。要把系统的 ASL 复制到一个指定的文件名下,使用 acpidump的-t,显示固定表的内容,和-d,来反汇编 AML:

# acpidump -td > my.asl

一些AML版本假设用户运行的是 Windows®。要覆盖这一点,在 /boot/loader.conf 中设置 hw.acpi.osname="Windows 2009",使用 ASL 中列出的最新的 Windows® 版本。

其他解决方法可能需要定制 my.asl。如果这个文件被编辑了,用下面的命令编译新的 ASL。警告通常可以被忽略,但错误是错误,通常会妨碍 ACPI 的正常工作。

# iasl -f my.asl

包括 -f 将强制创建 AML,即使在编译过程中出现了错误。一些错误,例如缺少返回语句,会由 FreeBSD 解释器自动解决。

iasl 的默认输出文件名是 DSDT.aml。通过编辑 /boot/loader.conf 来加载这个文件,而不是 BIOS 的错误副本,后者仍然存在于闪存中,如下所示:

acpi_dsdt_load="YES"
acpi_dsdt_name="/boot/DSDT.aml"

请确保将 DSDT.aml 复制到 /boot,然后重新启动系统。如果这能解决这个问题,请将新旧ASL的 diff(1)发送到 FreeBSD ACPI邮件列表,以便开发人员能够解决 acpica 中的错误行为。

12.13.4. 获得并提交调试信息

ACPI 驱动程序有一个灵活的调试工具。可以指定一组子系统和粗略的程度。调试的子系统被指定为层,并被分解为组件(ACPI_ALL_COMPONENTS)和 ACPI 硬件支持(ACPI_ALL_DRIVERS)。调试输出的粗略程度被指定为级别,范围从仅仅报告错误( ACPI_LV_ERROR )到一切( ACPI_LV_VERBOSE )。级别是一个位掩码,所以可以同时设置多个选项,用空格隔开。在实践中,应该用一个串行控制台来记录输出,这样它就不会因为控制台消息缓冲区的刷新而丢失。在 acpi(4) 中可以找到各个层和级别的完整列表。

调试输出在默认情况下是不启用的。要启用它,如果 ACPI 被编译到内核中,请在自定义的内核配置文件中添加 options ACPI_DEBUG。在 /etc/make.conf 中添加 ACPI_DEBUG=1 来全局启用它。如果使用模块而不是自定义内核,只重新编译 acpi.ko 模块,如下所示:

# cd /sys/modules/acpi/acpi && make clean && make ACPI_DEBUG=1

将编译好的 acpi.ko 复制到 /boot/kernel,并在 /boot/loader.conf 中添加所需的级别和层次。本例中的条目启用了所有 ACPI 组件和硬件驱动的调试信息,并以最简洁的级别输出错误信息:

debug.acpi.layer="ACPI_ALL_COMPONENTS ACPI_ALL_DRIVERS"
debug.acpi.level="ACPI_LV_ERROR"

如果所需的信息是由特定的事件触发的,比如挂起后再恢复,不要修改 /boot/loader.conf。相反,在启动并为特定事件准备好系统后,使用 sysctl 来指定层和级别。可以使用 sysctl 设置的变量与 /boot/loader.conf 中的 tunables 命名相同。

如果收集到了调试信息,就可以将其发送到 FreeBSD ACPI 邮件列表中,以便 FreeBSD ACPI 维护者使用这些信息来确定问题的根本原因并制定解决方案。

注意

在向该邮件列表提交调试信息之前,请确保安装了最新版本的 BIOS ,如果有的话,也请更新嵌入式控制器固件版本。

当提交错误报告时,请包含以下信息:

  • 对错误行为的描述,包括系统类型、型号以及导致错误出现的任何东西。如果是新的,尽可能准确地记下该 bug 开始出现的时间。

  • 运行 boot -vdmesg 的输出,包括由错误产生的任何错误信息。

  • 如果禁用 ACPI 有助于解决这个问题的话,那么请包含在禁用 ACPI 的情况下,boot -vdmesg 输出。

  • sysctl hw.acpi的输出,它列出了系统提供的功能。

  • 系统 ASL 的粘贴版本的 URL。不要直接将 ASL 发送到列表中,因为它可能非常大。通过运行这个命令生成一份 ASL 的副本:

    # acpidump -dt > name-system.asl
    

    用登录名代替姓名,用制造商/型号代替系统。例如,使用 njl-FooCo6000.asl

大多数 FreeBSD 开发者会关注 FreeBSD-CURRENT 邮件列表,但也应该把问题提交到 FreeBSD ACPI 邮件列表,以确保它被看到。在等待回复时要有耐心。如果问题没有立即显现出来,请提交一份错误报告。当输入 PR 时,请包括与上述要求相同的信息。这有助于开发人员跟踪问题并解决它。不要在没有给 FreeBSD ACPI 邮件列表发邮件的情况下发送 PR,因为这个问题很可能已经被报告过了。

12.13.5. 参考资料

你可以在以下地点找到更多关于 ACPI 的信息:

  • FreeBSD ACPI 邮件列表:https://lists.freebsd.org/pipermail/freebsd-acpi/
  • ACPI 2.0规范:http://acpi.info/spec.htm
  • acpi(4), acpi_thermal(4), acpidump(8), iasl(8) 和 acpidb(8) 的手册页面