分析 mcelog 引發 x86 RAC 失效原因

2021-08-19 15:00:33 字數 2922 閱讀 7729

大多數運維工程師都知道記憶體故障的頻率不如硬碟故障的頻率高,但是記憶體發生錯誤卻是很常見的,其中的奧秘就在於ecc記憶體。

ecc記憶體指的是帶有ecc功能的記憶體,即error checking and correcting,它實際上是一種錯誤檢查和糾正的技術,它能夠容許錯誤,並可以將錯誤更正,使系統得以持續地正常工作,不致因錯誤而中斷。ecc記憶體正是帶有這種技術的記憶體。那麼是不是只要使用了帶有ecc功能的記憶體就可以高枕無憂了呢?ecc技術實際上解決的是軟錯誤,例如電子干擾造成的傳輸錯誤,但是對於硬體錯誤來說,是無法被修正的。並且ecc技術也不是萬能的,它只能糾正1個位元錯誤和檢測2個位元錯誤,對1位元以上的錯誤無法糾正,對2位元以上的錯誤不保證能檢測。

對於記憶體錯誤,首先我們需要能夠確定是哪些元件導致的問題。是dimm?是記憶體控制器?還是記憶體頁?這就輪到本期主角上場了!它就是mcelog。在linux作業系統中,我們可以使用mcelog服務來跟蹤記憶體錯誤。正如上面所說,常見記憶體錯誤通常是軟錯誤,如dimm中乙個「卡住的」bit,這種錯誤是可以被ecc技術糾正的,mcelog會跟蹤記錄這一情況。但是當這個bit附近的乙個bit再次發生損壞時,就可能發展成乙個不能被修正的記憶體錯誤,此時,核心預設策略就是停止使用這個位。也就是說,此時核心將記憶體頁複製到其他地方,並且從記憶體頁管理「清單」中刪掉該頁,也叫做offline。那麼怎麼判斷該不該offline乙個記憶體頁呢?mcelog採取了如下方式:當修復記憶體錯誤的次數超過一定閾值(閾值預設設定為一塊記憶體頁中24小時內重複出現10次),該記憶體頁就會被offline。本案例中,用於資料庫的db伺服器會開啟「大頁」功能,一般來說,linux記憶體頁大小為4k,「大頁」為2m,在檢視日誌的時候,我們發現系統panic發生在mcelog服務offline乙個大頁的時候。日誌如下:

檢視日誌,我們發現系統從13日04:06開始,記錄有硬體(記憶體)錯誤。並且到8:25分時,系統連續報錯達到10次。因此,mcelog將錯誤的記憶體 offlining。記錄如下:

作業系統在8:25:41報錯,kernel: bug: unable to handle kernel null pointer dereference at (null),並且rip是page_check_address。

過日誌我們發現,本問題的關鍵就在於page_check_address ()這個核心函式。我們先劇透一下發生panic的真實原因,後續再進行詳細解釋:負責offline的控制模組調起page_check_address()函式,但是由於huge_pte_offset()函式返回了乙個null pointer,傳給了page_check_address()函式,而page_check_address()函式又沒有執行空指標判斷,因此出現了系統panic。

你肯定會問page_check_address()函式和huge_pte_offset()函式是幹嘛的?

記憶體的定址實際上是將虛擬位址轉化為實體地址的過程,如上圖所示。x86架構中,虛擬位址(又叫linear address)實際上是由記憶體中各表(pgd表、pud表、pmd表,pte表)的索引組成的,怎麼找到乙個記憶體真實的實體地址呢?首先,cr3是記憶體頁目錄(pgd表)的基址,通過線性位址中的pgd索引,我們找到了對應pud表的基址,然後通過pud索引,找到了pmd表,又通過pmd表索引,找到了pte表,通過pte索引找到了真實實體地址的起始位置,加上linear address中的offset,就是我們需要的真實實體地址了。

在這個過程中,huge_pte_offset()函式是用來獲得大頁的物理記憶體基址的。實際上,大頁的定址中,是沒有pte表的,大頁的linear address也沒有其對應索引,因此這個函式的返回值,實際上是pmd表的索引。

為什麼說本案例中huge_pte_offset()函式沒有獲取到pmd呢?上面的日誌檔案中,有一行:

sep 13 08:25:41 prod-mac kernel: pgd 106601a067 pud 1066085067 pmd 0

也就是說,發生系統panic的時候,pmd為空。

那麼page_check_address()函式呢?我們可以從它的返回值窺見:返回值為可用的pte指標。也就是說,該函式是用來獲取未被lock的pte指標的。本案例正是因為在定址過程中,出現了空的pmd表索引,而page_check_address()未對其進行判斷,得到了錯誤的pte,進而導致了panic。這是乙個核心bug啊!!還好,這個bug已經有了對應的修復:

mm/hugetlb: check for pte null pointer in __page_check_address()

我們來看看這個已經被修正過的函式:

修復過的page_check_address()函式對pte進行了空指標篩查。

針對這一核心bug,臨時的解決方案可以通過重啟mcelog服務並重置記錄記憶體錯誤的計數器來暫時避免下線動作的發生,但是這一方案並未從根本上解決問題,顯而易見,這種問題的終極解決方案就是公升級核心啦~

X86啟動過程分析

一直對計算機的啟動過程不甚清楚,總是一知半解。這幾天蒐集一些這方面的資料,通過學習對啟動過程有個更深入的了解。通常情況下,我們的系統裝在磁碟上。而cpu是不能夠直接訪問磁碟的,必須將磁碟上的內容讀入記憶體後才能被cpu訪問。那麼計算機是如何啟動並執行作業系統 的呢?這裡必定存在乙個將磁碟上的 載入到...

X86核心啟動分析二 從bootloader到核心

uboot也起到相同的效果。對於x86來說,bootloader和kernel的互動引數是hdr和cmdline,因為x86的相容性較好,架構變化不大。但是對於arm和ppc,嵌入式場景很多,所以後續採用更好的裝置樹架構,擴充套件性更好一些。uboot和kernel的交接點如下 load zimag...

常見運算的x86反彙編分析 2

c語言中的模運算是基本的運算,我們來看看debug模式下vs2008對下面的這句 求模運算的反彙編 int b a 8 1.0041366e 8b 45 f8 mov eax,dword ptr a 2.00413671 99 cdq 3.00413672 33 c2 xor eax,edx 4.0...