歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux電源管理wakelock

Linux電源管理wakelock

日期:2017/3/1 12:05:15   编辑:關於Linux

前言

之前說過Google為了在user space阻止系統suspend,為Android設計出一套新的電源管理: wakelocks, early_suspend等。此機制修改了Linux原生的susupend流程,定義子自己的休眠接口。起初Android為了合入此patch和Linux內核開發者有一段時間的討論。比如此地址:http://lwn.net/Articles/318611/ 但是在Linux合入wakeup event framework,提出了wakeup source概念,同時解決suspend和wakeup event之間的同步問題之後。Android也隨之拋棄了自己的wakelocks機制,重新利用Linux中wakeup source,設計了全新的wakelock。其實也就將kernel中的wakeup source開放到用戶空間。 從wakelock.c上面的注釋可以證明這一點。
/*
 * kernel/power/wakelock.c
 *
 * User space wakeup sources support.
 *
 * Copyright (C) 2012 Rafael J. Wysocki 
 *
 * This code is based on the analogous interface allowing user space to
 * manipulate wakelocks on Android.
 */

wakelock對比

userspace kernel 舊wakelock 往/sys/power/wake_lock寫入字符串阻止系統進入suspend。
往/sys/power/wake_unlock寫入字符串系統可以進入suspend。 wakelock在suspend的流程上加把鎖,阻止suspend。
wakeunlock就是去掉這把鎖。 新wakelock 基於wakeupeventframework機制。
wakelock就是在kernelspace激活一個wakeupevent。
wakeunlock就是deactive一個wakeupevent。 對user space來說,wakelock還是以前的wakelock,使用方法沒變,API也沒有變化。 對kernel space來說,變化是相當大,具體變化如上表。

數據結構

struct wakelock {
	char			*name;
	struct rb_node		node;
	struct wakeup_source	ws;
#ifdef CONFIG_PM_WAKELOCKS_GC
	struct list_head	lru;
#endif
};
.name: 該wakelock對應的name。 .node: 紅黑樹節點,用於存儲該wakelock。 .ws: 該wakelock對應的wakeup source。因為wakelock就是一個user space的wakeup source。 .lru: 用於wakelock的回收機制。

代碼分析

上面也說了,往/sys/power/wake_lock寫入字符串就阻止系統suspend下去,我們就帶者這個思路一直探索下去。:)
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;
}
此函數直接調用了pm_wake_lock函數。
int pm_wake_lock(const char *buf)
{
	const char *str = buf;
	struct wakelock *wl;
	u64 timeout_ns = 0;
	size_t len;
	int ret = 0;

	if (!capable(CAP_BLOCK_SUSPEND))
		return -EPERM;

	while (*str && !isspace(*str))
		str++;

	len = str - buf;
	if (!len)
		return -EINVAL;

	if (*str && *str != '\n') {
		/* Find out if there's a valid timeout string appended. */
		ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
		if (ret)
			return -EINVAL;
	}

	mutex_lock(&wakelocks_lock);

	wl = wakelock_lookup_add(buf, len, true);
	if (IS_ERR(wl)) {
		ret = PTR_ERR(wl);
		goto out;
	}
	if (timeout_ns) {
		u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;

		do_div(timeout_ms, NSEC_PER_MSEC);
		__pm_wakeup_event(&wl->ws, timeout_ms);
	} else {
		__pm_stay_awake(&wl->ws);
	}

	wakelocks_lru_most_recent(wl);

 out:
	mutex_unlock(&wakelocks_lock);
	return ret;
}
1. 判斷當前task是否有suspend系統的權限。 2. 解析傳入進來的字符串,如果傳入的字符串為"123 1000",則123就是wakelock存入到buf中,1000為定時器超時時間存入到timeout_ms中。 3. 調用wakelock_lookup_add函數,查找是否有相同的name的wakelock,如果有直接返回。如果沒有,重新創建wakelock,然後將此wakelock加入到wakelocks_tree中,同時創建該wakelock對應的wakeup source。 4. 如果該wakelock有超時時間,則調用__pm_wakeup_event函數上報一個timeout_ns的wakeup events。否則調用__pm_stay_awake函數上報一個沒有超時的wakeup events。
static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
					    bool add_if_not_found)
{
	struct rb_node **node = &wakelocks_tree.rb_node;
	struct rb_node *parent = *node;
	struct wakelock *wl;

	while (*node) {
		int diff;

		parent = *node;
		wl = rb_entry(*node, struct wakelock, node);
		diff = strncmp(name, wl->name, len);
		if (diff == 0) {
			if (wl->name[len])
				diff = -1;
			else
				return wl;
		}
		if (diff < 0)
			node = &(*node)->rb_left;
		else
			node = &(*node)->rb_right;
	}
	if (!add_if_not_found)
		return ERR_PTR(-EINVAL);

	if (wakelocks_limit_exceeded())
		return ERR_PTR(-ENOSPC);

	/* Not found, we have to add a new one. */
	wl = kzalloc(sizeof(*wl), GFP_KERNEL);
	if (!wl)
		return ERR_PTR(-ENOMEM);

	wl->name = kstrndup(name, len, GFP_KERNEL);
	if (!wl->name) {
		kfree(wl);
		return ERR_PTR(-ENOMEM);
	}
	wl->ws.name = wl->name;
	wakeup_source_add(&wl->ws);
	rb_link_node(&wl->node, parent, node);
	rb_insert_color(&wl->node, &wakelocks_tree);
	wakelocks_lru_add(wl);
	increment_wakelocks_number();
	return wl;
}
1. 獲取紅黑樹的root節點,通過while循環,在紅黑樹中根據wakelock的name查找是否有相同的,如果查找到,返回該wakelock。 2. 如果沒有找到,判斷當前的wakelock的數目是否超過系統的上限。
static inline bool wakelocks_limit_exceeded(void)
{
	return number_of_wakelocks > CONFIG_PM_WAKELOCKS_LIMIT;
}
通常CONFIG_PM_WAKELOCKS_LIMIT的數目為100。 3. 重新分配一個新的wakelock,設置wakelock, wakeup source的name,調用wakeup_source_add接口將此wakeup source加入到系統中。 4. 插入此wakelock到紅黑樹中。 5. 調用wakelocks_lru_add函數將此wakelock加入到wakelocks_lru_list表頭,用於回收使用。
static inline void wakelocks_lru_add(struct wakelock *wl)
{
	list_add(&wl->lru, &wakelocks_lru_list);
}
6. 增加系統wakelock的數量。 這時候系統持有一個wakelock,kernel層面就是有wakeup event正在處理中。當系統需要susupend的時候,就會調用pending接口檢查到有wakeup event事件在處理,就需要abort suspend。只有當user space通過在wake_unlock設置字符串後,系統就可以進入低功耗模式。所以接下來分析deactive wakeup event過程。
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;
}
此函數最終調用pm_wake_unlock接口。
int pm_wake_unlock(const char *buf)
{
	struct wakelock *wl;
	size_t len;
	int ret = 0;

	if (!capable(CAP_BLOCK_SUSPEND))
		return -EPERM;

	len = strlen(buf);
	if (!len)
		return -EINVAL;

	if (buf[len-1] == '\n')
		len--;

	if (!len)
		return -EINVAL;

	mutex_lock(&wakelocks_lock);

	wl = wakelock_lookup_add(buf, len, false);
	if (IS_ERR(wl)) {
		ret = PTR_ERR(wl);
		goto out;
	}
	__pm_relax(&wl->ws);

	wakelocks_lru_most_recent(wl);
	wakelocks_gc();

 out:
	mutex_unlock(&wakelocks_lock);
	return ret;
}
1. 依舊需要判斷當前task是否有suspend系統的權限。 2. 解析傳入的字符串。 3. 依舊調用wakelock_lookup_add函數查找是否有相同name,如果有返回wakelock,否則返回錯誤。 4. 調用__pm_relax將wakeup source狀態置為deactive。 5. 將wakelock從wakelocks_lru_list鏈表移除,然後又將其添加到鏈表頭。 6. 調用wakelocks_gc執行wakelock的垃圾回收。 wake_lock和wake_unlock在sys中show函數如下,也就是顯示系統中所有的lock和unlock的wakelock
ssize_t pm_show_wakelocks(char *buf, bool show_active)
{
	struct rb_node *node;
	struct wakelock *wl;
	char *str = buf;
	char *end = buf + PAGE_SIZE;

	mutex_lock(&wakelocks_lock);

	for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
		wl = rb_entry(node, struct wakelock, node);
		if (wl->ws.active == show_active)
			str += scnprintf(str, end - str, "%s ", wl->name);
	}
	if (str > buf)
		str--;

	str += scnprintf(str, end - str, "\n");

	mutex_unlock(&wakelocks_lock);
	return (str - buf);
}
此函數也就是遍歷紅黑樹,通過show_active變量判斷當前是lock或者unlock的。最後show出來即可。

wakelock垃圾回收

為什麼需要垃圾回收? 如果一個wakelock需要頻繁創建,銷毀,效率就比較低。此時就需要將一些頻繁使用的wakelock保存起來,再次使用的時候就可以快速獲取。但是當一個系統的wakelock超過系統的上限就需要將一些一直不再使用的wakelock回收,這時候就需要wakelock的回收機制。 1. 定義一個wakelock_lru鏈表用於保存系統中所有的wakelock
static LIST_HEAD(wakelocks_lru_list);
2. 定義一個變量,記錄系統中wakelock的數量。
static unsigned int wakelocks_gc_count;
3. 當創建wakelock的時候調用wakelocks_lru_add函數,將此wakelock添加到wakelock_lru鏈表head部。
static inline void wakelocks_lru_add(struct wakelock *wl)
{
	list_add(&wl->lru, &wakelocks_lru_list);
}
4. 當調用unlock的時候,調用wakelocks_lru_most_recent函數,將wakelock移動到鏈表的head部,表示此wakelock是最近訪問的。 5. 然後調用wakelocks_gc進行wakelock回收。
static void wakelocks_gc(void)
{
	struct wakelock *wl, *aux;
	ktime_t now;

	if (++wakelocks_gc_count <= WL_GC_COUNT_MAX)
		return;

	now = ktime_get();
	list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
		u64 idle_time_ns;
		bool active;

		spin_lock_irq(&wl->ws.lock);
		idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time));
		active = wl->ws.active;
		spin_unlock_irq(&wl->ws.lock);

		if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
			break;

		if (!active) {
			wakeup_source_remove(&wl->ws);
			rb_erase(&wl->node, &wakelocks_tree);
			list_del(&wl->lru);
			kfree(wl->name);
			kfree(wl);
			decrement_wakelocks_number();
		}
	}
	wakelocks_gc_count = 0;
}
a. 如果當前系統wakelock小於系統上限(WL_GC_COUNT_MAX=100),則不用回收。 b. 從wakelocks_lru_list鏈表的末尾取一個wakelock,因為末尾的都是不經常使用的wakelock。如果此wakelock的idle時間沒有超過(WL_GC_TIME_SEC * NSEC_PER_SEC)則不用回收,否則進行回收。 c. 如果此時wakelock的狀態是deactive的,則進行回收。 d. 移除該wakelock的wakeup source,同時從紅黑樹中去除,從wakelock_lru鏈表去除,釋放內存,減少wakelock的數目。
Copyright © Linux教程網 All Rights Reserved