Linux文件系统与持久性内存介绍
2020-08-16 18:17:40 阿炯


1、Linux 虚拟文件系统介绍

在 Linux 系统中一切皆文件,除了通常所说的狭义的文件以外,目录、设备、套接字和管道等都是文件。

文件系统在不同的上下文中有不同的含义:
在存储设备上组织文件的方法,包括数据结构和访问方法,到存储设备。
按照某种文件系统类型格式化的一块存储介质。我们常说在某个目录下挂载或卸载文件系统, 这里的文件系统就是这种意思。
内核中负责管理和存储文件的模块,即文件系统模块。

Linux文件系统的架构如下图所示,分为用户空间、内核空间和硬件3个层面:



注意:上图中方块对齐关系,很多时候我们分不清内核文件系统中 "cache" 和 "buffer" 的区别,毕竟两者都可以翻译为 "缓存区",但是从图中,就可以很清晰的看出所谓的 "cache" 其实指的就是图中的 "页缓存" 它是针对文件来说的,除了 "DAX"(直接访问方式的设备)它不使用 "缓存",其他的闪存类,块设备类设备都会使用到 "页缓存" 也就是 "cache",而 "buffer" 其实指的就是图中的 "块缓存" 它是针对块设备的。



1.1、硬件层面

外部存储设备分为块设备、闪存和 NVDIMM 设备 3 类,块设备主要有以下两种:

机械硬盘:机械硬盘的读写单位是扇区。访问机械硬盘的时候,需要首先沿着半径 方向移动磁头寻找磁道,然后转动盘片找到扇区。

闪存类块设备:使用闪存作为存储介质,里面的控制器运行固化的驱动程序,驱动 程序的功能之一是闪存转换层(Flash Translation Layer,FTL),把闪存转换为块设备, 外表现为块设备。常见的闪存类块设备是在个人计算机和笔记本电脑上使用的固态硬盘 splid State Drives,SSD),以及在手机和平板电脑上使用的嵌入式多媒体存储卡(embedded Multi Media Card,eMMc)和通用闪存存储(Universal Flash Storage,UFS)。 闪存类块设备相对机械硬盘的优势是:访问速度快,因为没有机械操作:抗振性很高, 便于携带。

闪存(Flash Memory)的主要特点如下:
在写入数据之前需要擦除一个擦除块,因为向闪存写数据只能把一个位从 1 变成 0,不能从 0 变成 1,擦除的目的是把擦除块的所有位设置为 1
一个擦除块的最大擦除次数有限,NOR闪存的擦除块的最大擦除次数是 10^4~10^3, NAND 闪存的擦除块的最大擦除次数是 10^3~10^6。

闪存按存储结构分为 NAND 闪存和 NOR 闪存,两者的区别如下:
NOR闪存的容量小,NAND 闪存的容量大。
NOR 闪存支持按字节寻址,支持芯片内执行(eXecute In Place,XIP),可以直接 在闪存内执行程序,不需要把程序读到内存中; NAND 闪存的最小读写单位是页或子页, 一个擦除块分为多个页,有的 NAND 闪存把页划分为多个子页。
NOR 闪存读的速度比 NAND 闪存块,写的速度和擦除的速度都比 NAND 闪存慢
NOR 闪存没有坏块;NAND 闪存存在坏块,主要是因为消除坏块的成本太高 NOR 闪存适合存储程序,一般用来存储引导程序比如 uboot 程序;NAND 闪存适 合存储数据。

为什么要针对闪存专门设计文件系统?主要原因如下:
NAND 闪存存在坏块,软件需要识别并且跳过坏块。
需要实现损耗均衡( wear leveling),损耗均衡就是使所有擦除块的擦除次数均衡, 避免一部分擦除块先损坏。

机械硬盘和 NAND 闪存的主要区别如下:
机械硬盘的最小读写单位是扇区,扇区的大小一般是 512 字节:NAND 闪存的最 小读写单位是页或子页。
机械硬盘可以直接写入数据:NAND 闪存在写入数据之前需要擦除一个擦除块。
机械硬盘的使用寿命比 NAND 闪存长:机械硬盘的扇区的写入次数没有限制:NAND 闪存的擦除块的擦除次数有限。
机械硬盘隐藏坏的扇区,软件不需要处理坏的扇区:NAND 闪存的坏块对软件可 见,软件需要处理坏块。

NVDIMM(Nonn-Volatile DIMM,非易失性内存:DIMM 是 Dual-Inline-Memory-Modules 的缩写,表示双列直插式存储模块,是内存的一种规格)设备把 NAND 闪存、内存和超级电容集成到一起,访问速度和内存一样快,并且断电以后数据不会丢失。在断电的瞬间, 超级电容提供电力,把内存中的数据转移到 NAND 闪存。



1.2、内核空间层面

在内核的目录 fs 下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的 文件操作接口,为了使不同的文件系统实现能够共存,内核实现了一个抽象层,称为虚拟文件系统(Virtual File System,VFS),也称为虚拟文件系统切换(Virtual Filesystem Switch,VFS) 文件系统分为以下几种。

块设备文件系统,存储设备是机械硬盘和固态硬盘等块设备,常用的块设备文件 系统是 EXT 和 btrfs。EXT 文件系统是 Linux 原创的文件系统,目前有 3 个 成版本:EXT[2-4]。

闪存文件系统,存储设备是 NAND 闪存和 NOR 闪存,常用的闪存文件系统是 JFFS2 ,(日志型闪存文件系统版本2, Journalling Flash File System version2)和 UBIFS(无序区块镜像文件系统, Unsorted Block Image File System)。内存文件系统的文件在内存中,断电以后文件丢失,常用的内存文件系统是 tmpfs, 用来创建临时文件。

伪文件系统,是假的文件系统,只是为了使用虚拟文件系统的编程接口,常用的 伪文件系统如下所示:
    sockfs,这种文件系统使得套接字(socket)可以使用读文件的接口 read 接收报文, 使用写文件的接口 write 发送报文。
    proc 文件系统,最初开发 proc 文件系统的目的是把内核中的进程信息导出到用户空间, 后来扩展到把内核中的任何信息导出到用户空间,通常把 proc 文件系统挂载在目录 "proc" 下。
    sysfs,用来把内核的设备信息导出到用户空间,通常把 sysfs 文件系统挂载在目录 "/sys"下。
    hugetlbfs,用来实现标准巨型页。
    cgroup 文件系统,控制组(control group cgroup)用来控制一组进程的资源, cgroup 文件系统使管理员可以使用写文件的方式配置 cgroup。
    cgroup2 文件系统, cgroup2 是 cgroup 的第二个版本, cgroup2 文件系统使管理员可 以使用写文件的方式配置 cgroup2。

这些文件系统又各自有着相关的特性:

页缓存:访问外部存储设备的速度很慢,为了避免每次读写文件时访问外部存储设备,文件系统模块为每个文件在内存中创建了一个缓存,因为缓存的单位是页,所以称为页缓存。

块设备层:块设备的访问单位是块,块大小是扇区大小的整数倍。内核为所有块设备实现了统一 的块设备层。

块缓存:为了避免每次读写都需要访问块设备,内核实现了块缓存,为每个块设备在内存中创 建一个块缓存。缓存的单位是块,块缓存是基于页缓存实现的。

IO 调度器:访问机械硬盘时,移动磁头寻找磁道和扇区很耗时,如果把读写请求按照扇区号排序, 可以减少磁头的移动,提高吞吐量。IO 调度器用来决定读写请求的提交顺序,针对不同的 使用场景提供了多种调度算法:NOOP(No Operation)、CFQ(完全公平排队, Complete Fair Queuing)和 deadline(限期)。NOOP 调度算法适合闪存类块设备,CFQ 和 deadline调度算 法适合机械硬盘。

块设备驱动程序:每种块设备需要实现自己的驱动程序。

内核把闪存称为存储技术设备( Memory Technology Device,MTD),为所有闪存实现 了统一的 MTD 层,每种闪存需要实现自己的驱动程序。针对 NVDIMM 设备,文件系统需要实现 DAX(Direct Access直接访问:X 代表 eXciting,没有意义,只是为了让名字看起来酷),绕过页缓存和块设备层,把 NVDIMM 设备里面的内存直接映射到进程或内核的虚拟地址空间。

libnvdimm 子系统提供对 3 种 NVDIMM 设备的支持:持久内存(persistent memory,PMEM) 模式的 NVDIMM 设备,块设备(block,BLK)模式的 NVDIMM 设备,以及同时支持PMEM 和 BLK 两种访问模式的 NVDIMM 设备。PMEM 访问模式是把 NVDIMM 设备当作内存,BLK 访问模式是把 NVDIMM 设备当作块设备。每种 NVDIMM 设备需要实现自己的驱动程序。



2、下一代存储技术NVIDMM

NVDIMM (Non-Volatile Dual In-line Memory Module) 是一种可以随机访问的, 非易失性内存。非易失性内存指的是即使在不通电的情况下, 数据也不会消失。因此可以在计算机掉电 (unexpected power loss), 系统崩溃和正常关机的情况下, 依然保持数据。 NVDIMM 同时表明它使用的是 DIMM 封装, 与标准DIMM 插槽兼容, 并且通过标准的 DDR总线进行通信。考虑到它的非易失性, 并且兼容传统DRAM接口, 又被称作Persistent Memory。

2.1、种类

目前, 根据 JEDEC 标准化组织的定义, 有三种NVDIMM 的实现。分别是:

NVDIMM-N

指在一个模块上同时放入传统 DRAM 和 flash 闪存,计算机可以直接访问传统 DRAM。支持按字节寻址,也支持块寻址。通过使用一个小的后备电源,为在掉电时数据从 DRAM 拷贝到闪存中提供足够的电能;当电力恢复时再重新加载到 DRAM 中。



NVDIMM-N示意图

NVDIMM-N 的主要工作方式其实和传统 DRAM是一样的。因此它的延迟也在10的1次方纳秒级。 而且它的容量,受限于体积,相比传统的 DRAM 也不会有什么提升。

同时它的工作方式决定了它的 flash 部分是不可寻址的,而且同时使用两种介质的作法使成本急剧增加,但是 NVDIMM-N 为业界提供了持久性内存的新概念。目前市面上已经有很多基于NVIMM-N的产品。

NVDIMM-F

指使用了 DRAM 的DDR3或者 DDR4 总线的flash闪存。我们知道由 NAND flash 作为介质的 SSD,一般使用SATA,SAS 或者PCIe 总线。使用 DDR 总线可以提高最大带宽,一定程度上减少协议带来的延迟和开销,不过只支持块寻址。

NVDIMM-F 的主要工作方式本质上和SSD是一样的,因此它的延迟在 10的1次方微秒级。它的容量也可以轻松达到 TB 以上。

NVDIMM-P

这是一个目前还没有发布的标准 (Under Development),预计将与 DDR5 标准一同发布。按照计划,DDR5将比DDR4提供双倍的带宽,并提高信道效率。这些改进,以及服务器和客户端平台的用户友好界面,将在各种应用程序中支持高性能和改进的电源管理。

NVDIMM-P 实际上是真正 DRAM 和 flash 的混合。它既支持块寻址, 也支持类似传统 DRAM 的按字节寻址。 它既可以在容量上达到类似 NAND flash 的TB以上, 又能把延迟保持在10的2次方纳秒级。

通过将数据介质直接连接至内存总线,CPU 可以直接访问数据,无需任何驱动程序或 PCIe 开销。而且由于内存访问是通过64 字节的 cache line,CPU 只需要访问它需要的数据,而不是像普通块设备那样每次要按块访问。

Intel 公司在2018年5月发布了基于3D XPoint™ 技术的Intel® Optane™ DC Persistent Memory。可以认为是NVDIMM-P 的一种实现。



Intel® Optane™ DC Persistent Memory

2.2、硬件支持

应用程序可以直接访问NVDIMM-P, 就像对于传统 DRAM那样。这也消除了在传统块设备和内存之间页交换的需要。但是向持久性内存里写数据是和向普通DRAM里写数据共享计算机资源的。包括处理器缓冲区, L1/L2缓存等。

需要注意的是, 要使数据持久, 一定要保证数据写入了持久性内存设备, 或者写入了带有掉电保护的buffer。软件如果要充分利用持久性内存的特性, 指令集架构上至少需要以下支持:

写的原子性

表示对于持久性内存里任意大小的写都要保证是原子性的, 以防系统崩溃或者突然掉电。IA-32 和 IA-64 处理器保证了对缓存数据最大64位的数据访问 (对齐或者非对齐) 的写原子性。 因此, 软件可以安全地在持久性内存上更新数据。这样也带来了性能上的提升, 因为消除了copy-on-write 或者 write-ahead-logging 这种保证写原子性的开销。

高效的缓存刷新(flushing)

出于性能的考虑, 持久性内存的数据也要先放入处理器的缓存(cache)才能被访问。经过优化的缓存刷新指令减少了由于刷新 (CLFLUSH) 造成的性能影响。

a. CLFLUSHOPT 提供了更加高效的缓存刷新指令

b. CLWB (Cache Line Write Back) 指令把cache line上改变的数据写回内存 (类似CLFLUSHOPT),但是无需让这条 cache line 转变成无效状态(invalid, MESI protocol),而是转换成未改变的独占状态(Exclusive)。CLWB 指令实际上是在试图减少由于某条cache line刷新所造成的下次访问必然的cache miss。

提交至持久性内存(Committing to Persistence)

在现代计算机架构下,缓存刷新的完成表明修改的数据已经被回写至内存子系统的写缓冲区。但是此时数据并不具有持久性。为了确保数据写入持久性内存,软件需要刷新易失性的写缓冲区或者在内存子系统的其他缓存。 新的用于持久性写的提交指令 PCOMMIT 可以把内存子系统写队列中的数据提交至持久性内存。

非暂时store操作的优化(Non-temporal Store Optimization)

当软件需要拷贝大量数据从普通内存到持久性内存中时(或在持久性内存之间拷贝), 可以使用弱顺序, 非暂时的store操作 (比如使用MOVNTI 指令)。 因为Non-temporal store指令可以隐式地使要回写的那条cache line 失效, 软件就不需要明确地flush cache line了(see Section 10.4.6.2. of Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 1)。

小结

上面介绍了NVDIMM 的几种实现方式,以及为了发挥NVDIMM-P 的性能所做的硬件上的优化和支持。下面会继续介绍软件方面的支持,包括编程模型、编程库、SPDK方面的支持等。


在上篇的 NVDIMM介绍中,我们讲解了NVDIMM几种硬件上的实现方式,以及为了支持和优化性能所做的硬件上的改变。接下来让我们来讨论一下为了充分发挥NVDIMM的性能,软件方面做了哪些支持。有些人可能会有疑问, 为什么用起来这么麻烦?既然是持久性内存,不是应该关机什么样, 开机什么样, 就可以了吗? 其实目前来看, 这种想法还不会变为现实。 因为除了DRAM是易失性的,比如 cache,寄存器这种也是易失性的。仅仅把内存做成持久性的也不能达成这样的目的。另一个问题是, memory leak。如果发生了内存泄漏,重启一下就好了。那如果是持久性内存的泄漏呢?这也是一个很棘手的问题。Pmem有些方面类似于内存,也有些方面类似于存储。但是,通常上我们不会认为Pmem能够替代内存或存储。其实,可以把它看作是一种补充,填补了内存和存储之间巨大的差异。

SPDK 在 17.10 中开始引入对于Pmem的支持。Pmem在SPDK的bdev层暴露为一个块设备,使用快设备接口和上层进行通信。如下图所示。



从图中我们可以看到libpmemblk 把块操作转换成了字节操作。它是怎么做到的呢? 在介绍libpmemblk 和 它背后的PMDK之前, 我们了解一下基础知识。

mmap和DAX

首先,我们来看传统的I/O方式, 即缓存I/O (Buffered I/O). 大多数操作系统默认的IO操作方式都是缓存IO。该机制使IO数据缓存在操作系统的page cache 中, 也就是说, 数据会被先拷贝到操作系统的内核空间的缓冲区中,然后才会从内核空间的缓冲区拷贝到指定的用户地址空间。



在Linux 中, 这种访问文件的方式就是通过read/write 系统调用来实现,如上图。接下来, 我们比较一下内存映射IO mmap()。

接下来, 我们比较一下内存映射IO mmap()。



通过mmap获得了对应文件的一个指针,然后就像操作内存一样进行赋值或者做memcpy/strcpy. 这种我们称之为load/store操作(这种操作一般需要msync、fsync来落盘)。

mmap因为建立了文件到用户空间的映射关系,可以看作是把文件直接拷贝到用户空间,减少了一次数据拷贝。但是mmap依然需要依靠page cache。



讲完了mmap,那么DAX是什么呢?DAX即direct access,这个特性是基于mmap的。而DAX的区别在于完全不需要page cache,直接对存储设备访问,所以它就是为了NVDIMM而生的。应用对于mmap的文件操作,是直接同步到NVDIMM上的。DAX目前在XFS, EXT4, Windows的 NTFS 上都已经支持。需要注意的是, 使用这个模式,要对应用程序或者文件系统进行修改。



2.3、NVM Programming Model

NVM Programming Model 大致定义了三种使用方式。

2.3.1 最左边Management 主要是通过driver提供的API对NVDIMM进行管理, 比如查看容量信息、健康状态、固件版本、固件升级、模式配置等等。

2.3.2 中间, 作为存储快设备使用, 使用支持NVDIMM driver 的文件系统和内核, 应用程序不用做任何修改,通过标准文件接口访问NVDIMM。

2.3.3 第三种, 基于文件系统的DAX特性,通过load/store操作,不需要page cache,同步落盘,没有系统调用, 没有中断。这也是NVM Programming Model 的核心, 能够充分释放NVDIMM的性能优势。但它的缺点在于,应用程序可能需要做一下改变。

PMDK

libpmemblk 实现了一个驻留在pmem中的同样大小的块的数组。里面每个块对于突然掉电,程序崩溃等情况依然保持原子事务性。libpmemblk是基于libpmem库的,libpmem是PMDK中提供的一个更底层的库, 尤其是对于flush的支持。它能够追踪每次对pmem的store操作,并保证数据落盘为持久性数据。

除此以外, PMDK 还提供了其他编程库, 比如 libpmemobj,libpmemlog,libvmmalloc 等。感兴趣可以访问其主页获取更多信息。


结语

至此,对于NVDIMM硬件和软件上的不同, 大家都有了一个大致的认识。Intel 在2018年5月发布了基于3D XPoint™ 技术的Intel® Optane™ DC Persistent Memory,引发了NVDIMM爆点。

2.4、上述内容可做如下的概述

NVIDMM分类

NVIDMM-N:memory mapped DRAM,提供字符访问接口,在三种产品中性能最好,容量最小

NVDIMM-F:memory mapped Flush,只提供块设备接口。Nand Flush直接链接到Memory controller channel。

NVIDMM-P:Under Development,提供块设备和字符设备访问接口。

特性

NVDIMM-N:NVDIMM-N既可以用作缓存,又可以作为块存储设备来用。典型代表是类似intel 的AEP。

NVIDMM-F:不同于NVIDMM-N主要用作缓存,NVIDMM-F主要用作存储。可以用来快速构建高密度的内存池存储池。


2.4.1 构建基于NVDMM的文件系统

门为PMEM设计的文件系统是NOVA Filesystem,感兴趣的读者可以参考NOVA的github。

ZUFS作为来自于NetApp的一个项目,ZUFS的全称是Zero-copy User Filesystem。声称是实现了完全的zero-copy,甚至文件系统的metadata都是zero-copy的。ZUFS主要是为了PMEM设计,但是也可以支持传统的磁盘设备,相当于是FUSE的zero-copy版本,是对FUSE的性能的提升。

在用作DRAM的模式下:
2.4.2.1 支持全系统掉电保护, 不少场景下为了防止异常掉电丢数据的commit and flush 的两阶段提交方法,可以省略成一阶段的commit on write 的方法

2.4.2.2 为DRAM和SSD物理之间提供了一个新的存储层

2.4.2.3 由于用作DRAM的时候,其访问速度比SSD可能有1~3个数量级的提升,在一些文件系统中可以去掉对page cach的依赖,这样反而更能控制上层业务的平均延时和服务稳定性。

DAX:顾名思义,DAX就是Direct Access, bypass page cache。读写直接操作PMEM上的数据,文件系统需要在mount 的时候,加入 "-o dax"参数。DAX极大地提高了文件系统在PMEM设备上的性能,但是还有一些问题没有解决,比如:
文件系统的metadata还是需要使用page cache或buffer cache。
"-o dax" mount option是对整个文件系统的,不能做更细粒度的控制。

2.4.3 没有一个API来告诉应用访问的文件是不是可以DAX访问的。



3、NVDIMM在Linux下的实现

持久内存是一种新型的计算机储存,其速度接近动态 RAM (DRAM),但同时具备 RAM 的按字节寻址能力以及固态硬盘 (SSD) 的性能;与传统的 RAM 一样,持久内存直接安装在主板上的内存插槽中。因此,它的物理外形规格与 RAM 相同,以 DIMM 的形式提供。这些内存称为 NVDIMM:非易失性双列直插式内存模块。

不过与 RAM 不同,持久内存在多个方面类似于基于闪存的 SSD。后两者采用固态内存电路的形式,但除此之外,两者都提供非易失性储存:系统断电或者重启动后,内存中的内容会得到保留。使用这两种媒体时,写入数据的速度比读取数据要慢;两者都支持有限的重新写入周期数。最后,与 SSD 一样,如果在特定的应用方案中更适合对持久内存进行扇区级别的访问,则也可以这样做。

不同的型号使用不同形式的电子储存媒体,例如 Intel 3D XPoint,或者将 NAND 闪存与 DRAM 结合使用。另外,行业正在开发新形式的非易失性 RAM。这意味着,不同的供应商和 NVDIMM 型号会提供不同的性能和持久性特征。

由于涉及的储存技术处于早期开发阶段,不同供应商的硬件可能会施加不同的限制。因此,以下叙述适用于一般性的场合。

持久内存的速度最多比 DRAM 要慢 10 倍,但比闪存要快大约 1000 倍。可在其中按字节重新写入数据,而不像在闪存中一样,需要擦除整个扇区,然后重新写入数据。尽管重新写入周期数有限,但大部分形式的持久内存可以应对数百万次重新写入,相比之下,闪存只能应对数千个周期。

这会产生两种重要后果:使用最新的技术无法运行仅包含持久内存的系统,因此无法实现完全非易失性的主内存,必须混合使用传统的 RAM 和 NVDIMM。操作系统和应用程序将在传统的 RAM 中执行,而 NVDIMM 可提供极速的补充性储存。

由于不同供应商的持久内存的性能特征不同,程序员可能需要考虑到特定服务器中 NVDIMM 的硬件规格,包括 NVDIMM 的数量,以及它们可以装入到哪些内存插槽。显然,这会对超级管理程序的使用、不同主机之间的软件迁移等造成影响。

ACPI 标准版本 6 中定义了此新型储存子系统。但是 libnvdimm 支持该标准颁布之前的 NVDIMM,可以相同的方式使用这些内存。


3.1、持久内存 (PMEM)

与 RAM 一样,PMEM 储存提供字节级别的访问。使用 PMEM 时,单个名称空间可以包含多个交错式的 NVDIMM,使这些 NVDIMM 都可用作单个设备。可通过两种方式来配置 PMEM 名称空间。

将 PMEM 与 DAX 搭配使用
为 Direct Access (DAX) 配置 PMEM 名称空间后,访问内存时会绕过内核的页面超速缓存,并直接进入媒体。软件可以单独直接读取或写入该名称空间的每个字节。

将 PMEM 与 BTT 搭配使用
与在传统的磁盘驱动器中一样,将按扇区访问配置为以 BTT 模式运行的 PMEM 名称空间,而不是像在 RAM 中一样采用按字节寻址的模式。某个转换表机制会将访问活动批处理成扇区大小的单元。

BTT 的优点在于,储存子系统会确保将每个扇区完全写入到基础媒体,如果某项写入操作出于某种原因而失败,则会取消注册该操作。因此,无法在给定的扇区中进行部分写入。此外,对 BTT 名称空间的访问会由内核超速缓存。缺点在于BTT 名称空间不支持 DAX。


3.2、用于管理持久内存的工具

要管理持久内存,必须安装 ndctl 包。安装此包也会安装 libndctl 包,后者提供一组用户空间库用于配置 NVDIMM。这些工具通过 libnvdimm 库运行。该库支持三种类型的 NVDIMM:
PMEM
BLK
同步 PMEM 和 BLK。

ndctl 实用程序提供一系列有用的手册页,可使用以下命令访问这些:
ndctl help subcommand

要查看可用子命令的列表请使用:
ndctl --list-cmds

可用的子命令包括:
version:显示 NVDIMM 支持工具的当前版本。
enable-namespace:使指定的名称空间可供使用。
disable-namespace:阻止使用指定的名称空间。
create-namespace:从指定的储存设备创建新的名称空间。
destroy-namespace:去除指定的名称空间。
enable-region:使指定的区域可供使用。
disable-region:阻止使用指定的区域。
zero-labels:擦除设备中的元数据。
read-labels:检索指定设备的元数据。
list:显示可用的设备。
help:显示有关工具用法的信息。


3.3、设置持久内存

3.3.1 查看可用的 NVDIMM 储存

可以使用 ndctl list 命令列出系统中所有可用的 NVDIMM。在以下示例中,系统包含三个 NVDIMM,这些 NVDIMM 位于单个三通道交错集内。
ndctl list --dimms

[
 {
  "dev":"nmem2",
  "id":"8089-00-0000-12325476"
 },
 {
  "dev":"nmem1",
  "id":"8089-00-0000-11325476"
 },
 {
  "dev":"nmem0",
  "id":"8089-00-0000-10325476"
 }
]

如果结合不同的参数,ndctl list 还可以列出可用的区域。

注意:区域可能不会按数字顺序显示。

请注意,尽管只有三个 NVDIMM,但它们却显示为四个区域。

ndctl list --regions

[
 {
  "dev":"region1",
  "size":68182605824,
  "available_size":68182605824,
  "type":"blk"
 },
 {
  "dev":"region3",
  "size":202937204736,
  "available_size":202937204736,
  "type":"pmem",
  "iset_id":5903239628671731251
  },
  {
   "dev":"region0",
   "size":68182605824,
   "available_size":68182605824,
   "type":"blk"
  },
  {
   "dev":"region2",
   "size":68182605824,
   "available_size":68182605824,
   "type":"blk"
  }
]

空间以两种不同的形式显示:三个 BLK 类型的独立 64 GB 区域,或者一个 PMEM 类型的合并 189 GB 区域,后者将三个交错式 NVDIMM 中的所有空间表示为单个卷。

请注意,available_size 的显示值与 size 的显示值相同。这意味着尚未分配任何空间。

3.3.2 将储存配置为使用 DAX 的单个 PMEM 名称空间

第一个示例将三个 NVDIMM 配置成使用 Direct Access (DAX) 的单个 PMEM 名称空间。第一个步骤是创建新的名称空间。

ndctl create-namespace --type=pmem --mode=fsdax --map=memory
{
 "dev":"namespace3.0",
 "mode":"memory",
 "size":199764213760,
 "uuid":"dc8ebb84-c564-4248-9e8d-e18543c39b69",
 "blockdev":"pmem3"
}

这会创建支持 DAX 的块设备 /dev/pmem3。设备名称中的 3 继承自父区域编号(在本例中为 region3)。

--map=memory 选项从 NVDIMM 中设置出一部分 PMEM 储存空间,以便可以使用这些空间来分配称作结构页面的内部内核数据结构。这样,便可以将新的 PMEM 名称空间与 O_DIRECT I/O 和 RDMA 等功能搭配使用。

最终 PMEM 名称空间的容量之所以小于父 PMEM 区域,是因为有一部分持久内存预留给了内核数据结构。

接下来,我们校验新的块设备是否可用于操作系统:
fdisk -l /dev/pmem3
Disk /dev/pmem3: 186 GiB, 199764213760 bytes, 390164480 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

与其他任何驱动器一样,在使用该设备之前,必须先将其格式化。在本示例中,我们使用 XFS 将其格式化:

mkfs.xfs /dev/pmem3
...

接下来,可将新的驱动器装入到某个目录:
mount -o dax /dev/pmem3 /mnt/pmem3

然后,可以校验是否获得了一个支持 DAX 的设备:
mount | grep dax
/dev/pmem3 on /mnt/pmem3 type xfs (rw,relatime,attr2,dax,inode64,noquota)

结果是,我们已获得一个使用 XFS 文件系统格式化的,且装有 DAX 的 PMEM 名称空间。

对该文件系统中的文件进行任何 mmap() 调用都会返回直接映射到 NVDIMM 上的持久内存的虚拟地址,并且会完全绕过页面超速缓存。对该文件系统中的文件进行任何 fsync 或 msync 调用仍可确保将修改后的数据完全写入到 NVDIMM。这些调用会刷新通过 mmap 映射在用户空间中修改的任何页面的关联处理器超速缓存行。

3.3.2.1 去除名称空间

在创建使用相同储存的其他任何类型的卷之前,我们必须卸载此 PMEM 卷,然后将其去除。

首先卸载该卷:
umount /mnt/pmem3

然后禁用名称空间:
ndctl disable-namespace namespace3.0
disabled 1 namespace

然后删除该卷:
ndctl destroy-namespace namespace3.0
destroyed 1 namespace

3.3.3 创建使用 BTT 的 PMEM 名称空间

在下一个示例中,我们将创建使用 BTT 的 PMEM 名称空间。

ndctl create-namespace --type=pmem --mode=sector
{
 "dev":"namespace3.0",
 "mode":"sector",
 "uuid":"51ab652d-7f20-44ea-b51d-5670454f8b9b",
 "sector_size":4096,
 "blockdev":"pmem3s"
}

接下来,校验新设备是否存在:
fdisk -l /dev/pmem3s
Disk /dev/pmem3s: 188.8 GiB, 202738135040 bytes, 49496615 sectors
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

与前面配置的支持 DAX 的 PMEM 名称空间一样,这个支持 BTT 的 PMEM 名称空间也会占用 NVDIMM 中的所有可用储存。

注意:设备名称 (/dev/pmem3s) 中的尾部 s 表示扇区 (sector),可用于轻松辨别配置为使用 BTT 的名称空间。

可按前一示例中所述格式化和装入卷。

此处显示的 PMEM 名称空间不能使用 DAX,它会使用 BTT 来提供扇区写入原子性。每次通过 PMEM 块驱动程序进行扇区写入时,BTT 都会分配一个新的扇区来接收新数据。完全写入新数据后,BTT 将以原子方式更新其内部映射结构,使新写入的数据可供应用程序使用。如果在此过程中的任意时间点发生电源故障,则写入内容将会完全丢失,在这种情况下,应用程序可以访问其旧数据,而这些数据仍旧保持不变。这可以防止出现所谓"扇区撕裂"的情况。

与其他任何标准块设备一样,可以使用某个文件系统格式化这个支持 BTT 的 PMEM 名称空间,并在该文件系统中使用它。无法将该名称空间与 DAX 搭配使用。但是,此块设备中的文件的 mmap 映射将使用页面超速缓存。


3.4、使用内存(DRAM)模拟持久化内存(Persistent Memory)

3.4.1 精简版:一般内核只需要两步即可进行持久性内存模拟

1)配置 grub:
vim /etc/default/grub

在里面加入如下语句,前一个为要模拟的大小,后一个为模拟的持久性内存在内存中开始的位置。也就是从内存4G开始,划分32G来模拟持久性内存。
GRUB_CMDLINE_LINUX="memmap=32G!4G"

2)更新 grub
update-grub && reboot

3.4.2 深入分析

现在真实的持久化内存对于普通用户来说还不可用,在进行实验和测试的时候可能需要模拟持久化内存来进行使用,现在在一台主机上测试划分一块内存区域来进行持久化内存的模拟。

环境:Ubuntu 18.04,一台普通的Dell台式机,运行内存 8G。

从Linux 4.0以来,Linux内核就具备了对持久性内存设备和仿真的支持,但为了便于配置,建议使用比4.2更新的内核。在内核中,使用对文件系统的DAX扩展创建了一个支持PMEM的环境。某些发行版(如Fedora 24及更高版本)内置了DAX/PMEM支持。

要了解内核是否支持DAX和PMEM,可以使用以下命令:
# egrep '(DAX|PMEM)' /boot/config-`uname –r`

如果内置了支持就会输出类似如下的内容:
    CONFIG_X86_PMEM_LEGACY_DEVICE=y
    CONFIG_X86_PMEM_LEGACY=y
    CONFIG_BLK_DEV_RAM_DAX=y
    CONFIG_BLK_DEV_PMEM=m
    CONFIG_FS_DAX=y
    CONFIG_FS_DAX_PMD=y
    CONFIG_ARCH_HAS_PMEM_API=y

但是很遗憾,我们的Ubuntu 18.04没有内置对DAX/PMEM的支持,所以输入上述命令什么输出都没有。接下来就在Ubuntu 18.04上模拟持久化内存。由于在Ubuntu 18.04上默认没有支持DAX和PMEM,所以需要我们重新编译内核,并在编译内核的配置选项中加入相关设置。

在这里重新编译内核,选择的版本是Linux-4.15。

首先输入命令:
make nconfig

进入到如下的配置界面,配置PMEM和DAX

Device Drivers
    NVDIMM Support
        <M>PMEM;
        <M>BLK;
        <*>BTT
        <*>NVDIMM DAX

配置PMEM

先进入到Device Drivers中,在Device Drivers中找到NVDIMM Support,需要将菜单栏向下翻,里面的内容并不只是我们看到的第一页,NVDIMM Support 不在第一页上。

进入到NVDIMM Support 中,将里面的内容都选中:
    <M>PMEM;
    <M>BLK;
    <*>BTT
    <*>NVDIMM DAX

配置文件系统DAX

使用esc回到make nconfig的初始页面

File System
    <*>Direct Access support

处理器特性设置

使用esc回到make nconfig的初始页面

Processor type and features
    <*>Support non-standard NVDIMMs and ADR protected memory

其实上述所有过程,在Linux-4.15中默认都已经做了,也就是我只要 make nconfig就可以了。

所有这些配置好之后就开始编译以及安装内核:
# make -j9
# make modules_install install

然后进入到新编译的内核Linux-4.15中

使用下面的命令打印出e820表:

dmesg | grep e820

得到如下的内容:
    [0.000000] e820: BIOS-provided physical RAM map:
    [0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009d7ff] usable
    [0.000000] BIOS-e820: [mem 0x000000000009d800-0x000000000009ffff] reserved
...

上述的usable就是我们可以使用的,从中可以划分部分区域来作为我们的持久化内存,在这里建议选取:
[0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000021f5fffff] usable,其中0x0000000100000000就是4G,需要配置grub来设置:

vim /etc/default/grub

我在里面直接配置4G的空间来模拟持久化内存,在grub中添加如下语句,表示空间大小为4G,从4G内存开始的内存空间用来模拟持久化内存:
GRUB_CMDLINE_LINUX="memmap=4G!4G"

配置好后,更新grub:update-grub

使用下面的命令查看是否成功:
dmesg | grep user

可以看到,这块区域已经被模拟为了持久化内存,然后我们在主机 /dev目录下可以看到pmem0的设备,至此就可以对模拟的持久化内存进行使用了。

使用方式--建立DAX文件系统

以ext4文件系统为例
mkdir /mnt/pmemdir
mkfs.ext4 /dev/pmem0
mount -o dax /dev/pmem0 /mnt/pmemdir

 这样就将目录 /mnt/pmem挂载到了持久化内存上,这个目录在之后的使用过程中就会用到。

参考来源:如何在英特尔® 架构服务器上仿真持久性内存


3.4.3 使用memmap内核选项

pmem驱动程序允许用户基于直接访问文件系统(DAX)来使用EXT4和XFS。添加了一个新的memmap选项,该选项支持保留一个或多个范围的未分配内存以用于模拟的持久内存。memmap参数文档在Linux内核的相关页面上。这个特性是在v4.0内核中向上扩展的。kernelv4.15引入了性能改进,推荐用于生产环境。


memmap选项使用memmap=nn[KMG]!ss[KMG]格式;其中nn是要保留的区域的大小,ss是起始偏移量,[KMG]指定大小(以千字节、兆字节或千兆字节为单位)。配置选项通过GRUB传递给内核,更改GRUB菜单项和内核参数在Linux发行版本之间有所不同,下面是一些常见Linux发行版的说明。有关更多信息,请参阅正在使用的Linux发行版和版本的文档。

内存区域将标记为e820类型12(0xc),这在引导时可见,使用dmesg命令查看这些消息。
$ dmesg | grep e820

GRUB配置中的'memmap=4G!12G':保留4GB内存,从12GB到16GB。有关详细信息,请参阅如何为系统选择正确的memmap选项。每个Linux发行版都有不同的方法来修改GRUB配置,按照发行版的文档进行操作即可,下面提供了一些常见的发行版以供快速参考。

1)、Ubuntu
$ sudo vim /etc/default/grub
GRUB_CMDLINE_LINUX="memmap=4G!12G"

更新完成grub后重启机器
$ sudo update-grub2


2)、RHEL
$ sudo vi /etc/default/grub
GRUB_CMDLINE_LINUX="memmap=4G!12G"

正式开始更新grub配置
On BIOS-based machines:
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg

On UEFI-based machines:
$ sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

可以使用多个配置,下面建立了两个2G大小的名称空间
"memmap=2G!12G memmap=2G!14G" will create two 2GB namespaces, one in the 12GB-14GB memory address offsets, the other at 14GB-16GB.

主机重新启动后,应该存在一个新的/dev/pmem{N}设备,在GRUB配置中指定的每个memmap区域都有一个。这些可以使用ls/dev/pmem*显示,命名约定从/dev/pmem0开始,并为每个设备递增。/dev/pmem{N}设备可用于创建DAX文件系统。

使用/dev/pmem设备创建并装载文件系统,然后验证是否为装入点设置了dax标志,以确认启用了dax功能。下面展示了如何创建和挂载EXT4或XFS文件系统。

1)、XFS

mkfs.xfs /dev/pmem0
mkdir /pmem && mount -o dax /dev/pmem0 /pmem
mount -v | grep /pmem
/dev/pmem0 on /pmem type xfs (rw,relatime,seclabel,attr2,dax,inode64,noquota)

2)、EXT4

mkfs.ext4 /dev/pmem0
mkdir /pmem && mount -o dax /dev/pmem0 /pmem
mount -v | grep /pmem
/dev/pmem0 on /pmem type ext4 (rw,relatime,seclabel,dax,data=ordered)


如何为系统选择正确的memmap选项

为memmap内核参数选择值时,必须考虑起始地址和结束地址代表可用的RAM。使用或与保留内存重叠可能导致损坏或未定义的行为,此信息可通过dmesg在e820表中轻松获得。

下面的示例服务器具有16GiB内存,"可用"内存介于4GiB(0x100000000)和~16GiB(0x3ffffffff)之间:

$ dmesg | grep BIOS-e820
[0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bffdffff] usable
[0.000000] BIOS-e820: [mem 0x00000000bffe0000-0x00000000bfffffff] reserved
[0.000000] BIOS-e820: [mem 0x00000000feffc000-0x00000000feffffff] reserved
[0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[0.000000] BIOS-e820: [mem 0x0000000100000000-0x00000003ffffffff] usable


要保留4GiB和16GiB之间的12GiB可用空间作为模拟持久内存,语法如下:
memmap=12G!4G

重新启动后一个新的用户定义的e820表项显示范围现在是"persistent(type12)":

$ dmesg | grep user:
[0.000000] user: [mem 0x0000000000000000-0x000000000009fbff] usable
[0.000000] user: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[0.000000] user: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[0.000000] user: [mem 0x0000000000100000-0x00000000bffdffff] usable
[0.000000] user: [mem 0x00000000bffe0000-0x00000000bfffffff] reserved
[0.000000] user: [mem 0x00000000feffc000-0x00000000feffffff] reserved
[0.000000] user: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[0.000000] user: [mem 0x0000000100000000-0x00000003ffffffff] persistent (type 12)

fdisk或lsblk程序可用于显示容量,例如:
# fdisk -l /dev/pmem0
Disk /dev/pmem0: 12 GiB,  12884901888 bytes, 25165824 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

# lsblk /dev/pmem0
NAME  MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
pmem0 259:0    0  12G  0 disk /pmem

注意:大多数Linux发行版都启用了内核地址空间布局随机化(KASLR),这是由CONFIG_RANDOMIZE_BASE定义的。启用后,内核可能会在没有警告的情况下使用先前为持久内存保留的内存,从而导致损坏或未定义的行为,因此建议在16GiB或更低的系统上禁用KASLR。有关详细信息,请参阅对应的Linux发行版文档,因为每个发行版各不相同。


4、参考来源

Linux虚拟文件系统介绍
28.持久内存
Persistent Memory Documentation
下一代存储技术的先行: NVDIMM 你了解吗(上)
下一代存储技术的先行: NVDIMM 你了解吗(下)