linux下的關機和重啟流程對於一般的桌面應用和網絡服務器來說並不重要,但是在用戶自己定義的嵌入式系統內核中就有一定的研究意義,通過了解Linux 關機重啟的流程,我們對它可以修改和自定義,甚至以此為基礎開發出全新的功能來。
1.概述
在linux下的關機和重啟可能由兩種行為引發,一是通過用戶編程,一是系統自己產生的消息。用戶和系統進行交互的方式也有兩個,一個是系統調用:sys_reboot,另一個就是apm或則acpi的設備文件,通過對其操作也可以使系統關機或者重啟。
2.通過系統調用sys_reboot的重啟
這個系統調用定義了一系列的MAGIC_NUMBER,在調用的開始部分首先檢查MAGIC_NUMBER是否正確,只有正確才繼續向下運行。在重啟的時候轉向分支
case LINUX_REBOOT_CMD_RESTART:
首先使用notifier_call_chain向其它部分發出重啟的消息,然後調用machine_restart函數完成重啟。
machine_restart函數的開始部分有一段SMP相關的代碼,主要完成多CPU時由一個CPU完成重啟操作,其它CPU處於等待狀態。之後系統根據一個變量reboot_thru_bios的內容判斷重啟方式,通過閱讀reboot_setup我們可以得知,這個參數的內容是在系統啟動時指定的,決定了是否利用bios,事實上是系統復位後的入口(FFFF:0000)地址的程序進行重啟。在不通過bios進行重啟的情況下,系統首先設定了重啟標志,然後向端口0xfe寫入數字0x64,這種重啟的具體原理我還不大清楚,似乎是模擬了一次reset鍵的按下,希望大家和我討論。在通過bios重啟的情況下,系統同樣先設定了重啟模式,然後切換到了實模式,通過一條ljmp $0xffff,$0x0完成了重啟。
3.通過系統調用sys_reboot進行關機
在系統調用的處理分支上,我們可以看到,首先同樣是檢查MAGIC_NUMBER,然後在
case LINUX_REBOOT_CMD_POWER_OFF:
的執行流程裡面,又是使用notifier_call_chain發出了關閉計算機電源的消息,緊接著執行了machine_power_off函數。我們在machine_power_off函數中可以看到,如果pm_power_off這個函數指針不為空,那麼系統就會通過調用這個函數進行關機。在apm已經加載的情況下(SMP除外),實際上pm_power_off函數實際上指向了apm.c中的apm_power_off,在這個函數裡系統通過apm_info結構裡的值,使用切換到實模式關機,或者使用apm_bios_call_simple函數調用保護模式下的apm接口關機兩種方法。
4.apm驅動本身的關機過程
apm使用其注冊的設備的ioctl接口完成apm的操作,在apm.c的do_ioctl函數中可以看見處理的分支。這裡只有suspend和standby的代碼,所以我們不能通過ioctl這種方法使用apm關機。
當用戶按下POWER開關的時候,如果有apm模塊,那麼關機流程是由apm來處理的。apm驅動在初始化的時候啟動了一個apm內核線程:apm_mainloop,系統會在這裡檢測到POWEROFF按鍵消息並且將其命名為APM_SYS_SUSPEND,以區別apm -s設置的APM_USER_SUSPEND模式。緊接著進入了apm_event_handler函數,又從apm_event_handler函數進入了check_events函數,處理函數對應的case分支上。系統同樣使用了suspend函數進行關機,不過由於其它參數的原因,suspend最後調用的是關機的流程。
5.解決問題實例
1)按POWER鍵時某些主板死機
經查只有某些特定的驅動裝載之後才會出現這樣的情況,並且當使用關機系統調用sys_reboot的時候沒有這樣的問題。分析apm的處理流程,懷疑是在關機前驅動程序沒有正確處理apm發出的詢問消息造成的。由於部分驅動程序沒有源代碼,決定hack掉apm.c的關機部分,讓兩種方式的關機走同樣的流程。於是把apm.c的check_events函數中對APM_SYS_SUSPEND部分改寫為如下代碼:
ret = exec_usermodehelper(poweroff_helper_path, argv, envp);
if (ret) {
printk(KERN_ERR
"apm.c: failed to exec %s , errno = %d\\n",
poweroff_helper_path, errno);
}
break;
For fast reboot support
static unsigned char fast_reboot_switch [] =
{
0x66, 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */
0x66, 0x25, 0x10, 0x11, 0x11, 0x11, /* andl $0x11111110,%eax */
0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */
0xea, 0x00, 0x00, 0x00, 0x70 /* ljmp $0x7000,$0x0000 */
};
系統就可以切換到實模式中,然後跳轉到7000H:0位置開始執行。
6.ACPI概述
在2.4.20內核中ACPI模塊被注明為試驗和未完成,裡面有一部分功能也許沒有實現。如果APM和APCI兩個模塊同時編譯進內核,APM在ACPI前被加載,APM起作用使ACPI退出。對於系統電量、電源實踐一類的支持(主要是在筆記本上有用),靠的是acpid這個daemon程序。
沒有一個功能類似apm的應用程序切換狀態,acpi的程序僅僅完成了對acpi狀態的查詢。用戶實現S0-S4的功能可以直接向/proc/acpi/sleep文件中寫入數字來實現。通過讀出(cat)其中的內容可以知道系統到底支持那些模式。
acpi模塊的源代碼主程序在linux/drivers/acpi/driver.c中,如果向sleep文件寫東西,就轉到了linux/drivers/acpi/ospm/system/sm_osl.c文件的sm_osl_proc_write_sleep函數中,這個函數後來調用了sm_osl_suspend函數。在這個函數裡完成了各種功能,包括保護各種狀態。最後真正的sleep是通過對acpi_enter_sleep_state的調用完成的,這個函數在linux/drivers/acpi/hardware/hwsleep.c文件中,這裡寫了acpi的寄存器使系統進入sleep狀態。寫寄存器的指令在這個目錄下面的hwregs.c中。
7.總結
本文對acpi的介紹非常簡略,實際上ACPI必定會成為將來linux內核中首選的電源管理方式。由於目前官方代碼中ACPI版本較低,所以沒有太詳細的論述,希望將來的內核能有所改變。