我們如何應對 Linux 內核崩潰
kdump 是一種用于獲取 Linux 內核崩潰轉儲的方法,而要找到關于其使用和內部結構的解釋性文檔可能有一些挑戰。在這篇文章中,我將深入探討 kdump 的基本用法以及 kdump/kexec 在內核中的實現。
首先,讓我們了解 kexec。kexec 是一個 Linux 內核到內核的引導加載程序,它可以幫助從第一個內核的上下文快速引導到第二個內核。使用 kexec,可以關閉第一個內核,繞過 BIOS 或固件階段,并直接跳轉到第二個內核,從而實現快速重啟,無需經歷傳統的 BIOS 階段。
kdump 和 kexec 可以一起使用。當第一個內核崩潰時,kexec 可以引導第二個內核,而第二個內核則用于復制第一個內核的內存轉儲。之后,可以使用調試工具如 gdb 和 crash 來分析這個崩潰的原因。在這里,我將使用術語“第一內核”表示當前運行的內核,“第二內核”表示通過 kexec 運行的內核,“捕獲內核”表示在當前內核崩潰時運行的內核。
kexec 機制涉及到內核和用戶空間中的多個組件。內核提供了幾個用于 kexec 重啟功能的系統調用。用戶空間的 kexec 工具(通常是 kexec-tools)利用這些調用,并提供可執行文件,用于加載和引導“第二內核”。一些發行版可能還會添加封裝器,以便捕獲和保存各種配置轉儲目標的轉儲。在這里,我將使用 Fedora Linux 發行版的 kexec-tools。
通過使用 kdump 和 kexec,你可以更有效地處理內核崩潰情況,加速系統的重啟過程,并方便地分析和調試內核問題。
Fedora kexec-tools 工具
在 Fedora 操作系統上,你可以通過運行以下命令安裝 fedora-kexec-tools:
bashCopy code
sudo dnf install kexec-tools
安裝完成后,你可以使用以下命令啟動 kdump 服務:
bashCopy code
sudo systemctl start kdump
啟動 kdump 服務時,它會創建一個包含保存 vmcore 所需資源的根文件系統(initramfs),以及執行將 vmcore 復制和轉儲到目標位置的命令。此服務還會加載內核和 initramfs 到內核崩潰區域的適當位置,以便在發生內核崩潰時執行它們。
在 Fedora 中,有兩個配置文件可供修改:
- /etc/kdump.conf:指定那些在修改后需要重新構建 initramfs 的配置參數。例如,如果將轉儲目標從本地磁盤更改為 NFS 掛載的磁盤,則需要重新加載與 NFS 相關的內核模塊。
- /etc/sysconfig/kdump:指定那些在修改后不需要重新構建 initramfs 的配置參數。例如,如果只需修改傳遞給“捕獲內核”的命令行參數,則不需要重新構建 initramfs。
如果內核在 kdump 服務啟動后出現故障,那么“捕獲內核”將執行,并進一步執行 initramfs 中的 vmcore 保存過程。然后,系統將重新啟動到穩定的內核。這種設置使得在系統遇到內核崩潰時能夠更有效地保存轉儲信息和進行故障排除。
kexec-tools 工具
通過編譯 kexec-tools 源代碼,你將獲得一個名為 kexec 的可執行文件。這個同名的可執行文件可以用于兩個主要操作:加載和執行“第二內核”或加載“捕獲內核”以在內核崩潰時執行。
對于加載“第二內核”,你可以使用以下命令:
bashCopy code
# kexec -l kernel.img --initrd=initramfs-image.img --reuse-cmdline
在這里,--reuse-cmdline 參數表示使用與“第一內核”相同的命令行。通過使用 --initrd 選項傳遞 initramfs。-l 參數表明你正在加載“第二內核”,這個內核不能在內核崩潰時執行。如果你想要加載并在內核崩潰時執行“捕獲內核”,則必須使用 -p 參數,而不是 -l。
以下是加載“捕獲內核”的示例命令:
bashCopy code
# kexec -p kernel.img --initrd=initramfs-image.img --reuse-cmdline
為了測試內核崩潰,你可以使用以下命令:
bashCopy code
echo c > /proc/sysrq-trigger
這將觸發內核崩潰,以便進行測試。有關 kexec-tools 提供的其他選項的詳細信息,你可以查閱 man kexec。在轉到下一部分之前,建議觀看一下 kexec_dump 的演示。
視頻地址:
https://img.linux.net.cn//static/video/kexec_kdump_demo-iOq_rJhrKhA.mp4
kdump: 端到端流
圖片
在上述流程圖中,必須在引導“第一內核”時為捕獲內核保留一定量的內存,通過在內核命令行中傳遞 crashkernel=Y@X 來實現,其中 Y 是保留的內存大小,X 是可選的。通常,使用 crashkernel=256M 對于大多數 x86_64 系統是合適的,但選擇適當的內存大小取決于多個因素,包括內核大小、initramfs 的大小以及運行時內存需求。
您可以使用 kexec 可執行文件傳遞內核和 initramfs 鏡像,如上文“kexec-tools”部分所示的命令。值得注意的是,“捕獲內核”可以與“第一內核”相同,也可以是不同的。通常,它們是相同的。Initramfs 是可選的,例如,當內核使用 CONFIG_INITRAMFS_SOURCE 編譯時,您可能不需要它。通常,使用一個不同的捕獲 initramfs 可以更好地執行 vmcore 的自動處理。
當“第一內核”崩潰時,它會執行必要的退出過程并切換到 purgatory(如果存在)。purgatory 的作用包括驗證加載二進制文件的 SHA256,如果驗證通過,則將控制權傳遞給“捕獲內核”。一旦“捕獲內核”接管,它將根據從 elfcorehdr 接收到的系統內存信息創建 vmcore。因此,在“捕獲內核”啟動后,您將在 /proc/vmcore 中看到來自“第一內核”的轉儲。根據使用的 initramfs,您可以進一步分析并將其復制到磁盤,也可以設置自動復制,然后重新啟動到穩定的內核。
內核系統調用
內核提供了兩個與 kexec 相關的系統調用:kexec_load() 和 kexec_file_load()。這兩個系統調用用于加載新的內核,以便通過 reboot() 系統調用啟動或在內核崩潰時執行。
- kexec_load():
kexec_load()
系統調用用于加載一個可以稍后通過
reboot()
執行的新內核。其原型定義如下:
cCopy code
long kexec_load(unsigned long entry, unsigned long nr_segments, struct kexec_segment *segments, unsigned long flags);
- 用戶空間需要傳遞給不同組件不同的段,如內核、initramfs 等。
kexec
可執行文件幫助準備這些段。
kexec_segment
結構如下:
cCopy codestruct kexec_segment {
void *buf; /* 用戶空間緩沖區 */
size_t bufsz; /* 用戶空間緩沖區長度 */
void *mem; /* 內核的物理地址 */
size_t memsz; /* 物理地址長度 */
};
- 如果傳遞 KEXEC_ON_CRASH 標志給 kexec_load(),加載的內核將不使用 reboot(LINUX_REBOOT_CMD_KEXEC) 啟動,而是在內核崩潰時執行。要使用 kexec,必須啟用 CONFIG_KEXEC,并為 kdump 啟用 CONFIG_CRASH_DUMP。
- kexec_file_load():
是一個更高級別的系統調用,它接受內核和 initramfs 的文件描述符,然后由內核完成其余部分。其原型如下:
cCopy code
long kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char __user *cmdline_ptr, unsigned long flags);
- 與 kexec_load() 不同,kexec_file_load() 還支持傳遞命令行。在此情況下,內核根據系統體系結構接受和執行命令行。目前,kexec_file_load() 僅支持 x86 和 PowerPC。
當內核崩潰時會發生什么?
當內核崩潰時,以下操作將在將控制權傳遞給 purgatory 或“捕獲內核”之前執行:
- 準備 CPU 寄存器。
- 更新 vmcoreinfo 備注。
- 關閉非崩潰的 CPU 并保存準備好的寄存器。
- 在此階段可能需要禁用中斷控制器。
- 執行 kexec 重新啟動,加載或刷新 kexec 段到內存,并將控制權傳遞給執行文件。輸入段可以是下一個內核的 purgatory 或起始地址。
ELF(Executable and Linkable Format)ELF 程序頭和崩潰轉儲
ELF(Executable and Linkable Format)是一種常用于可執行文件和共享庫的文件格式。在崩潰轉儲中,ELF 程序頭對于描述如何將程序加載到內存中非常重要。在 vmcore 中,大多數轉儲核心都是 ELF 格式的,因此理解 ELF 程序頭是很有幫助的。
每個 ELF 文件都有一個程序頭,由系統加載器讀取,描述了如何將程序加載到內存中。你可以使用 objdump -p elf_file 來查看程序頭。
以下是 vmcore 的 ELF 程序頭的示例:
# objdump -p vmcore
vmcore: file format elf64-littleaarch64
Program Header:
NOTE off 0x0000000000010000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0
filesz 0x00000000000013e8 memsz 0x00000000000013e8 flags ---
LOAD off 0x0000000000020000 vaddr 0xffff000008080000 paddr 0x0000004000280000 align 2**0
filesz 0x0000000001460000 memsz 0x0000000001460000 flags rwx
LOAD off 0x0000000001480000 vaddr 0xffff800000200000 paddr 0x0000004000200000 align 2**0
filesz 0x000000007fc00000 memsz 0x000000007fc00000 flags rwx
LOAD off 0x0000000081080000 vaddr 0xffff8000ffe00000 paddr 0x00000040ffe00000 align 2**0
filesz 0x00000002fa7a0000 memsz 0x00000002fa7a0000 flags rwx
LOAD off 0x000000037b820000 vaddr 0xffff8003fa9e0000 paddr 0x00000043fa9e0000 align 2**0
filesz 0x0000000004fc0000 memsz 0x0000000004fc0000 flags rwx
LOAD off 0x00000003807e0000 vaddr 0xffff8003ff9b0000 paddr 0x00000043ff9b0000 align 2**0
filesz 0x0000000000010000 memsz 0x0000000000010000 flags rwx
LOAD off 0x00000003807f0000 vaddr 0xffff8003ff9f0000 paddr 0x00000043ff9f0000 align 2**0
filesz 0x0000000000610000 memsz 0x0000000000610000 flags rwx
在這個例子中,有一個 note 段,其余的是 load 段。note 段提供了有關 CPU 信息,load 段提供了關于復制的系統內存組件的信息。
vmcore 從 elfcorehdr 開始,它具有與 ELF 程序頭相同的結構。
參見下圖中 elfcorehdr 的表示:
圖片
kexec-tools 讀取 /sys/devices/system/cpu/cpu%d/crash_notes 并準備 CPU PT_NOTE 的標頭。同樣,它讀取 /sys/kernel/vmcoreinfo 并準備 vmcoreinfo PT_NOTE 的標頭,從 /proc/iomem 讀取系統內存并準備存儲器 PT_LOAD 標頭。當“捕獲內核”接收到 elfcorehdr 時,它從標頭中提到的地址中讀取數據,并準備 vmcore。
- Crash Notes (/sys/devices/system/cpu/cpu%d/crash_notes):
Crash notes 是用于在系統崩潰時存儲有關 CPU 狀態的區域。它包含有關當前 PID 和 CPU 寄存器的信息。
- VMcoreinfo (/sys/kernel/vmcoreinfo):
- VMcoreinfo 是一個包含內核調試信息的文件。kexec-tools 讀取此文件并準備 vmcoreinfo PT_NOTE 的標頭。其中包含一些關鍵的宏定義,如 VMCOREINFO_PAGESIZE、VMCOREINFO_SYMBOL、VMCOREINFO_SIZE、VMCOREINFO_STRUCT_SIZE 等。
- makedumpfile:
是一個應用程序,用于處理/proc/vmcore的數據,排除不必要的頁面并在復制時進行壓縮。它還可以從轉儲中刪除敏感的符號信息。
通常在 kdump 環境中使用,可以使用以下示例命令:
bashCopy code
# makedumpfile -l --message-level 1 -d 31 /proc/vmcore makedumpfilecore
- 詳細信息請參閱 man makedumpfile。
kdump 調試
對于初學者使用 kdump 時可能遇到的問題:
問題:kexec -p kernel_image 執行失敗
問題:在“第一內核”結束后,在控制臺上沒有看到任何輸出(例如“bye”)
這可以幫助在早期階段看到更多的調試輸出。
- 確保第二內核的設置和參數正確??赡苄枰诿钚兄袀鬟f額外的選項以啟用調試信息。
- 如果體系結構不支持 purgatory 中的控制臺,很難進行調試。確認 SHA 驗證是否通過。
- 檢查是否有適用于您的體系結構和機器的正確配置。有些平臺可能需要特定的設置。
- 確保 kexec -e 命令成功啟動了第二內核。
- 檢查 kexec -e 之后的 kexec -l kernel_image 命令是否正常工作。
- 確認是否缺少支持的體系結構或特定機器的選項。
- 驗證 purgatory 的 SHA 驗證是否失敗。
- 檢查是否第二內核早已崩潰。
- 在第二內核的命令行中傳遞 earlycon 或 earlyprintk 選項。
- 如果問題仍然存在,使用 kexec-tools 郵件列表共享第一個內核和捕獲內核的 dmesg 日志。
- kexec -d -p kernel_image
- 在啟動時確保使用正確的內存參數,例如 crashkernel=256M。
- 運行 cat /proc/iomem | grep "Crash kernel",應該顯示一個合適的分配范圍。如果沒有顯示,可能是由于未正確傳遞 crashkernel= 參數。
- 運行 cat /sys/kernel/kexec_crash_size,它不應該返回零值。如果為零,表示崩潰內存沒有正確分配。
- 檢查是否分配了崩潰內存。
- 驗證 /proc/iomem 中是否有 "Crash kernel" 的分配范圍。
- 在命令行中確保傳遞正確的 crashkernel= 參數。
- 如果問題仍然存在,使用 -d 參數運行 kexec 命令,將輸出信息發送到 kexec-tools 郵件列表。