前言
poweroff
命令用于关闭 Linux 系统的电源。它不仅停止所有正在运行的进程,还会卸载文件系统,最终关闭计算机的电源。poweroff
通常需要超级用户权限(即 root 权限)才能执行。
基本用法
要关闭系统电源,可以执行以下命令:
或者,如果你已经是 root 用户,可以直接运行:
poweroff
与 systemctl
在许多现代 Linux 发行版中(如 Ubuntu 22.04),poweroff
实际上是一个指向 systemctl
的软连接。这意味着当你运行 poweroff
时,实际上是在调用 systemctl poweroff
。此处以笔者的x86-64位的ubuntu22.04系统为例
命令选项
poweroff
命令有一些选项,可以用于调整其行为:
一、Systemd poweroff命令源码分析
systemd的关机流程非常复杂,一次讲解难以做到面面俱到,所以笔者准备拆分分析其实现。此次分析,笔者以如下命令分析其具体流程,下次分析poweroff的正常关机流程。
(一) 用户态流程
systemctl程序的入口函数为run,poweroff命令再shell解析后,会作为systemctl的参数传入systemctl,然后在systemctl_dispatch_parse_argv函数内解析
systemctl_dispatch_parse_argv函数在解析到argv中的poweroff
选项后,会将arg_action
全局变量设置为ACTION_POWEROFF
,表征关机指令,然后再调用halt_parse_argv函数解析命令
halt_parse_argv函数在解析到‘-f’命令后,会将全局变量arg_force
置为2,如果没有在调用命令的时候指定‘-f’,则该变量默认为0。
捋清楚这些后,继续回到systemctl_dispatch_parse_argv函数,在响应操作时,ACTION_POWEROFF
的执行操作由halt_main函数实现。
systemctl-compat-halt.c内的halt_main函数在默认不指定‘-f’参数的情况下,会先尝试通过 logind
服务进行关机、重启等操作,如果 logind
服务不支持这些操作、没有足够权限、操作正在进行中或遇到其他错误,则尝试低级别的操作。通过这种方式,最大程度地保证了操作的成功执行,同时减少了与 logind
服务方式的差异
这段代码是整个systemd关机流程的核心,尤其其中的logind_reboot函数调用链及其复杂,笔者在本章内会掠过,作为下一次的主要内容。接下来分析。最新的systemd支持指定时间执行关机等操作,不指定的情况下arg_when默认为0,此处以不指定的情况分析。
" --when=TIME Schedule halt/power-off/reboot/kexec action after\n"
arg_dry_run函数判断是否模拟执行操作而不实际去执行任何会改变系统状态的动作,重点来看ACTION_POWEROFF
时执行的操作,使用 reboot(RB_POWER_OFF)
执行系统关机,若调用失败则返回错误码。
到此处,整个调用流程陷入内核态reboot syscall
。
(二)内核态流程
* 笔者分析的内核源码版本为5.19.17。
kernel/reboot.c
reboot syscall的源码如下,其中比较重要的函数为reboot_pid_ns,先来分析这个函数
内核函数reboot_pid_ns的作用是处理特定的命名空间(pid namespace)下的重启或关闭操作,pid命名空间是一种隔离机制,使得不同的命名空间可以有独立的进程号空间,是docker等容器的基础技术之一。以下是对代码进行分析和注释:
pid_ns->child_reaper是PID命名空间内的一号进程,在容器内就是容器的init进程, send_sig(SIGKILL, pid_ns->child_reaper, 1)
这段程序的作用就是直接kill掉init进程。容器init进程退出时,exit函数会调用exit_notify函数,forget_original_parent。
forget_original_parent函数用于处理进程的父子关系。当一个进程的父进程终止时,内核需要确保该进程的孤儿(即原本由已终止的父进程控制的子进程)能够被正确地重新分配到一个新的父进程。通常,这个新父进程是系统中的 init 进程(PID 1),因为 init 进程是所有孤儿进程的最终收容者。
`find_child_reaper` 函数,用于找到指定 PID 命名空间中的“child reaper”进程,即在该命名空间中负责处理孤儿进程的进程。通常,这个进程是命名空间中的初始化进程(通常是 PID 为 1 的进程)。
zap_pid_ns_processes函数,用于清理指定 PID 命名空间 (pid_namespace
) 中的所有进程。这个过程通常在命名空间的初始化进程终止时触发。以下是对该函数的详细注释和分析:
总的来说,zap_pid_ns_processes 确保在 PID 命名空间中的所有进程都能被正确终止和回收,无论它们是直接的子进程还是后续生成的孤儿进程,'systemctl poweroff -f'命令对于容器环境,是直接强杀所有容器内进程的,这对于维护系统的稳定性和资源回收尤为重要。
如果非容器环境,systemd进程退出后,其余的进程都会被系统的init进程收养,并且内核会向这些进程发送SIGHUP
然后在发送SIGCOUNT
。
接下来继续分析分析reboot syscall函数。
LINUX_REBOOT_CMD_POWER_OFF
CMD对应的操作为kernel_power_off函数,用于执行系统的电源关闭操作。以下是对代码进行详细分析和注释:
每个架构会有对应的machine_power_off函数如下是arm64的machine_power_off函数:
最后一个函数,do_kernel_power_off内核开发者对其的注释如下:
do_kernel_power_off函数的目的是执行已注册的内核关机处理函数链,它应当在关机流程的最后一步被调用。如果有注册的关机处理函数存在,它将立即关闭系统,否则不会执行任何操作,通过这种机制,内核确保在关机时能够调用适当的函数以关闭系统电源,如果没有合适的函数可调用,则避免出错。这样的设计可以带来灵活性,使平台相关的关机处理逻辑可以通过注册相应的函数来实现。
尾言
本节分析了'systemd poweroff -f'命令的技术原理。通过正确使用 poweroff
命令,你可以安全地关闭 Linux 系统并确保所有数据和文件系统在关机过程中得到正确处理。了解其各种选项和内部机制,可以帮助你更灵活和安全地管理系统的关闭过程。
如果各位大佬发现其中分析有问题的带你,可以在后台留言指正,下期会惊醒'systemd poweroff '正常关机流程的梳理,尽情期待!