reboot 流程

前言

        对于生在智能时代的我们,对关机和重启这两个概念肯定不会陌生,尤其经历早期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/)实际的启动顺序取决于单元自身的依赖关系。
最后通过系统调用,通过内核完成相关操作。

kernel

如下为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 会执行复位。 


硬件时序

  • 12
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值