查看原文
其他

分析 mcelog 引发 x86 RAC 失效原因

陈琳 twt企业IT社区 2022-07-03

来自社区专栏“平台人生”

http://www.talkwithtrend.com/Column/detail/id/11


某年某月某天早晨,运维工程师小A刚做完值班交接工作,忽然收到数据库RAC心跳超时的告警,紧接着又收到了一条节点二重启的告警,接着可怕的事情发生了,节点一也重启了!

从现场来看,节点一主机Oracle进程状态异常,此时数据库网络心跳超时,但磁盘心跳正常,根据数据库机制,节点二被踢出RAC集群。随后,节点一主机系统panic,引发重启。此时整个数据库服务不可用,引起系统中断。

那么这个可怕的原因到底是什么呢?

且听我慢慢给您分析。

大多数运维工程师都知道内存故障的频率不如硬盘故障的频率高,但是内存发生错误却是很常见的,其中的奥秘就在于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服务并重置记录内存错误的计数器来暂时避免下线动作的发生,但是这一方案并未从根本上解决问题,显而易见,这种问题的终极解决方案就是升级内核啦~


更多相关文章请点击阅读原文


长按二维码关注公众号

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存