Linux省电相关idle和suspend

时间:2022-07-30 15:49:25

suspend流程借用其他图和链接,比较详细了http://www.wowotech.net/linux_kenrel/suspend_and_resume.html

我这边记录下wakelock的相关接口和suspend过程中的frozenthread实现原理,还有部分idle流程。

wakelock内核和应用使用的相关接口
static inline void wake_lock(struct wake_lock *lock)
{
	__pm_stay_awake(&lock->ws);
}

static inline void wake_lock_timeout(struct wake_lock *lock, long timeout)
{
	__pm_wakeup_event(&lock->ws, jiffies_to_msecs(timeout));
}

static inline void wake_unlock(struct wake_lock *lock)
{
	__pm_relax(&lock->ws);
}
wakelock通过一个32位值来表示,高16位表示总的wakeevent发生次数,低16位标志当前置位的wakelock数量
没有wakelock时候触发suspend流程

wakeup.c可以看wakelock当前状态,是debug接口

在kernel\power\main.c封装着给应用层使用的接口,应用通过写文件来设置wakelock,/sys/power/wake_lock|wake_unlock
#ifdef CONFIG_PM_WAKELOCKS
static ssize_t wake_lock_show(struct kobject *kobj,
                  struct kobj_attribute *attr,
                  char *buf)
{
    return pm_show_wakelocks(buf, true);
}
static ssize_t wake_lock_store(struct kobject *kobj,struct kobj_attribute *attr,const char *buf, size_t n)
{
    int error = pm_wake_lock(buf);
    return error ? error : n;
}
power_attr(wake_lock);
static ssize_t wake_unlock_show(struct kobject *kobj,struct kobj_attribute *attr,char *buf)
{
    return pm_show_wakelocks(buf, false);
}
static ssize_t wake_unlock_store(struct kobject *kobj,struct kobj_attribute *attr,const char *buf, size_t n)
{
    int error = pm_wake_unlock(buf);
    return error ? error : n;
}
power_attr(wake_unlock);


在suspend流程中的suspend_prepare->suspend_freeze_processes会冻结线程
freeze_processes
freeze_kernel_threads
...
pm_freezing = true;
...
freeze_task
对于用户线程:
    if (!(p->flags & PF_KTHREAD)) {
        fake_signal_wake_up(p);
发送一个空的信号给各个应用线程
信号处理中do_signal,调用接口设置当前线程为unint,然后切换出去
try_to_freeze_nowarn
__refrigerator
    set_current_state(TASK_UNINTERRUPTIBLE);
    schedule();

对于内核线程,要宽松的多,主要操作只是唤醒int状态的线程,然后由线程自己去处理
freeze_task
    wake_up_state(p, TASK_INTERRUPTIBLE);
线程创建的时候需要如下:
while(kthread_freezable_should_stop)
{
    ...
}
kthread_freezable_should_stop
__refrigerator
set_current_state(TASK_UNINTERRUPTIBLE);
current->flags |= PF_FROZEN;

这样线程就不会参与调度了,切出去后不会再加入就绪队列,等到系统resume的时候再反过来依次恢复线程状态


cpu_suspend(0, XX_finish_suspend)
cpu_suspend(0, XX_finish_suspend)
cpu_suspend(0, XX_finish_suspend)

Linux省电相关idle和suspend

Linux省电相关idle和suspend





关于降低功耗,除了suspend,还可以让系统进入idle,毕竟suspend属于深度睡眠,进入和退出的成本都比较高。

当线程处于无事可做的时候,系统会调度到idle线程,idle线程中可以进入arm的wfi模式,具体的进入的低功耗模式还跟实际的体系架构有关,可以实现多级,每级有他对应的功耗级别和退出延迟值,系统根据运行情况选择进入哪种模式。

cpu_idle
tick_nohz_idle_enter
...
cpuidle_idle_call
    next_state = cpuidle_curr_governor->select(drv, dev)
    cpuidle_enter_state(dev, drv, next_state)    //选择调用定制的低功耗模式,如果没有的话会进入默认的wfi模式
...
tick_nohz_idle_exit
接口类型如下:
struct cpuidle_state {
    char        name[CPUIDLE_NAME_LEN];
    char        desc[CPUIDLE_DESC_LEN];

    unsigned int    flags;
    unsigned int    exit_latency; /* in US */
    int        power_usage; /* in mW */
    unsigned int    target_residency; /* in US */
    unsigned int    disable;

    int (*enter)    (struct cpuidle_device *dev,
            struct cpuidle_driver *drv,
            int index);

    int (*enter_dead) (struct cpuidle_device *dev, int index);
};