简介: RAS 技术广泛应用于服务器领域,用以确保服务器能够安全,可靠,长时间不间断的运行。本文着重论述 RAS 在 x86 平台上的相关应用。
RAS 的全称为 Reliability, Availability and
Serviceability。Reliability(可靠性)指的是系统必须尽可能的可靠,不会意外的崩溃,重启甚至导致系统物理损坏,这意味着一个
具有可靠性的系统必须能够对于某些小的错误能够做到自修复,对于无法自修复的错误也尽可能进行隔离,保障系统其余部分正常运转。
Availability(可用性)指的是系统必须能够确保尽可能长时间工作而不下线,即使系统出现一些小的问题也不会影响整个系统的正常运行,在某些情
况下甚至可以进行 Hot Plug 的操作,替换有问题的组件,从而严格的确保系统的 downtime
时间在一定范围内。Serviceability
指的是系统能够提供便利的诊断功能,如系统日志,动态检测等手段方便管理人员进行系统诊断和维护操作,从而及早的发现错误并且修复错误。综上所述,RAS
作为一个整体,其作用在于确保整个系统尽可能长期可靠的运行而不下线,并且具备足够强大的容错机制。这对于像大型的数据中心,网络中心如股票证券交易所,
电信机房,银行的数据库中心等应用环境是不可或缺的一部分。
由于 RAS 的独特性,构建一个稳固的 RAS 系统并不是一个简单的工程,这需要软硬件的协同配合。以 X86 平台为例,在
CPU,chipset,IO 控制器等硬件层面上需要提供诸如 ECC/Parity
检验这类的错误检测,错误纠正和错误报告扩展,同时为了提高可靠性,可能还需要进一步提供 RAID 存储以及可以 Hot Plug
的硬盘,内存,甚至是 CPU,以确保在必要的时候可以快速的替换出问题的组件。在 Firmware(这里可近似认为是
BIOS)这样的中间层则需要提供一个无缝的接口,对硬件报告的错误能够处理,同时也能够将错误或者处理的结果通知给 OS
以便软件能做进一步处理。对于 OS 和 application
而言,则需要提供更加细分的处理逻辑,譬如能够将错误分类分级以便进行有针对性的处理,记录等操作,或者提供测试接口以便自诊断或者调试。以下就以
x86 平台为例,对以上提及的几个方面分别介绍其功能和在 Linux 系统中的实现。
Hot Plug 包括常说的 Hot Plug-in/out。Hot Plug 的最大作用就是可以在线更新,从而确保系统能够持续可靠的运行。从硬件的角度看,Hot Plug 有四种状态:
-
CPU/Memory 不在 socket 上,firmware/BIOS 不知道它的存在
-
CPU/Memory 在 socket 上,firmware/BIOS 已知但是未通知 OS,因此 OS 未知
-
CPU/Memory 在 socket 上,firmware/BIOS 也通知了 OS,OS 已知但是未使能它,application 无法使用
-
CPU/Memory 在 socket 上,firmware/BIOS,OS 都已知并且 OS 使能了它,application 可以使用
从 2 到 3 的变化,就是通常所说的 Hot Add,反之则是 Hot Remove;从 3 到 4 的变化,就是通常说的
online,反之则是 offline。从系统设计的角度来看,任何一个 CPU/Memory 都是对等的,都可以执行 Hot Plug
操作,但是由于软硬件的某些限制,对于 BSP(Boot Strap Processor)CPU 一般是不可以被 Hot Remove 的。
下面以 CPU 的 Hot Add 为例描述这一过程:
-
用户将 CPU 插入一个空闲的 socket 中
-
用户通过 Hot Plug 的接口初始化 Hot Add 这一动作。接口可以是 OS 提供的 UI 接口,按一个按钮,或者是某些管理接口,如 IPMI,AMT
-
firmware/BIOS 对插入的 CPU 进行必要的初始化操作,如配置 QPI 总线的路由表,更新地址解码等
-
通过 ACPI 中断接口(SCI 中断)向 OS 产生一个 Hot Add 的事件
-
OS 在接收到这个 ACPI 事件后首先需要通过 ACPI 的 _OSI 方法检查当前系统是否支持"Module Device"的能力,如果是则表明可以进行 Hot Add 操作
-
OS 通过 ACPI 的 _MAT 方法得到 MADT 描述表,用来初始化 Local APIC/SAPIC 以及 local NMI 中断
-
OS 对新增的 CPU 进行相关的电源管理配置,如 P/C/T state
-
OS 调用 ACPI 的 _OST 方法通知 firmware/BIOS 本次 Hot Add 成功与否
在这个过程中,ACPICA(ACPICA 是 OS 用来和 firmware/BIOS 的接口)会完成绝大部分和 ACPI table 交互的工作,最终,ACPICA 会通过工作线程 acpi_os_execute_deferred将后继的事件分发处理交由 kernel 完成。
- static void acpi_os_execute_deferred(struct work_struct *work)
-
{
-
struct acpi_os_dpc *dpc = container_of(work, struct acpi_os_dpc, work);
-
-
if (dpc->wait)
-
acpi_os_wait_events_complete(NULL);
-
-
dpc->function(dpc->context);/* acpi_ev_notify_dispatchwill be called */
-
kfree(dpc);
-
}
-
-
acpi_ev_notify_dispatch ->
-
container_notify_cb
-
-
static void container_notify_cb(acpi_handle handle, u32 type, void *context)
-
{
-
struct acpi_device *device = NULL;
-
int result;
-
int present;
-
acpi_status status;
-
-
present = is_device_present(handle);
-
-
switch (type) {
-
case ACPI_NOTIFY_BUS_CHECK:
-
/* Fall through */
-
case ACPI_NOTIFY_DEVICE_CHECK:
-
printk(KERN_WARNING "Container driver received %s event\n",
-
(type == ACPI_NOTIFY_BUS_CHECK) ?
-
"ACPI_NOTIFY_BUS_CHECK" : "ACPI_NOTIFY_DEVICE_CHECK");
-
status = acpi_bus_get_device(handle, &device);
-
if (present) {
-
if (ACPI_FAILURE(status) || !device) {
-
result = container_device_add(&device, handle);
-
if (!result)
-
kobject_uevent(&device->dev.kobj,
-
KOBJ_ONLINE);
-
else
-
printk(KERN_WARNING
-
"Failed to add container\n");
-
}
-
} else {
-
if (ACPI_SUCCESS(status)) {
-
/* device exist and this is a remove request */
-
kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
-
}
-
}
-
break;
-
case ACPI_NOTIFY_EJECT_REQUEST:
-
if (!acpi_bus_get_device(handle, &device) && device) {
-
kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
-
}
-
break;
-
default:
-
break;
-
}
-
return;
-
}
对于 Hot Add 操作,首先系统会打印诸如 ”
Container driver received ACPI_NOTIFY_BUS_CHECK event”的信息到控制台,然后根据检测到的 Bus 状态和 Device 状态决定是否需要在 ACPI Bus 上添加一个新的设备,这里当然是需要的,因此 container_device_add会被调用,在执行过程中利用 sysfs 的对象模型,根据安装在 ACPI Bus 上的 driver 来匹配新增加的设备,经历
- acpi_bus_add
-
acpi_bus_scan
-
device_add
-
driver_probe_device
-
acpi_device_probe
-
acpi_processor_add
等一系列操作,最终一个新的 CPU 会被添加到相应的节点上并提供一个 sysfs 的接口供用户使用。然后用户可以通过执行诸如"echo 1 > /sys/devices/system/cpu/cpuX/online"这样的命令来使能新加入的 CPU,即 CPU online 操作,从而让 CPU 可以进入正常的调度作业。至此,一个新的 CPU 就被加入到正在运行的系统中了。
注:什么是 CPU_XXX_FROZEN 事件
在 CPU offline 操作中真正使用的事件只有 CPU_DOWN_PREPARE 和 CPU_DEAD,无论是 BSP 还是
non-boot 的 CPU。因为正在执行 offline 操作的 CPU,其上正在执行的 task
必定是活动的,否则无法执行代码。另外两个事件,CPU_DOWN_PREPARE_FROZEN / CPU_DEAD_FROZEN 其实是为了
non-boot CPU 在 suspend 的时候使用。换言之,suspend/resume 利用了 Hot Plug
的部分机制来完成自己的操作。如下所示:
- #define CPU_DOWN_PREPARE_FROZEN (
-
CPU_DOWN_PREPARE | CPU_TASKS_FROZEN)
-
suspend_enter ->
-
disable_nonboot_cpus ->
-
/* 1 means CPU_TASKS_FROZEN */
-
_cpu_down(cpu, 1)
online/offline 的 sys 接口位于 drivers/base/cpu.c 中:
- static SYSDEV_ATTR(online, 0644, show_online, store_online);
以 CPU offline 为例,实现过程如下:
- store_online->cpu_down(_cpu_down)
-
发送通知事件 CPU_DOWN_PREPARE 或者 CPU_DOWN_PREPARE_FROZEN 给准备 offline 的 CPU,让其进行准备工作。这包括:
- 将所有关联在准备 offline 的 CPU 上的进程迁移到其他 CPU 上
- 将所有关联在准备 offline 的 CPU 上的中断迁移到其他 CPU 上
-
OS 调用体系结构相关的函数 __cpu_disable()执行体系结构相关的清理工作
-
如果以上操作成功,则发送一个清理成功的事件到处于 CPU_DEAD 或者 CPU_DEAD_FROZEN 的 CPU 上
- static int __ref _cpu_down(unsigned int cpu, int tasks_frozen)
-
{
-
....
-
err = __raw_notifier_call_chain(&cpu_chain, CPU_DOWN_PREPARE| mod,
-
hcpu, -1, &nr_calls);
-
....
-
err = __stop_machine(take_cpu_down, &tcd_param, cpumask_of(cpu));
-
....
-
/* This actually kills the CPU. */
-
__cpu_die(cpu);
-
-
/* CPU is completely dead: tell everyone. Too late to complain. */
-
if (raw_notifier_call_chain(&cpu_chain, CPU_DEAD| mod,
-
hcpu) == NOTIFY_BAD)
-
BUG();
-
....
-
}
-
-
static int __ref take_cpu_down(void *_param)
-
{
-
struct take_cpu_down_param *param = _param;
-
int err;
-
-
/* Ensure this CPU doesn't handle any more interrupts. */
-
err = __cpu_disable();
-
if (err < 0)
-
return err;
-
-
raw_notifier_call_chain(&cpu_chain, CPU_DYING | param->mod,
-
param->hcpu);
-
-
/* Force idle task to run as soon as we yield: it should
-
immediately notice cpu is offline and die quickly. */
-
sched_idle_next();
-
-
return 0;
-
}
BSP Hot Remove 的限制
在 x86 架构中,默认的 timer 中断定义如下
- static struct irqaction irq0 = {
-
.handler = timer_interrupt,
-
.flags = IRQF_DISABLED |
-
IRQF_NOBALANCING|
-
IRQF_IRQPOLL | IRQF_TIMER,
-
.name = "timer"
-
};
由于 IRQF_NOBALANCING 的存在,在 IRQ descriptor 初始化的时候会和其他中断有所分别:
- __setup_irq
-
....
-
/* Exclude IRQ from balancing */
-
/* if requested */
-
if (new->flags & IRQF_NOBALANCING)
-
desc->status |= IRQ_NO_BALANCING;
以使用 IOAPIC 中断控制器为例,在初始化中断向量表的 RTE(Redirection Table Entry)时,由于 IRQ_NO_BALANCING 的限制会使得 IRQ0 被绑定在了 BSP CPU 上。
- setup_ioapic_dest
-
....
-
if (desc->status & (IRQ_NO_BALANCING|
-
IRQ_AFFINITY_SET))
-
mask = desc->affinity;
-
....
即使通过 /proc/irq/xx/smp_affnity 来修改 interrupt affinity 也是不可以的,因为在其对应的写接口实现中限制仍然存在:
- irq_affinity_proc_write
-
....
-
if (!irq_to_desc(irq)->chip->set_affinity
-
||no_irq_affinity ||
-
irq_balancing_disabled(irq))
-
return -EIO;
-
....
目前 Linux 内核对 CPU online/offline 的实现已经比较稳定,但是对于 CPU/Memory 的 Hot Add
支持还不是很稳定,因为这涉及到 firmware/BIOS 与 OS 的交互,如插入一个新的内存模块时 OS 需要得到用来更新 NUMA
节点相关的 SRAT 表,这需要 firmware/BIOS 和 OS 两方面协同工作。这还主要是针对 64bit 的平台而言,至于 32bit
的平台,基本上还处于一个不可用的状态。这主要是实用性的问题,毕竟这类需求多用在服务器平台上。而对于服务器而言,由于内存尺寸,寻址空间等限制很少会
选择 32bit 的平台,所以很多功能在 23bit 的平台上没有实现或者并没有充分测试过可用性。至于 Hot
Remove,无论是软件,firmware/BIOS 甚至是硬件平台,其支持程度都很不成熟,到目前为止还无法真正投入使用。这也一定程度上限制了
Linux 在服务器领域的进一步拓展。
阅读(2751) | 评论(0) | 转发(0) |