1. 当所有wake_lock被释放,自动进入休眠;
2. echo mem > /sys/power/state;(也需要等待wake_lock全部释放才能进入suspend);
1. 冻结用户态进程、内核线程;
2. 调用注册的设备的suspend回调,其顺序就是按照注册顺序;
3. 休眠核心设备、使cpu进入休眠状态(或者关闭--supper standby)。
注:冻结进程 --- 就是内核把进程列表中的所有的进程状态设置为停止,并且保存其运行上下文,当这些进程别解冻后,他们是不知道自己被冻结过,进程继续执行。
n linux/kernel/power/
内核实现的与平台无关的休眠框架,休眠的入口处,负责冻结进程、设备休眠、
在适当的地方回调具体平台实现的休眠函数。
n linux/arch/arm/mach-sunxi/pm/
平台相关的休眠处理:负责现场保护,保证resume后,能够恢复到休眠前的状态。
n linux/driver/base/power/
设备休眠实现,通过链表管理设备注册的suspend/resume回调函数。
当进入到suspend_prpare()函数后,内核为suspend分配一个虚拟终端来输出信息,然后广播一个内核要进入suspend的notify,接着调用suspend_freeze_processes()函数冻结所有的进程。
经过上一步的处理,所有的进程(包括workqueuq/kthread)都已经停止了,此处调用suspend_devices_and_enter()将外设休眠,如果平台定义了suspend_ops(我们的平台是在arch/arm/mach-sunxi/pm下定义的),这里就会调用suspend_ops->begin()。
设备休眠是在driver/base/power/main.c中实现的,device_suspend()->dpm_suspend()调用,dpm_suspend()会依次调用驱动注册的suspend()回调来休眠外设。
接下来,会将非启动的cpu关掉,此后只会有一个cpu在运行。
调用suspend_enter(),这个函数会关掉arch irq,调用device_power_down(),该函数会调用suspend_late()函数,这个函数就是让系统真正进入休眠;
调用平台实现的suspend_ops->enter()来执行平台自定义的休眠操作,我们平台的
super_standby就是在此处实现的,代码也停止在suspend_ops->enter()。
如果有效的唤醒源发生,系统将退出休眠状态,代码会重新开始执行,唤醒的顺序和休眠的顺序相反,所有系统设备和总线会首先唤醒,使能中断,使能休眠时停止的非启动cpu,以及调用平台相关的suspend_ops->finish()。
接着suspend_device_and_enter()也会继续唤醒每个设备,使能虚拟终端,最后调用
suspend_ops->end()。
从suspend_device_and_enter()返回到enter_state()中,此时外设已经唤醒,但是进程和任务都还在冻结状态,这里会调用suspend_finish()来解冻这些进程和任务,再发出notify来表示已经从suspend状态退出,唤醒终端。
与进程冻结相关的3个标志位:
n PF_NOFREEZE
-- 不可休眠的,对于user space的进程,这个bit都是0,即都是可休眠的;
n PF_FROZEN
-- 标志着该进程已经进入冻结状态;
n PF_FREEZE_SKIP
-- 如果设置了该bit,会跳过冻结处理。
这3个标志位都是保留在task_struct->flags中,即每个进程都有自己独有的冻结标志。
如果进程的PF_NOFREEZE没有设置,则在休眠的时候,这个进程就会被冻结掉,对于所有用户态的进程,这个bit都是没有设置的,即是说,休眠的时候,所有用户进程都是会被冻结的;但是,有一些内核线程,这个bit也是没有设置的,即需要冻结。
全局变量,标志着是否正在执行休眠流程;
冻结进程的第一步就是:atomic_inc(&system_freezing_cnt);
退出休眠的最后一步就是解冻进程:thaw_processes(),需要将system_freezing_cnt减1,
atomic_dec(&system_freezing_cnt);
参考Documentation/power/freezing-of-tasks.txt,try_to_freeze_tasks其实是:sends a fake signal to all user space processes,即通过向所有用户进程发出一个欺骗信号,通过信号处理来实现进程冻结的;在try_to_freeze_tasks中其实并没有将进程挂起或者说使其不再运行,仅仅是对进程发出了一个信号,同时在信号处理的开始首先检查是否需要进入冻结状态;把冻结进程交给信号处理来做,try_to_freeze_tasks要做的就是设置一些标志位来指示信号处理要冻结它了,这样该进程在返回用户空间的时候就乖乖进入到冻结状态了(用户态的进程都是从内核态返回用户态的时候冻结的);
进程冻结发生时刻
所谓的冻结,就是进程进入一个循环,判断是否在冻结状态;如果需要处于冻结状态,则将current->flags的PF_FROZEN置位。try_to_freeze()最终就是调用__refrigerator(),使当前进程进入这样一个循环:
// kernel/freezer.c __refrigerator // 冻结后的进程,其实就是在这样一个循环中,等到退出冻结状态 for (;;) { set_current_state(TASK_UNINTERRUPTIBLE); spin_lock_irq(&freezer_lock); current->flags |= PF_FROZEN; if (!freezing(current) || (check_kthr_stop && kthread_should_stop())) // 直到该条件不满足 current->flags &= ~PF_FROZEN; // 清除PF_FROZEN标志 spin_unlock_irq(&freezer_lock); if (!(current->flags & PF_FROZEN)) break; was_frozen = true; schedule(); } |
所有的freezeable的进程,try_to_freeze()会执行__refrigerator()设置进程的PF_FROZEN标志,进程状态设置为TASK_UNINTERRUPTIBLE,循环直到需要退出冻结状态(system_freezing_cnt为0),退出冻结状态前先清除PF_FROZEN标志;
对于user space的进程,try_to_freeze()是在signal-handing core中被调用的;但是对于内核态的线程,需要在合适的地方,主动调用try_to_freeze(),或者,使用wait_event_freezable()或wait_event_freezable_timeout(),这两个wait_event_freezable_*宏都会检查是否需要调用try_to_freeze(),举例如下:
// 内核线程默认是不休眠的,如果需要休眠,通过set_freezable修改PF_NOFREEZE set_freezable(); do { hub_events(); wait_event_freezable(khubd_wait, // 在合适的地方,检查是否需要进休眠 !list_empty(&hub_event_list) || kthread_should_stop()); } while (!kthread_should_stop() || !list_empty(&hub_event_list)); (from drivers/usb/core/hub.c::hub_thread()). |
从实现上看,设备电源管理就是在系统状态迁移的时候调用设备注册的特定回调函数,这些回调函数统一封装在一个数据结构中,struct dev_pm_ops:
struct dev_pm_ops { int (*prepare)(struct device *dev); void (*complete)(struct device *dev); int (*suspend)(struct device *dev); int (*resume)(struct device *dev); int (*freeze)(struct device *dev); int (*thaw)(struct device *dev); int (*poweroff)(struct device *dev); int (*restore)(struct device *dev); int (*suspend_late)(struct device *dev); int (*resume_early)(struct device *dev); int (*freeze_late)(struct device *dev); int (*thaw_early)(struct device *dev); int (*poweroff_late)(struct device *dev); int (*restore_early)(struct device *dev); int (*suspend_noirq)(struct device *dev); int (*resume_noirq)(struct device *dev); int (*freeze_noirq)(struct device *dev); int (*thaw_noirq)(struct device *dev); int (*poweroff_noirq)(struct device *dev); int (*restore_noirq)(struct device *dev); int (*runtime_suspend)(struct device *dev); int (*runtime_resume)(struct device *dev); int (*runtime_idle)(struct device *dev); }; |
该数据结构会包含在struct device / struct device_driver里面,由具体的device和driver进行初始化,休眠唤醒的时候,由pm core进行回调。实际使用最多的就是suspend和resume接口。
对于设备驱动,只需要实现相应的回调函数即可支持电源管理;剩下的都由pm core负责完成。在suspend/resume过程中,pm core会依次调用:
-> prepare -> suspend -> suspend_late -> suspend_noirq
-> wakeup
-> resume_noirq -> resume_early -> resume -> complete
struct class { ..... const struct dev_pm_ops *pm; ..... }; struct bus_type { .... const struct dev_pm_ops *pm; .... }; struct device_driver { .... const struct dev_pm_ops *pm; .... }; struct device_type { .... const struct dev_pm_ops *pm; .... }; |
设备模型中的class/bus_type/device_driver/device_type结构体都包含了dev_pm_ops数据结构,在设备的不同层次上,实现电源管理控制。
以i2c bus为例,在注册i2c_bus_type的时候,也指定了电源管理的回调dev_pm_ops为i2c_device_pm_ops:
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, }; |
其中,i2c_device_pm_ops.suspend = i2c_device_pm_suspend,即i2c休眠的时候,最终调用到的就是i2c_device_pm_suspend这个回调,i2c_device_pm_suspend的具体实现如下:
static int i2c_device_pm_suspend(struct device *dev) { const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; if (pm) return pm_generic_suspend(dev); else return i2c_legacy_suspend(dev, PMSG_SUSPEND); } |
可以看到,i2c_device_pm_suspend函数只是判断dev->driver->pm是否实现了,如果驱动的dev_pm_ops是有效的,则调用pm_gengric_suspend(dev)进行真正的设备休眠。
从i2c_bus这个例程中也可以看到,驱动实现的时候,只需要正确填写driver->pm上的回调函数即可,具体在什么时候回调,pm core已经实现好了。
4.2简单说介绍了一下,dev_pm_ops是如何嵌在device结构体上的;此处,将介绍内核是怎么通过链表,对所有的设备电源管理,代码实现在:drivers/power/base下。内核为设备电源管理定义了5个链表,分别对应设备休眠的不同阶段。
相关的链表:
LIST_HEAD(dpm_list); LIST_HEAD(dpm_prepared_list); LIST_HEAD(dpm_suspended_list); LIST_HEAD(dpm_late_early_list); LIST_HEAD(dpm_noirq_list); |
n dpm_list
在注册设备的时候,device->add() --> device_pm_add(),即对于每一个注册到系统中的设备,都会挂到dpm_list上;设备卸载的时候,也会从dpm_list上移除掉;正常运行状态下,设备都是挂在dpm_list上的;
n dpm_prepared_list
休眠过程中,pm core调用dpm_prepare()后,各设备从dpm_list迁移到dpm_prepared_list上;其实dpm_prepare()最终就是执行dev_pm_ops->prepare回调;根据优先顺序,获得用于prepare的callback函数;由于设备模型有bus、driver、device等多个层级,而prepare接口可能由任意一个层级实现;这里的优先顺序是:只要优先级高的层级注册了prepare,就会优先使用它,而不会使用优先级低的prepare。优先顺序为:dev->pm_domain->ops、dev->type->pm、dev->class->pm、dev->bus->pm、dev->driver->pm(这个优先顺序同样适用于其它callbacks);具体实现优先级判断的代码如下:
device_lock(dev); dev->power.wakeup_path = device_may_wakeup(dev); // 按照优先级顺序,查找有效的prepare()回调函数 if (dev->pm_domain) { info = "preparing power domain "; callback = dev->pm_domain->ops.prepare; } else if (dev->type && dev->type->pm) { info = "preparing type "; callback = dev->type->pm->prepare; } else if (dev->class && dev->class->pm) { info = "preparing class "; callback = dev->class->pm->prepare; } else if (dev->bus && dev->bus->pm) { info = "preparing bus "; callback = dev->bus->pm->prepare; } // 如果上面没有得到prepare,则使用driver->pm->prepare回调 if (!callback && dev->driver && dev->driver->pm) { info = "preparing driver "; callback = dev->driver->pm->prepare; } // 执行回调函数 if (callback) { error = callback(dev); suspend_report_result(callback, error); } device_unlock(dev); return error; |
n dpm_suspended_list
n dpm_late_early_list
n dpm_noirq_list
休眠唤醒链表迁移