基于wakeup_source的linux autosleep分析

来源:互联网 发布:mac安装win10双系统 编辑:程序博客网 时间:2024/06/05 07:21

1 引言
1.1 编写目的
       分析Linux PM的框架及其具体实现
1.2 阅读建议
       了解工作队列,等待队列,sys文件       
1.3 参考资料  
       Linux 3.4
1.5 运行平台
       Android 4.4
1.4 缩略语
    PM:Power Management

2 总体描述

   不管是嵌入式设备或者大型服务器,PM始终是Linux 必须关心的问题,因此一套成熟健壮的PM管理方案是必须的。目前大量手持设备都是基于Linux内核的,Android系统为了满足自身PM的需要,提出了wake_lock机制。但是这一直被Linux 主流所诟病,自从Linux3.4,负责Linux PM相关的Intel 员工 Rafael J. Wysocki 在主流内核上面实现了基于wakeup_source的PM,并且兼容用户态继续使用wake_lock。
 wakeup_source 定义的是唤醒事件,唤醒事件来源:内核产生,比如设备中断,rtc-alarm;用户态产生,避免自己在处理的时候,系统进入autosleep,用户进程被freeze。因此,autosleep就是一个优先级很低的任务了,只要一有wakeup_source产生,就不能进入睡眠或者已进入睡眠,就必须唤醒。Linux 实现周期性检测有内核定时器,工作队列。由于auto sleep优先级很低,目前采用工作队列机制。try_to_suspend这个工作项主要是读取目前的wakeup_count和in process count。如果in process count为0,说明现在很安静(很多唤醒事件都不产生了,这个概率其实很小),可以开始进入suspend。在进入suspend的时候,会保存wakeup_count,目的有两个:在进入suspend过程中,又有新的事件产生,就把每个事件的唤醒计数加1;在系统resume的时候,比较wake_count,如果保存事件和最新的事件计数不一致,说明产生了一个未知事件,让出cpu时间去处理这个未知事件。
  用户态使用 echo mem > /sys/power/auto_sleep激活try_to_suspend或者,使能auto_sleep;由try_to_suspend检查当没有唤醒事件了,进入pm_suspend;当/sys/power/state为 on;echo mem > /sys/power/state,直接pm_suspend

3 设计描述
3.1  数据结构描述
    struct wakeup_source {


  1. const char *name;
  2. struct list_head    entry;
  3. spinlock_t     lock;
  4. struct timer_list    timer;           //一些wakeup_source超时后,会变成deactive
  5. unsigned long     timer_expires;      //超时时间
  6. ktime_t total_time;
  7. ktime_t max_time;
  8. ktime_t last_time;
  9. ktime_t start_prevent_time;
  10. ktime_t prevent_sleep_time;
  11. unsigned long     event_count;       //产生事件计数
  12. unsigned long     active_count;      //激活计数
  13. unsigned long     relax_count;       //释放计数
  14. unsigned long     expire_count;      //超时被变dactive计数
  15. unsigned long     wakeup_count;      //当suspend后,唤醒系统计数
  16. bool     active:1;
  17. bool     autosleep_enabled:1;        //事件是否允许系统autosleep
  18. };
3.2 sys 接口描述
3.2.1 autosleep_show  显示autosleep状态 off表示不允许autosleep,mem表示允许

点击(此处)折叠或打开

  1. static ssize_t autosleep_show(struct kobject *kobj,
  2.              struct kobj_attribute *attr,
  3.              char *buf)
  4. {
  5.     suspend_state_t state = pm_autosleep_state();

  6.     if (state == PM_SUSPEND_ON)
  7.         return sprintf(buf, "off\n");

  8. #ifdef CONFIG_SUSPEND
  9.     if (state < PM_SUSPEND_MAX)
  10.         return sprintf(buf, "%s\n", valid_state(state) ?
  11.                         pm_states[state] : "error");
  12. #endif
  13. #ifdef CONFIG_HIBERNATION
  14.     return sprintf(buf, "disk\n");
  15. #else
  16.     return sprintf(buf, "error");
  17. #endif
  18. }
3.2.2 autosleep_store 设置 
点击(此处)折叠或打开正常
  1. static ssize_t autosleep_store(struct kobject *kobj,
  2.              struct kobj_attribute *attr,
  3.              const char *buf, size_t n)
  4. {
  5.     suspend_state_t state = decode_state(buf, n);
  6.     int error;

  7.     if (state == PM_SUSPEND_ON
  8.      && strcmp(buf, "off") && strcmp(buf, "off\n"))
  9.         return -EINVAL;

  10.     error = pm_autosleep_set_state(state);
  11.     return error ? error : n;
  12. }
3.2.3 state_show打印出目前系统支持的autosleep状态
点击(此处)折叠或打开
  1. tatic ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
  2.              char *buf)
  3. {
  4.     char *s = buf;
  5. #ifdef CONFIG_SUSPEND
  6.     int i;

  7.     for (i = 0; i < PM_SUSPEND_MAX; i++) {
  8.         if (pm_states[i] && valid_state(i))
  9.             s += sprintf(s,"%s ", pm_states[i]);
  10.     }
  11. #endif
  12. #ifdef CONFIG_HIBERNATION
  13.     s += sprintf(s, "%s\n", "disk");
  14. #else
  15.     if (s != buf)
  16.         /* convert the last space to a newline */
  17.         *(s-1) = '\n';
  18. #endif
  19.     return (s - buf);
  20. }
3.2.4 state_store 当系统 on的时候,才允许pm_suspend
点击(此处)折叠或打开
  1. static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
  2.              const char *buf, size_t n)
  3. {
  4.     suspend_state_t state;
  5.     int error;

  6.     error = pm_autosleep_lock();
  7.     if (error)
  8.         return error;

  9.     if (pm_autosleep_state() > PM_SUSPEND_ON) {
  10.         error = -EBUSY;
  11.         goto out;
  12.     }

  13.     state = decode_state(buf, n);
  14.     if (state < PM_SUSPEND_MAX)
  15.         error = pm_suspend(state);
  16.     else if (state == PM_SUSPEND_MAX)
  17.         error = hibernate();
  18.     else
  19.         error = -EINVAL;

  20.  out:
  21.     pm_autosleep_unlock();
  22.     return error ? error : n;
  23. }

3.3流程描述
3.3.1 PM初始化

点击(此处)折叠或打开

  1. static int __init pm_init(void)
  2. {
  3.     int error = pm_start_workqueue(); //
  4.     if (error)
  5.         return error;
  6.     hibernate_image_size_init();
  7.     hibernate_reserved_size_init();

  8.     touch_evt_timer_val = ktime_set(2, 0);
  9.     hrtimer_init(&tc_ev_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
  10.     tc_ev_timer.function = &tc_ev_stop;
  11.     tc_ev_processed = 1;

  12.     power_kobj = kobject_create_and_add("power", NULL);
  13.     if (!power_kobj)
  14.         return -ENOMEM;
  15.     error = sysfs_create_group(power_kobj, &attr_group); //创建sys文件接口
  16.     if (error)
  17.         return error;
  18.     return pm_autosleep_init(); //创建auto_sleep工作队列,也把用户态向autosleep 写入当作wakeup_source


3.3.2 Android手机起来后,会调用libsuspend.so。干的第一件事,就是向auto_sleep写入off
autosuspend_init->autosuspend_earlysuspend_init(会失败见3.2.4 state_store)->autosuspend_autosleep_init->autosuspend_autosleep_disable
对应内核就是
autosleep_store->pm_autosleep_set_state(PM_SUSPEND_ON)
点击(此处)折叠或打开
  1. int pm_autosleep_set_state(suspend_state_t state)
  2. {

  3. #ifndef CONFIG_HIBERNATION
  4.     if (state >= PM_SUSPEND_MAX)
  5.         return -EINVAL;
  6. #endif

  7.     __pm_stay_awake(autosleep_ws); //__pm_stay_awake,保持唤醒状态注意stay,没有定时器处理超时
  8.     mutex_lock(&autosleep_lock);   //临界资源保护,用户态在写入,try_to_suspend在检查autoslee_state

  9.     autosleep_state = state;      //设置当前autosleep状态,就是消耗完唤醒事件

  10.     __pm_relax(autosleep_ws);    //__pm_relax deactive唤醒事件

  11.     if (state > PM_SUSPEND_ON) {  //
  12.         pm_wakep_autosleep_enabled(true);
  13.         queue_up_suspend_work(); //如果是第一次到这里,激活工作队列;如果是非第一次,调度工作项,其实try_to_suspend会一直循环处理
  14.     } else {
  15.         pm_wakep_autosleep_enabled(false); //对于PM_SUSPEND_ON,设置wakeup_source的autosleep_enabled为 flase
  16.     }                                      //

  17.     mutex_unlock(&autosleep_lock);
  18.     return 0;
  19. }
autosleep工作队列处理函数
点击(此处)折叠或打开
  1. static void try_to_suspend(struct work_struct *work)
  2. {
  3.     unsigned int initial_count, final_count;

  4.     if (!pm_get_wakeup_count(&initial_count, true)) //true会阻塞到这里,等待in process 为0。继续下面的检查,否则goto out
  5.         goto out;

  6.     mutex_lock(&autosleep_lock);

  7.     if (!pm_save_wakeup_count(initial_count)) {   //保存wakeup_count,这里会再次判断in process 为0 。设置events_check_enabled为true。
  8.         mutex_unlock(&autosleep_lock);            //events_check_enabled只是表明,当前wakeup事件没有发生,可以尝试pm_suspend
  9.         goto out;
  10.     }

  11.     if (autosleep_state == PM_SUSPEND_ON) {      //ON是这一种情况,虽然进入了try_to_suspend,是否PM_SUSPEND_MEM造成的,但是状态又变了,应该完成这个工作项,不激活新的try_to_suspend
  12.         mutex_unlock(&autosleep_lock);
  13.         return;
  14.     }
  15.     if (autosleep_state >= PM_SUSPEND_MAX)
  16.         hibernate();
  17.     else
  18.         pm_suspend(autosleep_state);            //冻结用户进程,执行设备的suspend,diable offline cpu。

  19.     mutex_unlock(&autosleep_lock);

  20.     if (!pm_get_wakeup_count(&final_count, false)) //resume后,获取wakeup_count
  21.         goto out;

  22.     /*
  23.      * If the wakeup occured for an unknown reason, wait to prevent the
  24.      * system from trying to suspend and waking up in a tight loop.
  25.      */
  26.     if (final_count == initial_count)           //系统被唤醒了,但是final_count没有增加,说明产生了未知的事件
  27.         schedule_timeout_uninterruptible(HZ / 2);

  28.  out:
  29.     queue_up_suspend_work();
  30. }

4  debug描述
    mount -t  debugfs nodev /data/debug
    cat /data/debug/wakeup_sources   //观察wakeup_source情况
0 0
原创粉丝点击