您的位置:首页 > 运维架构 > Linux

基于wakeup_source的linux内核睡眠机制

2016-03-12 23:36 429 查看
一: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

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);//睡眠时写入mem
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原型:

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:

<6>[ 928.536418] CPU0: msm_cpu_pm_enter_sleep
mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

<6>[ 928.543066] PM: noirq resume of devices
complete after 6.020 msecs

<6>[ 928.548512] PM: early resume of devices
complete after 2.598 msecs

<6>[ 928.650793] PM: resume of devices complete
after 102.266 msecs

<6>[ 928.660290] Restarting tasks ... done.

<6>[ 928.681208] PM: suspend exit 1970-01-05
05:23:32.206389881 UTC

<6>[ 928.681229] active wake lock KeyEvents

<6>[ 928.681267] active wake lock qpnp_soc_wake

<6>[ 928.681284] active wake lock alarm, time
left 486

<6>[ 928.681342] active wake lock KeyEvents

<6>[ 928.681584] active wake lock qpnp_soc_wake

<6>[ 928.681600] active wake lock alarm, time
left 486

<6>[ 928.696345] request_suspend_state: wakeup
at 928691792356 (1970-01-05 05:23:32.221521704 UTC)

<6>[ 928.708608] mdss_dsi_panel_power on=1

还有一个与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;

}

平台赋值:

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:

<6>[ 90.964850] CPU0: msm_cpu_pm_enter_sleep
mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

<6>[ 90.965302] Enabling non-boot CPUs ...

<6>[ 90.968303] CPU3 is up

<6>[ 90.970699] PM: noirq resume of devices
complete after 2.382 msecs

<6>[ 90.973460] PM: early resume of devices
complete after 1.443 msecs

<6>[ 91.097369] PM: resume of devices complete
after 123.888 msecs

<6>[ 91.112858] Restarting tasks ... done.

<6>[ 91.141699] PM: suspend exit 1970-01-05
03:59:28.158266979 UTC

<6>[ 91.141776] PM: suspend entry 1970-01-05
03:59:28.158347917 UTC

<6>[ 91.141801] PM: Syncing filesystems ...
done.

<6>[ 91.222299] Freezing user space processes
... (elapsed 0.03 seconds) done.

<6>[ 91.258126] Freezing remaining freezable
tasks ... (elapsed 0.02 seconds) done.

<6>[ 91.278279] Suspending console(s) (use no_console_suspend
to debug)

<6>[ 91.384933] PM: suspend of devices complete
after 95.062 msecs

<6>[ 91.397910] PM: late suspend of devices
complete after 12.934 msecs

<6>[ 91.413019] PM: noirq suspend of devices
complete after 15.064 msecs

<6>[ 91.413059] Disabling non-boot CPUs ...

<6>[ 91.424477] CPU0: msm_cpu_pm_enter_sleep
mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

<6>[ 91.425144] Enabling non-boot CPUs ...

<6>[ 91.432354] CPU3 is up

<6>[ 91.444416] PM: noirq resume of devices
complete after 11.997 msecs

<6>[ 91.460948] PM: early resume of devices
complete after 10.458 msecs

<6>[ 91.577213] PM: resume of devices complete
after 116.231 msecs

<6>[ 91.584876] Restarting tasks ... done.

<6>[ 91.614571] PM: suspend exit 1970-01-05
03:59:35.550203849 UTC

<6>[ 91.614639] active wake lock rmt_storage_-1220268312

<6>[ 91.689912] PM: suspend entry 1970-01-05
03:59:35.625550620 UTC

<6>[ 91.689921] PM: Syncing filesystems ...
done.

<6>[ 91.700706] Freezing user space processes
...

<3>[ 91.712870] Freezing of user space aborted

<6>[ 91.712898]

<6>[ 91.712903] Restarting tasks ... done.

<6>[ 91.720540] PM: suspend exit 1970-01-05
03:59:35.656175256 UTC

<6>[ 91.720574] PM: suspend entry 1970-01-05
03:59:35.656214579 UTC

<6>[ 91.720586] PM: Syncing filesystems ...
done.

<6>[ 91.801097] Freezing user space processes
...

<3>[ 91.815050] Freezing of user space aborted

<6>[ 91.815075]

<6>[ 91.815083] Restarting tasks ... done.

<6>[ 91.823603] PM: suspend exit 1970-01-05
03:59:35.759241558 UTC

<6>[ 91.823633] PM: suspend entry 1970-01-05
03:59:35.759272964 UTC

<6>[ 91.823643] PM: Syncing filesystems ...
done.

<6>[ 91.911985] Freezing user space processes
... (elapsed 0.03 seconds) done.

<6>[ 91.949378] Freezing remaining freezable
tasks ... (elapsed 0.01 seconds) done.

<6>[ 91.969089] Suspending console(s) (use no_console_suspend
to debug)

<6>[ 92.085594] PM: suspend of devices complete
after 98.499 msecs

<6>[ 92.098615] PM: late suspend of devices
complete after 12.975 msecs

<6>[ 92.113909] PM: noirq suspend of devices
complete after 15.249 msecs

<6>[ 92.113951] Disabling non-boot CPUs ...

<6>[ 92.147320] CPU0: msm_cpu_pm_enter_sleep
mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

<6>[ 92.148265] Enabling non-boot CPUs ...

<6>[ 92.155121] CPU1 is up

<6>[ 92.163797] CPU2 is up

<6>[ 92.174144] CPU3 is up

<6>[ 92.186556] PM: noirq resume of devices
complete after 12.344 msecs

<6>[ 92.199945] PM: early resume of devices
complete after 10.020 msecs

<6>[ 92.317528] PM: resume of devices complete
after 117.548 msecs

<6>[ 92.326254] Restarting tasks ... done.

<6>[ 92.342025] PM: suspend exit 1970-01-05
04:00:48.122436614 UTC

<6>[ 92.342337] active wake lock qpnp-vadc-f611ac00

<6>[ 92.342357] active wake lock alarm, time
left 481

<6>[ 92.411428] PM: suspend entry 1970-01-05
04:00:48.191838905 UTC

<6>[ 92.411451] PM: Syncing filesystems ...
done.

<6>[ 92.419086] Freezing user space processes
...

<3>[ 92.431173] Freezing of user space aborted

<6>[ 92.431213]

<6>[ 92.431225] Restarting tasks ... done.

<6>[ 92.441575] PM: suspend exit 1970-01-05
04:00:48.221987864 UTC

<6>[ 92.441709] active wake lock qpnp-vadc-f611ac00

<6>[ 92.445004] PM: suspend entry 1970-01-05
04:00:48.225417551 UTC

<6>[ 92.445026] PM: Syncing filesystems ...
done.

<6>[ 92.525764] Freezing user space processes
...

<3>[ 92.541465] Freezing of user space aborted

<6>[ 92.541523]

<6>[ 92.541545] Restarting tasks ... done.

<6>[ 92.558768] PM: suspend exit 1970-01-05
04:00:48.339166145 UTC

<6>[ 92.558865] PM: suspend entry 1970-01-05
04:00:48.339268645 UTC

<6>[ 92.558898] PM: Syncing filesystems ...
done.

<6>[ 92.656122] Freezing user space processes
... (elapsed 0.04 seconds) done.

<6>[ 92.699091] Freezing remaining freezable
tasks ... (elapsed 0.01 seconds) done.

<6>[ 92.718891] Suspending console(s) (use no_console_suspend
to debug)

<6>[ 92.832084] PM: suspend of devices complete
after 97.905 msecs

<6>[ 92.845099] PM: late suspend of devices
complete after 12.971 msecs

<6>[ 92.860407] PM: noirq suspend of devices
complete after 15.264 msecs

<6>[ 92.860447] Disabling non-boot CPUs ...

<6>[ 92.895114] CPU0: msm_cpu_pm_enter_sleep
mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: