Power management (简体中文)/Suspend and hibernate (简体中文)

From ArchWiki
翻译状态:本文是 Power_management/Suspend_and_hibernate翻译。上次翻译日期:2020-10-06。如果英文版本有所更改,则您可以帮助同步翻译。

现主要有三种挂起方式:suspend to RAM(挂起到内存,通称挂起/睡眠),suspend to disk(通称休眠),和hybrid suspend或者suspend to both(内存和硬盘都保存当前内存状态,通称混合休眠):

  • 睡眠(挂起)将机器中大多数和RAM不相关的部件断电,电脑状态仅仅保存在RAM中。关闭多数外围设备,保留鼠标键盘等少数外围设备,以对用户操作进行快速响应。建议用户将笔记本设置为合盖时进入此模式。同样建议将系统设置为使用电池而用户离开时自动进入此模式。
  • 休眠将机器内容保存至swap space并完全断电。再次开机时从硬盘读取swap进内存,恢复运行状态。和睡眠不同,休眠时不会耗电。
  • 混合休眠将电脑状态保存进swap,但并不对电脑断电,而是引用睡眠机制,从而使未掉电的电脑能从内存中恢复。如果电脑掉电(断电且电池耗尽),系统也可以从硬盘的swap中恢复,尽管比从内存中恢复慢一些。

以上三者均由许多底层接口提供基础功能,再由高级接口进行一定调整,以适配一些较为复杂的硬件与内核模块(如显卡唤醒后的重新初始化)

底层接口

直接使用低级接口显然会比任何高级接口都快很多,因为执行睡眠/休眠前后的钩子(hooks)需要时间。然而,钩子可以提供很多功能,如正确设置设备时钟、恢复无线网络状态等。因此,虽然这些接口可以被直接使用,我们仍然建议用户使用高级接口进行睡眠/休眠。

内核休眠(软件休眠,swsusp)

最直接的方法是直接告知内核中的软件挂起代码(swsusp)进入挂起状态,具体的方法和状态取决于硬件支持的程度。在现代内核中,向 /sys/power/state 写入特定字符串是触发此挂起的主要机制。

更多信息详见Linux内核文档

用户空间软件休眠(uswswsp)

uswsusp(“Userspace Software Suspend”)是内核Suspend to RAM机制的封装,该机制在挂起前和恢复后从用户空间执行一些有关图形适配器的操作。

详见 Uswsusp

高级接口

这些软件包的目标是提供二进制文件或脚本,使用户可以调它们来进行睡眠或休眠。在实际使用中,往往由其它软件(如桌面环境/DE)将它们与电源按钮、菜单点击或笔记本电脑盖事件联系起来。如果不想借助其他软件调用高级接口,又希望设备能在某些电源事件(如合上盖子或电池耗尽)时自动睡眠/休眠,你也许需要看看Acpid

systemd

systemd为睡眠、休眠和混合休眠提供原生支持。详见 Power management#Power management with systemd 。systemd是 Arch Linux 默认使用的接口。

关于配置睡眠或休眠钩子,详见 Power management#Sleep hooks。又见 systemctl(1)systemd-sleep(8)systemd.special(7)

休眠

为了使用休眠,用户需要创建一个交换分区或文件。使用resume= 内核参数向内核传递swap信息,该参数应通过引导程序配置。您还需要configure the initramfs。这将让内核尝试从早期用户空间中的指定swap分区/文件恢复。下面将详细描述这三个步骤。

swap分区/文件的大小

即使swap比内存小,成功休眠的可能性仍然很大。内核文档如是说:

/sys/power/image_size控制由休眠机制创建的镜像的大小。它可以被写入一个字符串,表示将用作镜像大小上限的非负整数(以字节为单位)。休眠机制将尽力确保镜像大小不会超过这个数字。但是,如果这是不可能的,它将尝试使用尽可能小的镜像休眠。特别地,如果将“0”写入此文件,则休眠镜像将尽可能小。读取该文件将显示当前镜像大小限制,默认设置为可用RAM的2/5。

您可以减小/sys/power/image_size的设定值以使休眠镜像尽可能小(对于小swap),或者增大它以加快休眠过程(也许)。对于RAM很大大的系统,较小的值可能会显著提高系统从休眠中恢复的速度。这是一个临时文件,参阅Systemd#systemd-tmpfiles - temporary files以固定你的修改。

休眠镜像不能跨多个交换分区和/或交换文件。它必须完全适配一个交换分区或一个交换文件[1]

必需的内核参数

想要休眠,必须传递kernel parameter resume=swap_device。(以 persistent block device naming 方法中的任何一个替换掉swap_device )如:

  • resume=UUID=4209c845-f495-4c43-8a03-5363dd433153
  • resume=“PARTLABEL=swap分区卷标”
  • resume=/dev/archVolumeGroup/archLogicalVolume——如果swap在LVM逻辑卷上

内核参数只有在重新启动后才会生效。要想不重启而立即休眠,请从lsblk获取卷的主要和次要设备号,并 echo major:minor/sys/power/resume。如果使用交换文件,则还应将 resume offset 添加到/sys/power/resume_offset[2]

例如,如果交换设备是8:3

# echo 8:3 > /sys/power/resume

或者在休眠到交换文件时,如果交换文件位于卷8:2上并且offset为38912

# echo 8:2 > /sys/power/resume
# echo 38912 > /sys/power/resume_offset

休眠到交换文件

警告: Btrfs版本5.0之前的Linux内核不支持交换文件。不注意此警告可能会导致文件系统损坏。当通过循环设备装入Btrfs时,交换文件可能会被使用,这将导致交换性能严重下降。

使用交换文件需要设置resume=swap_device,另外还需要设置resume_offset=swap_file_offset内核参数。见 内核文档

swap_device 是交换文件所在的卷,其格式与root parameter相同。swap_file_offset的值可以通过运行filefrag -v swap_file来获得,输出为表格形式,所需的值位于physical_offset列的第一行。例如:

# filefrag -v /swapfile
Filesystem type is: ef53
File size of /swapfile is 4294967296 (1048576 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..       0:      38912..     38912:      1:            
   1:        1..   22527:      38913..     61439:  22527:             unwritten
   2:    22528..   53247:     899072..    929791:  30720:      61440: unwritten
...

在本例中,swap_file_offset 的值是38912(第一行physical_offset下面,两个英文句点..前面的数字)。

提示:
  • 以下命令的结果可用于获取swap_devicefindmnt -no UUID -T /swapfile
  • 以下命令的结果可用于获取swap_file_offsetfilefrag -v /swapfile | awk '{ if($1=="0:"){print substr($4, 1, length($4)-2)} }'
  • swap_file_offset的值也可以通过运行swap-offset swap_file来获得。工具集uswsusp中提供了swap-offset二进制文件。如果使用此方法,则必须在/etc/suspend.conf通过键resume deviceresume offset。在这种情况下,不需要重新启动。
注意:
  • 对于堆叠块设备,如加密容器(LUKS)、RAID或LVM,resume参数必须指向包含交换文件的文件系统的未锁定/映射设备。
  • 如果交换文件在/home/中,则systemd-logind将无法确定其大小,因此将阻止休眠。解决办法见systemd issue 15354
提示: 如果你只想休眠而不扩展RAM,则可能需要减少交换文件的swappiness

休眠到 Btrfs 上的交换文件中

最新版本的 systemd 支持在交换文件上休眠[3]

可以使用btrfs_map_physical.c计算resume_offset。 不要尝试使用filefrag工具,在Btrfs上,从filefrag获得的physical_offset不是实际offset。因为btrfs有一个虚拟磁盘地址空间以支持多个设备。[4] 下载或复制tool btrfs_map_physical.c到名为btrfs_map_physical.c的文件中,然后编译它,

$ gcc -O2 -o btrfs_map_physical btrfs_map_physical.c

然后运行。输出示例如下。

# ./btrfs_map_physical /path/to/swapfile
FILE OFFSET  EXTENT TYPE  LOGICAL SIZE  LOGICAL OFFSET  PHYSICAL SIZE  DEVID  PHYSICAL OFFSET
0            regular      4096          2927632384      268435456      1      4009762816
4096         prealloc     268431360     2927636480      268431360      1      4009766912
268435456    prealloc     268435456     3251634176      268435456      1      4333764608
536870912    prealloc     268435456     3520069632      268435456      1      4602200064
805306368    prealloc     268435456     3788505088      268435456      1      4870635520
1073741824   prealloc     268435456     4056940544      268435456      1      5139070976
1342177280   prealloc     268435456     4325376000      268435456      1      5407506432
1610612736   prealloc     268435456     4593811456      268435456      1      5675941888

请注意此工具返回的第一个physical offset。在此案例中,我们使用4009762816。同样需要在getconf PAGESIZE中获得pagesize。

要计算resume_offset的值,请将物理偏移量除以页面大小。在这个例子中,它是4009762816 / 4096 = 978946

休眠到精简配置的LVM卷中

在精简配置的LVM卷中休眠是可能的,但必须确保该卷已完全分配。否则系统将无法从中恢复。

可以通过简单地用零填充来完全分配LVM卷。例如:

# dd if=/dev/zero of=/dev/vg0/swap bs=1M status=progress

想确认卷是否已完全被分配,使用:

# lvs
 LV                   VG  Attr       LSize   Pool Origin    Data%  Meta%  Move Log Cpy%Sync Convert
 swap                 vg0 Vwi-aot--- 10.00g  pool           100

完全分配的卷将显示为具有100%的Data%

警告: 不要在用于休眠的精简配置的swap分区上使用TRIM,即:不要在/etc/fstab使用discard,也不要在swapon时加入-d--discard参数。否则已被使用的空间会被重新去分配

需要添加resume=/dev/sdxY (sdxY 是swap分区的名字) ,让系统在启动时读取swap分区中的内容。

配置 initramfs

1. 添加resume钩子或systemd钩子 编辑/etc/mkinitcpio.conf,在HOOKS行中添加resume钩子或systemd钩子: 例如该行原有内容是:

HOOKS="base udev autodetect modconf block filesystems keyboard fsck"

添加resume后就是:

HOOKS="base udev resume autodetect modconf block filesystems keyboard fsck"

添加systemd后是:

HOOKS="base udev systemd autodetect modconf block filesystems keyboard fsck"

resume和systemd二选一加入即可。

注意: 如果使用lvm分区,需要将resume放在lvm后面

使用lvm的示例:

HOOKS="base udev autodetect modconf block lvm2 resume filesystems keyboard fsck"

2. 重新生成 initramfs 镜像:

mkinitcpio -P

设置低电量休眠

用于带有内置电池的设备。

当电池电量极低时,使其休眠,以免丢失数据。 修改/etc/UPower/UPower.conf相关配置.示例,在电量低至%5时自动休眠:

PercentageLow=15  #<=15%低电量
PercentageCritical=10  #<=10%警告电量
PercentageAction=5  #<=5%执行动作(即CriticalPowerAction)的电量
CriticalPowerAction=Hibernate #(在本示例中是电量<=5%)执行休眠

当电池低至5%,设备会自动休眠。

CriticalPowerAction的取值有Poweroff、Hibernate和Hybid-sleep。

更多配置项参考该文件中的说明。

设置盖上笔记本盖子或按下电源键休眠

1. 编辑/etc/systemd/logind.conf , 盖上盖子休眠,添加:

HandleLidSwitch=hibernate

按下电源键休眠,添加:

HandlePowerKey=hibernate

2. 执行以下命令使其立即生效:

systemctl restart systemd-logind

其他详细的设置请参考 电源管理页面

故障排除

ACPI_OS_NAME

你也许需要调整你的 DSDT table 来让它工作。 参阅 DSDT 词条。

睡眠/休眠无法进行,或无法稳定进行

有相当多的错误报告指出,他们的屏幕在给出可读的错误信息前就关闭了,有时屏幕关闭了,却再也无法唤醒。这些故障在笔记本电脑和台式机上都曾发生。(这不是个官方解决方案),但切换到旧的内核,尤其是LTS内核,或许可以修复这个问题。

使用硬件看门狗(默认是禁用的,详阅systemd-system.conf(5) § OPTIONS中的RuntimeWatchdogSec=条目。存在bug的硬件看门狗可能会在系统成功创建休眠镜像前就重置电脑状态。

有时屏幕会因为 initramfs 中进行的设备初始化过程而变黑。移除 Mkinitcpio#MODULES 里你可能设置了的任何模块,尤其是KMS显卡驱动 early KMS 并重新构建initramfs,也许能解决这个问题。唤醒前初始化这样的设备可能会带来一些冲突,这些冲突将阻止系统从休眠中唤醒。但这一般不会影响系统从睡眠(挂起)中唤醒。可参考一篇博文: best practices to debug suspend issues

将旧的 radeon 驱动换成新的 AMDGPU 驱动也许能让休眠与唤醒过程更加成功。

对于英特尔核芯显卡,启用 early KMS 也许能解决休眠黑屏的问题。详见 Kernel mode setting#Early KMS start

在升级到内核版本 4.15.3 后,唤醒系统时可能会黑屏,只在黑屏上留下一个静止不动的鼠标指针。 禁用 Blacklisting 这个模块 nvidiafb 可能会有帮助。[5]

使用 Intel CPU 并且为触摸板加载 intel_lpss_pci 模块的笔记本电脑,可能会在唤醒时发生内核崩溃(Caps Lock灯闪烁)。[6]这个模块需要以这样的方式加入 initramfs 里:

/etc/mkinitcpio.conf
MODULES=(... intel_lpss_pci ...)

然后 regenerate the initramfs

网络唤醒(WoL, Wake-on-LAN)

如果网络唤醒被启用,网卡可能会消耗电力,即使电脑处于休眠状态。

挂起后不经操作就立即唤醒

对于带有 LynxPoint 或 LynxPoint-LP 芯片组的 Intel Haswell 系列电脑,有报告称它们会在挂起后不经用户操作就立即被唤醒。这与错误的 BIOS ACPI 信号实现方式,以及 xhci_hcd 如何处理它们都有关。作为一个临时性的解决方案,依据报告,受影响的电脑系列被逐个逐个地加入了内核里的一份黑名单(名叫XHCI_SPURIOUS_WAKEUP)。

电脑也有可能在其它情况下不经操作就立即唤醒,比如,一个 USB 设备在挂起后插入,从而启动了 ACPI 唤醒触发器。对于这样的情况,如果受影响的电脑系列未被加入上述黑名单,一个可行的方案是禁用相关的唤醒触发器。一个例子见后。[7]

查看当前配置:

$ cat /proc/acpi/wakeup
Device  S-state   Status   Sysfs node
...
EHC1      S3    *enabled  pci:0000:00:1d.0
EHC2      S3    *enabled  pci:0000:00:1a.0
XHC       S3    *enabled  pci:0000:00:14.0
...

可以看到相关的设备有 EHC1EHC2XHC (USB 3.0设备). 要切换它们的状态,你需要以 root 权限把设备名写入到下面的文件里。

# echo EHC1 > /proc/acpi/wakeup
# echo EHC2 > /proc/acpi/wakeup
# echo XHC > /proc/acpi/wakeup

这将让挂起重新可用。然而,这个设定只是临时性的,会随着每次重启而重置。若想自动化这个操作,参阅 systemd#systemd-tmpfiles - temporary filesBBS thread 来获取更多信息,以及可能的解决方案。

同时禁用 PTXH 和 XHC0 的示例。由于某些原因,两个 PTXH 和一个 XHC0 在一起(或在不同的文件中)不能正常工作。

#  cat /etc/tmpfiles.d/100-disable-usb-wake.conf
#    Path                  Mode UID  GID  Age Argument
w    /proc/acpi/wakeup     -    -    -    -   PTXHXHC0

如果你使用 nouveau 驱动,电脑挂起后不经操作就立即唤醒也许是这个驱动里的一个 bug,该 bug 使显卡不能挂起。一个可行的解决方案是正好在睡眠前卸载 nouveau 内核模块,并在唤醒后重新加载。想这么做,你需要创建如下脚本:

/usr/lib/systemd/system-sleep/10-nouveau.sh
#!/bin/bash

case $1/$2 in
  pre/*)
    # echo "Going to $2..."
    /usr/bin/echo "0" > /sys/class/vtconsole/vtcon1/bind
    /usr/bin/rmmod nouveau
    ;;
  post/*)
    # echo "Waking up from $2..."
    /usr/bin/modprobe nouveau
    /usr/bin/echo "1" > /sys/class/vtconsole/vtcon1/bind
    ;;
esac

第一行 echo 将 nouveaufb 与 flamebuffer 终端(fbcon)解绑。通常是例子中的 vtcon1,但也有可能是别的,比如 vtcon* 。参看 /sys/class/vtconsole/vtcon*/name 来弄清楚在你的电脑里到底哪个才是 flamebuffer 设备。[8]

系统在休眠后没有断电

当你把你的系统休眠,系统应该(在保存状态后)断电。有时你会看见电源 LED 仍然亮着。如果存在这样的情况,在 sleep.conf.d(5) 里把 HibernateMode 设置为 shutdown 也许有用:

/etc/systemd/sleep.conf.d/hibernatemode.conf
[Sleep]
HibernateMode=shutdown

在进行了上述设置后,如果其它一切正常,当调用 systemctl hibernate 时,电脑就会正常地休眠后断电了。

参看

linux-laptop笔记本相关 linux笔记本设置休眠