Power management (简体中文)/Suspend and hibernate (简体中文)
现主要有三种挂起方式: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
休眠到交换文件
使用交换文件需要设置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_device
:findmnt -no UUID -T /swapfile
- 以下命令的结果可用于获取
swap_file_offset
:filefrag -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 device
和resume offset
。在这种情况下,不需要重新启动。
- 对于堆叠块设备,如加密容器(LUKS)、RAID或LVM,
resume
参数必须指向包含交换文件的文件系统的未锁定/映射设备。 - 如果交换文件在
/home/
中,则systemd-logind将无法确定其大小,因此将阻止休眠。解决办法见systemd issue 15354。
休眠到 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%
/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的示例:
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 ...)
网络唤醒(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 ...
可以看到相关的设备有 EHC1
、EHC2
和 XHC
(USB 3.0设备). 要切换它们的状态,你需要以 root 权限把设备名写入到下面的文件里。
# echo EHC1 > /proc/acpi/wakeup # echo EHC2 > /proc/acpi/wakeup # echo XHC > /proc/acpi/wakeup
这将让挂起重新可用。然而,这个设定只是临时性的,会随着每次重启而重置。若想自动化这个操作,参阅 systemd#systemd-tmpfiles - temporary files 或 BBS 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
时,电脑就会正常地休眠后断电了。