PM-RPM Linux电源管理
如果能在保证系统运行的基础上,尽量节省对能量的消耗,就会大大提升该系统的生存竞争力
通俗的说就是: 只有在让系统跑的时候,我们才会给他上电,这就是电源管理的核心思想。
- 实时的关闭暂时不使用的部分(可称做“工作状态到非工作状态的转移”)。 例如手机在口袋时,屏幕没必要亮
- 当需要重新使用那些已关闭部分时(可称作"非工作状态到工作状态的转移"), 不能有太长时间的等待,且转移过程不能消耗太多的能量。
一般系统可以通过降低运行时钟,有空闲(少做事情),有休眠可以保证能量的节省消耗,比如单片机就有对应的低速模式,空闲模式和掉电模式,
对于linux而言休眠唤醒的大致流程可分为如下:
休眠:
- 暂停app(用户app, 内核线程)
- 暂停各类设备
- 停止cpu
唤醒:
- 启动cpu
- 启动各类设备
- 启动app
Linux电源管理分为系统睡眠模型和Runtime电源管理模型
系统休眠模型:把整个系统陷入休眠
- On(on) S0 - Working
- Standby (standby) S1 - CPU and RAM are powered but not executed 类似空闲模式
- Suspend to RAM(mem) S3 - RAM is powered and the running content is saved to RAM
- Suspend to Disk,Hibernation(disk) S4 - All content is saved to Disk and power down
Hibernation(冬眠,会关闭整个系统的供电,如果想醒来只能通过按power键)
STR/STD: 称为睡眠,并不会关闭所有的供电,通常会保留某些重要设备的供电(如键盘),那样,这些设备就可以唤醒系统
win: 睡眠–> suspend to ram , 休眠–>suspend to disk
ubuntu: suspend --> standby , hibernate – > suspend to disk
S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。
S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。
Runtime 电源管理模型
在上面的On状态下如何省电
- 可以降低运行时钟
- 关闭无用的设备
对于Linux电源管理,我们主要重点关注如下四个方面:
从功能上分为:
- linux电源管理框架
- 具体设备的电源管理(驱动)
从策略来看:
- APP决定如何进行电源管理
从设计来看:
- 如何设计硬件的供电系统,如何去写软件
linux系统suspend的实现
启动suspend to ram:
echo ram/disk > /sys/power/state
_______________________________________________________________________________________________
state_store (kernel/power/main.c)
pm_suspend (kernel/power/suspend.c)
enter_state (kernel/power/suspend.c)
suspend_prepare (kernel/power/suspend.c)
pm_prepare_console (kernel/power/console.c)
__pm_notifier_call_chain //通知所有关心“休眠消息”的驱动程序
suspend_freeze_processes //冻结app和内核线程
suspend_devices_and_enter //让设备进入休眠状态
platform_suspend_begin //如果平台相关的代码有整个begin函数,就调用这个, 这里4.88内核只有acpi支持freeze_ops暂不讨论
suspend_console //停止控制台
dpm_suspend_start (drviers/base/power/main.c)
dpm_prepare
对于dpm_list的每一个设备,都调用device_prepare, 对于该设备,调用它的
dev->pm_domain->ops.prepare; 或
dev->type->pm->prepare; 或
dev->class->pm->prepare; 或
dev->bus->pm->prepare; 或
dev->driver->pm->prepare;
dpm_suspend
对于dpm_prepare_list的每一个设备,都调用device_suspend, 对于该设备,调用它的
dev->pm_domain->ops.suspend; 或
dev->type->pm->suspend; 或
dev->class->pm->suspend; 或
dev->bus->pm->suspend; 或
dev->driver->pm->suspend;
suspend_enter
platform_suspend_prepare 如果有提供prepare函数,就调用
dpm_suspend_late
对于dpm_suspend_list的每一个设备,都调用device_suspend, 对于该设备,调用它的
dev->pm_domain->ops.suspend_late; 或
dev->type->pm->suspend_late; 或
dev->class->pm->suspend_late; 或
dev->bus->pm->suspend_late; 或
dev->driver->pm->suspend_late;
dpm_suspend_noirq
对于dpm_late_early_list的每一个设备,都调用device_suspend, 对于该设备,调用它的
dev->pm_domain->ops.suspend_noirq; 或
dev->type->pm->suspend_noirq; 或
dev->class->pm->suspend_noirq; 或
dev->bus->pm->suspend_noirq; 或
dev->driver->pm->suspend_noirq;
platform_suspend_prepare_noirq
disable_nonboot_cpus : 只有一个主cpu, 把其他的cpu关掉
arch_suspend_disable_irqs 关闭中断
syscore_suspend 关闭核心模块
suspend_ops->enter(state); 真正的进入休眠
...... 需要设置唤醒源
pm_cpu_prepare ---> s3c2410_pm_prepare
S3C2410_GSTATUS3 = s3c_cpu_resume
cpu_suspend: --->最后执行s3c2440_pm_suspend
......
以上是休眠过程:
==================================================
下面开始唤醒:
按键,导致uboot运行,读取S3C2410_GSTATUS3 ,执行s3c_cpu_resume
......
s3c_pm_restore_core
syscore_resume 开启核心模块
arch_suspend_enable_irqs 开中断
enable_nonboot_cpus 开启cpu
platform_resume_noirq --》 调用suspend_ops->wake
dpm_resume_noirq
对于dpm_noirq_list链表中的每一个device, 都执行
dev->pm_domain->ops.suspend_noirq; 或
dev->type->pm->resume_noirq; 或
dev->class->pm->resume_noirq; 或
dev->bus->pm->resume_noirq; 或
dev->driver->pm->resume_noirq;
dpm_resume_early
对于dpm_late_early_list链表中的每一个device, 都执行
dev->pm_domain->ops.resume_early; 或
dev->type->pm->resume_early; 或
dev->class->pm->resume_early; 或
dev->bus->pm->resume_early; 或
dev->driver->pm->resume_early;
platform_resume_finish
dpm_resume_end
resume_console
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
驱动程序里相关的电源管理函数的调用过程:
休眠: prepare->suspend->suspend_late->suspend_noirq
唤醒: resume_noirq->resume_early->resume->complete
电源管理在linux kernel中是一个庞大的子系统,涉及到供电(Power Supply), 充电(Charger), 时钟(Clock), 频率(Frequency), 电压(Voltage), 睡眠/唤醒(Suspend/Resume)等方方面面.
在对图片中的这些组件(也可以称作Framework)进行描述之前,先在这里了解一下基本概念:
注二: Framework是一个中间层的软件,提供软件开发的流程,其目的有三: 一是屏蔽具体的实现细节,固定对上的接口,这样可以方便上层软件的开发和维护。 二是尽可能抽象公共逻辑,并在Framework内实现, 以提高重用性,减少开发量; 三是向下层提供一系列回调函数, 下层软件可能面对差别较大的现实,但只要填充这些回调函数,即可完成所有逻辑,减小了开发难度。
- Power supply, 是一个供用户空间程序监控系统的供电状态(电池供电,usb供电,AC供电等等)的class。 通俗的讲,它是一个battery & charger驱动的Framework。
- Regulator Framework, Voltage/Current Regulator驱动的Framework。 该驱动用于调节Cpu等模块的电压和电流值
- Dynamic Tick/Clock event, 在传统的Linux Kernel中,系统Tick是固定周期的(比如10ms)一个,因此每隔一个Tick, 就会产生一个Timer中断。 这会唤醒处于idle或者sleep状态的cpu, 而很多时候这种唤醒是没有意义的。 因此新的kernel就产生了Dynamic Tick的概念, Tick不再是周期性的, 而是根据系统的中定时器的情况,不规律的产生,这样可以减少很多无用的Timer中断
- CPU idle, 用于控制CPU idle状态的frame work
- Generic PM传统意义上的Power Management, 如power off, Suspend to RAM, Suspend to Disk, Hibernate等
- Runtime Pm and wacklock, 运行时的Power Management,不再需要用户程序的干涉, 由内核统一调度,实时的关闭或打开设备, 以便在使用性能和省电性能之间找到最佳的平衡
- Runtime PM是linux kernel亲生的运行时电源管理机制, Wakelock是由Android 提出的机制。 这两种机制的目的是一样的, 因此只需要支持一种即可。
- Cpu Freq/device Freq, 用于实现CPU以及device频率调整的Framework
- OPP(Operating Performance Point)是指可以使SOCs或者device正常工作的电压和频率结合。内核提供这一个Layer, 是为了在众多的电压和频率组合中,筛选出一个相对固定的组合,从而使事情变得简单一些。
- PM QOS, 所谓的PM QOS, 是指在指定的运行状态下(不同电压, 频率,不同模式之间切换)的工作质量, 包括latency, timeout, throught三个参数, 单位分别为:us, us和kb/s。 通过QOS参数可以分析,改善系统的性能。
Geniric PM在Linux操作系统的表现形式
Linux操作系统中,和Generic PM有关的操作如下面图片:
共有三部分组成:
第一部分系统关机, 重启等操作的界面,包含Hibernate, Restart, Shutdown三个操作选项, 2、3部分是“电源管理属性”设置,所谓的电源管理属性,可以配置系统在不同供电模式下(如AC Power, Battery等)处于Inactive状态多久后,系统关闭Display, 或者进入Sleep状态。
本文将会围绕上面提到的各个名词,讲述它们的意义,在内核中的实现方式。 开始之前,先解释一下这些词汇的含义:
shutdown: 很好理解,就是关机的意思,同时意味着不再使用计算机
Restart : 就是重启系统的意思,重启的过程不再使用计算机
Hibernate: 在不需要使用计算机时,将它当前的所有现场(执行的程序,显示器显示的图像,正在播放的声音等)保存到一些断电不会丢失的存储器中(如硬盘中),然后将计算机关闭。 重新开启后, 系统会从存储器中将关闭前的现场读取出来并恢复,此时从使用者的角度来看,计算机好像没有被关闭过一样。
Sleep: 在计算机中,Hibernate需要把现场保存到断电不丢失的存储器中,并在醒来的时候都回来,这些可能需要较长时间(因为断电不丢失存储器的访问速度都比较慢)。 如果想快点, 就把现场保存到内存中就可以了,这就是Sleep, 不过这是要付出代价的,内存要保持供电, 这就要消耗能量, 鱼与熊掌不可兼得。
Auto Sleep:查看上述图片第三个部分,可以设置系统处于“Inactive状态多久后, 自动进入Sleep状态”。
Auto put display to Sleep: 原理类似, 只是操作的对象是Display.
Generic PM和Runtime PM的本质区别, 在使用者的主观意愿上,是否需要暂停使用计算机(哪怕短短的一段时间)。
这也是Generic PM在传统计算机操作系统中被广泛使用的原因, 因为那个时候对计算机的使用大多是主动方式。而对当前的移动互联来说,就非常不合时宜了,因为人们需要移动设备实时在线,实时接受被动事件(如来电), 也就不可能主观的暂停使用(哪怕短短的一段时间), 这种最终需求的差异,会导致在软件设计上有很大的差别,正因为如此,Runtime PM的出现和尽快成熟,才显得格外重要。
Generic PM的软件架构
在介绍完Generic PM的基本概念后,我们来看一下它在Linux内核中的整体实现, 并抽象出简单的软件架构,以便在后续的文章中, 对Generic PM的主要组成部分进行更为细致的分析。
根据上面的描述, Generic PM主要处理关机, 重启,冬眠(Hibernate),睡眠(Sleep, 在kernel中也称作Suspend), 在内核中,大致可以分为三个软件层次。
API接口: 用于向用户空间提供接口,其中关机和重启的接口形式是系统调用, Hibenate和Suspend的接口形式是sysfs.
PM core: 位于kernel/power目录下, 主要处理和硬件无关的核心逻辑
PM Driver: 分为两部分, 一是体系无关的Driver, 提供Driver框架(Framework)。 另一部分是具体的体系结构相关的Driver, 这也是电源管理驱动开发索要涉及的内容。
另外, 电源管理是一个系统级的模块,因而会涉及到设备模型,进程管理等等方方面面的内容、
Kernel支持的reboot方式
reboot是重启的意思,所以用它实现Restart是合理的,关机是一种特殊的restrat,因为你早晚也会开机,只不过持续的时间有点长而已。内核根据不同的方式,将reboot分为如下几种方式:
/*
* 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)动作。
注: ctrl+alt+del组合按键的响应是由具体的Driver实现的
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_poweroff三个处理函数,响应用户空间的reboot请求
- 这三个处理函数的处理流程大致相同, 主要包括: 向关心reboot过程的进程发送notify事件; 调用driver核心模块提供的 接口,关闭所有的外部设备; 调用drivers syscore模块提供的接口, 关闭system core; 调用Architecture相关的处理函数,进行后续的处理;最后调用machine相关的接口,实现真正意义上的reboot
- 另外,借助tty模块提供的Sysreq机制, 内核提供了其他途径的关机方法,如某些按键组合,向/proc文件写入命令
Reboot过程的内部动作和代码分析
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
该函数的参数解释如下:
reboot: 系统调用的名称
magic1/magic2: 两个int类型的"魔力数", 用于防止误操作。 具体在“include/uapi/linux/reboot.h”中定义,感兴趣的同学可以去看看(话说这些数字还是蛮有意思的,例如Linus同学及其家人的生日就在里面,猜出来的可以在文章下面留言)。
cmd: 上述的reboot方式
arg: 额外参数
1)判断调用者的用户权限,如果不是超级用户(superuser), 则直接返回错误(这也是我们在用用户空间执行reboot, halt和poweroff等命令时,必须是root用户的原因)
2)判断传入的magic number是否匹配,如果不匹配则直接返回错误。这样可以尽可能的防止误动作发生
3)调用reboot_pid_ns接口,检查是否需要由该接口处理reboot请求。 这是一个有关pid namespace的新特性, 也是linux内核重要的知识点。
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) 返回上述的处理结果,系统调用结束
kernel_restart, kernel_halt和kernel_power_off
这三个接口也位于“kernel/reboot.c”, 实现比较类似, 具体动作包括:
1)调用kernel_xxx_prepare函数,进行restart/halt/power_off前的准备工作,包括调用blocking_notifier_call_chain接口,向关心reboot事件的进程,发送SYS_RESTART、SYS_HALT或者SYS_POWER_OFF事件。对RESTART来说,还要将cmd参数一并发送出去。将下i同状态设置为相应的状态(SYS_RESTART、SYS_HALT或者SYS_POWER_OFF)。调用usermodehelper_disable接口,禁止User mode helper。 调用device_shutdown关闭所有设备。
2)如果是poweroff, 且存在PM相关的power off prepare函数(pm_power_off_prepare), 则调用该回调函数
3)调用migrate_to_reboot_cpu接口,将当前的进程(task)转移到一个CPU上; 注: 对于多CPU的机器,无论哪个cpu触发了当前的系统调用,代码都可以运行在任意的cpu上。这个接口将代码分派到特定的cpu上,并禁止调度器分派代码到其他的CPU上。 也就是说,这个接口被执行后,只有一个cpu在运行,用于完成之后的reboot动作。
4)调用system_shutdown接口,将系统核心器件关闭(例如中断等)
5)调用printk以及kmsg_dump, 向这个世界发出最后的声音(打印日志)
6)最后由machine-core的代码,接管后续的处理
device_shutdown
设别模型中和device_shutdown有关的逻辑包括:
- 每个设备(struct device)都会保存该设备的驱动(struct devcie_driver)指针,以及该设备所在总线(struct bus_type)的指针
- 设备驱动中有一个名称为“shutdown"的回调函数,用于在device_shutdown时,关闭该设备
- 总线中也有一个名称为”shutdown"的回调函数,用于在devvice_shutdown时关闭该设备
- 系统中的设备,都存在于“/sys/devices”目录下, 而该目录由名称为“devices_kset”的kset表示。kset中会使用一个链表保存其下的所有的kobject(也即”/sys/devices“目录下的所有设备)。最终结果就是以”devcie_kset"为root节点, 将内核中所有的设备(以相应的kobject为代表),组织成一个树状结构。
介绍完 以上的背景知识,我们来看device_shutdown的实现,就非常容易了。该接口位于”/driver/base/core.c“中,执行的逻辑如下:
oid device_shutdown(void)
{
struct device *dev, *parent;
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)) {
//遍历device_kset的链表,取出所有的设备
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和pm_runtime_barrier接口,停止所有的Runtime相关的电源管理动作(后面会详细描述有关Runtime PM的逻辑)
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
//依次调取class->shutdown函数,如果该设备的class提供了shutdown函数,有限调用class的shutdown函数,关闭设备, 如果没有依次调用bus的shutdown函数,最后时driver中的shutdown函数关闭设备
if (dev->class && dev->class->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->class->shutdown(dev);
} else 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);
}
直到处理完所有的函数
system_core_shutdown
system core的shutdown和deviceshutdown类似,也是从一个链表中,遍历所有的system core, 并调用它的shutdown接口。
machine_restart, machine_halt和machine_power_off
虽然都是以machine为前缀命名,这三个接口却是Architecture相关的处理函数,如ARM。 以ARM 为例, 它们在”arch/arm/kernel/process.c”中实现,具体如下
/*
* 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.
*/
对于多cpu的机器来说,Restart之前必须保证其他的CPU处于非活动状态,由其中的一个主CPU负责Restart动作。并且必须实现一个基于硬件的Restart的操作,以保证所有的CPU同步Restart,这是涉及的重点。
对于单cpu来说就简单了,直接用软件reset方式实现Restart
void machine_restart(char *cmd)
{
local_irq_disable();
smp_send_stop(); //确保其他CPU处于非活动状态
if (arm_pm_restart) //如果定义了这个函数
arm_pm_restart(reboot_mode, cmd); 这是一个回调函数,具体代码: EXPORT_SYMBOL_GPL(arm_pm_restart);
else
do_kernel_restart(cmd);
/* Give a grace period for failure to restart of 1s */
mdelay(1000); //等待1s 如果没有返回,就restart成功,否则错误,打印错误信息
/* Whoops - the platform was unable to reboot. Tell the user! */
printk("Reboot failed -- System halted\n");
while (1);
}
machine_halt
ARM的halt很简单, 就是先将其他的CPU停息来,并禁止当前的CPU中断后,死循环。
void machine_halt(void)
{
local_irq_disable();
smp_send_stop();
while (1);
}
machine_power_off
power off动作和restart类似,即停止其它CPU,调用回调函数。power off的回调函数和restart类似,就不再说明了。
电源驱动需要实现的内容
由以上的分析可知,在Reboot的过程中,大部分的逻辑是否内核处理的,具体的driver需要关注两点即可
- 实现各自的shutdown接口,以正确关闭对应的设备
- 实现machine-dependent接口,以确保底层的Machine可以正确的restart或者poweroff (这个我没注意到,接下来继续关注)