您的位置:首页 > 运维架构 > Linux

Linux的suspend机制的设计原理

2010-02-09 20:36 381 查看
Linux中实现了suspend-to-disk的机制,由pm_suspend_disk来完成,有网友问了一个问题:SMP在进入suspend(standby)模式的时候,各平台通用的电源管理代码会先把除了引导cpu以外的其他cpu都关掉,然后引导cpu自己再进入睡眠。当睡眠结束的时候,引导cpu会从进入睡眠的地方继续执行,而其他cpu则要重新启动。我的疑问是,既然进入suspend状态后,cpu可以保留休眠前的状态。那么为什么不能让非引导cpu也从休眠的地方继续,而是要有个停掉和重启的过程。这么做有什么特殊的考虑或者不这么做有什么难以克服的困难么?
这个问题十分具有代表性,到底为什么?我们不能靠理论获得答案,我们通过阅读linux源代码可以得到一些启示,在此先说一下大体的思想,其实在linux中并没有不能怎么样,只有怎么样不好,难道就真的不能保存所有的cpu的状态,等待resume的时候再恢复吗?其实完全可以,但是要想明白linux为什么没有这么做,还是必须理解suspend的本质,如果真的不理解什么是suspend,那么看代码或许是一个简单又快捷的方法了。其实suspend就是将机器的当前状态保存起来,然后休眠,这样可以节省能源,状态可以保存在内存也可以保存在磁盘,我们这里讨论保存在磁盘的情形,实际上不看代码也可以理解到,保存在磁盘的情况下,用户就不允许再写磁盘了,因为写磁盘可能会破坏机器suspend时保存的状态,既然如此,我们就可以抛开磁盘来讨论了,首先看一下进入suspend的代码:
int pm_suspend_disk(void)
{
int error;
error = prepare_processes(); //smp相关的代码,包括停掉除了启动cpu之外的别的cpu
...
error = device_suspend(PMSG_FREEZE); //设备暂时停止,因为以下要设置很多状态,这样可以保证状态稳定
...
if ((error = swsusp_suspend())) //机器准备进入suspend
goto Done;
if (in_suspend) {
device_resume(); //设备重新启用,因为我们知道,suspend状态的机器可以通过一些用户的动作激
活,比如晃动鼠标,比如按一下电源等等,因此在suspend期间必须保持设备是激活的,别的什么都可以停掉,毕竟节省能源吧,但
是特殊的中断一定要响应,因为正是通过中断将机器resume的。
error = swsusp_write();
...
}
既然到了这里,我们离cpu的suspend的实质就差不远了,记住,我们跟踪这一趟主要是为了弄清除了启动cpu之外的其它的cpu的行为,因此我们接着往下看:
static int prepare_processes(void)
{
int error;
pm_prepare_console();
disable_nonboot_cpus(); //这个函数很重要,在suspend的时候,它停掉了所有的非启动cpu,当然这只对smp系统有意义。
if (freeze_processes()) { //这个函数前面讲进程冻结的时候说过,这里就不多说了
...
}
接下来就看看disable_nonboot_cpus:
void disable_nonboot_cpus(void)
{
int cpu, error;
error = 0;
cpus_clear(frozen_cpus);
for_each_online_cpu(cpu) {
if (cpu == 0) //在suspend状态时,我们只针对启动cpu,因此略过启动的cpu
continue;
error = cpu_down(cpu); //将除了启动cpu之外的cpu全部down掉,其实不是真的关闭,而是促使其进入halt
...
}
int cpu_down(unsigned int cpu)
{
int err;
struct task_struct *p;
cpumask_t old_allowed, tmp;
...
err = blocking_notifier_call_chain(&cpu_chain, CPU_DOWN_PREPARE, (void *)(long)cpu); //通知所有的感兴趣的实体,这个cpu马上就不工作了,通知它们马上做准备和善后工作。
...
old_allowed = current->cpus_allowed; //这里和以下的标志位展示这个cpu马上就要“down”了,当前的进程不能在该cpu上运行了。
tmp = CPU_MASK_ALL;
cpu_clear(cpu, tmp);
set_cpus_allowed(current, tmp);
p = __stop_machine_run(take_cpu_down, NULL, cpu); //这个内核线程将要“down”掉的cpu从online处理器中清除,为了使这件事马上执行,这个函数中设置这个内核线程为最高的优先级,这样suspend可以很快地执行。
...错误处理,忽略
if (cpu_online(cpu))
goto out_thread;
while (!idle_cpu(cpu)) //等待该cpu卸下正在执行的任务而进入idle,idle并不是真正的任务,只是一个占位任务,因此在idle中suspend是安全的。
yield();
__cpu_die(cpu); //这个函数确认了这个cpu已经“down”掉,其实就是已经在cpu_idle中执行了halt而已,并没有真的关闭
...
}
上面函数有一句是if (cpu_online(cpu)),就是在等待这个cpu执行idle,那么它要是执行了idle呢?因为在__stop_machine_run启动的内核线程中已经将该cpu从online位图中清除,再加上cpu_idle的代码,这样的话,cpu就要执行halt了,这就是所谓的“除了启动cpu之外的别的cpu的停止”,这里可以可能出,并不是真的停止了别的cpu,而只是执行了halt,至于halt的意义可就值得一番研究了,总的来说,halt状态下的cpu只可以被中断打断,时钟中断是一种中断,当然可以使cpu脱离halt,但是cpu如果是dead状态的话还是不能将任务调度于其上,因此想让cpu工作,必须将其状态设置为不是dead,这就是resume的所用,你如我们晃一下鼠标,那么鼠标中断会唤醒启动cpu,为何仅仅唤醒启动cpu呢,因为在进入suspend的时候,启动cpu的信息已经被保存了,唤醒了启动cpu之后,resume函数进一步将其它的cpu从halt状态唤醒就可以了,实际上看看2.6.21内核之后的nohz就可以知道,nohz确实节省了不少能源因为suspend的时候不用再接收毫无意义的时钟中断了,实际上正常执行的时候如果没有任务被调度到这个cpu的队列上,那么也可以长期halt而不接收时钟中断。
解释到这里就可以明白,并不是不可以实现所有的cpu都保存当前状态然后等到resume的时候被恢复,而是这样做的话很麻烦,因为用户suspend机器的时候,各个cpu执行的上下文很不统一,有的在用户空间,有的在内核空间,有的在执行中断处理,如果保存这些状态的话很麻烦因此面临两个选择,一个是强制保存一个是等待其它的cpu进入到一个无关紧要的idle状态,权衡之后就会发现,后者是一个很好又很高效的方法,因此内核就选用了后者,也就是上面实现的那些。以下是我的回答:而且,机器的suspend并没有什么大不了的,看看代码就明白的,其它的cpu只是进入了play_dead函数从而进入了halt,halt还是可以被中断唤醒的,所以说在smp中suspend,别的cpu并没有真的关闭,而是进入了halt而已,可以说别的cpu还活着,等到resume的时候,只是启动cpu执行resume,别的cpu只是简单的设置一下状态就可以了,这样的实现很简单
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: