这篇翻译源于linux/Documentation/memeory-hotplug.txt。
内存的热插拔技术
这个文档主要介绍内存的热插拔技术的使用以及该技术的当前情况。因为该技术当期人然处于开发阶段,所以这个的内容经常会发生变化。
这个文档介绍的内容有一下几个方面:
1.介绍:
1.1 内存热插拔的目的;
1.2 内存热插拔的几个阶段;
1.3上、下线操作的内存单元;
2.内核的配置;
3.内存热插拔的sysfs文件;
4.物理内存的热添加阶段;
4.1 硬件(固件)的支持;
4.2 手动通知内存热添加事件;
5 .逻辑内存的热添加阶段;
5.1逻辑内存的状态;
5.2怎样去上线逻辑内存;
6.逻辑内存的移除;
6.1逻辑内存的下线和ZONO_MOVABLE
6.2 怎样去下线逻辑内存;
7.物理内存的移除;
8.内存热插拔事件的通知;
一.介绍:
1.1 内存热插拔的目的:
内存热插拔允许使用者去增加或减少内存的大小。这样做通常出于两种目的:
第一种:改变内存的大小;
第二种:从物理上安装或移除DIMMs或NUMA(not uniform memory access)节点。这样时为了交换DIMMS或NUMA节点,降低电量的消耗。
第一种主要应用于高度虚拟化环境之中。
第二种主要应用于支持电源管理的硬件。
设计内存热插拔就是为了以上两个目的。
1.2 内存热插拔的阶段:
内存热插拔分为两个阶段。
1) 物理内存热插拔阶段。
2)逻辑内存热插拔阶段。
第一个阶段目的是为了与硬件进行通信和构建或擦出热插拔内存需要的环境。基本上,对第二种目的来说这个阶段时必须的,
当内存被热插拔时,内核会识别新的内存,创建新的内存管理表和对新内存进行操作的sysfs文件。
如果固件支持操作系统中的新内存的连接通知,那么这个阶段会被自动的出发。ACPI可以通知系统中有新的内存加入这个事件。如果你的固件不支持的话,系统管理员可以使用“探索”操作。
逻辑内存热插拔阶段是为了去改变内存的状态,使内存对使用者可用或不可用。通过这个阶段,使用者所看到的内存的大小发生了变化。当一段内存可用时,内核把这段内存划分为许多可以自由使用的页。
在这个文档中,逻辑内存热插拔阶段被描述为上线、下线。
通过系统管理员对sysfs文件进行写操作,可以触发逻辑内存热插拔阶段。对这种热添加情况,在物理热插拔完成以后,逻辑热插拔阶段必须被执行通过手动操作。但是,如果你写了一个为内存热插拔的udev's hotplug scripts脚本的话,这些阶段可以被透明的执行。
1.3 上、下线操作的内存单元。
内存热插拔使用稀疏内存模型(SPARSEMEM)。SPARSEMEM把整个内存划分为大小相同的块。每一块也被叫做一段。每一段的大小是多少取决与计算机的体系结构。例如,power架构中每一段的大小为16MB,ia64架构中每一段的大小为1GB。对内存进行上下线操作是以内存段为单位的。
为了去知道内存段的大小,你可以去看看你系统中的这个文件:
/sys/devices/system/memory/block_size_bytes 单位是字节。
2. 内核的配置。
为了使用内存热插拔功能,内核必须得按照下面的配置选项进行编译。
---对所有内存热插拔:
内存模型---> 稀疏内存(Sparse Memory) (CONFIG_SPARSEMEM)
允许内存热添加( CONFIG_MEMORY_HOTPLUG )
---为了能移除内存,下面的选项是必须的
允许内存热移除(CONFIG_MEMORY_HOTREMOVE)
页迁移(CONFIG_MIGRATION)
---对ACPI(Advanced Configuration And Power Interface)内存热插拔,下面的配置选项也是必须的:
内存热插拔(该选项在ACPI支持菜单下面) (CONFIG_ACPI_HOTPLUG_MEMORY)
这个选项可能时内核模块。
---作为一个相关的配置,如果你的计算机支持通过ACPI进行NUMA(Non Uniform Memory Access)节点热插拔,
那么下面的这个选项也是必须的。
ACPI0004,PNP0A05 ,PNP0A06 Container Driver(在ACPI支持菜单下面 under ACPI Support Menu) (CONFIG_ACPI_CONTAINER)
这个选项也可能是个内核模块。
4. 内存热插拔sysfs文件
在/sys/devices/system/memory目录下按照 /sys/devices/system/memory/memoryXXX 形式存放着所有的内存段的设备信息.(XXX是内存段的ID号)
现在,XXX是通过start_address_of_section / section_size 来计算出来的。
例如,假设一个内存段的大小为1GB. 一个起始地址为0X1 0000 0000的内存设备在 /sys/devices/system/memory目录下面的文件是 :/sys/devices/system/memory/memory4( 0X1 0000 0000 / 1GB = 4). 这个设备覆盖的地址范围
[ 0X1 0000 0000 ---- 0X1 4000 0000)
在每个内存段设备文件下面,你可以看到4个文件:
/sys/devices/system/memory/memoryXXX/phys_index
/sys/devices/system/memory/memoryXXX/phys_device
/sys/devices/system/memory/memoryXXX/state
/sys/devices/system/memory/memoryXXX/removable
‘phys_index’ : 只读文件并且包含内存段的ID号,像XXX.
‘state’ : 可读写;
用于读时: 包含内存的上下线状态;
用于写时: 使用者可以指定“online”, “offline” 命令.
‘phys_device' : 只读 : 被设计用来显示物理内存的设备名.
‘removable' : 只读 : 包含一个整数值,这个整数值表示内存段是否可移除.
整数1,代表这个内存段是可移除。
整数0,代表这个内存段是不可移除的。
注意:在物理内存热插拔阶段之后,这些目录/文件才会出现。
如果CONFIG_NUMA选项可用,可以通过在 /sys/devices/system/node/node* directories 下面的符号链接来访问/sys/devices/system/memory/memoryXXX 内存段目录。
例如 /sys/devices/system/node/node0/memory9---->../../memory/memory9
4. 物理内存热添加阶段
4.1 硬件(固件)支持
在x86_64/ia64 平台上,ACPI支持内存热插拔。
一般来说,支持内存热插拔的固件(ACPI)定义了_HID “PNP0C80” 内存类对象。当PNP0C80接受到一个通知时,Linux's ACPI handler(Linux ACPI处理程序/函数)把热添加内存加入到系统,并且调用一个 hotplug udev scripts.
这个动作将会被自动完成。
但是内存热插拔脚本通常不会被包含在 udev 包中。你可能必须自己去写这些脚本或者手动的上下线内存。这个文档也介绍了怎样上线内存和怎样下线内存。
如果固件支持NUMA-node热插拔,并且定义一个 _HID “ACPI0004” , “PNP0A05”, 或“PNP0A06”对象。当对象接受到一个通知时,ACPI handler对定义在它里面的所有对象调用 hotplug 代码。如果找到了内存设备,memory hotplug代码将会被调用。
4.2 手动通知内存热添加事件。
在一些环境中,尤其是虚拟环境,固件将不会向内核通知内存添加事件。对于这些环境,可以使用“探索”接口(probe interface)。 探索接口依赖于 CONFIG_ARCH_MEMORY_PROBE选项。
现在,CONFIG_ARCH_MEMORY_PROBE仅仅被powerpc支持,但是CONFIG_ARCH_MEMORY_PROBE中并不包含与体系结构密切相关的代码。所以请添加配置如果你需要探索接口的话。
探索接口位于: /sys/devices/system/memory/probe
你可以通过 % echo start_address_of_new_memory > /sys/devices/system/memory/probe 来告诉内核新的物理内存的地址。那么,[ start_address_of_new_memory , start_address_of_new_memory + section )(新的内存的起始地址, 新的内存的起始地址 + 内存段的大小) 内存范围就会被热添加。在这种情况下,热插拔脚本并不会被调用(the current implementation)。你将必须自己去上线内存。Please see “How to online memory” in the text.
5.逻辑内存热添加阶段。
5.1 内存的状态
通过 % cat /sys/devices/system/memory/memoryXXX/state 文件去了解内存段的上下线状态。
如果内存段处于online state, 你将看到“online”;
如果内存段处于offline state, 你将看到 “offline”;
5.2 怎么去上线内存
即使内存已经被热添加了,被热添加的内存并不是处于ready-to-use的状态。
为了使用被添加的新的内存,你必须去上线(online)内存段。
为了上线新的内存,你必须要去写“online” 到内存段的状态文件中,像:
% echo online > /sys/devices/system/memory/memoryXXX/state。 之后,memoryXXX内存段的状态将是“online” 并且可用的内存大小也会增加。
目前,新添加的内存将会被添加到ZONE_NORMAL(for powerpc ZONE_DMA)。This may be changed in future.
6. 逻辑内存的移除
6.1 逻辑内存的下线和ZONE_MOVABLE。
内存下线比内存上线更加的复杂。因为内存下线必须使整个内存段都不未使用,如果内存段中包含有仍未释放的内存的换,内存下线操作就可能失败。
一般来说,内存下线使用2种技术。
第一种技术,回收(reclaim)并且释放在内存段中的所有内存。
第二种技术,迁移在内存段中的所有的页。
在目前的实现,Linux的内存下线使用第二种技术,通过页迁移来释放内存段中的所有的页。但是并不是所有的页都是可迁移的。在当前的Linux下,可迁移的页是匿名页(anonymous pages)和页缓存。由于通过迁移页来下线一个内存段,所以内核必须确保内存段中只包含可迁移的页。
现在,制作一个由可迁移的页组成的内存段的引导选项已经可以用了。通过指定“kernelcore= ” 或者“movablecore=” 引导选项,你可以创建一个ZONE_MOVABLE区域,这个区域被用于可移动的页。(See also Documentation/kernel-parameters.txt)
假设在引导时,系统的内存大小为“TOTAL”,这个引导选项按照如下方式创建ZONE_MOVABLE。
1.当使用kernelcore=YYYY引导选项时,为可移动页的内存大小为: TOTAL – YYYY,而不是 YYYY。
2.当使用movablecore= ZZZZ引导选项时,为可移动页的内存大小为: ZZZZ ,而不是TOTAL – ZZZZ。
注意:不幸的是,没有任何信息去显示那个内存段是属于ZONE_MOVABLE。
6.2 怎样去下线一个内存段
你可以通过使用与内存段上线相同的系统文件去下线一个内存段。
% echo offline > /sys/devices/system/memory/memoryXXX/state 。如果下线操作成功,这个内存段的状态将会被改变为“offline”。如果下线操作失败,内核将会返回一些错误值(像:-EBUSY)。即使一个内存段不属于ZONE_MOVABLE,你也可以尝试去下线它。如果它中没有不可移动的内存的话,你将会成功。
处于ZONE_MOVABLE的内存段被认为是能很容易下线的。但是如果这样的内存段处于繁忙的状态下,对它们进行下线,系统会返回-EBUSY。即使一个内存段由于-EBUSY而不能下线,你可以重试,并且有可能会成功与有可能失败。
Consideration:
内存热插拔的设计方向是为了使内存下线的可能性更高,确保在任何状况下都可以拔下内存。但是它需要更好的工作。在一些状况下返回-EBUSY是非常好的,因为使用者可以自己去决定是否需要再次的重试。目前,内存下线代码会每120seconds去做重试统计。
7.物理内存的移除
需要更好的实现但是...
--- 操作系统去通知固件硬件移除工作完成。
---如果不需要的话,避免移除。
8.内存热插拔事件通知者
内存热插拔有事件通知者(notifer)。有6中类型的通知。
MEMORY_GOING_ONLINE
在新的内存变为可用之前产生,目的是为了能准备子系统去处理内存。页分配器仍然不能从新的内存中分配页。
MEMORY_CANCEL_ONLINE
如果MEMORY_GOING_ONLINE失败,产生。
MEMORY_ONLINE
当内存已经成功的完成上线时产生。回调函数(callback)可以从新的内存中分配页。
MEMORY_GOING_OFFLINE
开始运行内存下线进程的时候产生。不再在可能再从内存中分配页了,但是将被下线的内存段中仍然有一些内存在使用。回调函数可以去释放已知的内存到子系统。这个子系统来自于指示的内存段。
MEMORY_CANCEL_OFFLLINE
如果MEMORY_GOING_OFFLINE失败,产生。来自于我们试图去下线的内存段的内存再一次可用了。
MEMORY_OFFLINE
内存下线完成以后产生。
通过hotplug_memory_notifier( callback_func, priority ) 可以注册一个回调程序。
回调函数的第二个参数是以上的六种类型。回调函数的第三个参数是一个指向struct memory_notify的指针。
Struct memory_notify
{
unsigned long start_pfn; // 是上下线内存段的start_pfn
unsigned long nr_pages; // 是上下线内存段的页数
int status_change_nid;
}
当节点掩码的N_HIGH_MEMORY是设置/清空, status_change_nid是被设置节点的ID。这意味着一个新的节点通过上线获取了一个新的内存,并且一个节点失去了所有的内存。如果,status_change_nid 是 -1,那么节点掩码的状态不能被改变。如果status_change_nid >= 0,回调函数将可以为这个节点创建/抛弃结构体,如果需要的话。
转载于:https://blog.51cto.com/weiguozhihui/1568258