标准linux休眠和唤醒机制分析

2019-07-13 01:13发布

标准linux休眠和唤醒机制分析

标准linux休眠和唤醒机制分析() 说明: 1. Based on linux2.6.32, only for mem(SDR) 2. 有兴趣请先参考阅读:电源管理方案APMACPI比较.doc Linux系统的休眠与唤醒简介.doc 3. 本文先研究标准linux的休眠与唤醒,android对这部分的增改在另一篇文章中讨论 4. 基于手上的一个项目来讨论,这里只讨论共性的地方 虽然linux支持三种省电模式:standbysuspend to ramsuspend to disk,但是在使用电池供电的手持设备上,几乎所有的方案都只支持STR模式(也有同时支持standby模式的),因为STD模式需要有交换分区的支持,但是像手机类的嵌入式设备,他们普遍使用nand来存储数据和代码,而且其上使用的文件系统yaffs一般都没有划分交换分区,所以手机类设备上的linux都没有支持STD省电模式。 一、项目power相关的配置 目前我手上的项目的linux电源管理方案配置如下,.config文件的截图,当然也可以通过make menuconfig使用图形化来配置: # # CPU Power Management # # CONFIG_CPU_IDLE is not set # # Power management options # CONFIG_PM=y # CONFIG_PM_DEBUG is not set CONFIG_PM_SLEEP=y CONFIG_SUSPEND=y CONFIG_SUSPEND_FREEZER=y CONFIG_HAS_WAKELOCK=y CONFIG_HAS_EARLYSUSPEND=y CONFIG_WAKELOCK=y CONFIG_WAKELOCK_STAT=y CONFIG_USER_WAKELOCK=y CONFIG_EARLYSUSPEND=y # CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set # CONFIG_CONSOLE_EARLYSUSPEND is not set CONFIG_FB_EARLYSUSPEND=y # CONFIG_APM_EMULATION is not set # CONFIG_PM_RUNTIME is not set CONFIG_ARCH_SUSPEND_POSSIBLE=y CONFIG_NET=y 上面的配置对应下图中的下半部分图形化配置。。。,看来是直接在Kconfig文件中删除了配置STD模式的选项。 使用上面的配置编译出来的系统,跑起来之后,进入sys目录可以看到相关的接口: # pwd /sys/power # ls state wake_lock wake_unlock wait_for_fb_sleep wait_for_fb_wake # cat state mem 如果配置了宏CONFIG_PM_DEBUG,那么在power目录下会多出一个pm_test文件,cat pm_test后,列出的测试选项有:[none] core processors platform devices freezer。关于这个test模式的使用,可以参考kernel文档:/kernel/documentation/power/Basic-pm-debugging.txt 这个文档我也有详细的阅读和分析。 二、sys/power和相关属性文件创建 系统bootup时候在sys下新建power和相关属性文件,相关源码位置: kernel/kernel/power/main.c static int __init pm_init(void) { int error = pm_start_workqueue();// CONFIG_PM_RUNTIME not set, so this fun is null if (error) return error; power_kobj = kobject_create_and_add("power", NULL); //建立power对应的kobjectsysfs_dirent对象,同时建立联系:kobject.sd = // &sysfs_dirent sysfs_dirent.s_dir->kobj = &kobject if (!power_kobj) return -ENOMEM; return sysfs_create_group(power_kobj, &attr_group); //建立一组属性文件,可以在power下建立一个子目录来存放这些属性文件, //不过需要在结构体attr_group中指定name,否则直接将这些属性文件放在 // power_kobj对应的目录下。 } core_initcall(pm_init);//看的出来,该函数是很早就被调用,initcall等级为1 static struct attribute_group attr_group = { .attrs = g, }; struct attribute_group { const char *name; mode_t (*is_visible)(struct kobject *, struct attribute *, int); struct attribute **attrs; }; //属性文件都是以最基本得属性结构struct attribute来建立的 static struct attribute * g[] = { &state_attr.attr, #ifdef CONFIG_PM_TRACE // not set &pm_trace_attr.attr, #endif #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) // not set &pm_test_attr.attr, #endif #ifdef CONFIG_USER_WAKELOCK // set &wake_lock_attr.attr, &wake_unlock_attr.attr, #endif NULL, }; #ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_DEBUG power_attr(pm_test); #endif #endif power_attr(state); #ifdef CONFIG_PM_TRACE power_attr(pm_trace); #endif #ifdef CONFIG_USER_WAKELOCK power_attr(wake_lock); power_attr(wake_unlock); #endif #define power_attr(_name) static struct kobj_attribute _name##_attr = { .attr = { .name = __stringify(_name), .mode = 0644, }, .show = _name##_show, .store = _name##_store, } //而这些被封装过的属性结构体,将来会使用kobjectktype.sysfs_ops->show(store)这两个通用函数通过container_of()宏找到实际的属性结构体中的showstore函数来调用。 关于更多sysfs的内容,请查看其他关于这部分内容的详细解析文档。   标准linux休眠和唤醒机制分析() 三、pm_test属性文件读写 int pm_test_level = TEST_NONE; static const char * const pm_tests[__TEST_AFTER_LAST] = { [TEST_NONE] = "none", [TEST_CORE] = "core", [TEST_CPUS] = "processors", [TEST_PLATFORM] = "platform", [TEST_DEVICES] = "devices", [TEST_FREEZER] = "freezer", }; // core >> processors >> platform >> devices >> freezer控制范围示意 cat pm_test的时候最终会调用函数pm_test_show(),在终端上打印出上面数组中的字符串,当前的模式用[]表示出来。 echo devices > pm_test的时候会最终调用到函数pm_test_store()中去,该函数中设置全局变量pm_test_level的值,可以是0-5,分别代表上none ~ freezer。该全局变量会在后面的suspendresume中被引用到。 memchr函数说明: 原型:extern void *memchr(void *buf, char ch, unsigned int count); 用法:#include    功能:从buf所指内存区域的前count个字节查找字符ch   说明:当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针;否则返回NULL 四、state属性文件 power_attr(state)宏定义了一个struct kobj_attribute结构体state_attr static struct kobj_attribute state_attr = { .attr = { .name = __stringify(state), .mode = 0644, }, .show = state_show, .store = state_store, } kobj_attribute结构体封装了struct attribute结构体,新建属性文件是依据struct attribute结构体。最终通过函数kobj_attr_showkobj_attr_store回调到实际的showstore函数(kobject.c) state_show()函数主要是显示当前系统支持哪几种省电模式。 static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { char *s = buf; #ifdef CONFIG_SUSPEND //def 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// undef, don't support STD mode s += sprintf(s, "%s ", "disk"); #else if (s != buf) /* convert the last space to a newline */ *(s-1) = ' '; #endif return (s - buf); } @ kernel/include/linux/suspend.h #define PM_SUSPEND_ON ((__force suspend_state_t) 0) #define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1) #define PM_SUSPEND_MEM ((__force suspend_state_t) 3) #define PM_SUSPEND_DISK ((__force suspend_state_t) 4) #define PM_SUSPEND_MAX ((__force suspend_state_t) 5) @ kernel/kernel/power/suspend.c const char *const pm_states[PM_SUSPEND_MAX] = { #ifdef CONFIG_EARLYSUSPEND// android修改了标准linux的休眠唤醒机制,增加了eraly suspendlate resume机制,如果是android内核,则这个宏是需要定义的。 [PM_SUSPEND_ON] = "on", #endif [PM_SUSPEND_STANDBY] = "standby", [PM_SUSPEND_MEM] = "mem", }; 该函数中值得注意的地方应该是valid_state(i),这个函数是用户配置的支持省电模式的验证函数,如果没有这个验证过程,cat时候打印出来的模式则是on standby mem,给上层用户的使用造成困扰。 那这个valid_state()函数在哪里定义的呢?一般定义于文件kernel/kernel/power/suspend.c static struct platform_suspend_ops *suspend_ops; void suspend_set_ops(struct platform_suspend_ops *ops) //该函数调用见后面 { mutex_lock(&pm_mutex); suspend_ops = ops; mutex_unlock(&pm_mutex); } bool valid_state(suspend_state_t state) { return suspend_ops && suspend_ops->valid && suspend_ops->valid(state); } 而实际平台的platform_suspend_ops结构体一般都是在文件arch/arm/mach-xxxx/pm.c中进行定义,对于mtk的平台是文件mtkpm.c如下: @ kernel/include/linux/suspend.h 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); void (*end)(void); void (*recover)(void); }; 经过后面的代码分析,得出了如下结论: 休眠唤醒过程依次会执行的函数是:beginprepareprepare_lateenterwake finish end。同颜 {MOD}的函数执行了恰好相反的工作。休眠的时候代码执行是停留在函数enter中,wake之后也是从suspend的时候停留的地方继续运行。 至于recover函数貌似只有在pm_test处于devices的模式下才会被调用到。 @ kernel/arch/arm/mach-mt6516/mtkpm.c static struct platform_suspend_ops mtk_pm_ops = { .valid = mtk_pm_state_valid, .begin = mtk_pm_begin, .prepare = mtk_pm_prepare, .enter = mtk_pm_enter, .finish = mtk_pm_finish, .end = mtk_pm_end, }; static int mtk_pm_state_valid(suspend_state_t pm_state) { return pm_state == PM_SUSPEND_MEM ; } void mtk_pm_init(void) { _Chip_PM_init(); /* Register and set suspend operation */ suspend_set_ops(&mtk_pm_ops); } 而函数mtk_pm_init()是在函数mt6516_init_irq()中调用。可以看出该平台只支持mem的省电模式。 state_store()函数: static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { #ifdef CONFIG_SUSPEND // set #ifdef CONFIG_EARLYSUSPEND //对标准linux而言,这个宏不存在 suspend_state_t state = PM_SUSPEND_ON; #else suspend_state_t state = PM_SUSPEND_STANDBY; #endif const char * const *s; #endif char *p; int len; int error = -EINVAL; p = memchr(buf, ' ', n); len = p ? p - buf : n; /* First, check if we are requested to hibernate */ if (len == 4 && !strncmp(buf, "disk", len)) { error = hibernate(); //如果值是disk,那么进入STD模式,该模式暂不讨论 goto Exit; } #ifdef CONFIG_SUSPEND // def 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) #ifdef CONFIG_EARLYSUSPEND // androidlinux内核会定义该宏,首先进入eraly suspend模式 if (state == PM_SUSPEND_ON || valid_state(state)) { error = 0; request_suspend_state(state); } #else // 标准linux内核直接enter_state()函数 error = enter_state(state); // kernel/kernel/power/suspend.c #endif #endif Exit: return error ? error : n; }   标准linux休眠和唤醒机制分析() 五、suspendresume代码走读 下面对suspend分的几个阶段都是按照pm test5中模式来划分的:freezerdevicesplatformprocessorscore suspend第一阶段:freezer int enter_state(suspend_state_t state) { int error; if (!valid_state(state)) return -ENODEV; if (!mutex_trylock(&pm_mutex))// def in kernel/kernel/power/main.c return -EBUSY; printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done. ");//同步文件系统 pr_debug("PM: Preparing system for %s sleep ", pm_states[state]); error = suspend_prepare(); // suspend前准备工作:Run suspend notifiers, allocate a console and stop all processes if (error) //如果一些准备工作失败,通常为冻结进程的时候某些进程拒绝进入冻结模式 goto Unlock; //释放锁,然后退出 if (suspend_test(TEST_FREEZER)) //检查上层下达的命令是否是: // echo freezer > /sys/power/pm_test // echo mem > /sys/power/state //是的话,延时5s后,然后做一些解冻进程等工作就返回 goto Finish; pr_debug("PM: Entering %s sleep ", pm_states[state]); error = suspend_devices_and_enter(state);//休眠外设 Finish: pr_debug("PM: Finishing wakeup. "); suspend_finish();//解冻进程,发广播通知等 Unlock: mutex_unlock(&pm_mutex); return error; } static int suspend_prepare(void) { int error; if (!suspend_ops || !suspend_ops->enter) // mtk_pm_enter() in mtkpm.c return -EPERM; pm_prepare_console(); //suspend分配一个虚拟终端来输出信息 error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); //广播一个通知给通知链pm_chain_head,该通知链上都是用函数register_pm_notifier()注册的pm通知项(也可以用宏pm_notifier定义和注册),这里按照注册时候的定义的优先级来调用该通知链上注册的回调函数。 // PM_SUSPEND_PREPARE是事件值,表示将要进入suspend if (error) goto Finish; error = usermodehelper_disable();//关闭用户态的helper进程 if (error) goto Finish; error = suspend_freeze_processes(); //冻结所有的进程,这里会保存所有进程当前的状态。 if (!error) return 0; //也许有一些进程会拒绝进入冻结状态,当有这样的进程存在的时候,会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程. suspend_thaw_processes(); //进程解冻 usermodehelper_enable(); // enable helper process Finish: pm_notifier_call_chain(PM_POST_SUSPEND);//广播退出suspend的通知 pm_restore_console(); return error; } // 如果不支持pm debug的话,该函数直接返回0 static int suspend_test(int level) { #ifdef CONFIG_PM_DEBUG if (pm_test_level == level) { printk(KERN_INFO "suspend debug: Waiting for 5 seconds. "); mdelay(5000); return 1; } #endif /* !CONFIG_PM_DEBUG */ return 0; } static void suspend_finish(void) { suspend_thaw_processes(); usermodehelper_enable(); pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); } 同步文件系统函数sys_sync()调用后,将会执行函数suspend_prepare()来做一些进入suspend之前的准备工作:Run suspend notifiers, allocate a console and stop all processesdisable user mode hleper process。之后将会调用suspend_test(TEST_FREEZER)来判断上层是否有下这样的命令下来:echo freezer > /sys/power/pm_test(如果pm debug支持),如果没有下这个命令或者系统根本就不支持pm debug,那么直接返回0。如果该测试函数返回了1,那么就不会继续往下走suspend的流程了,而是调用函数suspend_finish()来结束freezer模式的pm test。返回0,将会进入第二阶段继续suspend的流程。 suspend第二阶段:devices 后续所有对suspend划分的阶段都包含在函数suspend_devices_and_enter(state)之中,所以这个函数是关键所在。 int suspend_devices_and_enter(suspend_state_t state) { int error; if (!suspend_ops) //一个重要的函数指针结构体,特定平台的不一样, // kernel/arch/arm/mach-mt6516/mtkpm.c return -ENOSYS; if (suspend_ops->begin) { error = suspend_ops->begin(state); //调用特定平台实现的suspend_begin函数, //这里没做什么实际的工作,打印了点字符串 if (error) goto Close; //如果有错,执行特定平台的suspend_end函数 } suspend_console();// suspend console subsystem suspend_test_start(); // suspend devices超时警告测试 error = dpm_suspend_start(PMSG_SUSPEND);// suspend all devices,外设休眠 //关键函数之一, kernel/drivers/base/power/main.c if (error) { printk(KERN_ERR "PM: Some devices failed to suspend "); goto Recover_platform; //如果外设休眠过程出现错误,将会终止suspend过程,直接跳到某标签出resume外设 } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES)) // suspend第二阶段以此为界 goto Recover_platform; suspend_enter(state);//关键函数之一, pm test的后三种模式都在该函数中进行测试:platformcpuscore Resume_devices: suspend_test_start(); dpm_resume_end(PMSG_RESUME); suspend_test_finish("resume devices"); resume_console(); Close: if (suspend_ops->end) suspend_ops->end(); return error; Recover_platform: if (suspend_ops->recover) suspend_ops->recover();//该函数目前的平台没有实现 goto Resume_devices; } @kernel/drivers/base/power/main.c int dpm_suspend_start(pm_message_t state) { int error; might_sleep(); error = dpm_prepare(state); if (!error) error = dpm_suspend(state); //这两个函数的架构有一些类似 return error; 这两个函数如果在执行某一个device->prepare()