Linux电源管理3

Linux电源管理3(基于Linux6.6)---Generic PM Reboot介绍

 


一、 概述

1.1、Generic PM Reboot的背景

不同硬件平台(如x86、ARM、PowerPC等)通常有不同的重启机制。在一些平台上,重启可能需要通过特定的硬件指令或者控制寄存器来完成。因此,Linux在面对不同硬件平台时需要具备灵活的重启机制。

Generic PM Reboot接口通过为这些平台提供一个统一的、抽象的重启接口,使得系统可以在不同平台上使用相同的命令或方法来完成重启操作。这是Linux电源管理的一部分,旨在通过统一的接口实现跨平台的电源管理功能。

1.2、具体实现和功能

在Linux内核中,Generic PM Reboot机制通常通过reboot系统调用和poweroff命令来触发重启操作。其实现涉及以下几个关键方面:

  • PM Reboot函数:Linux内核提供了一个reboot()函数,它是一个系统调用,用于执行系统的重启操作。这个函数可以触发一个硬件重启或关闭系统(依赖于具体平台的实现)。

  • 平台相关实现:由于不同平台对重启操作的要求不同,Linux内核允许为特定硬件平台实现特定的重启方法。reboot操作通常依赖平台相关的驱动和控制器,如x86平台可能通过ACPI(高级配置与电源接口)来触发重启,而ARM平台可能通过不同的控制寄存器来实现。

  • 统一的接口:尽管底层实现平台特定,但Linux提供了统一的接口(如reboot()函数),让应用程序、脚本、系统管理工具等在调用时不需要关心具体的硬件细节。

1.3、在实际操作中

  • 重启操作:当Linux系统接收到重启命令时,Generic PM Reboot机制会根据平台的实现通过底层硬件接口进行重启。例如,在x86平台上,通常会通过ACPI接口或BIOS指令来执行重启操作;在ARM平台上,可能使用特定的硬件寄存器。

  • 重启命令:在Linux中,用户通常可以使用rebootshutdown -r等命令来触发系统重启。调用这些命令时,内核会执行与硬件平台相匹配的重启方法。

二、Linux支持的reboot方式

reboot是重启的意思,所以用它实现Restart是合理的,但怎么用它实现关机操作呢?答案是这样的:关机之后,早晚也会开机啊!所以关机是一种特殊的Restart过程,只不过持续的时间有点长而已。所以,内核根据不同的表现方式,将reboot分为如下的几种方式:

 include/uapi/linux/reboot.h

/*
 * 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的支持),暂不涉及。

三、Reboot相关的操作流程

在Linux操作系统中,可以通过reboot、halt、poweroff等命令,发起reboot,具体的操作流程如下:

  • 一般的Linux操作系统,在用户空间都提供了一些工具集合(如常在嵌入式系统使用的Busybox),这些工具集合包含了reboot、halt和poweroff三个和Reboot相关的命令。读者可以参考man帮助文档,了解这些命令的解释和使用说明。
  • 用户空间程序通过reboot系统调用,进入内核空间。
  • 内核空间根据执行路径的不同,提供了kernel_restart、kernel_halt和kernel_power_off三个处理函数,响应用空间的reboot请求。
  • 这三个处理函数的处理流程大致相同,主要包括:向关心reboot过程的进程发送Notify事件;调用drivers核心模块提供的接口,关闭所有的外部设备;调用drivers syscore模块提供的接口,关闭system core;调用Architecture相关的处理函数,进行后续的处理;最后,调用machine相关的接口,实现真正意义上的Reboot。
  • 另外,借助TTY模块提供的Sysreq机制,内核提供了其它途径的关机方法,如某些按键组合、向/proc文件写入命令等,后面会详细介绍。

四、Reboot过程的内部动作和代码分析

4.1、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;

	/*
	 * 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;

	/* 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) && !kernel_can_power_off())
		cmd = LINUX_REBOOT_CMD_HALT;

	mutex_lock(&system_transition_mutex);
	switch (cmd) {
	case LINUX_REBOOT_CMD_RESTART:
		kernel_restart(NULL);
		break;

	case LINUX_REBOOT_CMD_CAD_ON:
		C_A_D = 1;
		break;

	case LINUX_REBOOT_CMD_CAD_OFF:
		C_A_D = 0;
		break;

	case LINUX_REBOOT_CMD_HALT:
		kernel_halt();
		do_exit(0);

	case LINUX_REBOOT_CMD_POWER_OFF:
		kernel_power_off();
		do_exit(0);
		break;

	case LINUX_REBOOT_CMD_RESTART2:
		ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
		if (ret < 0) {
			ret = -EFAULT;
			break;
		}
		buffer[sizeof(buffer) - 1] = '\0';

		kernel_restart(buffer);
		break;

#ifdef CONFIG_KEXEC_CORE
	case LINUX_REBOOT_CMD_KEXEC:
		ret = kernel_kexec();
		break;
#endif

#ifdef CONFIG_HIBERNATION
	case LINUX_REBOOT_CMD_SW_SUSPEND:
		ret = hibernate();
		break;
#endif

	default:
		ret = -EINVAL;
		break;
	}
	mutex_unlock(&system_transition_mutex);
	return ret;
}

 

reboot系统调用的内部动作比较简单:

1)判断调用者的用户权限,如果不是超级用户(superuser),则直接返回错误;

2)判断传入的magic number是否匹配,如果不匹配,直接返回错误。这样就可以尽可能的防止误动作发生;

3)调用reboot_pid_ns接口,检查是否需要由该接口处理reboot请求;

4)如果是POWER_OFF命令,且没有注册power off的machine处理函数(pm_power_off),把该命令转换为HALT命令;

5)根据具体的cmd命令,执行具体的处理,包括,
      如果是RESTART或者RESTART2命令,调用kernel_restart。
      如果是CAD_ON或CAD_OFF命令,更新C_A_D的值,表示是否允许通过Ctrl+Alt+Del组合键重启系统。
      如果是HALT命令,调用kernel_halt。
      如果是POWER_OFF命令,调用kernel_power_off。
      如果是KEXEC命令,调用kernel_kexec接口。
      如果是SW_SUSPEND,调用hibernate接口。

6)返回上述的处理结果,系统调用结束。

4.2、kernel_restart、kernel_halt和kernel_power_off

kernel/reboot.c,实现比较类似,具体动作包括:

void kernel_restart(char *cmd)
{
	kernel_restart_prepare(cmd);
	do_kernel_restart_prepare();
	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_SHUTDOWN);
	machine_restart(cmd);
}
EXPORT_SYMBOL_GPL(kernel_restart);
void kernel_halt(void)
{
	kernel_shutdown_prepare(SYSTEM_HALT);
	migrate_to_reboot_cpu();
	syscore_shutdown();
	pr_emerg("System halted\n");
	kmsg_dump(KMSG_DUMP_SHUTDOWN);
	machine_halt();
}
EXPORT_SYMBOL_GPL(kernel_halt);
/**
 *	do_kernel_power_off - Execute kernel power-off handler call chain
 *
 *	Expected to be called as last step of the power-off sequence.
 *
 *	Powers off the system immediately if a power-off handler function has
 *	been registered. Otherwise does nothing.
 */
void do_kernel_power_off(void)
{
	struct sys_off_handler *sys_off = NULL;

	/*
	 * Register sys-off handlers for legacy PM callback. This allows
	 * legacy PM callbacks temporary co-exist with the new sys-off API.
	 *
	 * TODO: Remove legacy handlers once all legacy PM users will be
	 *       switched to the sys-off based APIs.
	 */
	if (pm_power_off)
		sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
						   SYS_OFF_PRIO_DEFAULT,
						   legacy_pm_power_off, NULL);

	atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);

	unregister_sys_off_handler(sys_off);
}

4.3、device_shutdown

设备模型中和device_shutdown有关的逻辑包括:

  • 每个设备(struct device)都会保存该设备的驱动(struct device_driver)指针,以及该设备所在总线(struct bus_type)的指针。
  • 设备驱动中有一个名称为“shutdown”的回调函数,用于在device_shutdown时,关闭该设备。
  • 总线中也有一个名称为“shutdown”的回调函数,用于在device_shutdown时,关闭该设备。
  • 系统的所有设备,都存在于“/sys/devices/”目录下,而该目录由名称为“devices_kset”的kset表示。而kset中会使用一个链表保存其下所有的kobject(也即“/sys/devices/”目录下的所有设备)。最终的结果就是,以“devices_kset”为root节点,将内核中所有的设备(以相应的kobject为代表),组织成一个树状结构。

device_shutdown的实现,就非常容易了。drivers/base/core.c,执行逻辑如下。

/**
 * device_shutdown - call ->shutdown() on each device to shutdown.
 */
void device_shutdown(void)
{
	struct device *dev, *parent;

	wait_for_device_probe();
	device_block_probing();

	cpufreq_suspend();

	spin_lock(&devices_kset->list_lock);
	/*
	 * Walk the devices list backward, shutting down each in turn.
	 * Beware that device unplug events may also start pulling
	 * devices offline, even as the system is shutting down.
	 */
	while (!list_empty(&devices_kset->list)) {
		dev = list_entry(devices_kset->list.prev, struct device,
				kobj.entry);

		/*
		 * hold reference count of device's parent to
		 * prevent it from being freed because parent's
		 * lock is to be held
		 */
		parent = get_device(dev->parent);
		get_device(dev);
		/*
		 * Make sure the device is off the kset list, in the
		 * event that dev->*->shutdown() doesn't remove it.
		 */
		list_del_init(&dev->kobj.entry);
		spin_unlock(&devices_kset->list_lock);

		/* hold lock to avoid race with probe/release */
		if (parent)
			device_lock(parent);
		device_lock(dev);

		/* Don't allow any more runtime suspends */
		pm_runtime_get_noresume(dev);
		pm_runtime_barrier(dev);

		if (dev->class && dev->class->shutdown_pre) {
			if (initcall_debug)
				dev_info(dev, "shutdown_pre\n");
			dev->class->shutdown_pre(dev);
		}
		if (dev->bus && dev->bus->shutdown) {
			if (initcall_debug)
				dev_info(dev, "shutdown\n");
			dev->bus->shutdown(dev);
		} else if (dev->driver && dev->driver->shutdown) {
			if (initcall_debug)
				dev_info(dev, "shutdown\n");
			dev->driver->shutdown(dev);
		}

		device_unlock(dev);
		if (parent)
			device_unlock(parent);

		put_device(dev);
		put_device(parent);

		spin_lock(&devices_kset->list_lock);
	}
	spin_unlock(&devices_kset->list_lock);
}

上述函数的解析:

1)遍历devices_kset的链表,取出所有的设备(struct device);

2)将该设备从链表中删除;

3)调用pm_runtime_get_noresume和pm_runtime_barrier接口,停止所有的Runtime相关的电源管理动作(后续的文章会详细描述有关Runtime PM的逻辑);

4)如果该设备的bus提供了shutdown函数,优先调用bus的shutdown,关闭设备;

5)如果bus没有提供shutdown函数,检测设备driver是否提供,如果提供,调用设备driver的shutdown,关闭设备;

6)直至处理完毕所有的设备。

 

4.4、machine_restart、machine_halt和machine_power_off

虽然以machine_为前缀命名,这三个接口却是属于Architecture相关的处理函数,如ARM。以ARM为例,arch/arm/kernel/reboot.c,具体如下。

machine_restart

/*
 * Restart requires that the secondary CPUs stop performing any activity
 * while the primary CPU resets the system. Systems with a single CPU can
 * use soft_restart() as their machine descriptor's .restart hook, since that
 * will cause the only available CPU to reset. 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)
{
	local_irq_disable();
	smp_send_stop();

	do_kernel_restart(cmd);

	/* Give a grace period for failure to restart of 1s */
	mdelay(1000);

	/* Whoops - the platform was unable to reboot. Tell the user! */
	printk("Reboot failed -- System halted\n");
	while (1);
}

 解析说明:

1)先转述一下该接口的注释;
对于多CPU的机器来说,Restart之前必须保证其它的CPU处于非活动状态,由其中的一个主CPU负责Restart动作。并且,必须实现一个基于硬件的Restart操作,以保证所有CPU同步Restart,这是设计的重点!
对于单CPU机器来说,就相对简单了,可以直接用软件reset的方式实现Restart。

2)调用smp_send_stop接口,确保其它CPU处于非活动状态;

3)调用machine-dependent的restart接口,实现真正的restart;

4)等待1s;

5)如果没有返回,则restart成功,否则失败,打印错误信息。

machine_halt

ARM的halt很简单,就是将其它CPU停下来,并禁止当前CPU的中断后,死循环!确实,中断被禁止了,又死循环了,不halt才怪。代码如下:

/*
 * Halting simply requires that the secondary CPUs stop performing any
 * activity (executing tasks, handling interrupts). smp_send_stop()
 * achieves this.
 */
void machine_halt(void)
{
	local_irq_disable();
	smp_send_stop();
	while (1);
}

machine_power_off

power off动作和restart类似,即停止其它CPU,调用回调函数。power off的回调函数和restart类似,就不再说明了。

/*
 * Power-off simply requires that the secondary CPUs stop performing any
 * activity (executing tasks, handling interrupts). smp_send_stop()
 * achieves this. When the system power is turned off, it will take all CPUs
 * with it.
 */
void machine_power_off(void)
{
	local_irq_disable();
	smp_send_stop();
	do_kernel_power_off();
}

五、总结

Generic PM Reboot 是 Linux 内核电源管理(Power Management, PM)的一部分,旨在提供一个跨平台的接口,统一处理不同硬件平台上的系统重启操作。通过这一机制,Linux 内核能够支持在多种硬件架构(如 x86、ARM、PowerPC 等)上执行一致的重启行为,而无需关注平台特定的硬件细节。

5.1、电源管理驱动Reboot

由上面的分析可知,在Reboot的过程中,大部分的逻辑是否内核处理的,具体的driver需要关注2点即可:

1)实现各自的shutdown接口,以正确关闭对应的设备

2)实现machine-dependent的接口,以确保底层的Machine可以正确restart或者power off

看来还是很简单的。

主要特点和功能:

  1. 跨平台统一接口

    • 由于不同硬件平台对重启操作的要求不同,Linux 内核通过 Generic PM Reboot 提供了一个统一的接口,使得不同平台可以通过相同的命令或方法进行系统重启。
  2. 平台特定实现

    • 尽管有统一的接口,但重启操作的具体实现依赖于硬件平台。例如,x86 系统可能通过 ACPI(高级配置与电源接口)来执行重启,而 ARM 系统可能通过特定的硬件寄存器来触发重启。
  3. reboot() 系统调用

    • reboot() 是用于触发重启操作的系统调用。它接收几个参数,其中 cmd 参数指定要执行的操作(如重启、关机等),并通过底层的硬件接口实现具体操作。
  4. 电源管理的一部分

    • 除了重启,电源管理还包括如睡眠模式、休眠模式等。Generic PM Reboot 是其中的一部分,专注于系统重启功能。
  5. 优势

    • 跨平台支持:为不同平台提供统一的电源管理接口,简化了跨平台的电源管理实现。
    • 简化开发:系统开发者和硬件平台开发者可以依赖统一接口,而无需为每种硬件编写独立的重启代码。
    • 一致性:在各种硬件平台上,用户和应用程序通过相同的命令(如 rebootshutdown -r)即可触发重启,增强了系统管理的一致性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值