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

posted @ 2021-09-08 21:38  Ditvelo  阅读(401)  评论(0)    收藏  举报