linux内核3.4基于wakeup_source的autosleep机制分析

点击打开链接

一:wakeup_source简介:
linux 3.4内核PM使用了wakeup_source来保持唤醒状态,也就是keep awake。之前android一直是基于Linux加入了wake_lock机制来阻止系统休眠,后来Linux 3.4内核加入了wakeup_source来管理,安卓4.4跟着升级内核也就摒弃了自己的臃肿的wake_lock机制,在对上层接口并不改变,在内核wake_lock实现直接基于wakeup_source来实现的。当然也会带来debug上的一些问题,比如以前的wake_lock自身带有强大的debug信息,那么我们在调试的时候可以自己看见dmesg中默认打印active wake lock XXX,很直观来辨别需要休眠的时候那个wake lock有问题阻止了休眠。这个需要我们自己来完善。个人认为改进很大,现在使用了autosleep机制,只要不存在任何active wakeup_source了,系统自动休眠,当有active wake_source自动block住,个人认为休眠更及时,非休眠时间在减少,同时不会消耗额外的资源。使用基于queue work与进程block来管理suspend。还有这里的wakeup_source个人觉得应该叫keepawake_source或者stayawake_souce,毕竟系统的唤醒也就是cpu的再次运行是由中断唤醒的而不是wakeup_source。同时安卓4.4还有一个重大改变就是去除了early suspend机制改为fb event通知机制。那么现在就只有suspend与resume,runtime suspend与runtime resume了。

/*
* struct wakeup_source – Representation of wakeup sources
*
@total_time: Total time this wakeup source has been active.

  • @max_time: Maximum time this wakeup source has been continuously active.

  • @last_time: Monotonic clock when the wakeup source’s was touched last time.

  • @prevent_sleep_time: Total time this source has been preventing autosleep.

  • @event_count: Number of signaled wakeup events.

  • @active_count: Number of times the wakeup sorce was activated.

  • @relax_count: Number of times the wakeup sorce was deactivated.

  • @expire_count: Number of times the wakeup source’s timeout has expired.

  • @wakeup_count: Number of times the wakeup source might abort suspend.

  • @active: Status of the wakeup source.

  • @has_timeout: The wakeup source has been activated with a timeout.

/
struct wakeup_source {
const char
name;
struct list_head entry;
struct list_head list;
spinlock_t lock;
struct timer_list timer;
unsigned long timer_expires; //超时时间,也就是wake_lock_timeout()里面的时间参数,超时后会执行deactivate函数
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count; //event计数
unsigned long active_count;//active计数
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1; //用于判断是否是active状态
bool autosleep_enabled:1;//这个变量是来标记active等时间的
};

//active任何wakeup_source都会执行该函数,标记active为true
/**
* wakup_source_activate – Mark given wakeup source as active.

  • @ws: Wakeup source to handle.

*
* Update the @ws’ statistics and, if @ws has just been activated, notify the PM
* core of the event by incrementing the counter of of wakeup events being
* processed.

/
static void wakeup_source_activate(struct wakeup_source
ws)
{
unsigned int cec;

ws->active = true;
ws->active_count++;
ws->last_time = ktime_get();
if (ws->autosleep_enabled)
ws->start_prevent_time = ws->last_time;

/ Increment the counter of events in progress. /
cec = atomic_inc_return(&combined_event_count);

trace_wakeup_source_activate(ws->name, cec);
}

//deactivate任何wakeup_source都会执行该函数,标记active为false
/**
* wakup_source_deactivate – Mark given wakeup source as inactive.

  • @ws: Wakeup source to handle.

*
* Update the @ws’ statistics and notify the PM core that the wakeup source has
* become inactive by decrementing the counter of wakeup events being processed
* and incrementing the counter of registered wakeup events.

/
static void wakeup_source_deactivate(struct wakeup_source
ws)
{
unsigned int cnt, inpr, cec;
ktime_t duration;
ktime_t now;

ws->relax_count++;
/*
* __pm_relax() may be called directly or from a timer function.

  • If it is called directly right after the timer function has been
  • started, but before the timer function calls __pm_relax(), it is
  • possible that __pm_stay_awake() will be called in the meantime and
  • will set ws->active. Then, ws->active may be cleared immediately
  • by the __pm_relax() called from the timer function, but in such a
  • case ws->relax_count will be different from ws->active_count.

*/
if (ws->relax_count != ws->active_count) {
ws->relax_count–;
return;
}

ws->active = false;

now = ktime_get();
duration = ktime_sub(now, ws->last_time);
ws->total_time = ktime_add(ws->total_time, duration);
if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
ws->max_time = duration;

ws->last_time = now;
del_timer(&ws->timer);
ws->timer_expires = 0;

if (ws->autosleep_enabled)
update_prevent_sleep_time(ws, now);

/*
* Increment the counter of registered wakeup events and decrement the
* couter of wakeup events in progress simultaneously.

*/
cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
trace_wakeup_source_deactivate(ws->name, cec);

split_counters(&cnt, &inpr);

if (!inpr && waitqueue_active(&wakeup_count_wait_queue)){
wake_up(&wakeup_count_wait_queue); //当不存在任何active wake_up source的时候唤醒try_to_suspend进程。
}

}

wakup_source的申请与释放:
1:使用安卓的wake_lock接口:wake_lock(),wake_lock_timeout(),wake_unlock();
2: 使用wakeup_source自带的接口:pm_stay_awake(),pm_relax();这里的name就是device name。

二:autosleep分析:

sys接口:sys/power/autosleep

亮屏时libsuspend会写入off,灭屏写入mem

static ssize_t autosleep_show(struct kobject kobj,
struct kobj_attribute
attr,
char *buf)
{
suspend_state_t state = pm_autosleep_state();

if (state == PM_SUSPEND_ON)
return sprintf(buf, “off\n”);

ifdef CONFIG_SUSPEND

if (state < PM_SUSPEND_MAX)
return sprintf(buf, “%s\n”, valid_state(state) ?
pm_states[state] : “error”);

endif

ifdef CONFIG_HIBERNATION

return sprintf(buf, “disk\n”);

else

return sprintf(buf, “error”);

endif

}

static ssize_t autosleep_store(struct kobject kobj,
struct kobj_attribute
attr,
const char *buf, size_t n)
{
suspend_state_t state = decode_state(buf, n);
int error;

if (state == PM_SUSPEND_ON
&& strcmp(buf, “off”) && strcmp(buf, “off\n”))
return -EINVAL;

error = pm_autosleep_set_state(state);
return error ? error : n;
}

power_attr(autosleep);

int pm_autosleep_set_state(suspend_state_t state)
{

ifndef CONFIG_HIBERNATION

if (state >= PM_SUSPEND_MAX)
return -EINVAL;

endif

__pm_stay_awake(autosleep_ws); //防止系统休眠

mutex_lock(&autosleep_lock);

autosleep_state = state;

__pm_relax(autosleep_ws); //释放上面的wake up source

if (state > PM_SUSPEND_ON) {
pm_wakep_autosleep_enabled(true); //设置所有wake up source里面的autosleep_enabled为真,这个变量不会对休眠有影响,但是会标记active的时间,使用debugfs可以看见
queue_up_suspend_work();//调度工作队列,会执行try_to_suspend(),其实state mem执行try_to_suspend(),一次就可以了,后面再分析。
} else {
pm_wakep_autosleep_enabled(false);//设置所有wake up source里面的autosleep_enabled为假
}

mutex_unlock(&autosleep_lock);
return 0;
}

重头戏:try_to_suspend

static void try_to_suspend(struct work_struct *work)
{
unsigned int initial_count, final_count;

if (!pm_get_wakeup_count(&initial_count, true)) //获取initial_count,这个函数会block住,当存在active wakeup source的时候,直到wakeup source为detative状态
goto out;

mutex_lock(&autosleep_lock);

if (!pm_save_wakeup_count(initial_count)) {//保存initial_count,不会block,当然也会检查是否有active wakeup source,当有active存在再次queue work。
mutex_unlock(&autosleep_lock);
goto out;
}

if (autosleep_state == PM_SUSPEND_ON) {//当为ON状态时,return。//在睡眠期间跟了很久没有遇见过这种情况
mutex_unlock(&autosleep_lock);
return;
}
if (autosleep_state >= PM_SUSPEND_MAX)
hibernate(); //hibernate高通平台目前不支持
else
pm_suspend(autosleep_state); //进入pm_suspend,dmesg会有PM: suspend entry 与PM: suspend exit来标记,这里面会执行freeze task,suspend与resume,disable cpu的操作。内核PM最重要的函数。

mutex_unlock(&autosleep_lock);

if (!pm_get_wakeup_count(&final_count, false))//获取final_count,非block,当然也会检查是否有active wakeup source,当有active存在再次queue work
goto out;

/*
* If the wakeup occured for an unknown reason, wait to prevent the
* system from trying to suspend and waking up in a tight loop.

*/
if (final_count == initial_count) //这里遇见未知原因,initial_count与final_count相等,超时500ms后继续往下执行。这种现象我也是跟了许久没有遇见过。
schedule_timeout_uninterruptible(HZ / 2);

out:
queue_up_suspend_work(); //调度queue work会再次执行该函数,实际上只要一次echo mem > sys/power/autosleep后这个进程一直会在auto_sleep cycle。
}

pm_get_wakeup_count原型,个人感觉这个是仿__wait_event_interruptible()而写的。

bool pm_get_wakeup_count(unsigned int *count, bool block)
{
unsigned int cnt, inpr;

if (block) { //当block为真时,该进程可能会block住
DEFINE_WAIT(wait);

for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr); //有active的wakeup_source存在就是block住,否则block
if (inpr == 0 || signal_pending(current))
break;

schedule();//在这里面block住,直到最后一个active的wakeup_source deactivate时会唤醒该进程,之后会break出来。
}
finish_wait(&wakeup_count_wait_queue, &wait);
}

split_counters(&cnt, &inpr);
*count = cnt;
return !inpr;
}

那么有2个问题,按power键唤醒系统是退出try_to_suspend了吗?
首先系统是如何被唤醒的?这个是由硬件中断唤醒的,比如我们这里的power键,还有其他的比如alarm等其他硬件中断,只要我们在中断申请时enbale_irq_wake(),那么睡眠期间,只要触发该中断就可以唤醒系统。那么在try_to_suspend里面唤醒后pm_suspend()执行完resume后会退出来,接着会获取final_count,在这里是存在active wake_up source的(在out之前加添的打印active wakeup_source,下面的dmesg证明了存在的wakeup_source),之后执行out,后调度工作队列,再次进入try_to_suspend(),在第一次获取initial_count后便会遇见active wakeup_source,这里面更多的是系统上层加的wakeup_source,那么try_to_suspend()会一直block在pm_get_wakeup_count()里面直到灭屏和所有wakeup_source deactivate时会再次进入pm_suspend()休眠。
这里也就解释了为什么只要执行一次ehco mem > sys/power/autosleep后自动可以休眠了的原因。

看下面的dmesg:

还有一个与wakeup source无关的问题,为什么suspend后就一直停留在那里不动了?
这个是cpu停止运转了,下面再分析下代码。
核心函数suspend_devices_and_enter():

/**
* suspend_devices_and_enter – Suspend devices and enter system sleep state.

  • @state: System sleep state to enter.

*/
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;

if (!suspend_ops)
return -ENOSYS;

trace_machine_suspend(state);
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console();
suspend_test_start();
error = dpm_suspend_start(PMSG_SUSPEND);//这里会执行所有driver的suspend函数,suspend里面有active wakeup_source或者return 为真的话,suspend会报错
if (error) {
printk(KERN_ERR “PM: Some devices failed to suspend\n”);
goto Recover_platform;
}
suspend_test_finish(“suspend devices”);
if (suspend_test(TEST_DEVICES))
goto Recover_platform;

do {
error = suspend_enter(state, &wakeup);//这里会diable cpu
} while (!error && !wakeup
&& suspend_ops->suspend_again && suspend_ops->suspend_again());

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();
trace_machine_suspend(PWR_EVENT_EXIT);
return error;

Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;

if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
goto Platform_finish;
}

error = dpm_suspend_end(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR “PM: Some devices failed to power down\n”);
goto Platform_finish;
}

if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late();
if (error)
goto Platform_wake;
}

if (suspend_test(TEST_PLATFORM))
goto Platform_wake;

error = disable_nonboot_cpus(); //disable nonboot cpu注意还有cpu需要下面disable的
if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;

arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());

error = syscore_suspend();
if (!error) {
wakeup = pm_wakeup_pending();
if (!(suspend_test(TEST_CORE) ||
wakeup)) {
error = suspend_ops->enter(state); //在这里cpu会停止运行,直到中断唤醒
//下面的全部是唤醒的操作了
events_check_enabled = false;
}
syscore_resume();
}

arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());

Enable_cpus:
enable_nonboot_cpus();

Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();

dpm_resume_start(PMSG_RESUME);

Platform_finish:
if (suspend_ops->finish)
suspend_ops->finish();

return error;
}

平台赋值,这里debug平台是基于msm8974的:
static const struct platform_suspend_ops lpm_suspend_ops = {
.enter = lpm_suspend_enter,//在这里面disbale cpu,停止运行程序
.valid = suspend_valid_only_mem,
.prepare_late = lpm_suspend_prepare,
.wake = lpm_suspend_wake,
};

添加显示wake_lock的dmesg:

Original: https://www.cnblogs.com/LoongEmbedded/p/5298184.html
Author: LoongEmbedded
Title: linux内核3.4基于wakeup_source的autosleep机制分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/10300/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

免费咨询
免费咨询
扫码关注
扫码关注
联系站长

站长Johngo!

大数据和算法重度研究者!

持续产出大数据、算法、LeetCode干货,以及业界好资源!

2022012703491714

微信来撩,免费咨询:xiaozhu_tec

分享本页
返回顶部
最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总