sys_nice源码分析

sys_nice源码分析

sys_nice系统调用用于改变进程的优先级,下面来看。

sys_nice
kernel/sched/core.c

SYSCALL_DEFINE1(nice, int, increment)
{
    long nice, retval;

    increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH);
    nice = task_nice(current) + increment;

    nice = clamp_val(nice, MIN_NICE, MAX_NICE);
    if (increment < 0 && !can_nice(current, nice))
        return -EPERM;

    set_user_nice(current, nice);
    return 0;
}

clamp宏让increment变量限制在(-NICE_WIDTH, NICE_WIDTH)范围内。NICE_WIDTH的默认值为40。即nice系统调用用户提供的进程优先级只能限制在-40到40的范围内。

#define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)

接下来通过task_nice函数获取进程当前优先级对应的nice值,并与increment相加获得新的nice值。
clamp_val宏和clamp类似,将nice值限制在(MIN_NICE, MAX_NICE)范围内。MIN_NICE为-19,MAX_NICE为20。
再往下调用can_nice函数检查新的nice值是否会超过系统的限制值。
最后通过set_user_nice函数将新的nice值设置到task_struct中。

sys_nice->task_nice
include/linux/sched.h

static inline int task_nice(const struct task_struct *p)
{
    return PRIO_TO_NICE((p)->static_prio);
}
#define PRIO_TO_NICE(prio)  ((prio) - DEFAULT_PRIO)
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO     MAX_USER_RT_PRIO

task_nice首先从进程task_struct结构中获得静态优先级static_prio,然后通过PRIO_TO_NICE宏将其转化成nice值。PRIO_TO_NICE宏其实就是(prio - 120)。相反,NICE_TO_PRIO宏其实就是(nice + 120)。

sys_nice->can_nice
kernel/sched/core.c

int can_nice(const struct task_struct *p, const int nice)
{
    int nice_rlim = nice_to_rlimit(nice);

    return (nice_rlim <= task_rlimit(p, RLIMIT_NICE));
}

static inline long nice_to_rlimit(long nice)
{
    return (MAX_NICE - nice + 1);
}

nice_to_rlimit将nice值从-20到19反向对应到2到41,即-20对应41,19对应2。
转化后检查该值是否小于系统的限制值。task_rlimit内部通过系统调用获得该限制值,该限制值在内核启动时就已经确定下来。

sys_nice->set_user_nice
kernel/sched/core.c

void set_user_nice(struct task_struct *p, long nice)
{
    int old_prio, delta, queued;
    unsigned long flags;
    struct rq *rq;

    rq = task_rq_lock(p, &flags);
    if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
        p->static_prio = NICE_TO_PRIO(nice);
        goto out_unlock;
    }
    queued = task_on_rq_queued(p);
    if (queued)
        dequeue_task(rq, p, 0);

    p->static_prio = NICE_TO_PRIO(nice);
    set_load_weight(p);
    old_prio = p->prio;
    p->prio = effective_prio(p);
    delta = p->prio - old_prio;

    if (queued) {
        enqueue_task(rq, p, 0);
        if (delta < 0 || (delta > 0 && task_running(rq, p)))
            resched_curr(rq);
    }
out_unlock:
    task_rq_unlock(rq, p, &flags);
}

task_rq_lock获得进程p所属的运行队列rq。

如果当前进程的调度策略是SCHED_DEADLINE、SCHED_FIFO和SCHED_RR的一种,则直接通过NICE_TO_PRIO宏将nice值转化为优先级值并设置到static_prio中即可。
SCHED_DEADLINE采用了EDF调度算法,主要针对运行时间比较敏感的进程,SCHED_FIFO和SCHED_RR调度策略主要是针对实时进程。

如果是其他的调度策略,最典型的是SCHED_NORMAL,此时首先通过task_on_rq_queued检查当前进程是否在运行队列上,如果是则返回1,否则返回0。如果当前进程在运行队列上,就要通过dequeue_task函数将其移除当前运行队列,等待重新设置进程权重后再放回运行队列。dequeue_task函数和后面的enqueue_task函数在《enqueue_task和dequeue_task源码分析》文章中分析了。

接下来也要设置static_prio。根据前面的分析,该值的范围在101到140之间。

set_load_weight函数根据当前进程的静态优先级设置其对应的调度实体sched_entity的权重,内核在调度进程时,最终会通过计算该权重将进程对应的调度实体插入到一个红黑树中,然后再从该树中找到一个最合适的进程运行。

下面以CFS调度策略为例,effective_prio对于CFS调度的普通进程而言其实就是获得static_prio。再计算delta表示新旧两个static_prio的差。然后如果前面将进程从运行队列中出队,这里就要通过enqueue_task函数将其重新入队。如果delta小于0,则表示进程的优先级提高,如果delta大于0,表示进程的优先级降低,并且此时进程正在运行,这两种情况都要通过resched_curr函数设置运行队列当前正在运行的进程的TIF_NEED_RESCHED标志位,让其重新调度一次。task_running宏检查进程是否在运行中。

static inline int task_running(struct rq *rq, struct task_struct *p)
{
    return p->on_cpu;
}

sys_nice->set_user_nice->task_has_dl_policy
kernel/sched/sched.h

static inline int task_has_dl_policy(struct task_struct *p)
{
    return dl_policy(p->policy);
}
static inline int dl_policy(int policy)
{
    return policy == SCHED_DEADLINE;
}
static inline int task_has_rt_policy(struct task_struct *p)
{
    return rt_policy(p->policy);
}
static inline int rt_policy(int policy)
{
    return policy == SCHED_FIFO || policy == SCHED_RR;
}

task_has_dl_policy函数通过dl_policy函数检查进程的调度策略是否是SCHED_DEADLINE;task_has_rt_policy函数通过rt_policy函数检查进程的调度策略是否是SCHED_FIFO或SCHED_RR。

sys_nice->set_user_nice->set_load_weight
kernel/sched/core.c

static void set_load_weight(struct task_struct *p)
{
    int prio = p->static_prio - MAX_RT_PRIO;
    struct load_weight *load = &p->se.load;

    if (p->policy == SCHED_IDLE) {
        ...
        return;
    }

    load->weight = prio_to_weight[prio];
    load->inv_weight = prio_to_wmult[prio];
}

根据前面的分析可知这里的静态优先级static_prio的范围在101到140之间,减去MAX_RT_PRIO即100后,prio的范围限制在1到40之间。
接下来获得进程对应的调度实体sched_entity的权重load_weight,这里只考虑使用CFS策略调度的普通进程,最后将刚刚计算的prio值作为数组下表,在prio_to_weight和prio_to_wmult数组中查找对应的权重值及它的倒数,并设置到load_weight的weight和inv_weight变量中。

static const int prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};

static const u32 prio_to_wmult[40] = {
 /* -20 */     48388,     59856,     76040,     92818,    118348,
 /* -15 */    147320,    184698,    229616,    287308,    360437,
 /* -10 */    449829,    563644,    704093,    875809,   1099582,
 /*  -5 */   1376151,   1717300,   2157191,   2708050,   3363326,
 /*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,
 /*   5 */  12820798,  15790321,  19976592,  24970740,  31350126,
 /*  10 */  39045157,  49367440,  61356676,  76695844,  95443717,
 /*  15 */ 119304647, 148102320, 186737708, 238609294, 286331153,
};

设置prio_to_wmult数组是为了提升算法速度,例如在CFS调度策略中,会选择一个进程并计算其运行的虚拟时间,这个计算过程就会使用进程权重的倒数,因此在这里提前计算。

sys_nice->set_user_nice->effective_prio
kernel/sched/core.c

static int effective_prio(struct task_struct *p)
{
    p->normal_prio = normal_prio(p);
    if (!rt_prio(p->prio))
        return p->normal_prio;
    return p->prio;
}
static inline int normal_prio(struct task_struct *p)
{
    int prio;

    if (task_has_dl_policy(p))
        prio = MAX_DL_PRIO-1;
    else if (task_has_rt_policy(p))
        prio = MAX_RT_PRIO-1 - p->rt_priority;
    else
        prio = __normal_prio(p);
    return prio;
}
static inline int __normal_prio(struct task_struct *p)
{
    return p->static_prio;
}

effective_prio对于采用CFS调度策略的普通进程而言,最终返回的就是进程的static_prio。

sys_nice->set_user_nice->resched_curr
kernel/sched/core.c

void resched_curr(struct rq *rq)
{
    struct task_struct *curr = rq->curr;
    int cpu;

    if (test_tsk_need_resched(curr))
        return;

    cpu = cpu_of(rq);

    if (cpu == smp_processor_id()) {
        set_tsk_need_resched(curr);
        set_preempt_need_resched();
        return;
    }

    ...
}

static inline int test_tsk_need_resched(struct task_struct *tsk)
{
    return unlikely(test_tsk_thread_flag(tsk,TIF_NEED_RESCHED));
}

static inline void set_tsk_need_resched(struct task_struct *tsk)
{
    set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}

static __always_inline void set_preempt_need_resched(void)
{
    raw_cpu_and_4(__preempt_count, ~PREEMPT_NEED_RESCHED);
}

test_tsk_need_resched检查thread_info结构的标志位中是否已经设置了TIF_NEED_RESCHED。
cpu_of宏获得运行队列rq对应的cpu,smp_processor_id宏则获得当前进程对应的cpu的id。下面只考虑两者相等的情况,此时,通过set_tsk_need_resched增加TIF_NEED_RESCHED到进程thread_info结构的标志位,再通过set_preempt_need_resched函数设置per-cpu变量__preempt_count。

int main(int argc, char *argv[]) { dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL, userspace_timestamp = DUAL_TIMESTAMP_NULL, kernel_timestamp = DUAL_TIMESTAMP_NULL, security_start_timestamp = DUAL_TIMESTAMP_NULL, security_finish_timestamp = DUAL_TIMESTAMP_NULL; struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0), saved_rlimit_memlock = RLIMIT_MAKE_CONST(RLIM_INFINITY); /* The original rlimits we passed * in. Note we use different values * for the two that indicate whether * these fields are initialized! */ bool skip_setup, loaded_policy = false, queue_default_job = false, first_boot = false; char *switch_root_dir = NULL, *switch_root_init = NULL; usec_t before_startup, after_startup; static char systemd[] = "systemd"; const char *error_message = NULL; uint64_t saved_ambient_set = 0; int r, retval = EXIT_FAILURE; Manager *m = NULL; FDSet *fds = NULL; assert_se(argc > 0 && !isempty(argv[0])); /* Take timestamps early on */ dual_timestamp_from_monotonic(&kernel_timestamp, 0); dual_timestamp_now(&userspace_timestamp); /* Figure out whether we need to do initialize the system, or if we already did that because we are * reexecuting. */ skip_setup = early_skip_setup_check(argc, argv); /* If we get started via the /sbin/init symlink then we are called 'init'. After a subsequent * reexecution we are then called 'systemd'. That is confusing, hence let's call us systemd * right-away. */ program_invocation_short_name = systemd; (void) prctl(PR_SET_NAME, systemd); /* Save the original command line */ save_argc_argv(argc, argv); /* Save the original environment as we might need to restore it if we're requested to execute another * system manager later. */ r = save_env(); if (r < 0) { error_message = "Failed to copy environment block"; goto finish; } /* Make sure that if the user says "syslog" we actually log to the journal. */ log_set_upgrade_syslog_to_journal(true); if (getpid_cached() == 1) { /* When we run as PID 1 force system mode */ arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; /* Disable the umask logic */ umask(0); /* Make sure that at least initially we do not ever log to journald/syslogd, because it might * not be activated yet (even though the log socket for it exists). */ log_set_prohibit_ipc(true); /* Always reopen /dev/console when running as PID 1 or one of its pre-execve() children. This * is important so that we never end up logging to any foreign stderr, for example if we have * to log in a child process right before execve()'ing the actual binary, at a point in time * where socket activation stderr/stdout area already set up. */ log_set_always_reopen_console(true); if (detect_container() <= 0) { /* Running outside of a container as PID 1 */ log_set_target_and_open(LOG_TARGET_KMSG); if (in_initrd()) initrd_timestamp = userspace_timestamp; if (!skip_setup) { r = mount_setup_early(); if (r < 0) { error_message = "Failed to mount early API filesystems"; goto finish; } } /* We might have just mounted /proc, so let's try to parse the kernel * command line log arguments immediately. */ log_parse_environment(); /* Let's open the log backend a second time, in case the first time didn't * work. Quite possibly we have mounted /dev just now, so /dev/kmsg became * available, and it previously wasn't. */ log_open(); if (!skip_setup) { disable_printk_ratelimit(); r = initialize_security( &loaded_policy, &security_start_timestamp, &security_finish_timestamp, &error_message); if (r < 0) goto finish; } r = mac_init(); if (r < 0) { error_message = "Failed to initialize MAC support"; goto finish; } if (!skip_setup) initialize_clock_timewarp(); clock_apply_epoch(/* allow_backwards= */ !skip_setup); /* Set the default for later on, but don't actually open the logs like this for * now. Note that if we are transitioning from the initrd there might still be * journal fd open, and we shouldn't attempt opening that before we parsed * /proc/cmdline which might redirect output elsewhere. */ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); } else { /* Running inside a container, as PID 1 */ log_set_target_and_open(LOG_TARGET_CONSOLE); /* For later on, see above... */ log_set_target(LOG_TARGET_JOURNAL); /* clear the kernel timestamp, because we are in a container */ kernel_timestamp = DUAL_TIMESTAMP_NULL; } initialize_coredump(skip_setup); r = fixup_environment(); if (r < 0) { log_struct_errno(LOG_EMERG, r, LOG_MESSAGE("Failed to fix up PID 1 environment: %m"), LOG_MESSAGE_ID(SD_MESSAGE_CORE_PID1_ENVIRONMENT_STR)); error_message = "Failed to fix up PID1 environment"; goto finish; } /* Try to figure out if we can use colors with the console. No need to do that for user * instances since they never log into the console. */ log_show_color(colors_enabled()); r = make_null_stdio(); if (r < 0) log_warning_errno(r, "Failed to redirect standard streams to /dev/null, ignoring: %m"); /* Load the kernel modules early. */ if (!skip_setup) (void) kmod_setup(); /* Mount /proc, /sys and friends, so that /proc/cmdline and /proc/$PID/fd is available. */ r = mount_setup(loaded_policy, skip_setup); if (r < 0) { error_message = "Failed to mount API filesystems"; goto finish; } /* The efivarfs is now mounted, let's lock down the system token. */ lock_down_efi_variables(); } else { /* Running as user instance */ arg_runtime_scope = RUNTIME_SCOPE_USER; log_set_always_reopen_console(true); log_set_target_and_open(LOG_TARGET_AUTO); /* clear the kernel timestamp, because we are not PID 1 */ kernel_timestamp = DUAL_TIMESTAMP_NULL; r = mac_init(); if (r < 0) { error_message = "Failed to initialize MAC support"; goto finish; } } /* Save the original RLIMIT_NOFILE/RLIMIT_MEMLOCK so that we can reset it later when * transitioning from the initrd to the main systemd or suchlike. */ save_rlimits(&saved_rlimit_nofile, &saved_rlimit_memlock); /* Reset all signal handlers. */ (void) reset_all_signal_handlers(); (void) ignore_signals(SIGNALS_IGNORE); (void) parse_configuration(&saved_rlimit_nofile, &saved_rlimit_memlock); r = parse_argv(argc, argv); if (r < 0) { error_message = "Failed to parse command line arguments"; goto finish; } r = safety_checks(); if (r < 0) goto finish; if (IN_SET(arg_action, ACTION_TEST, ACTION_HELP, ACTION_DUMP_CONFIGURATION_ITEMS, ACTION_DUMP_BUS_PROPERTIES, ACTION_BUS_INTROSPECT)) pager_open(arg_pager_flags); if (arg_action != ACTION_RUN) skip_setup = true; if (arg_action == ACTION_HELP) { retval = help() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; goto finish; } else if (arg_action == ACTION_VERSION) { retval = version(); goto finish; } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { unit_dump_config_items(stdout); retval = EXIT_SUCCESS; goto finish; } else if (arg_action == ACTION_DUMP_BUS_PROPERTIES) { dump_bus_properties(stdout); retval = EXIT_SUCCESS; goto finish; } else if (arg_action == ACTION_BUS_INTROSPECT) { r = bus_manager_introspect_implementations(stdout, arg_bus_introspect); retval = r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; goto finish; } assert_se(IN_SET(arg_action, ACTION_RUN, ACTION_TEST)); /* Move out of the way, so that we won't block unmounts */ assert_se(chdir("/") == 0); if (arg_action == ACTION_RUN) { if (!skip_setup) { /* Apply the systemd.clock_usec= kernel command line switch */ apply_clock_update(); /* Apply random seed from kernel command line */ cmdline_take_random_seed(); } /* A core pattern might have been specified via the cmdline. */ initialize_core_pattern(skip_setup); /* Make /usr/ read-only */ apply_protect_system(skip_setup); /* Close logging fds, in order not to confuse collecting passed fds and terminal logic below */ log_close(); /* Remember open file descriptors for later deserialization */ r = collect_fds(&fds, &error_message); if (r < 0) goto finish; /* Give up any control of the console, but make sure its initialized. */ setup_console_terminal(skip_setup); /* Open the logging devices, if possible and necessary */ log_open(); } log_execution_mode(&first_boot); r = cg_has_legacy(); if (r < 0) { error_message = "Failed to check cgroup hierarchy"; goto finish; } if (r > 0) { r = log_full_errno(LOG_EMERG, SYNTHETIC_ERRNO(EPROTO), "Detected cgroup v1 hierarchy at /sys/fs/cgroup/, which is no longer supported by current version of systemd.\n" "Please instruct your initrd to mount cgroup v2 (unified) hierarchy,\n" "possibly by removing any stale kernel command line options, such as:\n" " systemd.legacy_systemd_cgroup_controller=1\n" " systemd.unified_cgroup_hierarchy=0"); error_message = "Detected unsupported legacy cgroup hierarchy, refusing execution"; goto finish; } r = initialize_runtime(skip_setup, first_boot, &saved_rlimit_nofile, &saved_rlimit_memlock, &saved_ambient_set, &error_message); if (r < 0) goto finish; r = manager_new(arg_runtime_scope, arg_action == ACTION_TEST ? MANAGER_TEST_FULL : 0, &m); if (r < 0) { log_struct_errno(LOG_EMERG, r, LOG_MESSAGE("Failed to allocate manager object: %m"), LOG_MESSAGE_ID(SD_MESSAGE_CORE_MANAGER_ALLOCATE_STR)); error_message = "Failed to allocate manager object"; goto finish; } m->timestamps[MANAGER_TIMESTAMP_KERNEL] = kernel_timestamp; m->timestamps[MANAGER_TIMESTAMP_INITRD] = initrd_timestamp; m->timestamps[MANAGER_TIMESTAMP_USERSPACE] = userspace_timestamp; m->timestamps[manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_SECURITY_START)] = security_start_timestamp; m->timestamps[manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_SECURITY_FINISH)] = security_finish_timestamp; m->saved_ambient_set = saved_ambient_set; set_manager_defaults(m); set_manager_settings(m); manager_set_first_boot(m, first_boot); manager_set_switching_root(m, arg_switched_root); /* Remember whether we should queue the default job */ queue_default_job = !arg_serialization || arg_switched_root; before_startup = now(CLOCK_MONOTONIC); r = manager_startup(m, arg_serialization, fds, /* root= */ NULL); if (r < 0) { error_message = "Failed to start up manager"; goto finish; } /* This will close all file descriptors that were opened, but not claimed by any unit. */ fds = fdset_free(fds); arg_serialization = safe_fclose(arg_serialization); if (queue_default_job) { r = do_queue_default_job(m, &error_message); if (r < 0) goto finish; } after_startup = now(CLOCK_MONOTONIC); log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, "Loaded units and determined initial transaction in %s.", FORMAT_TIMESPAN(after_startup - before_startup, 100 * USEC_PER_MSEC)); if (arg_action == ACTION_TEST) { manager_test_summary(m); retval = EXIT_SUCCESS; goto finish; } r = invoke_main_loop(m, &saved_rlimit_nofile, &saved_rlimit_memlock, &retval, &fds, &switch_root_dir, &switch_root_init, &error_message); /* MANAGER_OK and MANAGER_RELOAD are not expected here. */ assert(r < 0 || IN_SET(r, MANAGER_REEXECUTE, MANAGER_EXIT) || (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && IN_SET(r, MANAGER_REBOOT, MANAGER_SOFT_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC, MANAGER_SWITCH_ROOT))); finish: pager_close(); if (m) { arg_reboot_watchdog = manager_get_watchdog(m, WATCHDOG_REBOOT); arg_kexec_watchdog = manager_get_watchdog(m, WATCHDOG_KEXEC); m = manager_free(m); } mac_selinux_finish(); if (IN_SET(r, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT)) r = do_reexecute(r, argc, argv, &saved_rlimit_nofile, &saved_rlimit_memlock, fds, switch_root_dir, switch_root_init, saved_ambient_set, &error_message); /* This only returns if reexecution failed */ arg_serialization = safe_fclose(arg_serialization); fds = fdset_free(fds); saved_env = strv_free(saved_env); #if HAVE_VALGRIND_VALGRIND_H /* If we are PID 1 and running under valgrind, then let's exit * here explicitly. valgrind will only generate nice output on * exit(), not on exec(), hence let's do the former not the * latter here. */ if (getpid_cached() == 1 && RUNNING_ON_VALGRIND) { /* Cleanup watchdog_device strings for valgrind. We need them * in become_shutdown() so normally we cannot free them yet. */ watchdog_free_device(); reset_arguments(); return retval; } #endif #if HAS_FEATURE_ADDRESS_SANITIZER /* At this stage we most likely don't have stdio/stderr open, so the following * LSan check would not print any actionable information and would just crash * PID 1. To make this a bit more helpful, let's try to open /dev/console, * and if we succeed redirect LSan's report there. */ if (getpid_cached() == 1) { _cleanup_close_ int tty_fd = -EBADF; tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); if (tty_fd >= 0) __sanitizer_set_report_fd((void*) (intptr_t) tty_fd); __lsan_do_leak_check(); } #endif if (r < 0) (void) sd_notifyf(/* unset_environment= */ false, "ERRNO=%i", -r); /* Try to invoke the shutdown binary unless we already failed. * If we failed above, we want to freeze after finishing cleanup. */ if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && IN_SET(r, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC)) { r = become_shutdown(r, retval); log_error_errno(r, "Failed to execute shutdown binary, %s: %m", getpid_cached() == 1 ? "freezing" : "quitting"); error_message = "Failed to execute shutdown binary"; } /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with * a mechanism to pick up systemd's exit status in the VM. */ (void) sd_notifyf(/* unset_environment= */ false, "EXIT_STATUS=%i", retval); watchdog_free_device(); arg_watchdog_device = mfree(arg_watchdog_device); if (getpid_cached() == 1) { if (error_message) manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, "%s.", error_message); freeze_or_exit_or_reboot(); } reset_arguments(); return retval; } 这段是systemd的主函数,请你详细讲解、梳理一下
最新发布
08-04
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值