標籤:linux mce machine check 機器
1.machine check 是什嗎?
machine check 是一種用來報告內部錯誤的一種硬體的方式。它包括 machine check exceptions 和 silent machine check。
其中,machine check exceptions(MCEs) 是在硬體不能糾正內部錯誤的時候發生,在這種情況下,通常會中斷 CPU 當前正在啟動並執行程式,並且調用一個特殊的例外處理常式。這種情況通常需要軟體來進行處理,即 machine check exception handler。
當硬體能夠糾正內部錯誤的時候,這種情況通常稱作 silent machine check。當這種錯誤發生的時候,硬體會把相應的錯誤資訊登記到特殊的寄存器中。之後,作業系統或者是韌體(BIOS)就可以從這寫寄存器中讀取資訊,登記和分析這些錯誤資訊有助於提前預測機器硬體的故障。
2.machine check 很重要
隨著每一代晶片中晶體管數量的增加,以及晶片大小的減小,硬體發生錯誤的機率也在提高,因此能夠處理這種錯誤變得越來越重要。
另外,現在將許多電腦整合在一起進行高效能的科學計算也越來越流行。這些叢集的電腦中,發生硬體錯誤的機率將比普通的電腦發生錯誤的機率要高,因此,為了保證可靠性,處理這些硬體錯誤也是很重要的。
產生 machine checks 的原因很多,這些來源包括 CPU, 緩衝, 內部匯流排, 記憶體等等,當然也有可能是驅動中的軟體錯誤。
3.x86 machine check architecture 概述
intel 和 amd 的晶片都屬於 x86 架構的。之前在 IBM 的機器中引入了記憶體(parity memory),當記憶體發生錯誤的時候,會出發一個NMI。隨後的機器丟棄了記憶體,但仍然報告一些硬體的錯誤。之後,在 intel pentium 中又將基本的 machine check 加入到 CPU 中,並引入了MCA(machine check architecture)。MCA 包括一個標準的異常(18號中斷),以及一些標準的寄存器 MSR(在有的地方全稱是 model specific register,另外一些稱為 machine specific register)。這些寄存器允許軟體來檢查,是否發生了一個 machine check ,允許或者禁止他們,檢測這些錯誤是否被恢複,是否汙染了 CPU 的狀態。
另外,bank中包括了更多的寄存器,bank 是具體的子系統產生的錯誤的分組,這些子系統包括 CPU,匯流排單元,緩衝和北橋等。bank 的數量和意義是依賴於具體的 CPU的。每一個bank 都有一定數量的子錯誤,這些子錯誤可以被禁止或者是允許。通常,一個通用的 machine check 處理函數允許所有的錯誤和 bank。另外,bank 中還儲存了與錯誤有關的地址。這個通用架構的優點就是一個單獨的 machine check 處理函數可以在許多不同的 CPU 上工作。當一個 machine check 被檢測到以後,核心就會讀取所有的 machine check 寄存器,以及報告錯誤的那個 bank 的寄存器。
對於不同錯誤的解碼和解釋是依賴於具體的 CPU 和 使用者的。一些通用的處理就可以完成,例如,當 bank 寄存器中含有一個合法的錯誤地址時,我們就假設在這個地址的記憶體處發生了錯誤。當然,處理函數根據錯誤是否被糾正以及錯誤是否汙染了 CPU 的上下文來作出相應的動作。
4.為什麼寫一個 machine check 處理函數是困難的
因為當前的核心服務都不能被使用。我們知道核心代碼可以運行在進程上下文和中斷上下文,在中斷上下文可以做的事情比進程上下文可以做的事情要少。在中斷上下文調用的函數必須合適的保護了它的資料結構,防止來自多個中斷的並發訪問。這些函數被稱為是“中斷安全的”。
但是,我們知道 machine check exception 在任何時候都可能會發生,甚至在所有中斷都被禁止的臨界區中也有可能會發生。因此在這種情況下,如果在 machine check exception 處理函數中調用了這些中斷安全的函數,就可能會死結在自旋鎖上。
由於為了讓代碼更加的簡單, silent machine check 處理函數和 machine check exception 處理函數共用了同一條代碼路徑,因此上面所討論的問題對於 silent machine check 處理函數同樣適用。
同樣,能夠儘快的處理 machine check 也是非常重要的,因為在發生了一個硬體錯誤之後,機器的狀態可能變得已經不太穩定了。當處理函數在等待機器進入一個更加容易被處理的狀態的時候,這個事件可能會變得不能被處理。例如,在等待的時間內,在同一個 bank 上,又發生了另一個錯誤,這個錯誤就會覆蓋之前的錯誤,並且變得不可處理。
對於一些複雜的 RAM 錯誤,處理函數除了等待就沒有別的辦法了,因為這要求和核心鎖進行同步。不像其他的異常,machine check 是非同步。這就是說,CPU 報告的錯誤並不在發生錯誤的那條指令處,這可能已經過了幾百個刻度,這就導致了處理的不可靠性。
5.登記 machine check
傳統的,登記 machine check 是由韌體來進行的(即BIOS),當作業系統沒有 machine check 處理函數時,那些 MC 寄存器將不會被清零。在下一次暖開機之後,BIOS將從最後一次 machine check 中找到資訊,並登記到記錄檔中。這種方式顯然存在很多缺點,例如,必須在每次機器重新啟動時才能夠登記記錄檔,不能夠記錄在同一個 bank 上發生的多個錯誤,在網路中收集資訊和將日誌寫到磁碟是很困難的。
因此,最好的方法是將登記日誌的任務交給作業系統,就可以解決這些問題。但是,當前大多數的 linux 使用者使用的都是 X 介面,因此控制口是不可見的。當作業系統登記一個致命的 machine check 後,X 介面看起來就像是凍住了一樣,不會響應使用者。為瞭解決這個問題,這種致命的 machine check 都在機器重啟時再登記。這也能夠將日檔案寫道磁碟裡,使後來的技術服務人員分析稱為可能。
將 machine check 記錄檔和 軟體錯誤記錄檔檔案分開是有必要的,因為使用者可能分不清出這兩種錯誤。經驗表明,最好將這兩種記錄檔完全分開。
6.重寫的x86-64處理函數
由於最初的 linux2.4 核心中的x86-64 machine check 處理函數是從 i386 的版本繼承而來的。但是後來發現這裡面存在一些 bug ,及一些設計上的錯誤。因此在 linux 2.5核心又對 x86-64 上的 machine check 處理函數進行了重寫。這次重寫緊跟 Intel 和 AMD 對 machine check 處理函數的標準。在這次重寫的代碼中沒有與具體的 CPU 相關的代碼,這些代碼都是完全按照通用的 x86 machine check 架構編寫的。另外,這次重寫的代碼在區分不可糾正錯誤和汙染 CPU 狀態的錯誤之間做了區分,在第一種情況下時,會在安全的時候將進程殺死,而不用系統 panic。而在之前的處理函數中,這兩種情況系統都會 panic 。但是,當進程處於核心態的時候,並且持有鎖,殺死這個進程就會造成系統死結。而死結比 panic 更難以處理,因此當處於核心態的進程發生了 machine check 的時候,核心選擇 panic。
在新編寫的處理函數中,建立了一個無鎖的二進位記錄檔系統,它完全和 printk 記錄檔分開。它將 machine check 記錄到一個緩衝區中,當緩衝區滿了之後,後來的資訊就會被丟棄,並且可以在使用者空間通過字元裝置 /dev/mcelog 來進行訪問。在使用者空間中使用應用程式 mcelog 有規律的對這個字元裝置進行讀取和解碼。
當遇到一個致命的 machine check 的時候,會在系統重新暖開機之後,由 BIOS 或者核心讀取這個錯誤。而其他的 slient machine check 可以通過 mcelog 按照一定的規律進行存取,並將他們寫道特殊的記錄檔中。
mce 結構如下:
/* A machine check record */struct mce { __u64 status; /* bank status register */ __u64 misc; /* misc register (always 0 right now) */ __u64 addr; /* address or 0 */ __u64 mcgstatus; /* global MC status register */ __u64 rip; /* Program counter or 0 for silent error */ __u64 tsc; /* cpu time stamp counter */ __u64 res1; /* for future extension */ __u64 res2; /* dito. */ __u8 cs; /* code segment */ __u8 bank; /* machine check bank */ __u8 cpu; /* cpu that raised the error */ __u8 finished; /* entry is valid */ __u32 pad;};
7.配置新的x86-64處理函數
新的處理函數可以在系統啟動並執行時候配置,通過讀或者寫/sys/devices/system/machinecheck/machinecheck0/下面的設定檔,合法的域包括:
(1)tolerant 容忍層級,這個層級越高,machine check 處理函數就冒著越大的風險來讓機器繼續運行,合法的層級如下:
0 當發生不可糾正的錯誤(uncorrected error)時,機器總是 panic 1 如果可能發生死結的時候,就 panic2 冒著較小的死結的風險而不去 panic3 從來不 panic 或者退出(用來測試)在核心的命令列上指定 oops=panic 意味著 0 容忍,對於一個叢集電腦來說,把 tolerant 設定為0可能是最好的,並且同時設定 panic=10 會強制機器重啟
(2)check_interval 以秒為單位來檢測 silent machine check 的時間間隔。預設的是 5 分鐘,如果是 0 的話就是不孕序後台檢查。
(3)bank0ctl...bankNctl bankN 中的二進位錯誤掩碼。預設的是允許所有 bank 中的錯誤。一個禁止的錯誤將會被忽略。
8.未來的工作:新的 RAM/cache 錯誤處理
RAM 錯誤是 machine check 事件的一種最常見的來源,但是由於記憶體控制器和 CPU 是非同步,所以導致錯誤報表可能會不太準確。處理函數假設進程中發生的錯誤在異常發生的時候被啟用,並且檢測進程是處於核心狀態還是使用者狀態,來決定是殺死一個進程還是發生 panic。當錯誤發生的時候,在核心調用或者環境切換之前這些資訊可能是陳舊的。一個更加可靠的做法是使用 MCn_ADDR寄存器中提供的物理錯誤地址,並使用 VM 結構來尋找這個地址所屬於的進程。這可能是多個進程共用的記憶體。
處理函數首先需要和進程的狀態同步,因為 VM 鎖不是中斷安全的。它應該首先通過在同一個 CPU 上發生一個自我中斷進入一個中斷上下文(這可能會延遲下一個本地中斷允許和標準中斷內容相關的執行)。然後這個中斷處理函數可以設定一個工作隊列項,來運行本地 CPU 上事件進程的一個回呼函數。
這個回呼函數可以使用 Linux2.6 提供的 mem_map 和 rmap 資料結構來查看這個錯誤頁的所有者。在核心頁緩衝中有很多情景:
Free page 忽略並清除錯誤Clean page 釋放頁並且重新從磁碟讀取資料Dirty page 殺死進程或者強製為映射的檔案快取資料IO錯誤Kernel page 依賴於容忍的層級殺死進程或者 panic
這個方法同樣可能處理不能糾正的緩衝錯誤。在未來,通過給應用程式發送一個帶有錯誤地址的訊號作為一個負載而不是簡單的無條件的殺死它,讓應用程式來對 machine check 來作出響應。程式之後可以決定如何處理被汙染的記憶體。帶有許資料緩衝的資料庫伺服器,通過磁碟備份,可以丟棄一個被汙染的快取頁面,並且重新讀取它。
Linux中的mce處理--mce學習筆記