关于mcelog引发x86 RAC失效的原因分析

某年某月某天早晨,运维工程师小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一个大页的时候。日志如下:

Sep 13 04:06:12 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 0

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: APEI generic hardware error status

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: severity: 2, corrected

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: section: 0, severity: 2, corrected

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: flags: 0x01

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: primary

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: fru_text: Card06, ChnA, DIMM0

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: section_type: memory error

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: physical_address: 0x0000002c84019e00

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: node: 5

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: card: 0

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: module: 0

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: bank: 6

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: row: 49921

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: column: 136

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: error_type: 0, unknown

…..

…..

Sep 13 04:06:55 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 04:22:19 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 04:22:19 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 05:02:31 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 05:43:35 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 06:23:45 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 07:03:52 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 07:44:03 prod-mac kernel: [Hardware Error]: Machine check events logged

Sep 13 08:25:15 prod-mac kernel: [Hardware Error]: Machine check events logged

查看日志,我们发现系统从13日04:06开始,记录有硬件(内存)错误。并且到8:25分时,系统连续报错达到10次。因此,mcelog将错误的内存 offlining。记录如下:

Sep 13 08:25:15 prod-mac mcelog: Corrected memory errors on page 2c84019000 exceed threshold 10 in 24h: 10 in 24h

Sep 13 08:25:15 prod-mac mcelog: Location SOCKET:3 CHANNEL:? DIMM:? []

Sep 13 08:25:15 prod-mac mcelog: Offlining page 2c84019e00

操作系统在8:25:41报错,kernel: BUG: unable to handle kernel NULL pointer dereference at (null),并且RIP是page_check_address。

Sep 13 08:25:41 prod-mac kernel: BUG: unable to handle kernel NULL pointer dereference at (null)

Sep 13 08:25:41 prod-mac kernel: IP: [] page_check_address+0x141/0x1d0

Sep 13 08:25:41 prod-mac kernel:PGD 106601a067 PUD 1066085067 PMD 0

Sep 13 08:25:41 prod-mac kernel: Oops: 0000 [#1] SMP

Sep 13 08:25:41 prod-mac kernel: last sysfs file: /sys/devices/system/memory/soft_offline_page

......

......

Sep 13 08:25:41 prod-mac kernel: Call Trace:

Sep 13 08:25:41 prod-mac kernel: [] try_to_unmap_one+0x40/0x500

Sep 13 08:25:41 prod-mac kernel: [] ? get_page_from_freelist+0x3d1/0x870

Sep 13 08:25:41 prod-mac kernel: [] ? do_lookup+0x9f/0x230

Sep 13 08:25:41 prod-mac kernel: [] try_to_unmap_file+0xb1/0x7b0

Sep 13 08:25:41 prod-mac kernel: [] ? alloc_huge_page_node+0x5e/0xb0

Sep 13 08:25:41 prod-mac kernel: [] try_to_unmap+0x2f/0x70

Sep 13 08:25:41 prod-mac kernel: []migrate_huge_pages+0xa6/0x2c0

Sep 13 08:25:41 prod-mac kernel: [] ? new_page+0x0/0x80

Sep 13 08:25:41 prod-mac kernel: []soft_offline_page+0x190/0x4b0

Sep 13 08:25:41 prod-mac kernel: [] store_soft_offline_page+0xa8/0xc0

Sep 13 08:25:41 prod-mac kernel: [] sysdev_class_store+0x29/0x30

Sep 13 08:25:41 prod-mac kernel: [] sysfs_write_file+0xe5/0x170

Sep 13 08:25:41 prod-mac kernel: [] vfs_write+0xb8/0x1a0

Sep 13 08:25:41 prod-mac kernel: [] sys_write+0x51/0x90

Sep 13 08:25:41 prod-mac kernel: [] system_call_fastpath+0x16/0x1b

Sep 13 08:25:41 prod-mac kernel: Code: c7 48 89 f8 0f 1f 40 00 48 c1 e0 12 48 ba 00 00 00 00 00 ea ff ff 48 c1 e8 1e 48 6b c0 38 4c 8d 64 10 10 4c 89 e7 e8 7f 64 3d 00 8b 45 00 a9 01 01 00 00 74 55 48 89 c7 48 89 f8 0f 1f 40 00

Sep 13 08:25:41 prod-mac kernel: RIP []page_check_address+0x141/0x1d0

Sep 13 08:25:41 prod-mac kernel: RSP

Sep 13 08:25:41 prod-mac kernel: CR2: 0000000000000000

Sep 13 08:25:41 prod-mac kernel: ------------[ cut here ]-------

通过日志我们发现,本问题的关键就在于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()

我们来看看这个已经被修正过的函数:

diff --git a/mm/rmap.c b/mm/rmap.c

index 55c8b8dc9ffb..068522d8502a 100644

--- a/mm/rmap.c

+++ b/mm/rmap.c

@@ -600,7 +600,11 @@ pte_t *__page_check_address(struct page *page, struct

mm_struct *mm,

spinlock_t *ptl;

if (unlikely(PageHuge(page))) {

+ /* when pud is not present, pte will be NULL */

pte = huge_pte_offset(mm, address);

+ if (!pte)

+ return NULL;

+

ptl = huge_pte_lockptr(page_hstate(page), mm, pte);

goto check;

}

修复过的page_check_address()函数对pte进行了空指针筛查。

针对这一内核bug,临时的解决方案可以通过重启mcelog服务并重置记录内存错误的计数器来暂时避免下线动作的发生,但是这一方案并未从根本上解决问题,显而易见,这种问题的终极解决方案就是升级内核啦~

Rhel版本

需要升级到哪个内核版本

RHEL 6.9.z

kernel-2.6.32-696.6.3.el6

RHEL 6.7

kernel-2.6.32-573.43.2.el6

RHEL 6.6

kernel-2.6.32-504.60.2.el6

RHEL 6.5

kernel-2.6.32-431.81.2.el6

RHEL 6.4

kernel-2.6.32-358.79.1.el6

RHEL 6.2

kernel-2.6.32-220.72.2.el6

RHEL7

kernel-3.10.0-514.el7

平台人生

欢迎关注!

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180104G06P6F00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券