当我们在openwrt系统的终端中敲下
reboot
指令后,系统需要进行一系列动作后,才会真正的进行硬件重启。而这一系列的动作可以分为——应用程序的停止和内核的停止。在Openwrt系统中,应用程序的停止是1号进程(也就是
procd
)完成的,应用程序全部停止后才会进入内核的重启流程。下面我们将分别来介绍应用程序和内核停止的流程。
首先,我们注意到reboot
指令是busybox
提供的,而且reboot
指令支持传入不同的参数来执行不同的重启模式。
# ls /sbin/reboot -alh
lrwxrwxrwx 1 root root 14 Apr 17 13:15 /sbin/reboot -> ../bin/busybox/# reboot --help
BusyBox v1.36.1 (2024-05-10 17:21:49 UTC) multi-call binary.
Usage: reboot [-d DELAY] [-nf]
Reboot the system-d SEC Delay interval -n Do not sync -f Force (don't go through init)
下面是reboot
指令的参数解释。
参数 | 说明 |
---|---|
-d SEC Delay interval | 延时几秒之后重启 |
-n | 不执行sync |
-f | 强制重启,跳过应用层直接走内核重启流程 |
关于reboot
指令的具体实现可以参考busybox源码:halt.c
int halt_main(int argc UNUSED_PARAM, char **argv)if (!(flags & 4)) { /* 不带 -f 参数 */
//TODO: I tend to think that signalling linuxrc is wrong
// pity original author didn't comment on it...if (rc) {/* talk to init */if (!ENABLE_FEATURE_CALL_TELINIT) {/* bbox init assumed */rc = kill(1, signals[which]);// 发送信号给1号进程if (init_was_not_there())rc = kill(1, signals[which]);} }} else {rc = reboot(magic[which]); // reboot -f }
reboot_43">应用层reboot流程
根据前面reboot的源码,如果执行的是不带 -f
选项的reboot
,那么busybox
就会发送信号给1号进程,由1号进程来执行重启流程。在openwrt系统中,1号进程就是procd
.
# psPID USER VSZ STAT COMMAND1 root 1868 S /sbin/procd
procd_signal()
是在procd启动时执行的,里面对SIGTERM
,SIGINT
,SIGUSR1
这些信号都注册了同一个回调函数——sa_shutdown
,sa_shutdown
其实是一个指向struct sigaction
的指针,该结构体定义了如何处理上述信号.- 当
procd
收到如上信号后,sa_shutdown
里面的回调函数会被调用,也就是signal_shutdown()
signal_shutdown
里面会根据不同的信号类型设置不同的event
,然后将event
传入procd_shutdown()
procd_shutdown()
里面设置state = STATE_SHUTDOWN
,随后调用state_enter()
进入STATE_SHUTDOWN
对应的状态处理流程procd_inittab_run()
会遍历整个handlers[]
,找到name=shutdown
的handler,然后执行其cb函数,也就是runrc()
runrc()
->rcS()
->_rc()
,_rc()
会依次执行/etc/rc.d/
下面所有K
开头的脚本,也是所有应用程序的停止脚本,这一步完成后,rcdone()
会被调用。rcdone()
会继续将STATE_SHUTDOWN
状态推进到STATE_HALT
状态,进入STATE_HALT
状态后,会执行reboot
系统调用进行内核阶段的重启,并且会带上前面的event
参数,event
参数也会被传递到内核,我们将在后续内核重启流程中继续介绍这个参数的具体作用。- 至此,所有的应用程序都已经停止(但1号进程未停止)
reboot_62">内核reboot流程
书接上回,前面提到应用层重启流程的最后一步是执行reboot()
函数,其中reboot
函数携带的参数可能是RB_AUTOBOOT
或者RB_POWER_OFF
,在musl libc
中(openwrt系统默认使用musl libc而不是glibc)reboot
函数的实现以及RB_AUTOBOOT
和RB_POWER_OFF
的定义如下:
//openwrt/build_dir/toolchain-aarch64_cortex-a53_gcc-12.3.0_musl/musl-1.2.4/include/sys/reboot.h
#define RB_AUTOBOOT 0x01234567
#define RB_HALT_SYSTEM 0xcdef0123
#define RB_ENABLE_CAD 0x89abcdef
#define RB_DISABLE_CAD 0
#define RB_POWER_OFF 0x4321fedc
#define RB_SW_SUSPEND 0xd000fce2
#define RB_KEXEC 0x45584543//openwrt/build_dir/toolchain-aarch64_cortex-a53_gcc-12.3.0_musl/musl-1.2.4/src/linux/reboot.c
#include <sys/reboot.h>
#include "syscall.h"int reboot(int type)
{//type=RB_AUTOBOOT or RB_POWER_OFFreturn syscall(SYS_reboot, 0xfee1dead, 672274793, type);
}
我们暂时只关注重启流程,所以type=RB_AUTOBOOT
,上面的reboot
函数并没有过多操作,直接就执行了reboot
系统调用,root
系统调用的整体流程如下:
- 进入内核reboot系统调用的处理函数中,用户层传入的参数
RB_AUTOBOOT
对应内核层的参数LINUX_REBOOT_CMD_RESTART
,所以后续会调用kernel_restart()
函数进行系统重启之前准备工作以及最后的重启动作(其他参数会对应其他动作,请自行参考源码) kernel_restart_prepare()
调用device_shutdown()
关闭所有注册的device
- 接下来
console
应该可以看到reboot: Restarting system
的打印信息 machine_restart()
在不同的平台实现可能不一样,在ARM64平台是调用do_kernel_restart()
通知具体的驱动进行重启,通常是通知看门狗驱动来进行最终的物理重启。