用户态reboot流程

  本文讲述reboot命令在用户态的执行过程,halt,poweroff,shutdown与其类似。本文适用于使用systemd作为系统1号进程的场景。另补充一点,reboot,halt,poweroff,shutdown是传统Unix SysV init初始化系统提供的命令,目前的Linux发行版大多使用systemd作为init进程,为了保持对sysvinit的兼容,systemd也提供了reboot等命令,严格来说,systemd本身提供的重启命令是systemctl reboot。

当我们在命令行中敲了reboot之后,发生了些什么

  reboothaltpoweroffshutdown都是符号链接,这些链接都指向systemctl。具体如下:

 # cd /usr/sbin/
 # ll {reboot,halt,poweroff,shutdown}
 lrwxrwxrwx. 1 root root 16 Feb  4 08:00 halt -> ../bin/systemctl
 lrwxrwxrwx. 1 root root 16 Feb  4 08:00 poweroff -> ../bin/systemctl
 lrwxrwxrwx. 1 root root 16 Feb  4 08:00 reboot -> ../bin/systemctl
 lrwxrwxrwx. 1 root root 16 Feb  4 08:00 shutdown -> ../bin/systemctl

  这里就会有个问题,当我们输入rebootpoweroffhaltshutdown命令的时候,都是执行的systemctl,那么systemctl怎么知道我们想执行的是rebootpoweroffhalt,还是shutdown呢?这就不得不说一个源于Unix的机制了,当加载符号链接指向的可执行文件时,会把符号链接的名字作为该可执行文件的第一个参数传给进程,即 argv[0],进程可以通过判断 argv[0] 来判断是哪个符号链接。一般情况下,argv[0] 都是可执行文件的名字,如执行systemctl list-units时,systemctl的第一个参数argv[0]就是systemctl,但使用符号链接的时候,如reboot,这时候systemctlargv[0]就是reboot
  所以,当在命令行里敲了reboot后,系统(或者说shell)会加载systemctl,并把它的第一个参数——argv[0]设置为reboothaltpoweroffshutdown同理。

systemctl中的reboot

  上面说到,执行reboot会加载systemctl,并把它的argv[0]设置为reboot。接下来讲讲rebootsystemctl里的具体执行过程。
  systemd包的src/systemctl/systemctl.c:1214行使用宏定义了systemctlmain函数(源码都在systemd包中,后面的篇幅省略systemd)。具体如下:

 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

  这个宏展开之后是这样的:

 int main(int argc, char *argv[])
 {
   
     int r;
     save_argc_argv(argc, argv);
     r = run(argc, argv);
     if (r < 0)
         (void) sd_notifyf(0, "ERRNO=%i", -r);
     ask_password_agent_close();
     polkit_agent_close();
     pager_close();
     mac_selinux_finish();
     static_destruct();
     return r < 0 ? EXIT_FAILURE : r;
 }

  该宏定义了main函数,main函数先保存了一把argcargv,然后将其作为参数,传给了run函数(第5行),很明显,run函数实现了主要的逻辑。
  run函数如下:

 static int run(int argc, char *argv[]) {
   
         int r;
         pid_t ppid;
         char *filter[] = {
   
                 "status", "show", "cat",
                 "is-active", "is-failed", "is-enabled", "is-system-running",
                 "list-units", "list-sockets", "list-timers", "list-dependencies",
                 "list-unit-files", "list-machines", "list-jobs",
                 "get-default", "show-environment", NULL
         };setlocale(LC_ALL, "");
         log_parse_environment();
         log_open();/* The journal merging logic potentially needs a lot of fds. */
         (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);sigbus_install();
 ​
         r = systemctl_dispatch_parse_argv(argc, argv);
         if (r <= 0)
                 goto finish;
 ​
         ppid = getppid();
         (void) print_process_cmdline_with_arg(ppid, argc, argv, filter);if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) {
   
                 if (!arg_quiet)
                         log_info("Running in chroot, ignoring request.");
                 r = 0;
                 goto finish;
         }/* systemctl_main() will print an error message for the bus connection, but only if it needs to */switch (arg_action) {
   case ACTION_SYSTEMCTL:
                 r = systemctl_main(argc, argv);
                 break;/* Legacy command aliases set arg_action. They provide some fallbacks, e.g. to tell sysvinit to
          * reboot after you have installed systemd binaries. */case ACTION_HALT:
         case ACTION_POWEROFF:
         case ACTION_REBOOT:
         case ACTION_KEXEC:
                 r = halt_main();
                 break;case ACTION_RUNLEVEL2:
         case ACTION_RUNLEVEL3:
         case ACTION_RUNLEVEL4:
         case ACTION_RUNLEVEL5:
         case ACTION_RESCUE:
                 r = start_with_fallback();
                 break;case ACTION_RELOAD:
         case ACTION_REEXEC:
                 r = reload_with_fallback();
                 break;case ACTION_CANCEL_SHUTDOWN:
                 r = logind_cancel_shutdown();
                 break;case ACTION_RUNLEVEL:
                 r = runlevel_main();
                 break;case ACTION_TELINIT:
                 r = exec_telinit(argv);
                 break;case ACTION_EXIT:
         case ACTION_SUSPEND:
         case ACTION_HIBERNATE:
         case ACTION_HYBRID_SLEEP:
         case ACTION_SUSPEND_THEN_HIBERNATE:
         case ACTION_EMERGENCY:
         case ACTION_DEFAULT:
                 /* systemctl verbs with no equivalent in the legacy commands. These cannot appear in
                  * arg_action. Fall through. */case _ACTION_INVALID:
         default:
                 assert_not_reached("Unknown action");
         }
 ​
 finish:
         release_busses();/* Note that we return r here, not 0, so that we can implement the LSB-like return codes */
         return r;
 }

  该函数也很简单,初始化了一堆进程的运行环境后,调了systemctl_dispatch_parse_argv(),该函数处理了argcargv。之后直接根据arg_action的值,用switch case进行处理,caseACTION_REBOOTACTION_HALTACTION_POWEROFF等,systemctl根据arg_action值的不同,采取了不同的行为。arg_action是一个全局变量,很明显,在前面有函数对其进行了处理,决定了systemctl将要执行的动作。
  systemctl_dispatch_parse_argv()函数处理了argcargv,也就是shell传过来的命令行参数,arg_action就是在这里被处理的。我们接下来分析systemctl_dispatch_parse_argv()函数。该函数如下:

 int systemctl_dispatch_parse_argv(int argc, char *argv[]) {
   
         assert(argc >= 0);
         assert(argv);if (invoked_as(argv, "halt")) {
   
                 arg_action = ACTION_HALT;
                 return halt_parse_argv(argc, argv);} else if (invoked_as(argv, "poweroff")) {
   
                 arg_action = ACTION_POWEROFF;
                 return halt_parse_argv(argc, argv);} else if (invoked_as(argv, "reboot")) {
   
                 if (kexec_loaded())
                         arg_action = ACTION_KEXEC;
                 else
                         arg_action = ACTION_REBOOT;
                 return halt_parse_argv(argc, argv);} else if (invoked_as(argv, "shutdown")) {
   
                 arg_action = ACTION_POWEROFF;
                 return shutdown_parse_argv(argc, argv);} else if (invoked_as(argv, "init")) {
   /* Matches invocations as "init" as well as "telinit", which are synonymous when run
                  * as PID != 1 on SysV.
                  *
                  * On SysV "telinit" was the official command to communicate with PID 1, but "init" would
                  * redirect itself to "telinit" if called with PID != 1. We follow the same logic here still,
                  * though we add one level of indirection, as we implement "telinit" in "systemctl". Hence,
                  * for us if you invoke "init" you get "systemd", but it will execve() "systemctl"
                  * immediately with argv[] unmodified if PID is != 1. If you invoke "telinit" you directly
                  * get "systemctl". In both cases we shall do the same thing, which is why we do
                  * invoked_as(argv, "init") here, as a quick way to match both.
                  *
                  * Also see redirect_telinit() in src/core/main.c. */if (sd_booted() > 0) {
   
                         arg_action = _ACTION_INVALID;
                         return telinit_parse_argv(argc, argv);
                 } else {
   
                         /* Hmm, so some other init system is running, we need to forward this request to it.
                          */
                         arg_action = ACTION_TELINIT;
                         return 1;
                 }} else if (invoked_as(argv, "runlevel")) {
   
                 arg_action = ACTION_RUNLEVEL;
                 return runlevel_parse_argv(argc, argv);
         }
 ​
         arg_action = ACTION_SYSTEMCTL;
         return systemctl_parse_argv(argc, argv);
 }

  该函数最主要的作用就是根据argv来给arg_action赋值。invoke_as()函数的主要逻辑是取出argv[0]参数中的基本文件名,判断其是否包含第二个实参子串。argv[0]是可执行文件名,可能是绝对路径,也可能是相对路径,invoke_as()会去除其路径,仅保留基本文件名,然后使用strstr()函数,判断是否包含第二个形式参数子串。如invoked_as(argv, "reboot")invoke_as函数就会取出argv[0]的基本名,比如是reboot,然后和第二个入参"reboot"比较,看看argv[0]的基本名中是否包含reboot,如果包含,就返回true,否则为false。具体可查看其源码。
  于是,在systemctl_dispatch_parse_argv()函数中,我们在命令行敲的reboot被识别,arg_action被赋值成了ACTION_REBOOTsystemctl_dispatch_parse_argv()::17行),然后走到了halt_main()函数(run()::48行),在halt_main()函数中,因为arg_dry_runarg_force都是0,所以来到了start_with_fallback()函数(halt_main()::38行)。在start_with_fallback()中,会先尝试基于systemd的关机方式,如果定义了HAVE_SYSV_COMPAT宏,也会针对sysv(传统Unix Init进程)进行一些处理。
  halt_main()start_with_fallback()函数如下:

 int halt_main(void) {
   
         int r;
 ​
         r = logind_check_inhibitors(arg_action);
         if (r < 0)
                 return r;/* Delayed shutdown requested, and was successful */
         if (arg_when > 0 && logind_schedule_shutdown() == 0)
                 return 0;/* No delay, or logind failed or is not at all available */
         if (geteuid() != 0) {
   
                 if (arg_dry_run || arg_force > 0) {
   
                         (void) must_be_root();
                         return -EPERM;
                 }/* Try logind if we are a normal user and no special mode applies. Maybe polkit allows us to
                  * shutdown the machine. */
                 if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT, ACTION_KEXEC, ACTION_HALT)) {
   
                         r = logind_reboot(arg_action);
                         if (r >= 0)
                                 return r;
                         if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS))
                                 /* Requested operation is not supported on the local system or already in
                                  * progress */
                                 return r;/* on all other errors, try low-level operation */
                 }
         }/* In order to minimize the difference between operation with and without logind, we explicitly
          * enable non-blocking mode for this, as logind's shutdown operations are always non-blocking. */
         arg_no_block = true;if (!arg_dry_run && !arg_force)
                 return start_with_fallback();assert(geteuid() == 0);if (!arg_no_wtmp) {
   
                 if (sd_booted() > 0)
                         log_debug("Not writing utmp record, assuming that systemd-update-utmp is used.");
                 else {
   
                         r = utmp_put_shutdown();
                         if (r < 0)
                                 log_warning_errno(r, "Failed to write utmp record: %m");
                 }
         }if (arg_dry_run)
                 return 0;
 ​
         r = halt_now(arg_action);
         return log_error_errno(r, "Failed to reboot: %m");
 }int start_with_fallback(void) {
   
         /* First, try systemd via D-Bus. */
         if (start_unit(0, NULL, NULL) == 0)
                 return 0;#if HAVE_SYSV_COMPAT
         /* Nothing else worked, so let's try /dev/initctl */
         if (talk_initctl(action_to_runlevel()) > 0)
                 return 0;
 #endifreturn log_error_errno(SYNTHETIC_ERRNO(EIO),
                                "Failed to talk to init daemon.");
 }

  start_with_fallback针对systemd的处理函数是start_unit()。在start_unit()中,会根据arg_action选中一个target,对于reboot,就是reboot.target,然后调用start_unit_one()函数,该函数使用

 bus_call_method(bus, bus_systemd_mgr, "EnqueueUnitJob", &enqueue_error, &reply, "sss", name, job_type, mode)

  通过DBUSsystemd发送信息,让systemd运行reboot.target。在这里,namereboot.targetmodestart
  接下来,reboot流程就来到了systemd
  对systemctl中的reboot做一个总结:首先处理argv[0],赋值arg_actionACTION_REBOOT,然后根据arg_action的值,通过DBUS总线,向systemd发送start reboot.target的信息,将reboot流程推进到systemd
  一定程度上来说,执行reboot等同于执行systemctl start reboot.target

systemd中的reboot

  systemd进程在内核启动末期加载。内核在启动晚期会加载initramfs中的/init进程,/init是一个符号链接,链向systemd,于是内核会加载initramfs中的systemd作为1号进程initramfs中的systemd在运行中会switch root到真正的根文件系统,在这个过程中,initramfs中的systemd会杀死所有从initramfs中启动的进程,并使用execve装载真正的根文件系统中的systemd,再进行一次初始化,重新加载各个服务(initramfs中的systemd和根文件系统中的systemd加载的服务并不一样,有较大差别)。
  根文件系统中的systemd启动后,会进行一系列的设置,进行一系列的初始化,最后会进入src/core/main.c::invoke_main_loop()函数的死循环。invoke_main_loop()函数的循环主体是manager_loop()manager_loop()

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,reboot是一个系统调用,用于重新启动计算机。它可以通过命令行或者在代码中调用。reboot命令有几个选项可以使用,比如-d选项可以在重启系统时不将操作写入日志文件,-f选项可以强制重启操作系统,-n选项可以在重启操作系统前不同步硬盘/存储介质等。\[1\]在用户空间,一般的Linux操作系统提供了一些工具集合,如reboot、halt和poweroff命令,它们都与重启相关。内核空间根据执行路径的不同,提供了kernel_restart、kernel_halt和kernel_poweroff三个处理函数,响应用户空间的reboot请求。这些处理函数的处理流程大致相同,包括向关心reboot过程的进程发送notify事件、关闭所有的外部设备、关闭system core等。最后,调用machine相关的接口实现真正意义上的reboot。此外,内核还提供了其他途径的关机方法,如通过按键组合或向/proc文件写入命令。\[2\]\[3\] #### 引用[.reference_title] - *1* [Linux 命令(223)—— reboot 命令](https://blog.csdn.net/K346K346/article/details/128373667)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Linux电源管理系统休眠唤醒之 reboot>](https://blog.csdn.net/qq_39575672/article/details/129708512)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值