• Linux 3.14 待机流程分析


    1:待机节点创建

    static int __init pm_init(void)
    {
    	int error = pm_start_workqueue();
    	if (error)
    		return error;
    	hibernate_image_size_init();
    	hibernate_reserved_size_init();
    	power_kobj = kobject_create_and_add("power", NULL);
    	if (!power_kobj)
    		return -ENOMEM;
    #ifdef CONFIG_X86_INTEL_XGOLD
    	power_hal_kobj = kobject_create_and_add("power_HAL_suspend",
    					power_kobj);
    	if (!power_hal_kobj)
    		return -ENOMEM;
    #endif
    	error = sysfs_create_group(power_kobj, &attr_group);
    	if (error)
    		return error;
    	pm_print_times_init();
    	return pm_autosleep_init();
    }
    
    core_initcall(pm_init);
    

    pm_init使用core_initcall,顾名思义这是内核一个核心节点。pm_init完毕创建"power"节点,至于怎样创建,节点位置,想了解的看下例如以下这一小部分

    struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
    {
    	struct kobject *kobj;
    	int retval;
    
    	kobj = kobject_create();
    	if (!kobj)
    		return NULL;
    
    	retval = kobject_add(kobj, parent, "%s", name);
    	if (retval) {
    		printk(KERN_WARNING "%s: kobject_add error: %d
    ",
    		       __func__, retval);
    		kobject_put(kobj);
    		kobj = NULL;
    	}
    	return kobj;
    }
    EXPORT_SYMBOL_GPL(kobject_create_and_add);
    

    节点是挂在kobj下。那么kobj在哪里呢?

    struct kobject *kobject_create(void)
    {
    	struct kobject *kobj;
    
    	kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    	if (!kobj)
    		return NULL;
    
    	kobject_init(kobj, &dynamic_kobj_ktype);
    	return kobj;
    }

    static struct kobj_type dynamic_kobj_ktype = {
    	.release	= dynamic_kobj_release,
    	.sysfs_ops	= &kobj_sysfs_ops,
    };

    所以。能够知道power节点是在sysfs的ops下创建。它的位置就是 /sys/power/


    1.1:power节点下子节点群创建

    power作为内核待机主要节点。也能够说是父节点,非常多相关节点都在它低下创建。这就要看它的attribute_group

    static struct attribute * g[] = {
    	&state_attr.attr,
    #ifdef CONFIG_PM_TRACE
    	&pm_trace_attr.attr,
    	&pm_trace_dev_match_attr.attr,
    #endif
    #ifdef CONFIG_PM_SLEEP
    	&pm_async_attr.attr,
    	&wakeup_count_attr.attr,
    #ifdef CONFIG_PM_AUTOSLEEP
    	&autosleep_attr.attr,
    #endif
    #ifdef CONFIG_PM_WAKELOCKS
    	&wake_lock_attr.attr,
    	&wake_unlock_attr.attr,
    #endif
    #ifdef CONFIG_PM_DEBUG
    	&pm_test_attr.attr,
    #endif
    #ifdef CONFIG_PM_SLEEP_DEBUG
    	&pm_print_times_attr.attr,
    #endif
    #endif
    #ifdef CONFIG_FREEZER
    	&pm_freeze_timeout_attr.attr,
    #endif
    	NULL,
    };
    

    例如以下是创建的节点群

     # ls /sys/power/
    autosleep
    pm_async
    pm_freeze_timeout
    pm_print_times
    pm_test
    power_HAL_suspend
    state
    wake_lock
    wake_unlock
    wakeup_count


    2:待机入口函数

    从之前的文章分析,内核还在3.10之前。state节点是待机唯一入口。

    当然内核延续性,这些接口作用都不变也能够用,例如以下命令待机:

    # cat /sys/power/state                                         
    freeze mem

    # echo mem > sys/power/state                                  
    [  168.644948] PM: suspend entry 2015-06-12 06:48:07.903751731 UTC
    [  168.651206] PM: Syncing filesystems ... done.
    [  168.704213] Freezing user space processes ... (elapsed 0.005 seconds) done.
    [  168.717090] Freezing remaining freezable tasks ... (elapsed 0.004 seconds) done.
    [  168.728986] Suspending console(s) (use no_console_suspend to debug)


    从上面子节点群,我们注意到添加了一个叫autosleep的节点。没错!这是新增的接口,autosleep替代曾经google加在linux之上的earlysuspend机制,所以从这个版本号開始,不须要google的android来担心内核添加什么待机方面代码了。


    2.1:autosleep入口函数

    我们先看这个入口函数的store回调

    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
    "))
    		return -EINVAL;
    
    	error = pm_autosleep_set_state(state);
    	return error ? error : n;
    }

    假设autosleep节点值是off的话。autosleep功能关闭。和旧接口state不同的是,autosleep不须要关心唤醒,所以传入的state不能是on。

    接下来就进入到autosleep的主要回调pm_autosleep_set_state去了。


    2.2:wake lock/unlock 和 __pm_stay_awake/__pm_relax

    内核3.10用的加锁解锁流程是

    wake_lock_init初始化一个lock

    wake_lock加锁(非超时锁。须要手动解锁)

    wake_lock_timeout加超时锁。到时自己主动解锁

    wake_unlock解锁

    wake_lock_destroy销毁一个lock


    内核3.14兼容这些接口。开发人员能够使用之前的驱动代码,也能够保持风格。由于这些接口都做了兼容,

    在include/linux/wakelock.h里面都有兼容性定义;如

    static inline void wake_lock_init(struct wake_lock *lock, int type,
    				  const char *name)
    {
    	wakeup_source_init(&lock->ws, name);
    }

    其它接口就不全列出来。

    内核3.14使用wakelock source概念,通过锁的红黑树记录并管理系统全部的锁,这是锁机制的最大改变

    static struct rb_root wakelocks_tree = RB_ROOT;

    我们后面单独介绍。如今先把使用流程搞清楚。


    2.3:try_to_suspend的作用

    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);
    
    	if (state > PM_SUSPEND_ON) {
    		pm_wakep_autosleep_enabled(true);
    		queue_up_suspend_work();
    	} else {
    		pm_wakep_autosleep_enabled(false);
    	}
    
    	mutex_unlock(&autosleep_lock);
    	return 0;
    }
    
    进入到autosleep的回调后,先加上一些锁,保证状态机的切换完毕。然后激活待机工作队列运行suspend_work


    try_to_suspend是autosleep最核心的部分,也是替换旧版内核使用timer结束后轮询是否还有timer来实现不停尝试待机。

    开函数名字就知道,它会一直尝试待机。这就要求它可以及时响应,当系统最后一个锁被释放,它要能及时响应进入待机。

    我们先看下它的函数实现

    static void try_to_suspend(struct work_struct *work)
    {
    	unsigned int initial_count, final_count;
    
    	if (!pm_get_wakeup_count(&initial_count, true))
    		goto out;
    检查wakeup count,注意所带參数true表示不仅仅是get,而是有可能会停留在里面
    	mutex_lock(&autosleep_lock);
    
    	if (!pm_save_wakeup_count(initial_count) ||
    		system_state != SYSTEM_RUNNING) {
    		mutex_unlock(&autosleep_lock);
    		goto out;
    	}
    保存当前的wakeup事件count,用于对wakeup事件count的统计。进一步是为了避免过多特别快的wakeup事件
    	if (autosleep_state == PM_SUSPEND_ON) {
    		mutex_unlock(&autosleep_lock);
    		return;
    	}
    	if (autosleep_state >= PM_SUSPEND_MAX)
    		hibernate();
    	else
    		pm_suspend(autosleep_state);
    满足待机条件。进入待机如后函数pm_suspend。这个曾经是在state_store调用的
    	mutex_unlock(&autosleep_lock);
    
    	if (!pm_get_wakeup_count(&final_count, false))
    		goto out;
    检查wakeup count,带參数false表示仅仅是get下count。不会block在里面,确切说仅仅是更新下count table
    	/*
    	 * 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)
    		schedule_timeout_uninterruptible(HZ / 2);
    假设唤醒过快(前面提到了),就等待0.5秒
     out:
    	queue_up_suspend_work();
    激活自己,又一次開始工作队列。这也是try-的意思
    }



    我们认识下pm_get_wakeup_count函数

    bool pm_get_wakeup_count(unsigned int *count, bool block)
    {
    	unsigned int cnt, inpr;
    
    	if (block) {
    假设參数是true,就会进入这个case,有可能会block在这里面
    		DEFINE_WAIT(wait);
    
    		for (;;) {
    			prepare_to_wait(&wakeup_count_wait_queue, &wait,
    					TASK_INTERRUPTIBLE);
    prepare好wait的条件
    			split_counters(&cnt, &inpr);
    			if (inpr == 0 || signal_pending(current))
    				break;
    
    			schedule();等待
    		}
    		finish_wait(&wakeup_count_wait_queue, &wait);结束等待,被interrupt了
    	}
    
    	split_counters(&cnt, &inpr);
    更新lock table,假设是參数false。就直接过来到这里,所以仅仅会更新table而不会block
    	*count = cnt;
    	return !inpr;
    }

    3:怎样在模块中加锁、解锁

    1. 使用兼容的接口

    wake_lock_init初始化一个lock

    wake_lock加锁(非超时锁,须要手动解锁)

    wake_lock_timeout加超时锁,到时自己主动解锁

    wake_unlock解锁

    wake_lock_destroy销毁一个lock


    2. 使用新的接口

    定义一个struct wakeup_source

    wakeup_source_register("wakelockname");注冊这个wakeup_source

    __pm_stay_awake(wakelockname);加锁(假设须要超时锁,在ws里面赋值timer_expires)

    __pm_relax(wakelockname)

    wakeup_source_unregister(wakelockname);撤销ws



  • 相关阅读:
    MHA自动切换流程
    手写源码之 简单实现on emit off
    手写源码 -- bind,call,aplly
    多维数组转化为一维数组
    electron+vue中使用nodeJs的fs模块以及上传文件
    制作海报
    vue中引入播放器(百度播放器和腾讯云播放器)
    webpack配置
    webpack
    css样式
  • 原文地址:https://www.cnblogs.com/cxchanpin/p/6920478.html
Copyright © 2020-2023  润新知