CPU/Memory 的Hot Plug | 安逸哇?

CPU/Memory 的 Hot Plug

Hot Plug 包括常说的 Hot Plug-in/out。Hot Plug 的{zd0}作用就是可以在线更新,从而确保系统能够持续可靠的运行。从硬件的角度看,Hot Plug 有四种状态:

   1. CPU/Memory 不在 socket 上,firmware/BIOS 不知道它的存在
   2. CPU/Memory 在 socket 上,firmware/BIOS 已知但是未通知 OS,因此 OS 未知
   3. CPU/Memory 在 socket 上,firmware/BIOS 也通知了 OS,OS 已知但是未使能它,application 无法使用
   4. 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 为例描述这一过程:

   1. 用户将 CPU 插入一个空闲的 socket 中
   2. 用户通过 Hot Plug 的接口初始化 Hot Add 这一动作。接口可以是 OS 提供的 UI 接口,按一个按钮,或者是某些管理接口,如 IPMI,AMT
   3. firmware/BIOS 对插入的 CPU 进行必要的初始化操作,如配置 QPI 总线的路由表,更新地址解码等
   4. 通过 ACPI 中断接口(SCI 中断)向 OS 产生一个 Hot Add 的事件
   5. OS 在接收到这个 ACPI 事件后首先需要通过 ACPI 的 _OSI 方法检查当前系统是否支持"Module Device"的能力,如果是则表明可以进行 Hot Add 操作
   6. OS 通过 ACPI 的 _MAT 方法得到 MADT 描述表,用来初始化 Local APIC/SAPIC 以及 local NMI 中断
   7. OS 对新增的 CPU 进行相关的电源管理配置,如 P/C/T state
   8. OS 调用 ACPI 的 _OST 方法通知 firmware/BIOS 本次 Hot Add 成功与否

在这个过程中,ACPICA(ACPICA 是 OS 用来和 firmware/BIOS 的接口)会完成绝大部分和 ACPI table 交互的工作,最终,ACPICA 会通过工作线程 acpi_os_execute_deferred将后继的事件分发处理交由 kernel 完成。

   1. CPU Hot-Add 的执行过程

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,让其进行准备工作。这包括:
          o 将所有关联在准备 offline 的 CPU 上的进程迁移到其他 CPU 上
          o 将所有关联在准备 offline 的 CPU 上的中断迁移到其他 CPU 上
    * OS 调用体系结构相关的函数 __cpu_disable()执行体系结构相关的清理工作
    * 如果以上操作成功,则发送一个清理成功的事件到处于 CPU_DEAD 或者 CPU_DEAD_FROZEN 的 CPU 上

   2. CPU offline 的执行过程

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 在服务器领域的进一步拓展。

相关文章

Comments

Leave a Reply




郑重声明:资讯 【CPU/Memory 的Hot Plug | 安逸哇?】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——