NVDIMM内核文档(翻译)

(原文:https://www.kernel.org/doc/Documentation/nvdimm/nvdimm.txt)

标题: LIBNVDIMM: 非易失性设备

本文涉及 libnvdimm 内核子系统和libndctl用户态库

相关邮件列表:linux-nvdimm@lists.01.org

(第13版)

1. 术语

PMEM 一个支持写持久化的系统物理地址(system-physical-address)的区间。一个由PMEM组成的块设备具备DAX(direct access)功能。一个PMEM地址区间可能包含多个DIMM的交错(interleave)。

BLK 由单个DIMM提供的包括一个或多个可编程内存映射的apertures,用于访问这个DIMM的内存介质。此间接层损失了交错(interleave)的性能优势,但保证了单DIMM范围内的故障边界。

DPA DIMM Physical Address (DIMM物理地址)是针对DIMM来说的偏移量。如果系统中只有一个DIMM,那么系统物理地址(system-physical-address)和DPA就是1:1关联的。如果内存控制器上管理有多个DIMMs,那么就必须考虑交错以确定给定系统物理地址与DPA的对应关系。BLK和单DIMM DPA区间之间总是1:1对应的关系。

SPA (译者补充) 即system-physical-address,系统物理地址。

DAX 是对文件系统的扩展,用于在从PMEM块设备到进程地址空间直接映射持久性内存时,绕过page cache和内核块层。

DSM 即Device Specific Method:控制特定设备(这里指固件)的ACPI方法。

DCR 即NVDIMM Control Region Structure,被在ACPI 6 Section 5.2.25.5定义,它定义了vendor-id、device-id 和一个给定 DIMM 的接口格式。

BTT 即Block Translation Table。持久性内存是可字节寻址的,但现存的软件可能期望写操作的掉电原子性最小是一个扇区的大小(512B)。BTT是一个带有原子更新语义的间接表,可以放到 PMEM/BLK 两种块设备驱动之前,来虚拟任意的原子写扇区大小。

LABEL DIMM设备上存储的元数据,用于区分PMEM和BLK。它还能在不同BTT分区上用不同的参数来支持多个BTT。注意传统的分区表 (GPT/MBR) 是基于BLK或PMEM设备之上的。

2. 总览

LIBNVDIMM 子系统提供对三种NVDIMMs的支持:PMEM、BLK和同时支持PMEM/BLK模式的设备。这三种模式的操作被在ACPI 6的NFIT(NVDIMM Firmware Interface Table)所描述。尽管LIBNVDIMM的实现非常通用,也兼容NFIT之前的平台,但是它的设计目标是支持ACPI 6所定义NVDIMM资源所需能力的超集。内核中很多的实现主要是为了处理通过PMEM访问的DPA与通过BLK访问的DPA别名情况。 发生这种情况时,需要通过LABEL来存储DPA信息,以方便实现同一时间只能通过一种模式独占访问。

2.1. Supporting Documents

ACPI 6: http://www.uefi.org/sites/default/files/resources/ACPI_6.0.pdf NVDIMM Namespace: http://pmem.io/documents/NVDIMM_Namespace_Spec.pdf DSM Interface Example: http://pmem.io/documents/NVDIMM_DSM_Interface_Example.pdf Driver Writer’s Guide: http://pmem.io/documents/NVDIMM_Driver_Writers_Guide.pdf

2.2. Git Trees

LIBNVDIMM: https://git.kernel.org/cgit/linux/kernel/git/djbw/nvdimm.git LIBNDCTL: https://github.com/pmem/ndctl.git PMEM: https://github.com/01org/prd

3. LIBNVDIMM 的 PMEM 和 BLK

NFIT问世之前,非易失内存的描述有很多种不同的方法,但通常只有很少的描述信息,比如一个掉电非易失的系统物理地址(system-physical-address)区间。现在,NFIT标准不仅规范化了PMEM的描述,还包含了BLK的描述,以及用于控制和配置NVDIMM设备的平台信息传递入口。

对于每个NVDIMM的访问方法(PMEM或BLK),LIBNVDIMM都提供了一个块设备驱动:

3.1. PMEM (nd_pmem.ko)

驱动一个系统物理地址(system-physical-address)区间,这个区间在系统内存中是连续的,同时有可能是多DIMM交错的(即被硬件内存控制器条带化)。若PMEM是交错的,硬件平台有可能会进一步提供DIMM间是怎么交错的额外信息。

3.2. BLK (nd_blk.ko)

这个驱动用一组平台定义的 apertures 来进行I/O。一组apertures只会访问一个DIMM设备。多个窗口(apertures)允许多个并发的访问,这很像打标签的命令队列,而且可能被不同的线程和不同的CPU所使用。

NFIT标准为BLK-aperture定义了基本的格式,不过标准也允许了厂商自定义的布局,非NFIT标准的BLK实现可能有其他的BLK I/O设计。因此,"nd_blk"会调用平台特定的代码来进行I/O。一个例子在"Driver Writer’s Guide"和“DSM Interface Example”两节中定义了。

4. 为什么用BLK ?

虽然PMEM支持通过CPU load/store指令以字节级粒度直接访问NVDIMM存储介质,但是这并不能达到最佳的RAS (recovery, availability, serviceability)模型的要求。一个错误的系统物理地址访问会导致一个CPU异常,而通过BLK-aperture进行的错误地址访问则会造成块窗口在一个寄存器中产生一个错误状态。显然后者和当前用主机总线控制器连接的磁盘的错误模型更一致。

而且,如果管理员想换一条内存,以单条DIMM为服务单元显然更方便。与此相比,PMEM形式上的数据则可能会在不透明的硬件中以特定的形式交错着。

4.1. PMEM vs BLK

BLK-apertures 解决了RAS的问题,但是它也是ND(NVDIMM)子系统复杂性的主要因素。PMEM和BLK在DPA空间上的别名问题让实现变得复杂。任何一个DIMM设备的DPA区间都可能对应着一个或者多个交错DIMM的系统物理地址组,同时也可能被用它的BLK-aperture被访问。通过系统物理地址和通过BLK-aperture同时访问相同的DPA会产生未定义的结果。因此,如果DIMM设备支持两种接口的配置,会包含一个DSM功能来支持store/retrieve一个LABEL。LABEL有效地把DPA空间分成用于系统地址空间的独占空间和BLK-apeture可以访问的区域。为了简单,一个DIMM的每个交错组被允许有一个PMEM “region”,剩余的DPA空间可以被划分成任意数量的不连续范围的BLK设备。

4.2. BLK-REGIONs, PMEM-REGIONs, Atomic Sectors 和 DAX

每个REGION允许多个BLK namespace的原因之一是这样每个BLK-namespace都可以被配置成一个不同扇区大小的BTT。尽管一个PMEM设备可以被配置成BTT,但LABEL的标准并没有给PMEM namespace留有保存扇区大小这个信息的空间。

这是由于PMEM最主要的访问模型是DAX,而BTT与DAX是不兼容的。但是,如果应用或文件系统仍然需要原子扇区更新,它可以在PMEM设备上注册BTT或者分区。详见Block Translation Table "btt"一节。

5. 示例NVDIMM平台

下文中有涉及sysfs的例子都会引用如下图例:

此示例平台中,一个socket中我们有四个DIMM设备和两个内存控制器,每个对DPA的不同访问接口(BLK或PMEM)都会被用一个随机分配id的region设备来标识(region0 – region5)。

  1. DIMM0和DIMM1的第一个分区被交错为REGION0。一个单独的PMEM namespace被加入到REGION0的SPA区间内,这个PMEM namespace会用一个用户指定的名字"pm0.0"占据DIMM0和DIMM1的大部分空间。这两个DIMM从(a)开始的交错SPA区间被用作BLK-aperture了,这个区域内我们在region2和region3的基础上创建了两个BLK-aperture "namespaces" — "blk2.0"和"blk3.0",这两个标记还可以是其他任意用户定义在LABEL中的易读名字。
  2. 在DIMM0和DIMM1最后的部分我们有一个交错的SPA空间region1,它除了横跨之前两个DIMM(DIMM0和DIMM1),还包含了DIMM2和DIMM3的一部分。REGION1的一部分被划分到一个称为“pm1.0”的PMEM namespace,剩余的被用作4个BLK-aperture namespace(交错部分的每个DIMM部分)"blk2.1"、"blk3.1"、"blk4.0"和"blk5.0"。
  3. DIMM2和DIMM3除region1中"pm1.0"的部分全被用作"blk4.0"和"blk5.0"namespace,这也说明,同一个BLK-aperture namespace并不要求在DPA空间上是连续的,只要求在同一个DIMM上。

CONFIG_NFIT_TEST被开启并且nfit_test.ko这个模块被载入时,到这个总线被内核提供在设备/sys/devices/platform/nfit_test.0下,这不仅测试了LIBNVDIMM,也测试了acpi_nfit.ko驱动。

6. LIBNVDIMM内核设备模型和LIBNDCTL用户态API

以下描述的是LIBNVDIMM sysfs的布局和一个相应可通过LIBNDCTL API查看的object层级示意图。示例sysfs路径和示意图是相对示例NVDIMM平台的,这个NVDIMM平台也被LIBNDCTL的单元测试部分用作LIBNVDIMM bus。

6.1. LIBNDCTL:Context

LIBNDCTL库中的每个API调用都需要一个保存日志参数和其他库实例状态的上下文(context)。这个库是基于libabc模板的: https://git.kernel.org/cgit/linux/kernel/git/kay/libabc.git

LIBNDCTL: 实例化一个新的库context的样例:

6.2. LIBNVDIMM / LIBNDCTL: Bus

一个bus和NFIT有1:1的关系,当前对基于ACPI的系统期待是这里只有一个平台级全局的NFIT。也就是说,注册多个NFITs并非难事,标准并没有禁止它。当前的设施是支持多个bus的,并且我们也用了这种能力也在单元测试中测试多个NFIT配置。

6.2.1 LIBNVDIMM:/sys/class中的control class设备

接收传递给DIMM的DSM消息的字符设备是用它的NFIT handle被识别的:

6.2.2 LIBNVDIMM: bus
6.2.3. LIBNDCTL: bus枚举示例

找到描述示例NVDIMM平台的bus的handle:

6.3 LIBNVDIMM / LIBNDCTL: DIMM (NMEM)

DIMM设备提供一个字符设备来发送命令到硬件,并且它是一个LABELs的container。如果是被NFIT定义的,那么一个可选的’nfit’属性子目录就被加到NFIT-specifics中。

注意,DIMM的内核设备名字为"nmemX",NFIT通过"内存设备到SPA区间映射结构"(Memory Device to System Physical Address Range Mapping Structure)描述这些设备,而且并非真的需要一个物理的DIMM设备,所以我们用了一个更通用的名字。

6.3.1. LIBNVDIMM:DIMM(NMEM)
6.3.2. LIBNDCTL: DIMM枚举示例

注意,在这个例子中,我们假定"nfit_handle"用一个32位的数定义DIMM:

  • 3:0 位: 内存通道中DIMM的序号
  • 7:4 位: 内存通道序号
  • 11:8 位:内存控制器ID
  • 15:12 位:socket ID (如果有node控制器的话,在Node控制器内的socket ID)
  • 27:16 位:Node控制器ID
  • 31:28位:保留位

6.4. LIBNVDIMM / LIBNDCTL: Region

对应于每个PMEM区间或者BLK-aperture集,一个通用region设备都会被注册。在上述示例中,“nfit_test.0”bus上有个6个region,包括2个PMEM和4个BLK-aperture集。一个region映射对应一个<DIMM, DPA开始偏移量, length> 元组。

LIBNVDIMM为region设备提供一个内置的驱动。这个驱动负责处理多个region间的DPA别名映射,通过解析LABEL(如果存在的话)来识别出具有独占DPA边界的NAMESPACE设备,以供nd_pmem或nd_blk设备驱动程序使用。

除了通用的“mapping”、“interleave_ways”和“size”属性外,REGION还导出了一些常用的属性。“nstype”是这个region所使用的namespace设备的整数类型,“devtype”是DEVTYPE变量的重复,被udev存在’add‘事件中,最后,在region被SPA定义的情况下,可选的“spa_index”也被提供。

6.4.1. LIBNVDIMM:region
6.4.2. LIBNDCTL: region枚举示例

PMEM的“spa-index”(交错集id)和BLK的“nfit_handle”(dimm id)等基于NFIT-unique数据的样例region检索流程:

6.4.3. 为什么不直接编码Region类型到Region名字中?

首先由于NFIT只定义了PMEM和BLK两种接口类型,看起来我们应该简单地将region设备命名为与这两种类型分别相关的设备名。但是,ND子系统明确地允许任何类型region有更自由的命名规则,并且总是希望用户态考虑去通过region的属性识别类型,原因有4点:

  1. 现在最少已经有两种以上的region和namespace类型。比如PMEM就有两种子类型。之前我们也提到过,PMEM可能由已知DIMM设备组成否则PMEM是匿名的。对于BLK region,NFIT标准已经开始接纳厂商自己实现的驱动。region包含的实际区别是体现在region属性而非region-name或者region-devtype的。

  2. 没有子namespace的region是一种可能的配置情况。比如,NFIT允许一个没有BLK-apertue的region公布DCR。这也等价于一个DIMM只能接受控制/配置消息,而不能进行IO操作。而且,这种情况也被体现在了“mapping”为0这一属性上,相反设备名字并不能体现这一信息。

  3. 将来第三种主要的接口出现怎么办?除了厂商特定的数显外,实现一种合乎想象的PMEM/BLK外的第三种类型是有可能的。对region这一层的设备层级用一个通用的命名方法对以后内核新出现的region类型依然是有意义的。用户态可以总是依赖通用region属性,并且“mapping”,“size”等并且期待它的子设备为“namespace”。设备模型层级的通用形式让LIBNVDIMM和LIBNDCTL更均匀一致和面向未来。

  4. 相对设备名,有更健壮的方法来决定一个设备的主要类型。请看下一小节。

6.4.4. 我怎么确定一个Region的主要类型?

除了使用推荐的libndctl或者对照内核头文件/usr/include/linux/ndctl.h来解码"nstype"整数属性之外,我们还有其他一些选项。

  1. 模块别名查找 (module alias lookup)

    region或是namespace的不同类型的区别主要在于用哪种块设备驱动连接一个LIBNDIMM namespace。你可以用modalias来查看模型的结果。需要特别注意的是这种方法对厂商所提供专用驱动的情况一样适用。如果厂商用自己的驱动来代替标准的nd_blk驱动,并不会对LIBNVDIMM的其余部分造成很大的影响。

    实际上,厂商很可能还希望支持一个厂商定制的region驱动(在nd_region之外)。比如,如果一个厂商定义了他们自己的LABEL格式,就需要一个自己的驱动去解析LABEL并确定出正确的namespace。模块解析的输出比region-name和region-devtype更准确。

  2. udev

    内核的“devtype”是注册到udev数据库中的:

    并且,它被作为一个region的属性公开,但记住“devtype”并不表示sub-type的变种,脚本因此也应该去了解其他的属性已确定具体的region类型。

  3. 特定于type的属性

    以现在的情况来看,一个BLK-aperture region绝对不会有“nfit/spa_index”属性,但是一个non-NFIT的PMEM region也没有这两个属性。一个“mapping”值为0 的BLK region,是一个上文所述的不允许I/O的DIMM。一个“mapping”值为0的PMEM region则单纯表示一个SPA区间。

6.5. LIBNVDIMM / LIBNDCTL: Namespace

在解析DPA别名和LABEL所存的边界信息之后,一个region上是支持一个或多个“namespace”设备。一个“namespace”设备的出现意味着一个nd_blk或者一个nd_pmem驱动触发,来载入并注册一个磁盘/块设备。

6.5.1. LIBNVDIMM: namespace

这是一个表示三个主要namespace类型的样例布局,其中namespace0.0代表DIMM作后端的PMEM(注意它有一个‘uuid’属性),namespace2.0表示一个BLK namespace(注意他有一个‘sector_size’属性),namespace6.0表示一个匿名PMEM namespace(注意它没有‘uuid’属性,因为它不支持LABEL):

6.5.2. LIBNDCTL: namespace 枚举样例

Namespace被相对他们的parent region被索引出来,如下例所示。这些索引多数在多次开关机之间都是不变的,但是子系统并不假设它们不变。对于一个静态的namespace,应该使用‘uuid’属性枚举:

6.5.3. LIBNDCTL: namespace 创建样例

如果一个region还有可用的空间来创建一个新的namespace,idle namespaces会被内核自动。Namespace的实例化涉及到找到一个idle namespace然后配置它。大部分的设置都是可以以任意顺序执行的,唯一的限制是‘uuid’必须在设置‘size’之前设置,这可以允许内核在内部用一个静态的标识追踪DPA的分配:

6.5.4. 为什么用“Namespace”这个词?
  1. 比如为什么不是“volume”这个词?“volume”可能会导致人们将ND子系统和device-mapper等volume管理系统搞混。
  2. 这个词起源于NVMe控制器中可以创建的对子设备的描述(详情请看NVMe的标准:http://www.nvmexpress.org/specifications/)。而且NFIT namespace意味着它的能力和可配置型和NVMe namepsace是平行对应的。

6.6. LIBNVDIMM / LIBNDCTL: Block Translation Table "BTT"

BTT(设计文档:http://pmem.io/2014/09/23/btt.html)是一种可以叠层的块设备驱动,它可以被置于PMEM或者BLK namespace类型的块设备或者块设备分区上。

6.6.1. LIBNVDIMM: btt布局

每个region至少由一个称为seed设备的BTT设备开始。为了激活它,要设置“namespace”, "uuid" 和 "sector_size"等属性,然后根据region的类型,将这个设备bind到nd_pmem或者nd_blk驱动上:

6.6.2. LIBNDCTL: btt创建示例

和namespace类似,每个region上会自动创建的idle BTT 设备的。每次这个“seed”btt设备被配置和一个新的“seed”会被创建。新建一个BTT配置涉及到2部,找到BTT并让它空闲,和将它关联到一个PMEM/BLK上来消费一个namespace。

一旦新的非活跃btt seed设备被实例化好,它就将出现在region之下。

一旦一个“namespace”被从BTT移除,这个BTT实例就将被删除或者重置成默认值。删除是在设备模型层次进行的。注意为了销毁一个BTT,“info block”需要被销毁,而且介质也需要被用raw模式进行写操作。默认情况下,内核会自动识别BTT的存在并关闭raw模式。这个自动识别可以被开启raw模式的ndctl_namespace_set_raw_mode() API强制覆盖。

6.6.3 LIBNDCTL视图总结

对于一个上述示例,以下是LIBNDCTL API视角下的对象图:

发表评论

电子邮件地址不会被公开。 必填项已用*标注