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

Linux 3.14 待机流程分析

2015-06-12 15:35 447 查看

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\n",
		       __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\n"))
		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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: