2017-03-11 更新:
- 優化部分文字描述;
- 默認情況下禁用 swap 分區, 當執行休眠操作時先啟用 swap 分區, 然后再執行休眠操作(給
/usr/bin/{swapon,swapoff}
添加 S 權限位, 以便普通用戶修改 swap 配置);
基礎配置
因為筆記本只有 180GB 的固態硬盤, 當初安裝系統就使用 swap 文件代替 swap 分區. 首先檢查下 swap 文件大小, 確定其足以 dump 整個內存:
$ swapon -s
文件名 類型 大小 已用 權限
/swapfile file 8388604 1467244 -1
然后就是配置 GRUB 啟動參數:
title Arch
kernel (hd0,5)/boot/vmlinuz-linux root=/dev/sda6 rw quiet resume=/dev/sda6 resume_offset=2537472
initrd (hd0,5)/boot/initramfs-linux.img
其中 resume
參數是 swap 文件所在分區, resume_offset
是真正存儲內存 dump 數據(physical_offset
)的偏移, 可通過 filefrag
命令獲取:
# filefrag -v /swapfile
Filesystem type is: ef53
File size of /swapfile is 8589934592 (2097152 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 0: 2537472.. 2537472: 1:
1: 1.. 2047: 2537473.. 2539519: 2047: unwritten
2: 2048.. 4095: 2899968.. 2902015: 2048: 2539520: unwritten
3: 4096.. 6143: 2914304.. 2916351: 2048: 2902016: unwritten
....
在我的電腦上, resume_offset
值就是 2537472.
測試和問題分析
完成以上准備工作后執行 systemctl hibernate
命令執行休眠(dump 內存到 swap 文件並關機), 重啟時發現, 只有少數幾次能夠從休眠中復原環境, 大多數是恢復失敗直接進入登錄頁面(另外, 4.4 版本的內核對休眠支持有bug, 重啟后黑屏, 升級 4.9 后解決該問題). 查看日志發現:
2月 25 20:10:07 hostname systemd[1]: Starting Hibernate...
2月 25 20:10:39 hostname kernel: PM: Hibernation image not present or could not be loaded.
2月 25 20:10:39 hostname kernel: PM: Hibernation image partition 8:6 present
2月 25 20:10:39 hostname kernel: PM: Hibernation image not present or could not be loaded.
為什么會提示 Hibernation image not present or could not be loaded
, 但為什么有時候又能成功呢? 猜測在某些情況下將內存 dump 到 swap 文件時出錯了.
查看 Wiki 發現有個 /sys/power/image_size
參數配置, Wiki 說:
/sys/power/image_size 用來控制將內存 dump 到硬盤時所占空間的大小. 在 dump 內存時, 所占用的硬盤空間不會超過 /sys/power/image_size 的大小. 如果內存數據太多, 那就只會 dump 最小的鏡像到硬盤. 如果該文件值為 0, 則在 dump 內存時盡可能壓縮數據占用最少的硬盤空間(上限是 swap 分區/文件的大小). 該文件的默認值是內存的 2/5 .
看了上面這段描述, 猜測是/sys/power/image_size 值過小致使內存 dump 文件不完整, 從而導致無法從休眠中啟動恢復環境. 於是將其修改為 0:
$ sudo tee /sys/power/image_size <<< 0
[sudo] username 的密碼:
0
在內存使用率超過 2/5 的情況下測試通過.
然而, 重啟后 /sys/power/image_size
配置值又恢復為默認的內存大小的 2/5, System: Temporary Files 介紹了一種方法:
新建文件 /etc/tmpfiles.d/modify_power_image_size.conf
, 內容為:
w /sys/power/image_size - - - - 0
重啟后確認已生效:
$ cat /sys/power/image_size
0
擴展配置
通過 acpid 捕獲合上筆記本屏幕事件
安裝並啟用 acpid
:
sudo pacman -S acpid
sudo systemctl enable acpid
編輯 /etc/acpi/handler.sh
的 button/lid
部分, 當合上筆記本屏幕時, 執行鎖屏和掛起(睡眠)操作:
DISPLAY=:0.0 su -c - your_username /usr/bin/slimlock &
systemctl suspend
處理合上筆記本屏幕事件:
button/lid)
case "$3" in
close)
logger 'LID closed'
DISPLAY=:0.0 su -c - your_username /usr/bin/slimlock &
systemctl suspend
;;
open)
logger 'LID opened'
;;
*)
logger "ACPI action undefined: $3"
;;
附 /etc/acpi/handler.sh
完整文件:
#!/bin/bash
# Default acpi script that takes an entry for all actions
case "$1" in
button/power)
case "$2" in
PBTN|PWRF)
logger 'PowerButton pressed'
;;
*)
logger "ACPI action undefined: $2"
;;
esac
;;
button/sleep)
case "$2" in
SLPB|SBTN)
logger 'SleepButton pressed'
;;
*)
logger "ACPI action undefined: $2"
;;
esac
;;
ac_adapter)
case "$2" in
AC|ACAD|ADP0)
case "$4" in
00000000)
logger 'AC unpluged'
;;
00000001)
logger 'AC pluged'
;;
esac
;;
*)
logger "ACPI action undefined: $2"
;;
esac
;;
battery)
case "$2" in
BAT0)
case "$4" in
00000000)
logger 'Battery online'
;;
00000001)
logger 'Battery offline'
;;
esac
;;
CPU0)
;;
*) logger "ACPI action undefined: $2" ;;
esac
;;
button/lid)
case "$3" in
close)
logger 'LID closed'
DISPLAY=:0.0 su -c - your_username /usr/bin/slimlock &
systemctl suspend
;;
open)
logger 'LID opened'
;;
*)
logger "ACPI action undefined: $3"
;;
esac
;;
*)
logger "ACPI group/action undefined: $1 / $2"
;;
esac
# vim:set ts=4 sw=4 ft=sh et:
借助 zenity
編寫系統操作的小程序
借助 zenity
編寫系統操作APP, 支持 睡眠(掛起)/深度睡眠/休眠/關機/重啟(在休眠前啟用 swap 分區):
#!/bin/sh
function Suspend() {
slimlock &
sleep 1
systemctl suspend
}
HybridSleep() {
slimlock &
sleep 1
/usr/bin/swapon /swapfile && systemctl hybrid-sleep
}
function Hibernate() {
slimlock &
/usr/bin/swapon /swapfile && systemctl hibernate
}
function Shutdown() {
zenity --question --text="確定關機?" && \
# echo "do shutdown" \
systemctl poweroff
}
function Reboot() {
zenity --question --text="確定重啟?" && \
# echo "do reboot" \
systemctl reboot
}
type=$(zenity --list \
--timeout="10" \
--width="200" \
--height="240" \
--title="要拋棄我啦?" \
--column="操作" \
"Suspend" \
"HybridSleep" \
"Hibernate" \
"Shutdown" \
"Reboot"
)
ret=$?
if [ $ret == 1 ]
then
# 點擊 "關閉" 或 "取消"
echo "no choice, exit"
exit
elif [ $ret == 5 ]
then
echo "timeout"
exit
fi
eval $type
參考鏈接: