openwrt reboot流程

openwrt 系统中,当执行了 reboot 命令,系统将会发生什么事情呢?如何进行重启的呢?下面来一起看一下。

reboot 应用层操作

首先,reboot 是由busybox(它是一个集成了常用Linux命令和工具的软件)提供的一个Linux命令。reboot 命令存在在系统的 /sbin 目录下,到该目录查看可以发现它是一个软链接,指向 /bin 目录下的 busybox 可执行程序。

当执行 reboot 命令,将会调用到busybox中的init/halt.c文件,实际上,关机、重启都是由该文件实现的。

reboot 其他参数

  • -w:仅保存系统用户使用信息,不会进行重启等操作;
  • -n:执行reboot命令前不会执行sync()函数;
  • -f:是否通过init进程关机,如果没有-f参数,则通过init进程关机;

no -f 关机

如果执行 reboot 命令时没有带 -f 参数,将会按照下面流程进行。

  1. 如果配置了 ENABLE_LINUXRC,将会kill linuxrc 程序(默认ENABLE_LINUXRC为0);
  2. 如果 ENABLE_FEATURE_CALL_TELINIT 配置为0,则给1号进程进行发送信号,signals[] = { SIGUSR1, SIGUSR2, SIGTERM }。(默认ENABLE_FEATURE_CALL_TELINIT 为0);
  3. 如果 ENABLE_FEATURE_CALL_TELINIT 配置为1,则通过execlp()函数进行reboot等操作;

下面进行分析第2步,看看给1号进程发送信号之后会发生什么。

1号进程是/sbin/procd,通过查看 procd 的代码可以看到在procd_signal()函数中有各个信号的处理。最终的在procd中,如果是SIGINT、SIGTERM信号,将会执行重启;如果是SIGUSR1、SIGUSR2信号,将执行关机,相应的操作都是通过procd_shutdown()函数完成的,只是传递的参数不一样而已。

procd_shutdown()

在该函数中,将init进程的state置为STATE_SHUTDOWN,再state_enter()中执行STATE_SHUTDOWN操作。通过 set_console() 函数重定向输出到终端,而后执行procd_inittab_run("shutdown")操作,实际上该操作就是执行 /etc/inittab 中的shutdown操作,而在openwrt平台,该操作就是执行 /etc/rc.d 中的shutdown操作(自启动脚本的shutdown操作),最后再执行sync()操作。

在这里回头看,从我们执行reboot命令,到busybox中的处理reboot参数,而后给1号进程发信号,1号进程执行 procd_shutdown() 函数的STATE_SHUTDOWN操作,但是似乎都没有最终的调用到 reboot 操作,那么,具体的reboot操作又是那里实现的呢?

奥秘就在procd_inittab_run("shutdown")

干活的procd_inittab_run(“shutdown”)

要知道procd_inittab_run()函数进行了什么操作,得先分析一下procd_inittab()函数(开机过程会调用该函数)。

procd_inittab()函数将会打开/etc/inittab文件,将里面的操作添加到系统的操作链表中,待procd_inittab_run()函数调用。

/etc/inittab文件是init守护进程在系统运行过程中会读取信息来启动或退出进程的配置文件。inittab文件的每一项具有以下字段:

id:runlevels:action:process

id:它是每个登记项的标识符,用于唯一标识每个登记项,不能重复;

runlevels:系统的运行级别,表示process的action要在哪个级别下运行,该段中可以定义多个运行级别,各级别之间直接写不用分隔符;如果为空,表示在所有的运行级别运行;

action:表示对应登记项的process在一定条件下所要执行的动作;

具体动作有:

  • respawn:当process终止后马上启动一个新的

  • wait:当进入指定的runlevels后process才会启动一次,并且到离开这个runlevels终止

  • initdefault:设定默认的运行级别,即我们开机之后默认进入的运行级别,不能是0,6,你懂的

  • sysinit:系统初始化,只有系统开机或重新启动的时候,这个process才会被执行一次

  • powerwait:当init接收到电源失败信号的时候执行相应的process,并且如果init有进程在运行,会等待这个进程完成之后,再执行相应的process

  • powerfail:当init接收到电源失败信号的时候执行相应的process,并且如果init有进程在运行,不会等待这个进程完成,它会直接执行相应的process

  • powerokwait:电源已经故障,但是在等待执行对应操作的时候突然来电了就执行对应的process

  • powerfailnow:当电源故障并且init被通知UPS电源已经快耗尽执行相对应的process

  • ctrlaltdel:当用户按下ctrl+alt+del这个组合键的时候执行对应的process

  • boot:只有在引导过程中,才执行该进程,但不等待该进程的结束;当该进程死亡时,也不重新启动该进程

  • bootwait:只有在引导过程中,才执行该进程,并等待进程的结束;当该进程死亡时,也不重新启动该进程

  • off:如果process正在运行,那么就发出一个警告信号,等待20秒后,再通过杀死信号强行终止该process。如果process并不存在那么就忽略该登记项

  • once:启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程死亡时,init也不重新启动该进程

  • shutdown:当系统重启、重启时将会执行的程序

process:表示启动哪个程序或脚本或执行哪个命令等;

简单了解上面信息之后,我们来看看procd_inittab_run("shutdown")的操作,而在openwrt系统,/etc/inittab文件中标识shutdown的信息如下:

::shutdown:/etc/init.d/rcS K shutdown

所以通过上面可以得到,将会执行 /etc/init.d/rcS 程序,但是在系统中并没有看到该文件,同时再回到procd_inittab_run()函数的实现将会看到,调用的将会是相应handler的cb()函数,shutdown操作的将会是调用runrc()函数。而在runrc()中,实际上就是执行rcS(K, shutdown, rcdone),再查看rcS()的实现,就发现是执行/etc/rc.d目录下的K*脚本中的shutdown操作。将K*脚本中的shutdown操作添加到任务队列中,然后再运行,任务逐个运行,到最后一个任务的时候,将会设置最后一个任务是运行工作队列中的empty_cb(),实际上,empty_cb() 就是工作队列中的任务都运行完时调用的。而上面在调用rcS()函数的时候,将empty_cb()函数设置为rcdone(),下面来看看rcdone()的操作。

rcdone()的操作比较简单,只是将init进程的state变量加1,再调用state_enter()。上面在procd_shutdown()中已经将state置为STATE_SHUTDOWN了,当前再加1,那state就是STATE_HALT。

state_enter() 的STATE_HALT

在HALT操作中,先signal(SIGCHLD, SIG_IGN)通知内核,init进程忽略子进程结束,子进程结束后由内核回收。而后通过kill(-1, SIGTERM)、kill(-1, SIGKILL)操作,发信号给除1号进程外的所有进程,使它们退出。最后,将创建子进程调用reboot(reboot_event),至此,reboot应用层操作转到内核。

reboot内核流程

在应用层调用reboot()函数之后,将会到内核的kernel/reboot.c中的SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)函数。

在内核中,先检查当前是否具有root权限,因为reboot只能由root权限才可以执行,而后重启的话将会执行kernel_restart(),关机则执行kernel_power_off()。

kernel_restart()

在该函数中,主要进行下面的操作:

  1. kernel_restart_prepare():调用device_shutdown()函数,逐个调用device的shutdown()函数;
  2. migrate_to_reboot_cpu():将当前运行程序切换到CPU0;
  3. syscore_shutdown():调用通过register_syscore_ops()注册的shutdown的回调函数,比如irq、cpufreq、clock等核心模块的shutdown函数;
  4. machine_restart():调用到平台的restart()函数;

machine_restart() 的实现依赖不同的平台,如ARM64将会调用到arch/arm64/kernel/process.c中的machine_restart()。在ARM64平台中,将会先关闭中断使能,再关闭除CPU0外的其他CPU核,最后将会通过 arm_pm_restart() 函数指针调用到各自平台实现。

总结

通过上面的简单介绍,总结一下reboot流程。

  1. 接着将会发信号到1号进程procd,而procd将会执行shutdown操作,实际上就是调用/etc/rc.d目录下的K*的shutdown操作;
  2. 上面的K*都操作完之后,将会更新init进程的state为STATE_HALT;
  3. 在STATE_HALT中,将会退出除一号进程外的其他进程,接着执行reboot函数进入内核;
  4. 在内核中,调用device、irq、clock等的shutdown操作,关中断,关核,调用相应平台的重启接口;

猜你喜欢

转载自blog.csdn.net/weixin_41944449/article/details/118161225