内存热插拔

内存热插拔事件通知器

热插拔事件被发送到通知队列。

include/linux/memory.h 中定义了六种通知类型

MEM_GOING_ONLINE

在新内存可用之前生成,以便子系统能够准备好处理内存。页分配器仍无法从新内存中进行分配。

MEM_CANCEL_ONLINE

如果 MEM_GOING_ONLINE 失败则生成。

MEM_ONLINE

在内存成功上线时生成。回调可以从新内存中分配页面。

MEM_GOING_OFFLINE

生成以开始内存下线过程。不再可能从该内存中进行分配,但部分要下线的内存仍在被使用。回调可用于从指定内存块中释放子系统已知的内存。

MEM_CANCEL_OFFLINE

如果 MEM_GOING_OFFLINE 失败则生成。我们尝试下线的内存块中的内存再次可用。

MEM_OFFLINE

在内存下线完成时生成。

可以通过调用以下函数注册回调例程

hotplug_memory_notifier(callback_func, priority)

优先级值较高的回调函数会在优先级值较低的回调函数之前被调用。

回调函数必须具有以下原型

int callback_func(
  struct notifier_block *self, unsigned long action, void *arg);

回调函数的第一个参数 (self) 是指向通知链块的指针,该块指向回调函数本身。第二个参数 (action) 是上述事件类型之一。第三个参数 (arg) 传递 struct memory_notify 的指针

struct memory_notify {
        unsigned long start_pfn;
        unsigned long nr_pages;
        int status_change_nid_normal;
        int status_change_nid;
}
  • start_pfn 是上线/下线内存的 start_pfn。

  • nr_pages 是上线/下线内存的页数。

  • status_change_nid_normal 在 nodemask 的 N_NORMAL_MEMORY 被设置/清除时(或将要被设置/清除时)设置节点 ID,如果此值为 -1,则 nodemask 状态未更改。

  • status_change_nid 在 nodemask 的 N_MEMORY 被设置/清除时(或将要被设置/清除时)设置节点 ID。这意味着一个新的(无内存的)节点通过上线获得了新内存,或者一个节点失去了所有内存。如果此值为 -1,则 nodemask 状态未更改。

    如果 status_changed_nid* >= 0,回调应在必要时为该节点创建/废弃结构。

回调例程应返回 include/linux/notifier.h 中定义的 NOTIFY_DONE、NOTIFY_OK、NOTIFY_BAD、NOTIFY_STOP 值之一

NOTIFY_DONE 和 NOTIFY_OK 对后续处理没有影响。

NOTIFY_BAD 用作对 MEM_GOING_ONLINE、MEM_GOING_OFFLINE、MEM_ONLINE 或 MEM_OFFLINE 操作的响应,以取消热插拔。它停止通知队列的后续处理。

NOTIFY_STOP 停止通知队列的后续处理。

锁定内部机制

在添加/移除使用内存块设备(即普通 RAM)的内存时,应持有 device_hotplug_lock,以便

  • 与上线/下线请求(例如通过 sysfs)同步。通过这种方式,只有在内存完全添加后,用户空间才能访问内存块设备(.online/.state 属性)。当移除内存时,我们知道没有人处于关键区。

  • 与 CPU 热插拔及类似操作(例如与 ACPI 和 PPC 相关)同步

特别是,当添加内存且用户空间尝试比预期更快地上线该内存时,使用 device_hotplug_lock 可以避免潜在的锁反转问题

  • device_online() 将首先获取 device_lock(),然后是 mem_hotplug_lock

  • add_memory_resource() 将首先获取 mem_hotplug_lock,然后是 device_lock()(在创建设备期间,即 bus_add_device() 期间)。

由于设备在获取 device_lock() 之前对用户空间可见,这可能导致锁反转。

内存的上线/下线应通过 device_online()/device_offline() 完成——以确保与通过 sysfs 的操作正确同步。建议持有 device_hotplug_lock(例如,保护 online_type)

在添加/移除/上线/下线内存或添加/移除异构/设备内存时,我们应始终以写模式持有 mem_hotplug_lock 以串行化内存热插拔(例如,对全局/区域变量的访问)。

此外,以读模式持有 mem_hotplug_lock(与 device_hotplug_lock 相反)允许高效实现 get_online_mems/put_online_mems,因此访问内存的代码可以防止内存消失。