对于生在智能时代的我们,对关机和重启这两个概念肯定不会陌生,尤其经历早期Android智能机的人们,印象最深恐怕就是重启,当然现在不会了,还没有改过来的都被淘汰了。在Linux系统中我们使用reboot完成这个动作,下面以rk px30 linux4.4.194为例,分析这个过程。
重启reboot基本上都是通过一条命令实现,之所以聊一下应用层,主要是新的系统中reboot shutdown halt 等,这些操作被systemd统一管理了,也就是说reboot,shutdown,halt等命令是指向systemctl的软链接,执行reboot相当于执行 systemctl reboot,systemctl reboot 会切换到 reboot.target.
下面使用systemd进行系统关闭/重启的依赖关系图:
(conflicts with (conflicts with
all system all file system
services) mounts, swaps,
| cryptsetup
| devices, ...)
| |
v v
shutdown.target umount.target
| |
\_______ ______/
\ /
v
(various low-level
services)
|
v
final.target
|
_____________________________________/ \_________________________________
/ | | \
| | | |
v v v v
systemd-reboot.service systemd-poweroff.service systemd-halt.service systemd-kexec.service
| | | |
v v v v
reboot.target poweroff.target halt.target kexec.target
下面是reboot的service
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Reboot
Documentation=man:systemd-halt.service(8)
DefaultDependencies=no
Requires=shutdown.target umount.target final.target
After=shutdown.target umount.target final.target
SuccessAction=reboot-force
conflicts with all system services:指那些定义了Conflicts=shutdown.target 和 Before=shutdown.target 依赖关系(除非明确设置了 DefaultDependencies=no ,否则 service 单元将会自动添加这些依赖)的服务,这些服务在shutdown.target运行之前会停止。
实际执行过程从上到下,以reboot为例:
1.停止和shutdown.target、umount.target冲突的服务。
2.shutdown.target、umount.target
3.various low-level services
4.final.target
5.systemd-reboot.service
该服务执行的命令行:ExecStart=/bin/systemctl --force reboot,这条命令会调用systemd-shutdown,它将以简单而强大的方式卸载任何剩余的文件系统,杀死任何剩余的进程并释放任何其他剩余的资源,而不再考虑任何服务或单元概念。一般这是最后执行的服务。
6.reboot.target
目标单元的功能仅仅是通过依赖关系将一组单元汇聚在一起, 形成一个同步点,并给这个同步点取一个众所周知的名称, 以便用作启动目标或其他单元的依赖。对于shutdown.target、umount.target、final.target、reboot.target这些目标单元,其组内的单元(.wants/、.requires/)实际的启动顺序取决于单元自身的依赖关系。
最后通过系统调用,通过内核完成相关操作。
如下为reboot的系统调用代码kernel/reboot.c:
/*
* Reboot system call: for obvious reasons only root may call it,
* and even root needs to set up some magic numbers in the registers
* so that some mistake won't make this reboot the whole machine.
* You can also set the meaning of the ctrl-alt-del-key here.
*
* reboot doesn't sync: do that yourself before calling this.
*/
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
struct pid_namespace *pid_ns = task_active_pid_ns(current);
char buffer[256];
int ret = 0;
#检测权限
/* We only trust the superuser with rebooting the system. */
if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
return -EPERM;
//加入一些验证,防止误操作
/* For safety, we require "magic" arguments. */
if (magic1 != LINUX_REBOOT_MAGIC1 ||
(magic2 != LINUX_REBOOT_MAGIC2 &&
magic2 != LINUX_REBOOT_MAGIC2A &&
magic2 != LINUX_REBOOT_MAGIC2B &&
magic2 != LINUX_REBOOT_MAGIC2C))
return -EINVAL;
//调用reboot_pid_ns接口,检查是否需要由该接口处理reboot请求。这是一个有关pid namespaces的新特性,也是Linux内核重要的知识点
/*
* If pid namespaces are enabled and the current task is in a child
* pid_namespace, the command is handled by reboot_pid_ns() which will
* call do_exit().
*/
ret = reboot_pid_ns(pid_ns, cmd);
if (ret)
return ret;
//如果是POWER_OFF命令,且没有注册power off的machine处理函数(pm_power_off),把该命令转换为HALT命令;
/* Instead of trying to make the power_off code look like
* halt when pm_power_off is not set do it the easy way.
*/
if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
cmd = LINUX_REBOOT_CMD_HALT;
mutex_lock(&reboot_mutex);
switch (cmd) {
case LINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
/*省略不相干代码*/
default:
ret = -EINVAL;
break;
}
mutex_unlock(&reboot_mutex);
return ret;
}
1、内核根据不同的表现形式把重启分为下面几类:
/*
* Commands accepted by the _reboot() system call.
*
* RESTART Restart system using default command and mode.
* HALT Stop OS and give system control to ROM monitor, if any.
* CAD_ON Ctrl-Alt-Del sequence causes RESTART command.
* CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task.
* POWER_OFF Stop OS and remove all power from system, if possible.
* RESTART2 Restart system using given command string.
* SW_SUSPEND Suspend system using software suspend if compiled in.
* KEXEC Restart system using a previously loaded Linux kernel
*/
#define LINUX_REBOOT_CMD_RESTART 0x01234567
#define LINUX_REBOOT_CMD_HALT 0xCDEF0123
#define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF
#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000
#define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC
#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4
#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2
#define LINUX_REBOOT_CMD_KEXEC 0x45584543
RESTART,正常的重启,也是我们平时使用的重启。执行该动作后,系统会重新启动。
HALT,停止操作系统,然后把控制权交给其它代码(如果有的话)。具体的表现形式,依赖于系统的具体实现。
CAD_ON/CAD_OFF,允许/禁止通过Ctrl-Alt-Del组合按键触发重启(RESTART)动作。
注1:Ctrl-Alt-Del组合按键的响应是由具体的Driver(如Keypad)实现的。POWER_OFF,正常的关机。执行该动作后,系统会停止操作系统,并去除所有的供电。
RESTART2,重启的另一种方式。可以在重启时,携带一个字符串类型的cmd,该cmd会在重启前,发送给任意一个关心重启事件的进程,同时会传递给最终执行重启动作的machine相关的代码。内核并没有规定该cmd的形式,完全由具体的machine自行定义。
SW_SUSPEND,即前一篇文章中描述的Hibernate操作。
KEXEC,重启并执行已经加载好的其它Kernel Image(需要CONFIG_KEXEC的支持),暂不涉及。
上面已经注释过一些,下面我们重点看一下 LINUX_REBOOT_CMD_RESTART 命令的处理函数kernel_restart
/**
* kernel_restart - reboot the system
* @cmd: pointer to buffer containing command to execute for restart
* or %NULL
*
* Shutdown everything and perform a clean reboot.
* This is not safe to call in interrupt context.
*/
void kernel_restart(char *cmd)
{
kernel_restart_prepare(cmd);
migrate_to_reboot_cpu();
syscore_shutdown();
if (!cmd)
pr_emerg("Restarting system\n");
else
pr_emerg("Restarting system with command '%s'\n", cmd);
kmsg_dump(KMSG_DUMP_RESTART);
machine_restart(cmd);
}
kernel_restart_prepare
void kernel_restart_prepare(char *cmd)
{
blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
system_state = SYSTEM_RESTART;
usermodehelper_disable();
device_shutdown();
}
进行restart/halt/power_off前的准备工作,包括,
调用blocking_notifier_call_chain接口,向关心reboot事件的进程,发送SYS_RESTART将cmd参数一并发送出去。驱动需要关注该事件需要调用register_reboot_notifier注册,
将系统状态设置为相应的状态(SYS_RESTART)。
调用usermodehelper_disable接口,禁止User mode helper。
调用device_shutdown,关闭所有的设备
migrate_to_reboot_cpu
void migrate_to_reboot_cpu(void)
{
/* The boot cpu is always logical cpu 0 */
int cpu = reboot_cpu;
cpu_hotplug_disable();
/* Make certain the cpu I'm about to reboot on is online */
if (!cpu_online(cpu))
cpu = cpumask_first(cpu_online_mask);
/* Prevent races with other tasks migrating this task */
current->flags |= PF_NO_SETAFFINITY;
/* Make certain I only run on the appropriate processor */
set_cpus_allowed_ptr(current, cpumask_of(cpu));
}
该函数主要是把当前调用重启接口的任务,绑定到固定的一个CPU上,通常为当前online的序号排第一的CPU。此处不用纠结为什么绑定到第一个online的而不是第二个第三个,主要原因一是为了简单,二是防止在重启时再有什么任务迁移。
syscore_shutdown
/**
* syscore_shutdown - Execute all the registered system core shutdown callbacks.
*/
void syscore_shutdown(void)
{
struct syscore_ops *ops;
mutex_lock(&syscore_ops_lock);
list_for_each_entry_reverse(ops, &syscore_ops_list, node)
if (ops->shutdown) {
if (initcall_debug)
pr_info("PM: Calling %pF\n", ops->shutdown);
ops->shutdown();
}
mutex_unlock(&syscore_ops_lock);
}
回调所有注册syscore shutdown回调的回调函数,通常注册syscore的回调有3个:suspend\resume\shutdown,其中suspend和resume在低功耗流程中调用,shutdown则在此处调用。
machine_restart
/*
* Restart requires that the secondary CPUs stop performing any activity
* while the primary CPU resets the system. Systems with multiple CPUs must
* provide a HW restart implementation, to ensure that all CPUs reset at once.
* This is required so that any code running after reset on the primary CPU
* doesn't have to co-ordinate with other CPUs to ensure they aren't still
* executing pre-reset code, and using RAM that the primary CPU's code wishes
* to use. Implementing such co-ordination would be essentially impossible.
*/
void machine_restart(char *cmd)
{
/* Disable interrupts first */
local_irq_disable();
smp_send_stop();
do_kernel_i2c_restart(cmd);
/*
* UpdateCapsule() depends on the system being reset via
* ResetSystem().
*/
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_reboot(reboot_mode, NULL);
/* Now call the architecture specific reboot code. */
if (arm_pm_restart)
arm_pm_restart(reboot_mode, cmd);
else
do_kernel_restart(cmd);
/*
* Whoops - the architecture was unable to reboot.
*/
printk("Reboot failed -- System halted\n");
while (1);
}
复位cpu会通过smp_send_stop接口把其他cpu停下来,接着调用复位命令,看一下这个do_kernel_restart,也是回调函数列表,主要处理通过register_restart_handler注册到register_restart_handler的设备,其中比较关键的clk函数下面贴出来看一下
static int rockchip_restart_notify(struct notifier_block *this,
unsigned long mode, void *cmd)
{
if (cb_restart)
cb_restart();
writel(0xfdb9, rst_base + reg_restart);
return NOTIFY_DONE;
}
参考一下px30的datasheet《2.3 System Reset Solution》
到此ARM 会执行复位。