.. Kenneth Lee 版权所有 2017-2020
:Authors: Kenneth Lee :Version: 1.0
Linux RAS特性分析
(如果你看到这一行,说明本文仍处于修改状态,内容正在进行逻辑整理和信息确认,非 常不可靠)
RAS,是Reliabilty,Availability,Serviceability的缩写,是对一台服务器可以被可靠 使用的要求。这个是个相当粗糙的定义。如果深入一点理解,所谓R,表示服务器提供正确 输出的能力,比如我要求你给我计算1+1,你给我回应了,但你说等于3,这R就有问题了。 A表示能提供输出的能力,比如我让你给我计算1+1,你确实不说这等于3,但你什么都不说 ,那这个A就有问题了(总算是比说等于3好一点)。而S是说,如果你发现这个设备不能提 供服务了(A出了问题),你把坏掉的部分换掉或者修好的能力。
但这仍是个非常粗糙,指向不明的定义。但RAS确实就是这么个不清不楚的定义。
或者我们可以换个角度,整个RAS的最终评价标准,常常是MTBF,Mean Time Between Failure,也就是你的服务器有多长时间能“正确提供所需功能”。我们经常说某个系统是4 个9还是6个9,说的就是MTBF这个指标。
但MTBF是个综合指标,和你的硬件,软件,使用方法(使用时的规章制度)统统有关,设 计上是没有那么容易说清楚的。作者曾经参与过一个火车调度系统的设计,这种系统的每 个输出,都是三套独立的高可靠系统用不同算法进行运算的结果,然后再从这三个结果中 投票决定实际的输出。从这类的设计就可以看出,系统一级的MTBF保障手段,通常不相信 决策系统的任何一环的。
但我们一般做单机的时候考虑不到这么高的高度,我们只能保证硬件基本上是可靠的。软 件本身有没有问题,这个就要另外找方法来保证了。所以,这里谈Linux RAS特性,更多的 是谈Linux如何处理硬件不可靠导致的问题,而不是如何保证全系统如何达到更高的MTBF。
基于PC的Linux和基于工作站的Unix不同,它一开始根本就是个玩具,就不是个高可靠的系 统。所以Linux在架构上就不具有一般比如Solari或者AIX那种完整的RAS管理系统。所以, Linux就没有一套逻辑完整的RAS机制。甚至现在你看Linux内核中的 Documentations/admin-guide/ras.rst,它也仅仅介绍了EDAC子系统,而说不清楚整个RAS 到底在Linux中的逻辑是怎么样的。
但正如我在这个专栏(包括那个道德经的专栏)中说的,这个世界不是基于逻辑的,它是 基于“度”的(更微小逻辑的组合,这些组合超过你理智思维可以容纳幅度)。不是你有RAS 或者没有RAS,或者提供什么特定的RAS功能就可以判断这个系统是否成功的。当云计算兴 起,中规中矩的Unix RAS设计显得成本高昻而没有意义,反而Linux加上一些半吊子的RAS 设计,取得更好的竞争优势。
半吊子设计的结果就是一般没有人知道这整个设计的设计逻辑是什么。所以本文尝试基于 4.13-rc7内核,提取一下Linux内核对RAS的认知模型和支持情况。
RAS特性,首先保证的是R。系统挂了,可以重启,但明明应该给我一个2的,你给我一个3 ,这就不可救药了。所以在硬件设计上,优先保护的是这个R。而R的保护方法是就是在整 个系统的传输系统(DDRC,Cache控制器,总线控制器等)加入各种完整性校验,比如内存 的ECC,IO的CRC,其他协议栈的各种Checksum等。当硬件系统发现这些错误后,它会尝试 修正它,或者告知系统发生了这个错误。
硬件设计者在做这种判断的时候,非常关注错误的传播问题。你在一个总线的节点上发现 了一个错误,然后你把这个错误通过中断报告给CPU,中断是要经过中断子系统上报的,在 这个上报没有生效前,这个错误的结果可能已经发给了CPU,然后CPU可能用这个结果计算 出另一个结果,那个结果还被写到另一个变量中,然后写回到内存上,甚至保存到磁盘上 。然后这个时候你才上来个中断,告诉我刚才那个结果是错的……我找谁说理去?所以硬件 根据错误上报的传播范围,错误的分类会非常复杂(比如,在硬件标准的概念空间中会有“ Deferred Error”这种改变表示错误已经被传播出去了。而Linux则简单得多,Linux在大部 分应用中,只分辨两种错误:CE的和UE的,前者是可以修正的(或者已经修正的),后者 是不能修正的。所以,一定程度上,我们可以把各种硬件的错误,都归结为这两种类型中 的一种),在Linux下,再复杂的报告,暂时可以理解为是这两种中的一种。
当硬件侦测到一个错误,它有两种方法报告CPU,一种方法是中断。这是个异步的过程,很 容易造成传播范围不可控。如果这是CE的,错误已经被硬件主动修复,或者可以有手段修 复(比如通过产生同步异常)。要不是UE的,这基本上就要导致停机乃至整体隔离了。
第二种错误的报告方法是把错误随着CPU核的读写响应(消息)返回给CPU,让CPU产生一个 同步异常,CPU就有办法修复这样的错误,这种异常体现在CPU核上,就是一个page_fault 中断,只是不同的类型,CPU可以通过给对应的进程发SIGBUS一类的信号来控制这个错误。
这两种方法可以配合起来同时使用,前者用于记录错误,后者用来避免错误被传播。
[EDAC]
Linux最早处理异步中断错误的框架叫EDAC,Error Detection And Correction。它包括两 个子系统,一个称为edac_mc,另一个称为edac_device。前者负责收集内存控制器报告的 错误,后者负责其他控制器(比如L3 Cache控制器)报告的错误。通常是把控制器的驱动 写成一个平台设备,然后在probe的时候注册为edac_mc或者edac_device。控制器驱动 ioremap设备的IO空间,注册处理中断,在收到中断以后,从IO空间中读到参数,再调用 edac的框架报告函数,报告相关的错误:::
edac_mc_handle_error()
edac_raw_mc_handle_error()
edac_device_handle_ce()
edac_device_handle_ue()
这些报告函数主要干这些事:
printk:把错误打印到print ring buffer里面
trace_mc_event:把错误写到ftrace的一个跟踪项中
统计:把这个错误报告到根据DIMM条,Rank,Row的分类进行统计,为后续进行硬件替 换提供参考
如果硬件报这是个UE,而且这个控制器要求UE即停机,则复位系统
我说Linux的RAS不成系统,这里可见一斑:printk是个参考,是不适合正式处理的, trace_xxx是个跟踪,不开跟踪就不能工作,统计不能用于单个处理。说到底,EDAC被看作 一个可有可无的特性了。
所以,当我们给Linux实现RAS特性的(硬件的)时候,必须有意识地用page_fault一类的 异常来控制传播范围,只把中断的报告看作是统计上的补充。
EDAC是比较原始的实现,需要为每个平台的控制器写独立的驱动。ACPI标准的APEI表从 BIOS层提供了标准的报告形式。APEI是ACPI Platform Error Interface的缩写,它包括多 张表:
BERT: Boot Error Record Table,这个用于记录前一次复位前BIOS记下来的错误, Linux读到这个记录会打印出来,以便知道比如上次因为UE而服务的原因
EINJ:Error INJection,BIOS提供的硬件故障注入的接口,Linux把这个封装成debugfs 的属性了,可以通过这个注入需要的硬件错误
ERST:Error Record Serialization Table,这个表用于配合BERT,从OS侧辅助BIOS把 临时前的错误信息保存到持久设备中。做硬件的同学要考虑适配这套框架,保证错误可 以被固化到持久的存储中
HEST: Hardware Error Source Table,这个描述系统有多少个错误检测设备,Linux中 这个被实现为多个(每条记录一个)平台设备
GHES:General Hardware Error Source,这是硬件报错的接口。Linux中,这个被实现 为HEST定义的设备的驱动,每个那样的平台设备,Probe到这个驱动上后,再注册为一个 edac_mc设备,这样就和EDAC框架结合起来了
从这里可以看到,APEI一定程度上是基于EDAC框架的,但它同时也提供独立于EDAC之外的 功能。所以,高级的标准的服务器,更应该选择的是走APEI路线,而不是EDAC的路线。
只要硬件提供APEI接口,Linux上就不需要额外的驱动了。
Linux还有一个传统的报告系统,这个是硬件的概念,所谓Machine Check Exception,也 是用于报告EDAC类似的错误的,但现在这个主要是比如PowerPC用得比较多,在x86上,现 在这个系统还在,但RAS有关的报告已经大部分被APEI取代了,所以我们基本上现在可以忽 略它,而聚焦到APEI支持上。
如果我们发现uc的内存问题,一种办法当然可以立即停机。但其实还有一种方法是隔离掉 这片内存。这个功能依然做在GHES上,ghes_handle_memory_failure()调用 mm/mm-failure.c中的异常函数,Linux会把这个page标记为HWPOISON的,之后相关的页, VM或者进程就会被隔离掉。
这个功能对比AIX这样的高级货,实在拿不出手,因为这并不能避免错误被传播出去。但对 一般数据中心来说,可能也够了。对大部分数据中心来说,你能说你这个节点不可信就够 了,本来也不指望你还能提供内存双备这种不计成本的高级特性来。
PCIE总线有自己的错误报告标准,叫AER。实现为PIC配置空间的一个capability。和其他 EDAC的控制器一样,也实现为IO和中断的形式。我觉得如果做得好的话,它应该被注册为 一个edac_device。但实际情况是,它直接通过printk和trace_aer_event()完成错误报告 了。
AER和EDAC的处理策略几乎和EDAC一样,不同的是,当它发现错误的时候,会根据错误的类 型,对链路,端口等硬件进行独立的复位,尝试修复系统。
AER错误现在通过APEI的GHES表收集的,PCIE总线驱动仅仅提供处理的手段。所以,要支持 PCIE的错误,也需要实现APEI。
其他非规整的硬件,现在基本上都是通过trace_xxxx()这样的方式记录的。比如ARM就使用 了自己的trace_arm_event()和trace_non_standard_event(),所以,综合起来,全面收集 所有RAS数据的方法,主要是ftrace接口。也就是说,在服务器上,无论你是否要跟踪, ftrace是必开的功能,否则RAS收集程序将无法工作。
RAS是Linux下一个常用的ras收集器,很多高级服务器当然会提供自己的收集daemon,但原 理基本上也和rasdaemon差不多。它就是等在ftrace上,创建一个独立的ftrace instance ,收集需要的ftrace事件,然后记录到sqlite3数据库中。
现在rasdaemon收集的事件包括:
EDAC_MC
AER
non_standard_event
mce
extlog_mem_event(这是x86 MCE特有的信息)
Linux的故障注入做得很分散,每个独立的功能有自己独立的故障注入方式,AER有AER自己 的,APEI有APEI自己的,memory的同步故障则通过HWPOISON_INJECT来注入。
总结起来,Linux现在提供了基本的RAS功能(重点是保证R),足以支撑“有错必须要知道” 这样的需要。主线方案应该是APEI+ftrace+rasdaemon。支持好这个,就提供了Linux最基 本的RAS功能了。