原文:Linux電源管理(3)_Generic PM之重新啟動過程
1.前言
在使用計算機的過程中,關機和重啟是最先學會的兩個操作。同樣,這兩個操作在Linux中也存在,可以關機和重啟。這就是這里要描述的對象。在Linux Kernel中,主流的關機和重新啟動都是通過“ reboot”系統調用(具體可參考kernel / sys.c)來實現的。另外,除了我們常用的shutdown和restart兩類操作之外,該系統調用也提供了其他的reboot方式,也會在這里一一說明。
2.內核支持的reboot方式
也許你會奇怪,reboot是重啟的意思,所以用它實現Restart是合理的,但怎么用它實現關機操作呢?答案是這樣的:關機之后,早晚也會開機啊!所以關機是一種特殊的重新啟動過程,只不過持續的時間有點長而已。所以,內核根據不同的表現方式,將重新啟動分成如下的幾種方式:
???????
1: / *
2: * _reboot()系統調用接受的命令。
3: *
4: *重新啟動使用默認命令和模式重新啟動系統。
5: * HALT停止OS,并將系統控制權交給ROM監視器(如果有)。
6: * CAD_ON Ctrl-Alt-Del序列導致RESTART命令。
7: * CAD_OFF Ctrl-Alt-Del序列將SIGINT發送到初始化任務。
8: * POWER_OFF如果可能,請停止OS并從系統中斷開所有電源。
9: * RESTART2使用給定的命令字符串重新啟動系統。
10: * SW_SUSPEND使用軟件掛起的掛起系統(如果已編譯)。
11: * KEXEC使用先前加載的Linux內核重新啟動系統
12: * /
13:
14: #define LINUX_REBOOT_CMD_RESTART 0x01234567
15: #define LINUX_REBOOT_CMD_HALT 0xCDEF0123
16: #define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF
17: #define LINUX_REBOOT_CMD_CAD_OFF 0x00000000
18: #define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC
19: #define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4
20: #define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2
21: #define LINUX_REBOOT_CMD_KEXEC 0x4558454
操作類型 | 英文全稱/別名 | 功能描述 | 備注 |
RESTART | Restart | 正常重啟系統,重新加載內核和用戶空間。 | 用戶最常用的重啟方式,等同于 reboot 命令。 |
HALT | Halt | 停止操作系統,將控制權交給其他代碼(如 BIOS/UEFI 或監控程序)。 | 表現形式依賴具體硬件實現,可能完全斷電或進入低功耗狀態。 |
CAD_ON/CAD_OFF | Ctrl+Alt+Del On/Off | 允許/禁止通過 Ctrl+Alt+Del 組合鍵觸發重啟(RESTART)。 | 需鍵盤驅動支持,默認行為由內核配置決定。 |
POWER_OFF | Power Off | 正常關機,停止操作系統并切斷電源(ACPI 狀態 S5 )。 | 等同于 poweroff 命令,需硬件支持完全斷電。 |
RESTART2 | Restart with Command | 重啟時攜帶自定義字符串( cmd ),傳遞給關注重啟事件的進程或機器相關代碼。 | 用途由設備廠商自定義(如安卓的恢復模式)。 |
SW_SUSPEND | Software Suspend | 掛起到內存(Suspend-to-RAM)或磁盤(Hibernate),進入低功耗狀態。 | 依賴 CONFIG_SUSPEND 內核選項,詳見后續休眠專題。 |
KEXEC | Kernel Execute | 跳過 BIOS/UEFI,直接重啟到預加載的其他內核鏡像(需 CONFIG_KEXEC 支持)。 | 用于快速內核熱替換或崩潰恢復(如 kexec -l 加載新內核)。 |
3.重新啟動相關的操作流程
在Linux操作系統中,可以通過重新啟動,停止,關閉電源等命令,啟動重新啟動,具體的操作流程如下:
一般的Linux操作系統,在用戶空間都提供了一些工具集合(如常在嵌入式系統使用的Busybox),這些工具集合包含了重新引導,停止和關機三個和重新引導相關的命令。
用戶空間程序通過reboot系統調用,進入內核空間,內核空間根據執行路徑的不同,提供了kernel_restart,kernel_halt和kernel_power_off三個處理函數,響應用空間的reboot請求這三個處理函數的處理流程大致相同,主要包括:向有關重新引導過程的進程發送通知事件;調用驅動程序核心模塊提供的接口,關閉所有的外部設備;調用驅動程序syscore模塊提供的接口,關閉系統核心;調用架構相關的處理函數,進行后續的處理;最后,調用計算機相關的接口,實現真正意義上的重新啟動另外,采用TTY模塊提供的Sysreq機制,內核提供了其他途徑的關機方法,如某些按鍵組合,向/ proc文件寫入命令等
4.重新啟動過程的內部動作和代碼分析
4.1重啟系統調用
重新啟動系統調用的實現位于“ kernel / sys.c”,其函數原型如下:???????
1: SYSCALL_DEFINE4(reboot,int,magic1,int,magic2,unsigned int,cmd,
2: void __user *,arg)
reboot,該系統調用的名稱。
magic1,magic2,兩個int類型的“魔力數”,用于防止誤操作。具體在“ include / uapi / linux / reboot.h”中定義,感興趣的同學可以去看看(話說這些數字還是蠻有意思的,例如Linus同學及其家人的生日就在里面,猜出來的可以在文章下面留言)。
cmd,第2章所描繪的reboot方式。
arg,其他的額外參數。
reboot系統調用的內部動作比較簡單:
1)確定調用者的用戶權限,如果不是超級用戶(superuser),則直接返回錯誤(這也是我們再用戶空間執行reboot,halt,poweroff等命令時,必須是root用戶的原因);
2)判斷預測的魔術數是否匹配,如果不匹配,直接返回錯誤。這樣就可以進行的防止誤動作發生;
3)調用reboot_pid_ns接口,檢查是否需要由該接口處理reboot請求。
4)如果是POWER_OFF命令,且沒有注冊電源關閉的機器處理函數(pm_power_off),把該命令轉換為HALT命令;
5)根據特定的cmd命令,執行具體的處理,包括,
? ? ? 如果是RESTART或RESTART2命令,調用kernel_restart。
? ? ? 如果是CAD_ON或CAD_OFF命令,更新C_A_D的值,則表示允許通過Ctrl + Alt + Del組合鍵重新啟動系統。
? ? ? 如果是HALT命令,調用kernel_halt。
? ? ? 如果是POWER_OFF命令,調用kernel_power_off。
? ? ? 如果是KEXEC命令,調用kernel_kexec接口
? ? ? 如果是SW_SUSPEND,則調用hibernate接口
6)返回上述的處理結果,系統調用結束。
4.2 kernel_restart,kernel_halt和kernel_power_off
1)調用kernel_xxx_prepare函數,進行重新啟動/停止/ power_off前的準備工作,包括,
? ? ? 調用blocking_notifier_call_chain接口,向關心reboot事件的進程,發送SYS_RESTART,SYS_HALT或SYS_POWER_OFF事件。并發送出去。
? ? ? 將系統狀態設置為相應的狀態(SYS_RESTART,SYS_HALT或SYS_POWER_OFF)。
? ? ? 調用usermodehelper_disable接口,禁止用戶模式輔助
? ? ? 調用device_shutdown,關閉所有的設備
2)如果是power_off,并且存在PM相關的power off prepare函數(pm_power_off_prepare),則調用該調用函數;
3)調用migrate_to_reboot_cpu接口,將當前的進程(任務)移到一個CPU上;
注2:對于多CPU的機器,無論由哪個CPU觸發了當前的系統調用,代碼都可以運行在任意的CPU上。這個接口將代碼分派到一個特定的CPU上,并禁止調度器分派代碼到其他CPU上。首先,這個接口被執行后,只有一個CPU在運行,用于完成后續的重新啟動動作。
4)調用syscore_shutdown接口,將系統核心器件關閉(例如中斷等);
5)調用printk以及kmsg_dump,向這個世界發出最后的聲音(打印日志);
6)最后,由machine-core的代碼,接管后續的處理。
4.3 device_shutdown
設備模型中和device_shutdown有關的邏輯包括:
1.每個設備(結構設備)都會保存該設備的驅動(結構設備驅動程序)指針,以及該設備所在的總線(結構總線類型)的指針
2.設備驅動中有一個名稱為“ shutdown”的某種函數,用于在device_shutdown時,關閉該設備
3.總線中也有一個名稱為“ shutdown”的某種函數,用于在device_shutdown時,關閉該設備
4.系統的所有設備,都存在于“ / sys / devices /”目錄下,而該目錄由名稱為“ devices_kset”的kset表示。kset中會使用一個鏈表保存其下所有的kobject(也即“ / sys / devices /”目錄下的所有設備)。”的最終結果就是,以“ devices_kset”為根目錄,將內核中所有的設備(以相應的kobject為代表),組織成一個樹狀結構
device_shutdown的實現,該接口位于“ drivers / base / core.c”中,執行邏輯如下。???????
1: / **
2: * device_shutdown-在每個設備上調用-> shutdown()以關閉。
3: * /
4: 無效device_shutdown(void)
5: {
6: 結構設備* dev,* parent;
7:
8: spin_lock(&devices_kset-> list_lock);
9: / *
10: *向后移動設備列表,依次關閉每個設備。
11: *請注意,設備拔出事件也可能會開始拉
12: *設備離線,即使系統正在關閉。
13: * /
14: while (!list_empty(&devices_kset->list)) {
15: dev = list_entry(devices_kset->list.prev, struct device,
16: kobj.entry);
17:?
18: /*
19: * hold reference count of device's parent to
20: * prevent it from being freed because parent's
21: * lock is to be held
22: */
23: parent = get_device(dev->parent);
24: get_device(dev);
25: /*
26: * Make sure the device is off the kset list, in the
27: * event that dev->*->shutdown() doesn't remove it.
28: */
29: list_del_init(&dev->kobj.entry);
30: spin_unlock(&devices_kset->list_lock);
31:?
32: /* hold lock to avoid race with probe/release */
33: if (parent)
34: device_lock(parent);
35: device_lock(dev);
36:?
37: /* Don't allow any more runtime suspends */
38: pm_runtime_get_noresume(dev);
39: pm_runtime_barrier(dev);
40:?
41: if (dev->bus && dev->bus->shutdown) {
42: if (initcall_debug)
43: dev_info(dev, "shutdown\n");
44: dev->bus->shutdown(dev);
45: } else if (dev->driver && dev->driver->shutdown) {
46: if (initcall_debug)
47: dev_info(dev, "shutdown\n");
48: dev->driver->shutdown(dev);
49: }
50:?
51: device_unlock(dev);
52: 如果(父母)
53: device_unlock(parent);
54:?
55: put_device(dev);
56: put_device(父);
57:?
58: spin_lock(&devices_kset-> list_lock);
59: }
60: spin_unlock(&devices_kset-> list_lock);
61: async_synchronize_full();
62: }
4.4 system_core_shutdown
系統核心的關機和設備的關機類似,也是從一個鏈表中,遍歷所有的系統核心,并調用它的關機接口。
4.5 machine_restart,machine_halt和machine_power_off
雖然以machine_為預先命名,這三個接口卻是屬于架構相關的處理函數,如ARM。以ARM為例,它們在“ arch / arm / kernel / process.c”中實現,具體如下。
4.5.1機器重啟???????
1: / *
2: *重新啟動要求輔助CPU停止執行任何活動
3: *,同時主CPU重置系統。具有單個CPU的系統可以
4: *使用soft_restart()作為其機器描述符的.restart鉤子,因為
5: *將導致唯一可用的CPU復位。具有多個CPU的系統必須
6: *提供硬件重新啟動實施,以確保所有CPU立即復位。
7: *這是必需的,以便在主CPU上復位后運行的任何代碼
8: *不必與其他CPU協調以確保它們仍然不處于運行狀態
9: *執行預重置代碼,并使用主CPU的代碼希望的RAM
10: *使用。實施這種協調基本上是不可能的。
11: * /
12: void machine_restart(char * cmd)
13: {
14: smp_send_stop();
15:?
16: arm_pm_restart(reboot_mode,cmd);
17:?
18: / *給失敗的寬限期1s * /
19: mdelay(1000);
20:?
21: / *糟糕-平臺無法重新啟動。告訴用戶!* /
22: printk(“重新啟動失敗-系統停止\ n”);
23: local_irq_disable();
24: 而(1);
25: }
0)先轉述一下該接口的注釋;
對于多CPU的機器而言,重新啟動之前必須保證其他的CPU位于非活動狀態,由其中一個主CPU負責重新啟動動作。并且,必須實現一個基于硬件的重新啟動操作,以保證所有CPU同步重啟,這是設計的重點!
對于單CPU機器而言,就相對簡單了,可以直接用軟件reset的方式實現重啟。
1)調用smp_send_stop接口,確保其他CPU位于非活動狀態;
2)調用機器相關的重啟接口,實現真正的重啟。該接口是一個變量函數,由“ arch / arm / kernel / process.c”聲明,由具體的機器代碼實現。格式如下:
? ? ? void(* arm_pm_restart )(char str,const char * cmd)= null_restart;
? ? ? EXPORT_SYMBOL_GPL(arm_pm_restart);
3)等待1s;
4)如果沒有返回,則重啟成功,否則失敗,打印錯誤信息。
4.5.2 machine_halt
ARM的halt很簡單,就是將其他CPU停下來,并禁止當前CPU的中斷后,死循環!確實,中斷被禁止了,又死循環了,不halt才怪。代碼如下:???????
1: / *
2: *停止僅要求輔助CPU停止執行任何操作
3: *活動(執行任務,處理中斷)。smp_send_stop()
4: *實現此目標。
5: * /
6: void machine_halt(void)
7: {
8: smp_send_stop();
9:?
10: local_irq_disable();
11: 而(1)
12: }
4.5.3 machine_power_off
power off動作和restart類似,即停止其他CPU,調用其他函數。power off的某些函數和restart類似,就不再說明了。
5.總結與思考
5.1建筑和機器的概念
本文是我們在分析Linux內核時第一次遇到架構和機器的概念,順便解釋一下。內核代碼中最常見的目錄結構就是:arch / xxx / mach-xxx /(例如arch / arm / mach-bcm /)。由該目錄結構可知,架構(簡稱arch)是指具體的體系結構,如ARM,X86等。機器呢,是指具體體系結構下的一個或多個的SOC,如bcm等。
5.2電源管理驅動(和reboot有關的部分)需要實現的內容
由上面的分析可知,在Reboot的過程中,大部分的邏輯是否內核處理的,具體的驅動程序需要關注2點即可:
1)實現各自的關機接口,以正確關閉對應的設備
2)實現機器相關的接口,以確保足夠的機器可以正確重啟或關閉電源
看來還是很簡單的