平台人生
作者平台人生2018-01-04 10:32
软件开发工程师, 平台人生

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

字数 7795阅读 7195评论 0赞 3

作者:陈琳


某年某月某天早晨,运维工程师小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: {1}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 0
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: APEI generic hardware error status
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: severity: 2, corrected
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: section: 0, severity: 2, corrected
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: flags: 0x01
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: primary
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: fru_text: Card06, ChnA, DIMM0
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: section_type: memory error<<<<<<<<<<<<<内存错误
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: physical_address: 0x0000002c84019e00<<<<<<<<<<<<<<<<物理地址为0x0000002c84019e00
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: node: 5
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: card: 0
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: module: 0
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: bank: 6
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: row: 49921
Sep 13 04:06:55 prod-mac kernel: {1}[Hardware Error]: column: 136
Sep 13 04:06:55 prod-mac kernel: {1}[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  <<<<<<<<<<<<<<<<<<<Offlining 的内存地址为报错中的地址 

操作系统在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: [<ffffffff81153e71>] 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: [<ffffffff81154660>] try_to_unmap_one+0x40/0x500 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8112d561>] ? get_page_from_freelist+0x3d1/0x870 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8119892f>] ? do_lookup+0x9f/0x230 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81155521>] try_to_unmap_file+0xb1/0x7b0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff811626ae>] ? alloc_huge_page_node+0x5e/0xb0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81155c9f>] try_to_unmap+0x2f/0x70 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff811755e6>] migrate_huge_pages+0xa6/0x2c0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8117d830>] ? new_page+0x0/0x80 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8117f1b0>] soft_offline_page+0x190/0x4b0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81379138>] store_soft_offline_page+0xa8/0xc0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8136af69>] sysdev_class_store+0x29/0x30 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81203e45>] sysfs_write_file+0xe5/0x170 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81188f78>] vfs_write+0xb8/0x1a0 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff81189871>] sys_write+0x51/0x90 
Sep 13 08:25:41 prod-mac kernel: [<ffffffff8100b072>] 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 <49> 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  [<ffffffff81153e71>] page_check_address+0x141/0x1d0 
Sep 13 08:25:41 prod-mac kernel: RSP <ffff881063a8bb88> 
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()函数是干嘛的?

首先我们要知道内存是如何寻址的

微信图片_20180104102657.jpg

微信图片_20180104102657.jpg

内存的寻址实际上是将虚拟地址转化为物理地址的过程,如上图所示。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服务并重置记录内存错误的计数器来暂时避免下线动作的发生,但是这一方案并未从根本上解决问题,显而易见,这种问题的终极解决方案就是升级内核啦~
QQ截图20180104102859.png

QQ截图20180104102859.png

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

3

添加新评论0 条评论

Ctrl+Enter 发表

本文隶属于专栏

作者其他文章

相关文章

相关问题

相关资料

X社区推广