常在河邊走,哪能不溼鞋。用Linux,總有死機的時候,如果運氣好,會看到一些所謂”Oops”信息(在屏幕上或系統日誌中),比如:
Unable to handle kernel paging request at virtual address f899b670
printing eip:
c01de48c
*pde = 00737067
Oops: 0002 [#1]
Modules linked in: bluesmoke_e752x bluesmoke_mc md5 ipv6 parport_pc lp parport nls_cp936 vfat fat dm_mod button battery asus_acpi ac joydev yenta_socket pcmcia_core uhci_hcd ehci_hcd snd_intel8x0 snd_ac97_codec snd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd_page_alloc
snd_mpu401_uart snd_rawmidi snd_seq_device snd soundcore ipw2200 ieee80211 ieee80211_crypt sk98lin ext3 jbd
CPU: 0
EIP: 0060:[] Not tainted VLI
EFLAGS: 00210286 (2.6.9-11.21AXKProbes)
EIP is at kobject_add+0×83/0xd7
eax: c038db78 ebx: c038db04 ecx: f899b670 edx: f8a4a630
esi: c038db4c edi: f8a4a614 ebp: c038db80 esp: d7568f2c
ds: 007b es: 007b ss: 0068
Process modprobe (pid: 8227, threadinfo=d7568000 task=f4ea99b0)
Stack: f8a4a614 ffffffea f8a4a5e4 00000000 c01de4f9 f8a4a614 c038db00 c024a1d4
f8a4a5c0 f8a4a5e4 f8a4a5f4 d7568000 c024a661 1d244b3c 00000000 0000000a
c032421b 00000000 00000000 00000015 00000014 00000016 f89ddb34 f8a4a5c0
Call Trace:
[] kobject_register+0×19/0×39
[] bus_add_driver+0×36/0×97
[] driver_register+0×82/0×89
[] pci_register_driver+0×85/0xa1
[] init_module+0xa/0×14 [bluesmoke_e752x]
[] sys_init_module+0x1ec/0×323
[] syscall_call+0×7/0xb
Code: 85 d2 0f 85 06 04 00 00 85 ed 75 0d 8b 47 28 83 c0 10 e8 82 01 00 00 89 c5 8b 47 28 8d 57 1c 83 c0 08 89 47 1c 8b 48 04 89 50 04 <89> 11 89 4a 04 8b 47 28 8b 18 8d 4b 48 89 c8 ba ff ff 00 00 0f
Oops可以看成是內核級的Segmentation Fault。應用程序如果進行了非法內存訪問或執行了非法指令,會得到Segfault信號,一般的行爲是coredump,應用程序也可以自己截獲Segfault信號,自行處理。如果內核自己犯了這樣的錯誤,則會打出Oops信息。
有不少文章說明如何理解這些Oops (http://pczou.blogchina.com/545558.html),這裏只想解釋一下它所產生的過程(以2.6系列內核爲例):
首先是處理硬件發出的內存訪問異常(fault),有些異常是無辜的(比如demand-paging),而有些則是內核的錯誤所致。
1. do_page_fault() arch/i386/mm/fault.c
如果是內核進行了非法訪問,do_page_fault()會先打出EIP, PDE等信息,例如:
Unable to handle kernel paging request at virtual address f899b670
printing eip:
c01de48c
*pde = 00737067
然後調用 die(“Oops”, regs, error_code);
這之後,如果系統還活着(至少要滿足兩個條件:1. 在進程上下文 2. 沒有設置panic_on_oops),會殺死當前進程。然後繼續運行,好像什麼事情都沒有發生一樣。不過,這樣的好事不經常發生,發生了也不會太持久。
2. do_page_fault() -> die() arch/i386/kernel/traps.c
die() 首先打出一行:
Oops: 0002 [#1]
其中0002代表錯誤碼 (讀錯誤、發生在內核空間),#1代表Oops發生次數。
* error_code:
* bit 0 == 0 means no page found, 1 means protection fault
* bit 1 == 0 means read, 1 means write
* bit 2 == 0 means kernel, 1 means user-mode
然後,調用 show_registers(regs) 輸出寄存器、當前進程、堆棧、指令代碼等信息:
Modules linked in: bluesmoke_e752x bluesmoke_mc md5 ipv6 parport_pc lp parport nls_cp936 vfat fat dm_mod button battery asus_acpi ac joydev yenta_socket pcmcia_core uhci_hcd ehci_hcd snd_intel8x0 snd_ac97_codec snd_pcm_oss snd_mixer_oss snd_pcm snd_timer
snd_page_alloc snd_mpu401_uart snd_rawmidi snd_seq_device snd soundcore ipw2200 ieee80211 ieee80211_crypt sk98lin ext3 jbd
CPU: 0
EIP: 0060:[] Not tainted VLI
EFLAGS: 00210286 (2.6.9-11.21AXKProbes)
EIP is at kobject_add+0×83/0xd7
eax: c038db78 ebx: c038db04 ecx: f899b670 edx: f8a4a630
esi: c038db4c edi: f8a4a614 ebp: c038db80 esp: d7568f2c
ds: 007b es: 007b ss: 0068
Process modprobe (pid: 8227, threadinfo=d7568000 task=f4ea99b0)
Stack: f8a4a614 ffffffea f8a4a5e4 00000000 c01de4f9 f8a4a614 c038db00 c024a1d4
f8a4a5c0 f8a4a5e4 f8a4a5f4 d7568000 c024a661 1d244b3c 00000000 0000000a
c032421b 00000000 00000000 00000015 00000014 00000016 f89ddb34 f8a4a5c0
Call Trace:
[] kobject_register+0×19/0×39
[] bus_add_driver+0×36/0×97
[] driver_register+0×82/0×89
[] pci_register_driver+0×85/0xa1
[] init_module+0xa/0×14 [bluesmoke_e752x]
[] sys_init_module+0x1ec/0×323
[] syscall_call+0×7/0xb
Code: 85 d2 0f 85 06 04 00 00 85 ed 75 0d 8b 47 28 83 c0 10 e8 82 01 00 00 89 c5 8b 47 28 8d 57 1c 83 c0 08 89 47 1c 8b 48 04 89 50 04 <89> 11 89 4a 04 8b 47 28 8b 18 8d 4b 48 89 c8 ba ff ff 00 00 0f
如果是在中斷上下文,則直接調用panic()【panic 輸出oops信息後將掛起系統】。如果是在進程上下文,則根據panic_on_oops的設置選擇是否panic()。panic_on_oops的缺省設置是”0″,即在Oops發生時不會進行panic()操作。可以通過sysctl進行設置:
sysctl -w kernel.panic_on_oops=1
有panic_on_oops這樣的設置,說明Oops不一定導致系統死亡,也不一定需要重新啓動系統。正如用戶程序segfault時可能還能堅持運行一樣。不過Oops一旦發生,系統已經有些不正常了,即使表面上可能還正常,不過可能有些鎖已經被佔用而無法釋放,很快會導致系統死鎖。
那麼,panic()是什麼呢?panic()和用戶空間的abort()類似,簡單清理一下,就可以放心去死(reboot)了。
3. do_page_fault() -> die() -> panic()
panic會根據 kernel.panic 的設置決定 reboot 前的延時,如果 kernel.panic=0,則打開中斷,陷入死循環。反之,則在幾秒之後,reboot系統。
可以看出雖然都是死,但死因不同,死亡時的表現更是五花八門。常見的死因有:
- 非法內存訪問 (比如訪問地址0)
- 非法指令
有時候核心成心發出非法指令,比如BUG() (include/asm/bug.h) 中所做的,以引起Oops。類似用戶程序中調用assert()。
死亡發生的地點也很關鍵,直接導致了死亡的不同表現,比如:
- 進程上下文
- 中斷上下文
在中斷上下文中,由於中斷是關閉的,而且往往會佔用一些鎖,這種情況下一般除了死,沒有什麼別的辦法。
在進程上下文中要自由一些,如果運氣好的話,可以苟延殘喘一段時間。