systemd待机分析
在使用systemd的linux发行版上,待机一般都是通过dbus调用systemd待机,当然也可以手动systemctl suspend待机,这样systemd也会通过dbus通知其他应用,然后开始待机。
systemd待机相关源码位于src/sleep/sleep.c 由于只考虑待机,待机状态定义在 src/shared/sleep-config.c ,如果systemd接收到的参数是suspend时,将mem,standby,freeze赋值给states字符数组,如下:
if (streq(verb, "suspend")) {
allow = allow_suspend != 0;
/* empty by default */
modes = TAKE_PTR(suspend_mode);
if (suspend_state)
states = TAKE_PTR(suspend_state);
else
states = strv_new("mem", "standby", "freeze");
待机入口函数如下:
static int run(int argc, char *argv[]) {
bool allow;
_cleanup_strv_free_ char **modes = NULL, **states = NULL;
usec_t delay = 0;
int r;
log_setup_service();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
r = parse_sleep_config(arg_verb, &allow, &modes, &states, &delay); //解析配置文件,如果未配置则使用默认的设置。
if (r < 0)
return r;
if (!allow)
return log_error_errno(SYNTHETIC_ERRNO(EACCES),
"Sleep mode \"%s\" is disabled by configuration, refusing.",
arg_verb);
if (streq(arg_verb, "suspend-then-hibernate"))
return execute_s2h(delay);
else
return execute(modes, states); //开始执行待机/休眠
}
真正执行待机的函数如下
static int execute(char **modes, char **states) {
char *arguments[] = {
NULL,
(char*) "pre",
arg_verb,
NULL
};
static const char* const dirs[] = {
SYSTEM_SLEEP_PATH,
NULL
};
int r;
_cleanup_fclose_ FILE *f = NULL;
/* This file is opened first, so that if we hit an error,
* we can abort before modifying any state. */
f = fopen("/sys/power/state", "we");
if (!f)
return log_error_errno(errno, "Failed to open /sys/power/state: %m");
setvbuf(f, NULL, _IONBF, 0);
/* Configure the hibernation mode */
if (!strv_isempty(modes)) {
r = write_hibernate_location_info();
if (r < 0)
return log_error_errno(r, "Failed to write hibernation disk offset: %m");
r = write_mode(modes);
if (r < 0)
return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");;
}
//执行/usr/lib/systemd/system-sleep/ 下的所有脚本,传递的第一个参数为pre
execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
LOG_MESSAGE("Suspending system..."),
"SLEEP=%s", arg_verb);
//将状态值写入 /sys/power/state
r = write_state(&f, states);
if (r < 0)
log_struct_errno(LOG_ERR, r,
"MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
LOG_MESSAGE("Failed to suspend system. System resumed again: %m"),
"SLEEP=%s", arg_verb);
else
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
LOG_MESSAGE("System resumed."),
"SLEEP=%s", arg_verb);
//唤醒后继续执行 /usr/lib/systemd/system-sleep/ 下的所有脚本,传递的第一个参数为post
arguments[1] = (char*) "post";
execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
return r;
}
下面详细分析 r = write_state(&f, states);
函数,具体函数如下:
static int write_state(FILE **f, char **states) {
char **state;
int r = 0;
STRV_FOREACH(state, states) {
int k;
k = write_string_stream(*f, *state, WRITE_STRING_FILE_DISABLE_BUFFER);
if (k >= 0)
return 0;
log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state);
if (r >= 0)
r = k;
fclose(*f);
*f = fopen("/sys/power/state", "we");
if (!*f)
return -errno;
}
return r;
}
write_state () 将states的状态写入 /sys/power/state 进行待机;
其中STRV_FOREACH(state, states) 为for循环,将states字符数组的值循环给state,由write_string_stream将state写入/sys/power/state;
write_string_stream写完值时开始待机,systemd停留在此函数里,唤醒后才会得到返回值k;
返回待机正常的返回值时,跳出write_state继续执行;
如果得到错误的待机返回值,则继续将states字符数组的下一个值给state并写入/sys/power/state,直到进入待机或者三种状态全部待机失败。
此过程我打log,重新编译systemd,复现待机失败的情况确认过
如果需要知道为什么systemd将字符写进 /sys/power/state
时就开始待机,得分析内核的源码 kernel/power/main.c