linux-3.4 电源管理框架(1)

2019-07-13 07:40发布

1. linux 中支持的电源管理

省电模式

'standby' (Power-On Suspend) 显示屏断电,主机通电 ================= 待机 'mem' (Suspend-to-RAM) 挂起到内存,需要外部中断唤醒 ========= 待机 'disk' (Suspend-to-Disk) 挂起到硬盘,比挂起到内存恢复更加耗时 = 休眠 关机:计算机所有程序关闭,系统退出,关闭电源
休眠:计算机保存现有软件状态,系统退出,关闭电源
待机:运行状态保存在内存当中。关闭 CPU、硬盘等,只对内存供电,不会关闭电源 linux 用户空间操作示例: # cat /sys/power/state (读操作) freeze standby disk === 表明该系统支持三种电源管理 # echo "freeze" > /sys/power/state (写操作) 会导致电脑发生 suspend-to-ram 在 linux 内核里面 /kernel/power/main.c 文件中有 power_attr(state); 展开可得 static struct kobj_attribute state_attr = { .attr = { .name = __stringify(state), /* 转换为字符串属性,即 #state */ .mode = 0644, /* 权限设置为 0644, */ }, .show = state_show, /* 发生读取操作时候调用到的函数 */ .store = state_store, /* 发生写入操作时候调用到的函数 */ } 内核空间与用户空间的映射关系
内核空间(internel) ——->用户空间(externel)
内核对象(kernel objects) ——->目录(directories)
对象属性(object attributes) ——->普通文件(regular files)
对象关系(object relationshiops) ——->符号链接(symbolic links)
static int __init pm_init(void) { int error = pm_start_workqueue(); if (error) return error; hibernate_image_size_init(); hibernate_reserved_size_init(); power_kobj = kobject_create_and_add("power", NULL); /* 创建一个叫 power 的 kobject(也就是目录),隶属于 sysfs 文件系统 */ if (!power_kobj) return -ENOMEM; error = sysfs_create_group(power_kobj, &attr_group); /* 创建 power 目录下面的属性文件组,也就是普通的文件,这个在 attr_group 中定义 */ if (error) return error; return pm_autosleep_init(); } # echo freeze > /sys/power/state (写操作) 此条命令会导致的后果是 state_store 被调用,freeze 被作为参数传入 static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { suspend_state_t state; int error; error = pm_autosleep_lock(); /* 获取锁 */ if (error) return error; if (pm_autosleep_state() > PM_SUSPEND_ON) { /* 判断状态,如果不是 ON 就说明设备已进入某种省电模式 */ error = -EBUSY; goto out; } state = decode_state(buf, n); /* 解析传入的字符串,转换为状态值 */ if (state < PM_SUSPEND_MAX) error = pm_suspend(state); /* 进入 suspend 模式 */ else if (state == PM_SUSPEND_MAX) error = hibernate(); else error = -EINVAL; out: pm_autosleep_unlock(); return error ? error : n; }

2. suspend 框架

设备通知函数 pm_notifier_call_chain

在电源管理开始之后立马被调用,该函数用于给指定对象发送指定的通知,原型 int pm_notifier_call_chain(unsigned long val),它的参数就是通知的类型码: /* Hibernation 与 suspend 事件 */ #define PM_HIBERNATION_PREPARE 0x0001 /* 开始执行关机动作 */ #define PM_POST_HIBERNATION 0x0002 /* 关机完毕 */ #define PM_SUSPEND_PREPARE 0x0003 /* 开始对系统执行 suspend 操作 */ #define PM_POST_SUSPEND 0x0004 /* suspend 结束 */ #define PM_RESTORE_PREPARE 0x0005 /* 开始恢复保存的镜像 */ #define PM_POST_RESTORE 0x0006 /* 恢复完毕 */ pm_notifier_call_chain 函数的目的是:对 pm_chain_head 链表中的所有对象的 notifier_call 成员进行回调 1、 链表的生成: /* 向内核注册一个 notifiter,该链表具有同步机制 */ int register_pm_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&pm_chain_head, nb); } 2、 notifiter 结构体:
该结构体被注册之后里面的 notifier_call 函数指针会在 pm_notifier_call_chain 被调用的时候进行回调,里面可以根据不同的操作码选择相应的操作 struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block __rcu *next; int priority; };

3. 进程冻结函数 suspend_freeze_processes

目的:冻结用户空间进程、内核空间线程 任务:每一个任务都有三个标志位,PF_NOFREEZE, PF_FROZENPF_FREEZER_SKIP(辅助用)。应该在每一个进程的 task_pcb 模块里面被设置,所有的用户进程与部分内核线程都是 PF_NOFREEZE unset 状态,此时可以正常的进入 suspend 或者 hibernate 模式。

实现过程

  1. freeze_processes() 函数最先被执行
  2. system_freezing_cnt 变量标识系统是否正在执行休眠过程,该变量在 freeze_processes() 函数中被设置
  3. try_to_freeze_tasks() 函数被执行,用来向所有的用户进程发送一个 fake signal,并且唤醒所有的内核线程
  4. 所有的可休眠任务都调用 try_to_freeze() 来进行回应,最终导致 __refrigerator() 函数被调用,该函数设置任务的 PF_FROZEN 标志,改变任务状态为 TASK_UNINTERRUPTIBLE,使之循环直到 PF_FROZEN 标志被清除
  5. freeze_kernel_threads() 在用户进程被冻结之后被调用,用来冻结内核线程。用户进程的 try_to_freeze() 函数在信号处理码被发送之后自动的被调用,而内核线程则需要在适当的位置直接调用 try_to_freeze() 或者 wait_event_freezable()|wait_event_freezable_timeout() 宏定义来实现 try_to_freeze 的功能。

4. 两个重要的结构体

platform_suspend_ops 结构体,PM 核心层

1. 结构体原型

/** * @valid: 判断传入的系统睡眠模式是否被该平台所支持 * @begin: 为过渡到给定的系统睡眠状态进行初始化,在挂起设备之前被执行 * @prepare: 准备进入指定的模式,在设备被挂起之后执行,也就是 .suspend() 方法被回调之后,设备驱动的 .suspend_late() 回调方法之前 * @prepare_late: 结束准备,在 disabling nonboot CPUs 之前和设备驱动的 .suspend_late() 回调方法之后被执行. * @enter: 进入指定的状态,该回调函数强制要求被调用 * @wake: 系统离开睡眠模式时被执行(可选)。如果 @prepare_late() 生效,那么该方法也必须生效 * @finish: 结束唤醒过程(可选)。如果 @prepare() 生效,该方法也就必须生效 * @suspend_again: 检测系统是否需要再次挂起。如果该平台在 suspend 过程中想轮询传感器或者执行一些不需要借助用户空间和所有设备的代码,可以在该方法里面实现 * @end: 在恢复设备时被 PM 核心调用,用来指明系统已经返回到活动状态或者在进入睡眠过程中被取消(可选)。如果 @begin() 方法生效,该方法也应该被生效 * @recover: 在 suspend 失败时恢复系统 */ struct platform_suspend_ops { int (*valid)(suspend_state_t state); int (*begin)(suspend_state_t state); int (*prepare)(void); int (*prepare_late)(void); int (*enter)(suspend_state_t state); void (*wake)(void); void (*finish)(void); bool (*suspend_again)(void); void (*end)(void); void (*recover)(void); };

2. 回调过程

suspend 过程:@begin() -> @prepare() -> @prepare_late() -> @enter()
唤醒过程:@wake() -> @finish() -> @end()

dev_pm_ops 结构体,设备驱动层

1. 结构体原型

/** * 外部可见的状态转换依靠包含该结构体的回调来处理,这个过程涉及了两个层级的回调(PM core 的子系统级和设备驱动级)。首先,PM core 执行 PM domains, device types, classes 和 bus types 提供的回调。它们都是子系统级的回调(如果提供了的话) ********* 它们最后都应该执行由设备驱动提供的回调。比如 platform_bus_type,它的 pm 回调函数最终还是会执行 device driver 提供的 pm 回调函数,子系统级的回调只是起一个跳转作用 ********* * @prepare: 该回调函数的原则上是为了防止在设备返回之后它的子设备被注册。 如果 @prepare() 检测到无法处理的状态 (如:子设备已经在注册过程中), 它可能会返回 -EAGAIN, 以便 PM core 可以再次操作它 (例如一个新的子设备被注册完毕) 从竞态条件恢复. * @complete: 撤销 @prepare() 所做的改变,也就是与 @prepare 相反. * @suspend: 在系统进入 sleep 状态之前执行,也就是主内存上下文被保护之前 * @suspend_late: 接着 @suspend() 的操作. * @resume: 在系统从 sleep 被唤醒时执行,也就是主内存上下文被保护之后。 =============================================================================== 上面是 suspend 对应的回调,下面是 hibernation 对应的回调 =============================================================================== * @freeze: Hibernation 专属, 在创建 hibernation 镜像之前执行,类似于 suspend * @thaw: Hibernation 专属, 在创建 hibernation 镜像之后或者创建失败的情况下执行。 * @poweroff: Hibernation 专属, 在保存 hibernation 镜像之后执行. * @restore: Hibernation 专属, 在从 hibernation 镜像恢复主内存上下文之后执行, 类似 @resume(). * @suspend_noirq: 完成 suspend 动作。执行所有额外的挂起设备所需要的操作,这些操作可能与它的驱动的中断处理有竞争。 * @freeze_noirq: 完成 freeze 操作。执行所有额外的休眠设备所需要的操作,这些操作可能与它的驱动的中断处理有竞争。 * @thaw_noirq: 为 thaw 做准备 * @poweroff_noirq: 完成 poweroff 操作,类似于 @suspend_noirq()。 * @restore_noirq: 为 restore 操作做准备,类似于 @resume_noirq()。 * * 查看 Documentation/power/devices.txt 来获得更多与以上回调相关的信息。 * =============================================================================== 下面是与 runtime power management 模型有关的回调 =============================================================================== * @runtime_suspend: 为 device 准备环境,在此环境下设备不能与 CPU(s) 和 RAM 通信。 * @runtime_resume: 使设备进入全面激活状态以响应硬件或软件产生的唤醒事件。 * wakeup event generated by hardware or at the request of software. If * @runtime_idle: 设备似乎处于非活动状态,如果满足必要条件,会被放入低功耗状态。 * * 查看 Documentation/power/runtime_pm.txt 来了解 runtime 模型。 * */ 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); };

2. 回调过程

suspend 过程:@prepare() -> @suspend() -> @suspend_late() -> @suspend_noirq()
resume 过程:@resume_noirq() -> @resume_early() -> @resume() -> @complete()

回调过程的思维导图:

思维导图描述
图形描述