導航:首頁 > 編程系統 > linuxsuspend喚醒

linuxsuspend喚醒

發布時間:2023-03-24 10:05:59

A. linux觸摸屏驅動中什麼時候會調用suspend這個函數

android系統摁下電源鍵後會讓系統進入休眠以達到節電的目的。內核驅動中和休眠相關的就是suspend和resume函數。

suspend函數用於休眠,resume函數用於喚醒。下面分析驅動中的這兩個函數是如何被調用到的。

驅動部分:

首先需要分析驅動的注冊過程,較新的內核都是採用DTS方式來取代在內核中直接定義platform_device數據結構的注冊方式,本文是基於DTS機制的內核來分析。

proct對應的dts文件在編譯時被編譯為dtb文件,uboot在啟動時候會將其地址傳給內核,內核在啟動過程中會去解析,具體解析是在start_kernel()->setup_arch() --> unflatten_device_tree()中具體分析可以參考網上,解析的最終結果會存放在allnodes地址處,這個allnodes隨後在machine的init函數
中被使用,init函數中會根據allnodes中的節點數據組合成platform_device數據結構,然後將其注冊到platform匯流排上,下面簡要分析一下並重點關注這些初始化過程中和
pm相關的初始化。

我參與的項目中machine的init函數就是via_init_machine函數,在這個函數中就是調用了of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)這個函數來解析allnodes的。of_platform_populate是系統提供的介面。下面分析這個介面的實現:

[html] view plain
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;

root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;

for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}

of_node_put(root);
return rc;
}
root最後就是取到的根節點,然後其作為參數傳遞給of_platform_bus_create,of_platform_device_create_pdata的實現如下:

[html] view plain
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;

/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}

auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}

if (of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}

dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;

for_each_child_of_node(bus, child) {
pr_debug(" create child: %s\n", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
return rc;
}
根據傳入參數,我們這里直接分析of_platform_device_create_padate函數,如下:

[html] view plain
struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;

if (!of_device_is_available(np))
return NULL;

dev = of_device_alloc(np, bus_id, parent);
if (!dev)
return NULL;

#if defined(CONFIG_MICROBLAZE)
dev->archdata.dma_mask = 0xffffffffUL;
#endif
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;

/* We do not fill the DMA ops for platform devices by default.
* This is currently the responsibility of the platform code
* to do such, possibly using a device notifier
*/

if (of_device_add(dev) != 0) {
platform_device_put(dev);
return NULL;
}

return dev;
}

of_platform_device_create_padate->of_device_alloc->platform_device_alloc

便在platform_device_alloc函數中進行進行alloc和初始化了,實現如下:

[html] view plain
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa;

pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
pa->pdev.id = id;
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release;
arch_setup_pdev_archdata(&pa->pdev);
}

return pa ? &pa->pdev : NULL;
}
可以看到有個device_initialize,這裡面對pdev.dev做一些列的初始化,其中有一個函數就是device_pm_init,這個函數就是我們一直關心的device相關的pm函數,具體實現如下:

[html] view plain
void device_pm_init(struct device *dev)
{
dev->power.is_prepared = false;
dev->power.is_suspended = false;
init_completion(&dev->power.completion);
complete_all(&dev->power.completion);
dev->power.wakeup = NULL;
spin_lock_init(&dev->power.lock);
pm_runtime_init(dev);
INIT_LIST_HEAD(&dev->power.entry);
dev->power.power_state = PMSG_INVALID;
}

可以看見它對device和功耗相關的數據做了一些初始化,我們這里先重點關注下dev->power.entry,初始化一個鏈表頭,所以他/它很有可能會在後面加到某個鏈表裡面去,而那個鏈表應該是用來保存所有的device用的。系統中所有的platform_device都是通過這種方式注冊到系統中的,那麼應該所有的platform_device都會初始化一個dev->power.entry,如果到時候把所有的dev->power.entry都添加到某個鏈表上去,那麼系統到時候查詢的時候只要找到這個list head就可以找到所有的platform_device了。嗯,不過這是我們的猜測。我們接下去分析來驗證下。

platform_device通過alloc之後已經初始化好了,那麼接下去就可以添加到系統中了,所以我們再回頭看of_platform_device_create_pdata的實現。
函數在of_device_alloc之後把dev->dev.bus賦值給了platform_bus_type,接著就調用了of_device_add函數,在of_device_add函數中最後通過device_add添加到了bus上,但是device_add中有個函數需要我們關系,就是device_pm_add(dev),實現如下:

[html] view plain
void device_pm_add(struct device *dev)
{
pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
mutex_lock(&dpm_list_mtx);
if (dev->parent && dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list);
dev_pm_qos_constraints_init(dev);
mutex_unlock(&dpm_list_mtx);
}

可以看到這里list_add_tail(&dev->power.entry, &dpm_list);這就驗證了我們之前的猜測。所有注冊到系統中的設備,最終都是會添加到dpm_list這條鏈表上。

那麼系統在休眠的時候是如何通過dmp_list這表鏈表來suspend設備的呢?接下去就是我們要分析的電源管理部分內容。

系統電源部分:
電源管理相關文件在kernel/power目錄下,前面已經分析到。系統中注冊的設備都是會添加到dmp_list這條鏈表上的。那麼睡眠的時候系統應該是會查找dmp_list這條鏈表,
然後通過這條鏈表依次去查到對應的driver,然後調用driver中的suspend方法。下面我們來驗證。

2.在suspend會輪詢bus下的driver,然後一次調用到driver->pm->suspend方法,然後進入休眠。

3.state_store->pm_suspend->enter_state->suspend_devices_and_enter->dpm_suspend_start->dpm_suspend->device_suspend->__device_suspend->pm_op->(ops->suspend)

B. Linux內核睡眠喚醒狀態

Linux內核支持四種系統睡眠狀態即: mem、standby、freeze and disk 。

可通過文件 /sys/power/state 進行讀寫訪問,區別如下:

在 RockPI 4A 單板 Debian 系統 Linux 4.4 內核中,查看電源狀態,僅支持 freeze和mem 兩種。

原因:

1、 Platform 驅動只實現了 mem 類型的 suspend

2、只有在 hibernation 可用時,才支持 STD

1、 psci 初始化流程

suspend_set_ops() 函數賦值數組 pm_states 實現如下:

2、power state顯示

/sys/power/state 文件顯示的內容,通過 state_show() 函數實現,該函數最終顯示數組 pm_states 的內容。

參考:

Documentation/power/states.txt

C. linux中程序處於停止態可以被喚醒嗎

在Linux中,休眠主要分三個主要的步驟:
1) 凍結用戶態進程和內核態任務
2) 調用注冊的設備的suspend的回調函數, 順序是按照注冊順序
3) 休眠核心設備和使CPU進入休眠態, 凍結進程是內核把進程列表中所有的進程的狀態都設置為停止,並且保存下所有進程的上下文.
當這些進程被解凍的時候,他們是不知道自己被凍結過的,只是簡單的繼續執行。
如何讓Linux進入休眠呢?用戶可以通過讀寫sys文件/sys /power/state 是實現控制系統進入休眠. 比如
# echo mem > /sys/power/state
命令系統進入休眠. 也可以使用

# cat /sys/power/state
來得到內核支持哪幾種休眠方式.
1. 相關代碼

• kernel/kernel/power/main.c
• kernel/arch/arm/mach-xxx/pm.c
• kernel/driver/base/power/main.c

接下來讓我們詳細的看一下Linux是怎麼休眠/喚醒的:
用戶對於/sys/power/state 的讀寫會調用到 kernel/kernel/power/main.c中的state_store(),
用戶可以寫入 const char * const pm_states[] 中定義的字元串, 比如"mem", "standby"。

const char *const pm_states[PM_SUSPEND_MAX] = {
#ifdef CONFIG_EARLYSUSPEND
[PM_SUSPEND_ON] = "on",
#endif
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
};
常見有standby(suspend to RAM)、mem(suspend to RAM)和disk(suspend to disk),只是standby耗電更多,返回到正常工作狀態的時間更短。

然後state_store()會調用enter_state()<註:這是經典Linux調用流程, 在Android系統中,
Kernel將調用request_suspend_state,而不是enter_state>,它首先會檢查一些狀態參數,然後同步文件系統。

2. 准備, 凍結進程
當進入到suspend_prepare()中以後, 它會給suspend分配一個虛擬終端來輸出信息, 然後廣播一個系統要進入suspend的Notify,
關閉掉用戶態的helper進程, 然後一次調用suspend_freeze_processes()凍結所有的進程, 這里會保存所有進程當前的狀態,
也許有一些進程會拒絕進入凍結狀態, 當有這樣的進程存在的時候, 會導致凍結失敗,此函數就會放棄凍結進程,並且解凍剛才凍結的所有進程。

3. 讓外設進入休眠
現在, 所有的進程(也包括workqueue/kthread) 都已經停止了,內核態人物有可能在停止的時候握有一些信號量,
所以如果這時候在外設裡面去解鎖這個信號量有可能會發生死鎖,所以在外設的suspend()函數裡面作lock/unlock鎖要非常小心,
這里建議設計的時候就不要在suspend()裡面等待鎖。而且因為suspend的時候,有一些Log是無法輸出的,所以一旦出現問題,非常難調試。

然後kernel在這里會嘗試釋放一些內存。

最後會調用suspend_devices_and_enter()來把所有的外設休眠, 在這個函數中,
如果平台注冊了suspend_ops(通常是在板級定義中定義和注冊,在kernel/arch/arm/mach-xx/pm.c中調用suspend_set_ops),
這里就會調用 suspend_ops->begin(); 然後調用dpm_suspend_start,他們會依次調用驅動的suspend() 回調來休眠掉所有的設備。

當所有的設備休眠以後, suspend_ops->prepare()會被調用, 這個函數通常會作一些准備工作來讓板機進入休眠。
接下來Linux,在多核的CPU中的非啟動CPU會被關掉,通過注釋看到是避免這些其他的CPU造成race condio,接下來的以後只有一個CPU在運行了。

suspend_ops 是板級的電源管理操作, 通常注冊在文件 arch/arch/mach-xxx/pm.c 中.

接下來, suspend_enter()會被調用, 這個函數會關閉arch irq, 調用 device_power_down(), 它會調用suspend_late()函數,
這個函數是系統真正進入休眠最後調用的函數,通常會在這個函數中作最後的檢查。 如果檢查沒問題, 接下來休眠所有的系統設備和匯流排,
並且調用 suspend_pos->enter() 來使CPU進入省電狀態,這時就已經休眠了。代碼的執行也就停在這里了。

三、Linux Resume流程

如果在休眠中系統被中斷或者其他事件喚醒,接下來的代碼就會開始執行,這個喚醒的順序是和休眠的循序相反的,
所以系統設備和匯流排會首先喚醒,使能系統中斷,使能休眠時候停止掉的非啟動CPU, 以及調用suspend_ops->finish(),
而且在suspend_devices_and_enter()函數中也會繼續喚醒每個設備,使能虛擬終端, 最後調用 suspend_ops->end()。

在返回到enter_state()函數中的,當 suspend_devices_and_enter() 返回以後,外設已經喚醒了,
但是進程和任務都還是凍結狀態, 這里會調用suspend_finish()來解凍這些進程和任務, 而且發出Notify來表示系統已經從suspend狀態退出, 喚醒終端。

到這里,所有的休眠和喚醒就已經完畢了,系統繼續運行了。

D. 真心請問Linux kernel進入Suspend有關問題

就是如此簡單 2.6內核的編譯安裝已經大大簡化了 當然 新的內核被拷貝到 /boot後 還是需要手動修改 gurb.conf / menu.lst的

E. 如何在Android 或Linux 下,做Suspend /Resume 的Debug

在Linux或Android下,做power management 的調適時,常遇到沒有足夠的information,可以做為debug時的依據和參考

我們整理了幾個常用的參數或Command,可供設計者,得到足夠的Informaiton 做Suspend / Resume的function Debug。

加boot 參數 no_console_suspend
基本上我們常常使用console做為suspend function的debug的Information source,但原始的source code在suspend過程中,會將console關掉。所以我們看到一定程度後就再也看不到message了。

但是我們並不知道在Suspend的過程中,系統到底發生了什麼事,可能造成無法suspend。
為此,我們就會在kernel 啟動參數中加上no_console_suspend這個參數。在AM/DM37x APM中是修改boot.scr檔案參數
#!/bin/sh
cat <<EOF > boot.cmd
if fatload mmc 0 82000000 uImage
then
echo ***** Kernel: /dev/mmcblk0p1/uImage *****
fi
echo ***** RootFS: /dev/mmcblk0p2 *****
# Pantherboard DVI output
#setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 mem=512M root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=off omap_vout.vid1_static_vrfb_alloc=y omapdss.def_disp=dvi vram=32M omapfb.mode=dvi:1280x720MR-32 omapfb.vram=0:16M mpurate=1000'
# Pantherboard LCD output
setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 mem=512M root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=off omap_vout.vid1_static_vrfb_alloc=y omapdss.def_disp=lcd omapfb.mode=lcd:800x480MR-32 vram=8M omapfb.vram=0:8M mpurate=1000'

將no_console_suspend加上去到boot 參數後就好了

setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 mem=512M root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=off omap_vout.vid1_static_vrfb_alloc=y omapdss.def_disp=lcd omapfb.mode=lcd:800x480MR-32 vram=8M omapfb.vram=0:8M mpurate=1000 no_console_suspend'

如果是OMAP4 APM的話,請修改Kernel 參數的所在檔案即可(在U-boot Source code中)

這個是基本的參數,所以在Android或Linux上都可以使用。 kernel把console suspend掉以後, 不管裡面出了什麼事情, 在Console上都看不到。 而使用這個參數後,大部分在suspend/resume時候的死機都可以通過Console看到kernel Panic的信息, 這樣我們才會知道是哪裡出了問題。 因為有的時候resume出錯, 或者suspend到很後面出錯的console不加這個參數都看不到。

但這個參數在TI OMAP3/OMAP4/AM37x/DM37x有可能造成有時Suspend 完當掉或是resume 失敗的問題,假如已經抓到問題在那的時候,您就可以將這個參數Disable,不然很可能就會Debug不下去。

initcall_debug
這個同樣kernel參數,使用的時機是,當我們不知道是那個driver在suspend/resume過程中出錯的時候,可以使用這個參數來找出問題所在。在下完這個參數後,Kernel在suspend時,會將每個driver或task的狀況report出來。我們可以藉由這些information,Check 在suspend時,每個task和driver是否已經完成進suspend 的相關准備工作…

打開這個參數的方法有二種
一種是在console下Command,啟動這個function…

echo 1 > /sys/mole/kernel/parameters/initcall_debug
echo 9 > /proc/sys/kernel/printk

其中上面的第一條命令是打開initcall_debug, 這個是所有的kernel都會有的。

而第二條命令是要提高kernel message 級別,因為initcall的這些信息都是KERN_DEBUG級別的, 所以需要提高printk的級別才可以看到, 要不然suspend/resume的時候掛了,你就沒有機會看到這些信息了。

另一種啟動方法是寫在kernel的啟動參數下,就可以了。

setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 mem=512M root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=off omap_vout.vid1_static_vrfb_alloc=y omapdss.def_disp=lcd omapfb.mode=lcd:800x480MR-32 vram=8M omapfb.vram=0:8M mpurate=1000 initcall_debug no_console_suspend'

同樣的,這個參數也有可能造成AM37x/DM37x/OMAP4 APM發生進suspend當掉的問題。所以一旦知道問題所在,麻煩請將這個參數Disable掉。
suspend_test
這個方法可以用rtc這種軟體的方式來做循環的suspend/resume, 盡管對於Android這樣並不是很足夠, (還要再模擬一個POWER_KEY上去才夠), 但是對於測試Driver的穩定性, 還是有一定用處的。不要認為suspend了幾次可以,那麼就可以通過幾千次的測試。這個suspend是5秒鍾用RTC喚醒,然後回到Android後5秒鍾又會自動睡下去,但是對於通用Linux,你可以寫個script來讓他起來一會再睡下去,或許這個工具比較有用rtcwakeup(google rtcwakeup)。

使用方法:
編譯一個有這個功能的kernel, make menuconfig 以後選上

CONFIG_PM_DEBUG=y
CONFIG_PM_TEST_SUSPEND=y

這兩個選項,燒寫新的kernel,然後打開你需要測試的Device, 比如WIFI,3G

echo "core" > /sys/power/pm_test
echo "mem" > /sys/power/state

這樣, 它就會循環休眠和喚醒了。

wakelock
Android和Linux在Power Management相關的最大的就是wakelock機制的有無。Android有時候會碰到suspend進不去,或者suspend到最後又莫名奇妙的wake up的問題。這些都有可能是wakelock引起的,或者說是wakelock的使用者引起的。怎麼fine tune呢,使用Console在Android 系統下設定:
echo 15 > /sys/mole/wakelock/parameters/debug_mask
echo 15 > /sys/mole/userwakelock/parameters/debug_mask

15是代表16進制的F, 在wakelock裡面就是把所有的debug信息打開, 起碼現在是這樣設定的。如果以後不夠用了,可能就會改成255.
這樣你能看到kernel和frameworks層對於wakelock的操作、申請及釋放。這樣看申請和釋放成對否就可以了。
注意: wakelock有一種是timeout的,就是說多少毫秒以後,會自動釋放,對於這些wakelock,申請和釋放可能是不成對的。

power.0
有時看到系統suspend到了最後, 然後遇到power.0後suspend失敗,然後整個系統又resume回來。這個是android專有的,因為power.0是android注冊到suspend最後的一個行程, 它會在CPU進入suspend之前檢查一下有沒有wakelock存在, 如果這時候還有沒有釋放的wakelock, 那麼它會return -EBUSY然後導致整個suspend失敗。 Check這個問題的方法就是把上面wakelock的debug信息打開,然後看看是哪個去申請了wakelock,然後Release它。

這個錯誤的錯誤信息大概是這樣的:

pm_noirq_op(): platform_pm_suspend_noirq+0x0/0x38 returns -11
PM: Device power.0 failed to suspend late: error -11

earlysuspend
在android裡面中另外一個和Power Management有相關的機制叫earlysuspend, 同樣可以打開debug message,用來做Android earlysuspend debug之用:
echo 15 > /sys/mole/earlysuspend/parameters/debug_mask

來把相關的debug信息印出來, 例如那個earlysuspend要被call之類的。
suspend/resume 時間 fine tune
有的時候你要調試suspend/resume的時間太慢的問題。 一種方法是用initcall_debug, 然後把printk的時間戳打上, 然後看那個process最慢,再來Check原因是什麼
我有一個patch,專門用來調試這個問題的,但是upstream不接受, 說非要用這種折磨人的方法才行, 但是如果你想用可以下下來打上去用一下。
地址在這里:http://www.spinics.net/lists/linux-pm/msg24063.html
用法是, 打上這個PATCH以後, 在KERNEL裡面選擇上PM_DEBUG, SUSPEND_DEVICE_TIME_DEBUG 這兩個選項。
然後
echo 微秒> /sys/power/device_suspend_time_threshold

比如
echo 50000 > /sys/power/device_suspend_time_threshold

注意這里是微秒哦。 。 。 它會把在suspend/resume的時候慢的那些driver打出來,然後你去幹掉它。 。

F. 如何查找喚醒android系統

如果在休眠中系統被中斷或者其他事件喚醒,接下來的代碼就會開始執行,這個喚醒的順序是和休眠的循序相反的,所以系統設備和匯流排會首先喚醒,使能系統中斷,使能休眠時候停止掉的非啟動CPU,以及調用suspend_ops->finish(),而且在suspend_devices_and_enter()函數中也會繼續喚醒每個設備,使能虛擬終端,最後調用suspend_ops->end().

在返回到enter_state()函數中的,當suspend_devices_and_enter()返回以後,外設已經喚醒了,但是進程和任務都還是凍結狀態,這里會調用suspend_finish()來解凍這些進程和任務,而且發出Notify來表示系統已經從suspend狀態退出,喚醒終端.

到這里,所有的休眠和喚醒就已經完畢了,系統繼續運行了.

Android系統Suspend和resume的函數流程
Android 休眠(suspend)介紹
在一個打過android補丁的內核中,state_store()函數會走另外一條路,會進入到request_suspend_state()中,這個文件在earlysuspend.c中.這些功能都是android系統加的,後面會對earlysuspend和lateresume進行介紹。

涉及到的文件:

linux_source/kernel/power/main.c

linux_source/kernel/power/earlysuspend.c

linux_source/kernel/power/wakelock.c

特性介紹

1)EarlySuspend

Early suspend是android引進的一種機制,這個機製作用在關閉顯示的時候,一些和顯示有關的設備,比如LCD背光,重力感應器,觸摸屏,這些設備都會關掉,但是系統可能還是在運行狀態(這時候還有wakelock)進行任務的處理,例如在掃描SD卡上的文件等.在嵌入式設備中,背光是一個很大的電源消耗,所以android會加入這樣一種機制。

2)LateResume

Late Resume是和suspend配套的一種機制,是在內核喚醒完畢開始執行的,主要就是喚醒在EarlySuspend的時候休眠的設備.

當所有的喚醒已經結束以後,用戶進程都已經開始運行了,喚醒通常會是以下的幾種原因:

來電

如果是來電,那麼Modem會通過發送命令給rild來讓rild通知WindowManager有來電響應,這樣就會遠程調用PowerManagerService來寫"on"到/sys/power/state來執行lateresume的設備,比如點亮屏幕等.

用戶按鍵用戶按鍵事件會送到WindowManager中,WindowManager會處理這些按鍵事件,按鍵分為幾種情況,如果案件不是喚醒鍵(能夠喚醒系統的按鍵)那麼WindowManager會主動放棄wakeLock來使系統進入再次休眠,如果按鍵是喚醒鍵,那麼WindowManger就會調用PowerManagerService中的介面來執行Late Resume.

Late Resume會依次喚醒前面調用了EarlySuspend的設備.

3)WakeLock

Wake Lock在Android的電源管理系統中扮演一個核心的角色.Wake Lock是一種鎖的機制,只要有人拿著這個鎖,系統就無法進入休眠,可以被用戶態程序和內核獲得。這個鎖可以是有超時的或者是沒有超時的,超時的鎖會在時間過去以後自動解鎖。如果沒有鎖了或者超時了,內核就會啟動休眠的那套機制來進入休眠。

3)AndroidSuspend

當用戶寫入mem或者standby到/sys/power/state中的時候,state_store()會被調用,然後Android會在這里調用request_suspend_state()而標準的Linux會在這里進入enter_state()這個函數.如果請求的是休眠,那麼early_suspend這個workqueue就會被調用,並且進入early_suspend狀態。調用request_suspend_state()後在suspend_work_queue工作線程上面注冊一個early_suspend_work工作者,

然後又通過staticDECLARE_WORK(early_suspend_work, early_suspend);注冊一個工作任務early_suspend。所以系統最終會調用early_suspend函數。

注冊加入suspend和resume流程
platform_device_register()-->platform_device_add()-->device_add()-->device_pm_add()-->,最終加入到了dpm_list的鏈表中,在其中的dpm_suspend和dpm_suspend中通過遍歷這個鏈表來進行查看哪個device中包含suspend和resume項。

系統喚醒和休眠

Kernel層[針對AndroidLinux2.6.28內核]:

其主要代碼在下列位置:

Drivers/base /main.c

kernel/power /main.c

kernel/power/wakelock.c

kernel/power/earlysuspend.c

其對Kernel提供的介面函數有

EXPORT_SYMBOL(wake_lock_init);//初始化Suspendlock,在使用前必須做初始化

EXPORT_SYMBOL(wake_lock);//申請lock,必須調用相應的unlock來釋放它

static DEFINE_TIMER(expire_timer,expire_wake_locks, 0, 0);//定時時間到,加入到suspend隊列中;

EXPORT_SYMBOL(wake_unlock);//釋放lock

EXPORT_SYMBOL_GPL(device_power_up);//打開特殊的設備

EXPORT_SYMBOL_GPL(device_power_down);//關閉特殊設備

EXPORT_SYMBOL_GPL(device_resume);//重新存儲設備的狀態;

EXPORT_SYMBOL_GPL(device_suspend);:保存系統狀態,並結束掉系統中的設備;

EXPORT_SYMBOL(register_early_suspend);//注冊earlysuspend的驅動

EXPORT_SYMBOL(unregister_early_suspend);//取消已經注冊的earlysuspend的驅動

Android的suspent執行流程
函數的流程如下所示:

應用程序通過對/sys/power/state的寫入操作可以使系統進行休眠的狀態,會調用/kernel/power/main.c中的state_store函數。pm_states包括:

PM_SUSPEND_ON,PM_SUSPEND_STANDBY,PM_SUSPEND_MEM滿足的狀態。

1)當狀態位PM_SUSPEND_ON的狀態的時候,request_suspend_state();當滿足休眠的狀態的時候,調用request_suspend_state在suspend_work_queue工作線程上創建early_suspend_work隊列,queue_work(suspend_work_queue,&early_suspend_work)。

2)然後通過DECLARE_WORK(early_suspend_work,early_suspend);在early_suspend_work工作隊列中添加工作任務調用early_suspend,所以early_suspend函數會被調用。

3)early_suspend函數中通過

list_for_each_entry(pos,&early_suspend_handlers, link) {

if (pos->suspend != NULL)

pos->suspend(pos);

在鏈表中找注冊的suspend函數,這個suspend是early的。early_suspend後面調用wake_unlock函數。語句:wake_unlock(&main_wake_lock);

4)wake_unlock()中調用mod_timer啟動expire_timer定時器,當定時時間到了,則執行expire_wake_locks函數,將suspend_work加入到suspend_work_queue隊列中,分析到這里就可以知道了early_suspend_work和suspend_work這兩個隊列的先後順序了(先執行early,定義一段時間後才執行suspend_work),然後會在suspend_work隊列中加入suspend的工作任務,所以wakelock.c中的suspend函數會被調用。

5)suspend調用了pm_suspend,通過判斷當前的狀態,選擇enter_state(),在enter_state中,經過了suspend_prepare,suspend_test和suspend_device_and_enter(),在suspend_device_and_enter中調用dpm_suspend_start(),然後調用dpm_suspend()。

6)dpm_suspend中利用while循環在dpm_list鏈表查找所有devic,然後調用device_suspend來保存狀態和結束系統的設備。到了這里,我們就又可以看見在初始化的時候所看到的隊列dpm_list。

dpm_list鏈表的添加是在device_pm_add中完成,請看上一節中。

Wake Lock
我們接下來看一看wakelock的機制是怎麼運行和起作用的,主要關注wakelock.c文件就可以了。

wake lock有加鎖和解鎖兩種狀態,加鎖的方式有兩種,一種是永久的鎖住,這樣的鎖除非顯示的放開,是不會解鎖的,所以這種鎖的使用是非常小心的.第二種是超時鎖,這種鎖會鎖定系統喚醒一段時間,如果這個時間過去了,這個鎖會自動解除.

鎖有兩種類型:

WAKE_LOCK_SUSPEND這種鎖會防止系統進入睡眠

WAKE_LOCK_IDLE這種鎖不會影響系統的休眠,作用我不是很清楚.

在wakelock中,會有3個地方讓系統直接開始suspend(),分別是:

1)在wake_unlock()中,如果發現解鎖以後沒有任何其他的wakelock了,就開始休眠

2)在定時器都到時間以後,定時器的回調函數會查看是否有其他的wakelock,如果沒有,就在這里讓系統進入睡眠.

3)在wake_lock()中,對一個wakelock加鎖以後,會再次檢查一下有沒有鎖,我想這里的檢查是沒有必要的,更好的方法是使加鎖的這個操作原子化,而 不是繁冗的檢查.而且這樣的檢查也有可能漏掉.

Android於標准Linux休眠的區別

pm_suspend()雖然會調用enter_state()來進入標準的Linux休眠流程,但是還是有一些區別:

當進入凍結進程的時候,android首先會檢查有沒有wakelock,如果沒有,才會停止這些進程,因為在開始suspend和凍結進程期間有可能有人申請了wake lock,如果是這樣,凍結進程會被中斷.

在suspend_late()中,會最後檢查一次有沒有wakelock,這有可能是某種快速申請wakelock,並且快速釋放這個鎖的進程導致的,如果有這種情況,這里會返回錯誤,整個suspend就會全部放棄.如果pm_suspend()成功了,LOG的輸出可以通過在kernelcmd裡面增加"no_console_suspend"來看到suspend和resume過程中的log輸出。

Android的電源管理主要是通過Wakelock來實現的,在最底層主要是通過如下隊列來實現其管理:

LIST_HEAD(dpm_list);

系統正常開機後進入到AWAKE狀態,,Backlight會從最亮慢慢調節到用戶設定的亮度,系統screenoff timer(settings->sound & display-> Display settings ->Screen timeout)開始計時,在計時時間到之前,如果有任何的activity事件發生,如Touchclick, keyboard pressed等事件,則將Resetscreen off timer, 系統保持在AWAKE狀態.如果有應用程序在這段時間內申請了Fullwake lock,那麼系統也將保持在AWAKE狀態,除非用戶按下powerkey.在AWAKE狀態下如果電池電量低或者是用AC供電screenoff timer時間到並且選中Keepscreen on while pluged in選項,backlight會被強制調節到DIM的狀態。

如果Screenoff timer時間到並且沒有Fullwake lock或者用戶按了powerkey,那麼系統狀態將被切換到NOTIFICATION,並且調用所有已經注冊的early_suspend_handlers函數,通常會把LCD和Backlight驅動注冊成earlysuspend類型,如有需要也可以把別的驅動注冊成earlysuspend,這樣就會在第一階段被關閉.接下來系統會判斷是否有partialwake lock acquired, 如果有則等待其釋放,在等待的過程中如果有useractivity事件發生,系統則馬上回到AWAKE狀態;如果沒有partialwake lock acquired, 則系統會馬上調用函數pm_suspend關閉其它相關的驅動,讓CPU進入休眠狀態。

系統在Sleep狀態時如果檢測到任何一個Wakeupsource,則CPU會從Sleep狀態被喚醒,並且調用相關的驅動的resume函數,接下來馬上調用前期注冊的earlysuspend驅動的resume函數,最後系統狀態回到AWAKE狀態.這里有個問題就是所有注冊過earlysuspend的函數在進Suspend的第一階段被調用可以理解,但是在resume的時候,Linux會先調用所有驅動的resume函數,而此時再調用前期注冊的earlysuspend驅動的resume函數有什麼意義呢?個人覺得android的這個earlysuspend和lateresume函數應該結合Linux下面的suspend和resume一起使用,而不是單獨的使用一個隊列來進行管理。

G. Linux內核睡眠喚醒調試

本文基於 RockPI 4A 單板 Debian 系統 Linux4.4 內核介紹下睡眠喚醒( suspend/resume )的一些調試方法。

1、關閉串口睡眠

在Linux內核睡眠過程中,會先調用 suspend_console() 函數使串口進入睡眠狀態,這樣會導致後續設備驅動的睡眠過程不可見。可以在boot啟動參數中增加 no_console_suspend 參數,顯示設備驅動睡眠日誌。

2、修改串口日誌等級

修改串口日誌列印等級,顯示更多調試信息。

3、打開設備睡眠喚醒時間

設置 pm_print_times 參數,可以顯示設備驅動睡眠喚醒時間,方便調試時查看哪個函數處理佔用時間過長。

在調試Linux內核睡眠喚醒功能時,可以使用 RTC 做喚醒源,在系統睡眠5秒後,自動喚醒系統。

在 arch/arm64/configs/rockchip_linux_defconfig 文件中配置宏 CONFIG_PM_TEST_SUSPEND 。

喚醒日誌如下:

閱讀全文

與linuxsuspend喚醒相關的資料

熱點內容
maya粒子表達式教程 瀏覽:84
抖音小視頻如何掛app 瀏覽:283
cad怎麼設置替補文件 瀏覽:790
win10啟動文件是空的 瀏覽:397
jk網站有哪些 瀏覽:134
學編程和3d哪個更好 瀏覽:932
win10移動硬碟文件無法打開 瀏覽:385
文件名是亂碼還刪不掉 瀏覽:643
蘋果鍵盤怎麼打開任務管理器 瀏覽:437
手機桌面文件名字大全 瀏覽:334
tplink默認無線密碼是多少 瀏覽:33
ipaddgm文件 瀏覽:99
lua語言編程用哪個平台 瀏覽:272
政采雲如何導出pdf投標文件 瀏覽:529
php獲取postjson數據 瀏覽:551
javatimetask 瀏覽:16
編程的話要什麼證件 瀏覽:94
錢脈通微信多開 瀏覽:878
中學生學編程哪個培訓機構好 瀏覽:852
榮耀路由TV設置文件共享錯誤 瀏覽:525

友情鏈接