本文轉載自:http://www.wowotech.net/pm_subsystem/reboot.html
1. 前言
在使用計算機的過程中,關機和重啟是最先學會的兩個操作。同樣,這兩個操作在Linux中也存在,稱作shutdown和restart。這就是本文要描述的對象。
在Linux Kernel中,主流的shutdown和restart都是通過“reboot”系統調用(具體可參考kernel/sys.c)來實現的,這也是本文使用“Generic PM之Reboot過程”作為標題的原因。另外,除了我們常用的shutdown和restart兩類操作之外,該系統調用也提供了其它的reboot方式,也會在這里一一說明。
2. Kernel支持的reboot方式
也許你會奇怪,reboot是重啟的意思,所以用它實現Restart是合理的,但怎么用它實現關機操作呢?答案是這樣的:關機之后,早晚也會開機啊!所以關機是一種特殊的Restart過程,只不過持續的時間有點長而已。所以,內核根據不同的表現方式,將reboot分為如下的幾種方式:
/* 2: * Commands accepted by the _reboot() system call. 3: * 4: * RESTART Restart system using default command and mode. 5: * HALT Stop OS and give system control to ROM monitor, if any. 6: * CAD_ON Ctrl-Alt-Del sequence causes RESTART command. 7: * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. 8: * POWER_OFF Stop OS and remove all power from system, if possible. 9: * RESTART2 Restart system using given command string. 10: * SW_SUSPEND Suspend system using software suspend if compiled in. 11: * KEXEC Restart system using a previously loaded Linux kernel 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,正常的重啟,也是我們平時使用的重啟。執行該動作后,系統會重新啟動。
HALT,停止操作系統,然后把控制權交給其它代碼(如果有的話)。具體的表現形式,依賴於系統的具體實現。
CAD_ON/CAD_OFF,允許/禁止通過Ctrl-Alt-Del組合按鍵觸發重啟(RESTART)動作。
注1:Ctrl-Alt-Del組合按鍵的響應是由具體的Driver(如Keypad)實現的。
POWER_OFF,正常的關機。執行該動作后,系統會停止操作系統,並去除所有的供電。
RESTART2,重啟的另一種方式。可以在重啟時,攜帶一個字符串類型的cmd,該cmd會在重啟前,發送給任意一個關心重啟事件的進程,同時會傳遞給最終執行重啟動作的machine相關的代碼。內核並沒有規定該cmd的形式,完全由具體的machine自行定義。
SW_SUSPEND,即前一篇文章中描述的Hibernate操作,會在下一篇文章描述,這里就暫不涉及。
KEXEC,重啟並執行已經加載好的其它Kernel Image(需要CONFIG_KEXEC的支持),暫不涉及。
3. Reboot相關的操作流程
在Linux操作系統中,可以通過reboot、halt、poweroff等命令,發起reboot,具體的操作流程如下:
一般的Linux操作系統,在用戶空間都提供了一些工具集合(如常在嵌入式系統使用的Busybox),這些工具集合包含了reboot、halt和poweroff三個和Reboot相關的命令。讀者可以參考man幫助文檔,了解這些命令的解釋和使用說明
用戶空間程序通過reboot系統調用,進入內核空間
內核空間根據執行路徑的不同,提供了kernel_restart、kernel_halt和kernel_power_off三個處理函數,響應用空間的reboot請求
這三個處理函數的處理流程大致相同,主要包括:向關心reboot過程的進程發送Notify事件;調用drivers核心模塊提供的接口,關閉所有的外部設備;調用drivers syscore模塊提供的接口,關閉system core;
調用Architecture相關的處理函數,進行后續的處理;最后,調用machine相關的接口,實現真正意義上的Reboot
另外,借助TTY模塊提供的Sysreq機制,內核提供了其它途徑的關機方法,如某些按鍵組合、向/proc文件寫入命令等,后面會詳細介紹
4. Reboot過程的內部動作和代碼分析
4.1 Reboot系統調用
Reboot系統調用的實現位於“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)判斷傳入的magic number是否匹配,如果不匹配,直接返回錯誤。這樣就可以盡可能的防止誤動作發生; 3)調用reboot_pid_ns接口,檢查是否需要由該接口處理reboot請求。這是一個有關pid namespaces的新特性,也是Linux內核重要的知識點,我們會在其它文章中描述,這里就不多說了; 4)如果是POWER_OFF命令,且沒有注冊power off的machine處理函數(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
這三個接口也位於“kernel/sys.c”,實現比較類似,具體動作包括:
1)調用kernel_xxx_prepare函數,進行restart/halt/power_off前的准備工作,包括, 調用blocking_notifier_call_chain接口,向關心reboot事件的進程,發送SYS_RESTART、SYS_HALT或者SYS_POWER_OFF事件。對RESTART來說,還好將cmd參數一並發送出去。 將系統狀態設置為相應的狀態(SYS_RESTART、SYS_HALT或SYS_POWER_OFF)。 調用usermodehelper_disable接口,禁止User mode helper(可參考“Linux設備模型(3)_Uevent”相關的描述)。 調用device_shutdown,關閉所有的設備(具體內容會在下一節講述); 2)如果是power_off,且存在PM相關的power off prepare函數(pm_power_off_prepare),則調用該回調函數; 3)調用migrate_to_reboot_cpu接口,將當前的進程(task)移到一個CPU上; 注2:對於多CPU的機器,無論哪個CPU觸發了當前的系統調用,代碼都可以運行在任意的CPU上。這個接口將代碼分派到一個特定的CPU上,並禁止調度器分派代碼到其它CPU上。也就是說,這個接口被執行后,只有一個CPU在運行,用於完成后續的reboot動作。 4)調用syscore_shutdown接口,將系統核心器件關閉(例如中斷等); 5)調用printk以及kmsg_dump,向這個世界發出最后的聲音(打印日志); 6)最后,由machine-core的代碼,接管后續的處理。
4.3 device_shutdown
在理解device_shutdown之前,我們需要回憶一下前幾篇有關Linux設備模型的文章。同時,借助對電源管理的解析,我們會把在Linux設備模型系列文章中沒有描述的部分補回來。設備模型中和device_shutdown有關的邏輯包括:
每個設備(struct device)都會保存該設備的驅動(struct device_driver)指針,以及該設備所在總線(struct bus_type)的指針(具體參考“Linux設備模型(5)_device和device driver”) 設備驅動中有一個名稱為“shutdown”的回調函數,用於在device_shutdown時,關閉該設備(具體參考“Linux設備模型(5)_device和device driver”) 總線中也有一個名稱為“shutdown”的回調函數,用於在device_shutdown時,關閉該設備(具體參考“Linux設備模型(6)_Bus”) 系統的所有設備,都存在於“/sys/devices/”目錄下,而該目錄由名稱為“devices_kset”的kset表示。而由“Linux設備模型(2)_Kobject”的描述可知,kset中會使用一個鏈表保存其下所有的kobject(也即“/sys/devices/”目錄下的所有設備)。
最終的結果就是,以“devices_kset”為root節點,將內核中所有的設備(以相應的kobject為代表),組織成一個樹狀結構
介紹完以上的背景知識,我們來看device_shutdown的實現,就非常容易了。該接口位於“drivers/base/core.c”中,執行邏輯如下。
/** 2: * device_shutdown - call ->shutdown() on each device to shutdown. 3: */ 4: void device_shutdown(void) 5: { 6: struct device *dev, *parent; 7: 8: spin_lock(&devices_kset->list_lock); 9: /* 10: * Walk the devices list backward, shutting down each in turn. 11: * Beware that device unplug events may also start pulling 12: * devices offline, even as the system is shutting down. 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: if (parent) 53: device_unlock(parent); 54: 55: put_device(dev); 56: put_device(parent); 57: 58: spin_lock(&devices_kset->list_lock); 59: } 60: spin_unlock(&devices_kset->list_lock); 61: async_synchronize_full(); 62: }
1)遍歷devices_kset的鏈表,取出所有的設備(struct device);
2)將該設備從鏈表中刪除;
3)調用pm_runtime_get_noresume和pm_runtime_barrier接口,停止所有的Runtime相關的電源管理動作(后續的文章會詳細描述有關Runtime PM的邏輯);
4)如果該設備的bus提供了shutdown函數,優先調用bus的shutdown,關閉設備;
5)如果bus沒有提供shutdown函數,檢測設備driver是否提供,如果提供,調用設備driver的shutdown,關閉設備;
6)直至處理完畢所有的設備。
4.4 system_core_shutdown
system core的shutdown和設備的shutdown類似,也是從一個鏈表中,遍歷所有的system core,並調用它的shutdown接口。后續蝸蝸會專門寫一篇文章介紹syscore,這里暫不描述。
4.5 machine_restart、machine_halt和machine_power_off
雖然以machine_為前綴命名,這三個接口卻是屬於Architecture相關的處理函數,如ARM。以ARM為例,它們在“arch/arm/kernel/process.c”中實現,具體如下。
4.5.1 machine_restart
1: /* 2: * Restart requires that the secondary CPUs stop performing any activity 3: * while the primary CPU resets the system. Systems with a single CPU can 4: * use soft_restart() as their machine descriptor's .restart hook, since that 5: * will cause the only available CPU to reset. Systems with multiple CPUs must 6: * provide a HW restart implementation, to ensure that all CPUs reset at once. 7: * This is required so that any code running after reset on the primary CPU 8: * doesn't have to co-ordinate with other CPUs to ensure they aren't still 9: * executing pre-reset code, and using RAM that the primary CPU's code wishes 10: * to use. Implementing such co-ordination would be essentially impossible. 11: */ 12: void machine_restart(char *cmd) 13: { 14: smp_send_stop(); 15: 16: arm_pm_restart(reboot_mode, cmd); 17: 18: /* Give a grace period for failure to restart of 1s */ 19: mdelay(1000); 20: 21: /* Whoops - the platform was unable to reboot. Tell the user! */ 22: printk("Reboot failed -- System halted\n"); 23: local_irq_disable(); 24: while (1); 25: }
0)先轉述一下該接口的注釋;
對於多CPU的機器來說,Restart之前必須保證其它的CPU處於非活動狀態,由其中的一個主CPU負責Restart動作。並且,必須實現一個基於硬件的Restart操作,以保證所有CPU同步Restart,這是設計的重點!
對於單CPU機器來說,就相對簡單了,可以直接用軟件reset的方式實現Restart。
1)調用smp_send_stop接口,確保其它CPU處於非活動狀態;
2)調用machine-dependent的restart接口,實現真正的restart。該接口是一個回調函數,由“arch/arm/kernel/process.c”聲明,由具體的machine代碼實現。格式如下:
void (*arm_pm_restart)(char str, const char *cmd) = null_restart;
EXPORT_SYMBOL_GPL(arm_pm_restart);
3)等待1s;
4)如果沒有返回,則restart成功,否則失敗,打印錯誤信息。
4.5.2 machine_halt
ARM的halt很簡單,就是將其它CPU停下來,並禁止當前CPU的中斷后,死循環!確實,中斷被禁止了,又死循環了,不halt才怪。代碼如下:
1: /* 2: * Halting simply requires that the secondary CPUs stop performing any 3: * activity (executing tasks, handling interrupts). smp_send_stop() 4: * achieves this. 5: */ 6: void machine_halt(void) 7: { 8: smp_send_stop(); 9: 10: local_irq_disable(); 11: while (1); 12: }
4.5.3 machine_power_off
power off動作和restart類似,即停止其它CPU,調用回調函數。power off的回調函數和restart類似,就不再說明了。
5. 總結與思考
5.1 Architecture和Machine的概念
本文是我們在分析Linux內核時第一次遇到Architecture和Machine的概念,順便解釋一下。內核代碼中最常見的目錄結構就是:arch/xxx/mach-xxx/(例如arch/arm/mach-bcm/)。由該目錄結構可知,Architecture(簡稱arch)是指具體的體系結構,如ARM、X86等等。Machine呢,是指具體體系結構下的一個或一系列的SOC,如bcm等。
5.2 電源管理驅動(和reboot有關的部分)需要實現內容
由上面的分析可知,在Reboot的過程中,大部分的邏輯是否內核處理的,具體的driver需要關注2點即可:
1)實現各自的shutdown接口,以正確關閉對應的設備
2)實現machine-dependent的接口,以確保底層的Machine可以正確restart或者power off
看來還是很簡單的。