Linux Kernel and Android 休眠与唤醒(中文版)
Linux Kernel and Android 休眠與喚醒(中文版)
四月 18th, 2010 0 Comments/1664 hits
Table of Contents
- 簡介
- 國際化
- 版本信息
- 對(duì)于休眠(suspend)的簡單介紹
- Linux Suspend 的流程
- 相關(guān)的文件:
- 準(zhǔn)備, 凍結(jié)進(jìn)程
- 讓外設(shè)進(jìn)入休眠
- Resume
- Android 休眠(suspend)
- 涉及到的文件:
- 特性介紹
- Early Suspend
- Late Resume
- Wake Lock
- Android Suspend
- Early Suspend
- Late Resume
- Wake Lock
- Suspend
- Android于標(biāo)準(zhǔn)Linux休眠的區(qū)別
簡介
休眠/喚醒在嵌入式Linux中是非常重要的部分,嵌入式設(shè)備盡可能的進(jìn)入休眠狀 態(tài)來延長電池的續(xù)航時(shí)間.這篇文章就詳細(xì)介紹一下Linux中休眠/喚醒是如何工作 的, 還有Android中如何把這部分和Linux的機(jī)制聯(lián)系起來的.
國際化
- English Version: link
- 中文版: link
作者: zhangjiejing <kzjeef#gmail.com> ?Date: 2010-04-07, http://www.thinksrc.com
版本信息
- Linux Kernel: v2.6.28
- Android: v2.0
對(duì)于休眠(suspend)的簡單介紹
在Linux中,休眠主要分三個(gè)主要的步驟:
- 順序是按照注冊(cè)順序
# echo standby > /sys/power/state
命令系統(tǒng)進(jìn)入休眠. 也可以使用
# cat /sys/power/state
來得到內(nèi)核支持哪幾種休眠方式.
Linux Suspend 的流程
相關(guān)的文件:
你可以通過訪問?Linux內(nèi)核網(wǎng)站?來得到源代碼,下面是文件的路徑:
- linux_soruce/kernel/power/main.c
- linux_source/kernel/arch/xxx/mach-xxx/pm.c
- linux_source/driver/base/power/main.c
接下來讓我們?cè)敿?xì)的看一下Linux是怎么休眠/喚醒的. Let 's going to see how these happens.
用戶對(duì)于/sys/power/state 的讀寫會(huì)調(diào)用到 main.c中的state_store(), 用戶可以寫入 const char * const pm_state[] 中定義的字符串, 比如"mem", "standby".
然后state_store()會(huì)調(diào)用enter_state(), 它首先會(huì)檢查一些狀態(tài)參數(shù),然后同步文件系統(tǒng). 下面是代碼:
/**
?*????? enter_state - Do common work of entering low-power state.
?*????? @state:???????? pm_state structure for state we're entering.
?*
?*????? Make sure we're the only ones trying to enter a sleep state. Fail
?*?? ???if someone has beat us to it, since we don't want anything weird to
?*????? happen when we wake up.
?*????? Then, do the setup for suspend, enter the state, and cleaup (after
?*????? we've woken up).
?*/
static int enter_state(suspend_state_t state)
{
??????? int error;
??????? if (!valid_state(state))
??????????????? return -ENODEV;
??????? if (!mutex_trylock(&pm_mutex))
??????????????? return -EBUSY;
??????? printk(KERN_INFO "PM: Syncing filesystems ... ");
??????? sys_sync();
??????? printk("done.\n");
??????? pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
??????? error = suspend_prepare();
??????? if (error)
??????????????? goto Unlock;
??????? if (suspend_test(TEST_FREEZER))
??????????????? goto Finish;
??????? pr_debug("PM: Entering %s sleep\n", pm_states[state]);
??????? error = suspend_devices_and_enter(state);
?Finish:
??????? pr_debug("PM: Finishing wakeup.\n");
??????? suspend_finish();
?Unlock:
??????? mutex_unlock(&pm_mutex);
??????? return error;
}
準(zhǔn)備, 凍結(jié)進(jìn)程
當(dāng)進(jìn)入到suspend_prepare()中以后, 它會(huì)給suspend分配一個(gè)虛擬終端來輸出信 息, 然后廣播一個(gè)系統(tǒng)要進(jìn)入suspend的Notify, 關(guān)閉掉用戶態(tài)的helper進(jìn)程, 然 后一次調(diào)用suspend_freeze_processes()凍結(jié)所有的進(jìn)程, 這里會(huì)保存所有進(jìn)程 當(dāng)前的狀態(tài), 也許有一些進(jìn)程會(huì)拒絕進(jìn)入凍結(jié)狀態(tài), 當(dāng)有這樣的進(jìn)程存在的時(shí)候, 會(huì)導(dǎo)致凍結(jié)失敗,此函數(shù)就會(huì)放棄凍結(jié)進(jìn)程,并且解凍剛才凍結(jié)的所有進(jìn)程.
/**
?*????? suspend_prepare - Do prep work before entering low-power state.
?*
?*????? This is common code that is called for each state that we're entering.
?*????? Run suspend notifiers, allocate a console and stop all processes.
?*/
static int suspend_prepare(void)
{
??????? int error;
??????? unsigned int free_pages;
??????? if (!suspend_ops || !suspend_ops->enter)
??????????????? return -EPERM;
??????? pm_prepare_console();
??????? error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
??????? if (error)
??????????????? goto Finish;
??????? error = usermodehelper_disable();
??????? if (error)
??????????????? goto Finish;
??????? if (suspend_freeze_processes()) {
??????????????? error = -EAGAIN;
??????????????? goto Thaw;
??????? }
??????? free_pages = global_page_state(NR_FREE_PAGES);
??????? if (free_pages < FREE_PAGE_NUMBER) {
??????????????? pr_debug("PM: free some memory\n");
??????????????? shrink_all_memory(FREE_PAGE_NUMBER - free_pages);
??????????????? if (nr_free_pages() < FREE_PAGE_NUMBER) {
??????????????????????? error = -ENOMEM;
??????????????????????? printk(KERN_ERR "PM: No enough memory\n");
??????????????? }
??????? }
??????? if (!error)
??????????????? return 0;
?Thaw:
??????? suspend_thaw_processes();
??????? usermodehelper_enable();
?Finish:
??????? pm_notifier_call_chain(PM_POST_SUSPEND);
??????? pm_restore_console();
??????? return error;
}
讓外設(shè)進(jìn)入休眠
現(xiàn)在, 所有的進(jìn)程(也包括workqueue/kthread) 都已經(jīng)停止了, 內(nèi)核態(tài)人物有 可能在停止的時(shí)候握有一些信號(hào)量, 所以如果這時(shí)候在外設(shè)里面去解鎖這個(gè)信號(hào) 量有可能會(huì)發(fā)生死鎖, 所以在外設(shè)的suspend()函數(shù)里面作lock/unlock鎖要非常 小心,這里建議設(shè)計(jì)的時(shí)候就不要在suspend()里面等待鎖. 而且因?yàn)閟uspend的時(shí)候,有一些Log是無法輸出的,所以一旦出現(xiàn)問題,非常難調(diào)試.
然后kernel在這里會(huì)嘗試釋放一些內(nèi)存.
最后會(huì)調(diào)用suspend_devices_and_enter()來把所有的外設(shè)休眠, 在這個(gè)函數(shù)中, 如果平臺(tái)注冊(cè)了suspend_pos(通常是在板級(jí)定義中定義和注冊(cè)), 這里就會(huì)調(diào)用 suspend_ops->begin(), 然后driver/base/power/main.c 中的 device_suspend()->dpm_suspend() 會(huì)被調(diào)用,他們會(huì)依次調(diào)用驅(qū)動(dòng)的suspend() 回調(diào)來休眠掉所有的設(shè)備.
當(dāng)所有的設(shè)備休眠以后, suspend_ops->prepare()會(huì)被調(diào)用, 這個(gè)函數(shù)通常會(huì)作一些準(zhǔn)備工作來讓板機(jī)進(jìn)入休眠. 接下來Linux,在多核的CPU中的非啟動(dòng)CPU會(huì)被關(guān)掉, 通過注釋看到是避免這些其他的CPU造成race condion,接下來的以后只有一個(gè)CPU在運(yùn)行了.
suspend_ops 是板級(jí)的電源管理操作, 通常注冊(cè)在文件 arch/xxx/mach-xxx/pm.c 中.
接下來, suspend_enter()會(huì)被調(diào)用, 這個(gè)函數(shù)會(huì)關(guān)閉arch irq, 調(diào)用 device_power_down(), 它會(huì)調(diào)用suspend_late()函數(shù), 這個(gè)函數(shù)是系統(tǒng)真正進(jìn)入 休眠最后調(diào)用的函數(shù), 通常會(huì)在這個(gè)函數(shù)中作最后的檢查. 如果檢查沒問題, 接 下來休眠所有的系統(tǒng)設(shè)備和總線, 并且調(diào)用 suspend_pos->enter() 來使CPU進(jìn)入 省電狀態(tài). 這時(shí)候,就已經(jīng)休眠了.代碼的執(zhí)行也就停在這里了.
/**
?*????? suspend_devices_and_enter - suspend devices and enter the desired system
?*????????????????????????????????? sleep state.
?*????? @state:?????????? state to enter
?*/
int suspend_devices_and_enter(suspend_state_t state)
{
??????? int error, ftrace_save;
??????? if (!suspend_ops)
??????????????? return -ENOSYS;
??????? if (suspend_ops->begin) {
??????????????? error = suspend_ops->begin(state);
??????????????? if (error)
??????????????????????? goto Close;
??????? }
??????? suspend_console();
??????? ftrace_save = __ftrace_enabled_save();
??????? suspend_test_start();
??????? error = device_suspend(PMSG_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;
??????? if (suspend_ops->prepare) {
??????????????? error = suspend_ops->prepare();
??????????????? if (error)
??????????????????????? goto Resume_devices;
??????? }
??????? if (suspend_test(TEST_PLATFORM))
??????????????? goto Finish;
??????? error = disable_nonboot_cpus();
??????? if (!error && !suspend_test(TEST_CPUS))
??????????????? suspend_enter(state);
??????? enable_nonboot_cpus();
?Finish:
??????? if (suspend_ops->finish)
??????????????? suspend_ops->finish();
?Resume_devices:
??????? suspend_test_start();
??????? device_resume(PMSG_RESUME);
??????? suspend_test_finish("resume devices");
??????? __ftrace_enabled_restore(ftrace_save);
??????? resume_console();
?Close:
??????? if (suspend_ops->end)
??????????????? suspend_ops->end();
??????? return error;
?Recover_platform:
??????? if (suspend_ops->recover)
??????????????? suspend_ops->recover();
??????? goto Resume_devices;
}
Resume
如果在休眠中系統(tǒng)被中斷或者其他事件喚醒, 接下來的代碼就會(huì)開始執(zhí)行, 這個(gè) 喚醒的順序是和休眠的循序相反的,所以系統(tǒng)設(shè)備和總線會(huì)首先喚醒,使能系統(tǒng)中 斷, 使能休眠時(shí)候停止掉的非啟動(dòng)CPU, 以及調(diào)用suspend_ops->finish(), 而且 在suspend_devices_and_enter()函數(shù)中也會(huì)繼續(xù)喚醒每個(gè)設(shè)備,使能虛擬終端, 最后調(diào)用 suspend_ops->end().
在返回到enter_state()函數(shù)中的, 當(dāng) suspend_devices_and_enter() 返回以后, 外設(shè)已經(jīng)喚醒了, 但是進(jìn)程和任務(wù)都還是凍結(jié)狀態(tài), 這里會(huì)調(diào)用suspend_finish()來解凍這些進(jìn)程和任務(wù), 而且發(fā)出Notify來表示系統(tǒng)已經(jīng)從suspend狀態(tài)退出, 喚醒終端.
到這里, 所有的休眠和喚醒就已經(jīng)完畢了, 系統(tǒng)繼續(xù)運(yùn)行了.
Android 休眠(suspend)
在一個(gè)打過android補(bǔ)丁的內(nèi)核中, state_store()函數(shù)會(huì)走另外一條路,會(huì)進(jìn) 入到request_suspend_state()中, 這個(gè)文件在earlysuspend.c中. 這些功能都 是android系統(tǒng)加的, 后面會(huì)對(duì)earlysuspend和late resume 進(jìn)行介紹.
涉及到的文件:
- linux_source/kernel/power/main.c
- linux_source/kernel/power/earlysuspend.c
- linux_source/kernel/power/wakelock.c
特性介紹
Early Suspend
Early suspend 是android 引進(jìn)的一種機(jī)制, 這種機(jī)制在上游備受爭議,這里 不做評(píng)論. 這個(gè)機(jī)制作用在關(guān)閉顯示的時(shí)候, 在這個(gè)時(shí)候, 一些和顯示有關(guān)的 設(shè)備, 比如LCD背光, 比如重力感應(yīng)器, 觸摸屏, 這些設(shè)備都會(huì)關(guān)掉, 但是系 統(tǒng)可能還是在運(yùn)行狀態(tài)(這時(shí)候還有wake lock)進(jìn)行任務(wù)的處理, 例如在掃描 SD卡上的文件等. 在嵌入式設(shè)備中, 背光是一個(gè)很大的電源消耗,所以 android會(huì)加入這樣一種機(jī)制.
Late Resume
Late Resume 是和suspend 配套的一種機(jī)制, 是在內(nèi)核喚醒完畢開始執(zhí)行的. 主要就是喚醒在Early Suspend的時(shí)候休眠的設(shè)備.
Wake Lock
Wake Lock 在Android的電源管理系統(tǒng)中扮演一個(gè)核心的角色. Wake Lock是一種鎖的機(jī)制, 只要有人拿著這個(gè)鎖, 系統(tǒng)就無法進(jìn)入休眠, 可以被用戶態(tài)程序和內(nèi)核獲得. 這個(gè)鎖可以是有超時(shí)的或者是沒有超時(shí)的, 超時(shí)的鎖會(huì)在時(shí)間過去以后自動(dòng)解鎖. 如果沒有鎖了或者超時(shí)了, 內(nèi)核就會(huì)啟動(dòng)休眠的那套機(jī)制來進(jìn)入休眠.
Android Suspend
當(dāng)用戶寫入mem 或者 standby到 /sys/power/state中的時(shí)候, state_store()會(huì)被調(diào)用, 然后Android會(huì)在這里調(diào)用 request_suspend_state() 而標(biāo)準(zhǔn)的Linux會(huì)在這里進(jìn)入enter_state()這個(gè)函數(shù). 如果請(qǐng)求的是休眠, 那么early_suspend這個(gè)workqueue就會(huì)被調(diào)用,并且進(jìn)入early_suspend狀態(tài).
void request_suspend_state(suspend_state_t new_state)
{
??????? unsigned long irqflags;
??????? int old_sleep;
??????? spin_lock_irqsave(&state_lock, irqflags);
??????? old_sleep = state & SUSPEND_REQUESTED;
??????? if (debug_mask & DEBUG_USER_STATE) {
????????????? ??struct timespec ts;
??????????????? struct rtc_time tm;
??????????????? getnstimeofday(&ts);
??????????????? rtc_time_to_tm(ts.tv_sec, &tm);
??????????????? pr_info("request_suspend_state: %s (%d->%d) at %lld "
??????????????????????? "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
??????????????????????? new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",
??????????????????????? requested_suspend_state, new_state,
??????????????????????? ktime_to_ns(ktime_get()),
??????????????????????? tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
??????????????????????? tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
??????? }
??????? if (!old_sleep && new_state != PM_SUSPEND_ON) {
??????????????? state |= SUSPEND_REQUESTED;
??????????????? queue_work(suspend_work_queue, &early_suspend_work);
??????? } else if (old_sleep && new_state == PM_SUSPEND_ON) {
??????????????? state &= ~SUSPEND_REQUESTED;
??????????????? wake_lock(&main_wake_lock);
??????????????? queue_work(suspend_work_queue, &late_resume_work);
??????? }
??????? requested_suspend_state = new_state;
??????? spin_unlock_irqrestore(&state_lock, irqflags);
}
Early Suspend
在early_suspend()函數(shù)中, 首先會(huì)檢查現(xiàn)在請(qǐng)求的狀態(tài)還是否是suspend, 來 防止suspend的請(qǐng)求會(huì)在這個(gè)時(shí)候取消掉(因?yàn)檫@個(gè)時(shí)候用戶進(jìn)程還在運(yùn)行),如 果需要退出, 就簡單的退出了. 如果沒有, 這個(gè)函數(shù)就會(huì)把early suspend中 注冊(cè)的一系列的回調(diào)都調(diào)用一次, 然后同步文件系統(tǒng), 然后放棄掉 main_wake_lock, 這個(gè)wake lock是一個(gè)沒有超時(shí)的鎖,如果這個(gè)鎖不釋放, 那 么系統(tǒng)就無法進(jìn)入休眠.
?? static void early_suspend(struct work_struct *work)
{
??????? struct early_suspend *pos;
??????? unsigned long irqflags;
??????? int abort = 0;
??????? mutex_lock(&early_suspend_lock);
??????? spin_lock_irqsave(&state_lock, irqflags);
??????? if (state == SUSPEND_REQUESTED)
??????????????? state |= SUSPENDED;
??????? else
??????????????? abort = 1;
??????? spin_unlock_irqrestore(&state_lock, irqflags);
??????? if (abort) {
????? ??????????if (debug_mask & DEBUG_SUSPEND)
??????????????????????? pr_info("early_suspend: abort, state %d\n", state);
??????????????? mutex_unlock(&early_suspend_lock);
??????????????? goto abort;
??????? }
??????? if (debug_mask & DEBUG_SUSPEND)
??????? ????????pr_info("early_suspend: call handlers\n");
??????? list_for_each_entry(pos, &early_suspend_handlers, link) {
??????????????? if (pos->suspend != NULL)
??????????????????????? pos->suspend(pos);
??????? }
??????? mutex_unlock(&early_suspend_lock);
??????? if (debug_mask & DEBUG_SUSPEND)
??????????????? pr_info("early_suspend: sync\n");
??????? sys_sync();
abort:
??????? spin_lock_irqsave(&state_lock, irqflags);
??????? if (state == SUSPEND_REQUESTED_AND_SUSPENDED)
??????????????? wake_unlock(&main_wake_lock);
??????? spin_unlock_irqrestore(&state_lock, irqflags);
}
Late Resume
當(dāng)所有的喚醒已經(jīng)結(jié)束以后, 用戶進(jìn)程都已經(jīng)開始運(yùn)行了, 喚醒通常會(huì)是以下的幾種原因:
- 來電
如果是來電, 那么Modem會(huì)通過發(fā)送命令給rild來讓rild通知WindowManager有 來電響應(yīng),這樣就會(huì)遠(yuǎn)程調(diào)用PowerManagerService來寫"on" 到 /sys/power/state 來執(zhí)行l(wèi)ate resume的設(shè)備, 比如點(diǎn)亮屏幕等.
- 用戶按鍵用戶按鍵事件會(huì)送到WindowManager中, WindowManager會(huì)處理這些 按鍵事件,按鍵分為幾種情況, 如果案件不是喚醒鍵(能夠喚醒系統(tǒng)的按鍵) 那么WindowManager會(huì)主動(dòng)放棄wakeLock來使系統(tǒng)進(jìn)入再次休眠, 如果按鍵 是喚醒鍵,那么WindowManger就會(huì)調(diào)用PowerManagerService中的接口來執(zhí)行 Late Resume.
- Late Resume 會(huì)依次喚醒前面調(diào)用了Early Suspend的設(shè)備.
static void late_resume(struct work_struct *work)
{
??????? struct early_suspend *pos;
??????? unsigned long irqflags;
??????? int abort = 0;
??????? mutex_lock(&early_suspend_lock);
??????? spin_lock_irqsave(&state_lock, irqflags);
??????? if (state == SUSPENDED)
???? ???????????state &= ~SUSPENDED;
??????? else
??????????????? abort = 1;
??????? spin_unlock_irqrestore(&state_lock, irqflags);
??????? if (abort) {
??????????????? if (debug_mask & DEBUG_SUSPEND)
??????????????????????? pr_info("late_resume: abort, state %d\n", state);
??????????????? goto abort;
??????? }
??????? if (debug_mask & DEBUG_SUSPEND)
??????????????? pr_info("late_resume: call handlers\n");
??????? list_for_each_entry_reverse(pos, &early_suspend_handlers, link)
??????????????? if (pos->resume != NULL)
??????????????????????? pos->resume(pos);
??????? if (debug_mask & DEBUG_SUSPEND)
??????????????? pr_info("late_resume: done\n");
abort:
??????? mutex_unlock(&early_suspend_lock);
}
Wake Lock
我們接下來看一看wake lock的機(jī)制是怎么運(yùn)行和起作用的, 主要關(guān)注 wakelock.c文件就可以了.
wake lock 有加鎖和解鎖兩種狀態(tài), 加鎖的方式有兩種, 一種是永久的鎖住, 這樣的鎖除非顯示的放開, 是不會(huì)解鎖的, 所以這種鎖的使用是非常小心的. 第二種是超時(shí)鎖, 這種鎖會(huì)鎖定系統(tǒng)喚醒一段時(shí)間, 如果這個(gè)時(shí)間過去了, 這個(gè)鎖會(huì)自動(dòng)解除.
鎖有兩種類型:
在wake lock中, 會(huì)有3個(gè)地方讓系統(tǒng)直接開始suspend(), 分別是:
Suspend
當(dāng)wake_lock 運(yùn)行 suspend()以后, 在wakelock.c的suspend()函數(shù)會(huì)被調(diào)用,這 個(gè)函數(shù)首先sync文件系統(tǒng),然后調(diào)用pm_suspend(request_suspend_state),接 下來pm_suspend()就會(huì)調(diào)用enter_state()來進(jìn)入Linux的休眠流程..
????? static void suspend(struct work_struct *work)
{
??????? int ret;
??????? int entry_event_num;
??????? if (has_wake_lock(WAKE_LOCK_SUSPEND)) {
??????????????? if (debug_mask & DEBUG_SUSPEND)
??????????????????????? pr_info("suspend: abort suspend\n");
??????????????? return;
??????? }
??????? entry_event_num = current_event_num;
??????? sys_sync();
??????? if (debug_mask & DEBUG_SUSPEND)
??????????????? pr_info("suspend: enter suspend\n");
??????? ret = pm_suspend(requested_suspend_state);
??????? if (current_event_num == entry_event_num) {
??????????????? wake_lock_timeout(&unknown_wakeup, HZ / 2);
??????? }
}
Android于標(biāo)準(zhǔn)Linux休眠的區(qū)別
pm_suspend() 雖然會(huì)調(diào)用enter_state()來進(jìn)入標(biāo)準(zhǔn)的Linux休眠流程,但是還 是有一些區(qū)別:
- 當(dāng)進(jìn)入凍結(jié)進(jìn)程的時(shí)候, android首先會(huì)檢查有沒有wake lock,如果沒有, 才會(huì)停止這些進(jìn)程, 因?yàn)樵陂_始suspend和凍結(jié)進(jìn)程期間有可能有人申請(qǐng)了 wake lock,如果是這樣, 凍結(jié)進(jìn)程會(huì)被中斷.
- 在suspend_late()中, 會(huì)最后檢查一次有沒有wake lock, 這有可能是某種快速申請(qǐng)wake lock,并且快速釋放這個(gè)鎖的進(jìn)程導(dǎo)致的,如果有這種情況, 這里會(huì)返回錯(cuò)誤, 整個(gè)suspend就會(huì)全部放棄.如果pm_suspend()成功了,LOG的輸出可以通過在kernel cmd里面增加 "no_console_suspend" 來看到suspend和resume過程中的log輸出。
?
?
總結(jié)
以上是生活随笔為你收集整理的Linux Kernel and Android 休眠与唤醒(中文版)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux HZ Tick Jiffie
- 下一篇: LINUX 使用tcgetattr函数与