Linux kernel pm之我理解

时间:2022-04-14 18:19:53

近期搜了很多有关Linux PM的相关资料,但是总感觉叙述晦涩难懂,一来是由于对kernel的理解还未到高度,二来确实手头事情太多,没有办法沉下心来阅读code,今天晚上偷了个闲,仔细读了一遍有关pm的技术文档与source code,颇有心得,记于此。


开始理解一下Linux PM的一些基本概念, sleep, hibernate 两个概念,根据文档上说,sleep是 suspend to mem, hibernate是 suspend to disk, 我理解sleep比如我们的手机长期没有active后,自动锁屏,进入sleep状态;而hibernate则是关机状态,将当前在memory中的image,存放在disk之中。文中还说sleep的细节要比hibernate要来的简单,进入suspend的过程是: prepare---suspend---suspend_noirq; wakeup过程是 resume_noirq----resume---complete,具体调用过程也比较复杂,英文文档,我很烂的英文也就记得不是那么清楚,大概就是按照device topology来逐个调用device的suspend,其中suspend是从device-->class-->bus 这个过程调用,而resume是从bus-->class>device这个过程调用,而hibernate过程更加复杂,不过暂时不影响我们理解Linux pm框架。


就构架上理解,pm主要有两个目录,一是 kernel/power下的code, 还有一份是 driver/base/power下的code。


driver/base/power下主要是pm的driver实现,说到这个,我想到了driver这个概念,一般来说,driver是指对某个或某类device的控制,但是有很多很多的driver并不是对hw的device进行控制,而是去用driver来封装一些内核接口,或者内核数据,实现一些特定的功能,而并不是一定需要控制特别的device,可以是一个虚拟的device,其实真是的hw device 也就是抽象成为一个数据结构来进行管理的。从pm的driver,也可以更加清楚Linux 设备驱动模型的一些细节上的概念。

kernel/power下的code,则是向kernel的其他module公开了一些pm driver的接口以及pm module的init,以实现对pm的操作。
main.c
static int __init pm_init(void)
{
    int error = pm_start_workqueue();
    if (error)
        return error;
    power_kobj = kobject_create_and_add("power", NULL);   //在sys下创建一个名为"power"的节点
    if (!power_kobj)
        return -ENOMEM;
    return sysfs_create_group(power_kobj, &attr_group);   //在"power"节点下,添加我们所定义的属性。
}
看一下 attr_group, 它是一个attribute group 其中如果我们在complie kernel的时候,定义了CONFIG_PM_DEBUG macro,那么我们就会在power的属性下面看到pm_test的一项值。
power_attr(pm_trace);
#endif /** CONFIG_PM_TRACE */


static struct attribute * g[] = {
 &state_attr.attr,
#ifdef CONFIG_PM_TRACE
 &pm_trace_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
 &pm_async_attr.attr,
 &wakeup_count_attr.attr,
#ifdef CONFIG_PM_DEBUG
 &pm_test_attr.attr,
#endif
#endif
 NULL,
};


static struct attribute_group attr_group = {
 .attrs = g,
};
我们对pm的一些控制,就是在对这些属性值进行控制的。对外export的一个很重要的attribute 就是 status.
/***
 *  state - control system power state.
 *
 *  show() returns what states are supported, which is hard-coded to
 *  'standby' (Power-On Suspend), 'mem' (Suspend-to-RAM), and
 *  'disk' (Suspend-to-Disk).
 *
 *  store() accepts one of those strings, translates it into the
 *  proper enumerated value, and initiates a suspend transition.
 */
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
     char *buf)
{
 char *s = buf;
#ifdef CONFIG_SUSPEND
 int i;


 for (i = 0; i < PM_SUSPEND_MAX; i++) {
  if (pm_states[i] && valid_state(i))
   s += sprintf(s,"%s ", pm_states[i]);
 }
#endif
#ifdef CONFIG_HIBERNATION
 s += sprintf(s, "%s\n", "disk");
#else
 if (s != buf)
  /** convert the last space to a newline */
  *(s-1) = '\n';
#endif
 return (s - buf);
}


static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
      const char *buf, size_t n)
{
#ifdef CONFIG_SUSPEND
 suspend_state_t state = PM_SUSPEND_STANDBY;
 const char * const *s;
#endif
 char *p;
 int len;
 int error = -EINVAL;


 p = memchr(buf, '\n', n);
 len = p  : p - buf : n;


 /** First, check if we are requested to hibernate */
 if (len == 4 && !strncmp(buf, "disk", len)) {
  error = hibernate();
  goto Exit;
 }   


#ifdef CONFIG_SUSPEND
 for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
  if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
   break;
 }
 if (state < PM_SUSPEND_MAX && *s)
  error = enter_state(state);   ///根据传递进来的state,进入这个函数进行相应处理。
#endif


 Exit:
 return error   error : n;
}


power_attr(state);



写着写着才发现,我down的source code似乎比较老,这月的流量用完了,看来新的要回家再下了。关于pm的框架,有新老之分,之后建议的是用sysfs来进行管理,在设备模式管理中,struct bus_type struct device struct device_driver中都有嵌入有关pm的相关结构体,在向系统注册的时候,调用device_add时,会有对向sys/device/.../mydevice 加入"power"的attribute。
int device_add(struct device *dev)
{
...
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);   ///Add a device to the PM core's list of active devices
...
}
这样,就把我们向设备模型注册的device给加入到pmcore之中了。


今天先扯到这儿吧,明天再搞了。