天天看點

Linux中的mce處理--mce學習筆記     1.machine check 是什麼?     2.machine check 很重要     3.x86 machine check architecture 概述     4.為什麼寫一個 machine check 處理函數是困難的     5.登記 machine check      6.重寫的x86-64處理函數     7.配置新的x86-64處理函數     8.未來的工作:新的 RAM/cache 錯誤處理

     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 如果可能發生死鎖的時候,就 panic
2 冒着較小的死鎖的風險而不去 panic
3 從來不 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 來作出響應。程式之後可以決定如何處理被污染的記憶體。帶有許資料緩存的資料庫伺服器,通過磁盤備份,可以丢棄一個被污染的緩存頁,并且重新讀取它。

繼續閱讀