持久内存编程

房东的猫 提交于 2020-04-06 02:31:31

持久内存编程
2013年6月我写了关于非易失性内存(NVM)的未来接口。其中描述了SNIA NVM Programming technical work group(TWG)正在开发的NVM编程模型。在过去的四年里,规范已经发布,正如预测的那样,编程模型已成为大量后续工作的重点。该编程模型,在规范中描述为NVM.PM.FILE,可以将PM当做文件被操作系统映射到内存。本文,介绍持久内存编程模型如何在操作系统中实现,已经做了哪些工作,以及我们还面临着哪些挑战。
持久内存背景
PM和storage class memory是同一的术语,具有字节寻址、加载/存储内存访问特性,但具备持久性。本文,关注将PM挂在系统内存总线上,例如DRAM DIMM,创建一类称为NVDIMMs的非易失DIMMs。
为进一步阐述所说的持久内存是什么,仅讨论NVDIMMs,允许软件像访问内存一样访问。提供了内存语义的所有优点,例如CPU CACHE一致性、其他设备直接内存访问DMA、缓冲线粒度访问,即可字节寻址。为提供这些语义,设备必须足够块以便指令访问CPU时拖延CPU合情合理。NAND flash当做持久内存时比较慢,因为需要以块为单位进行访问,并需要足够长的时间进行上下文切换。硬件访问的时间通常以毫秒为单位,NAND Flash SSD以微妙为单位,PM以纳秒为单位。依赖于硬件媒体类型,NVDIMM可能速度还比不上DRAM,但是速度已经堪比他的速度了。
现在市场上的一些NVDIMM产品,运行时使用DRAM作为媒介,断电时自动将内容备份到NAND Flash,再次上电时将NAND Flash内容返回DRAM。这些产品提供DRAM的性能,但是需要额外的部件和电池来存储数据,相比DRAM,为每个DIMM提供小容量、每G高消耗的特性。新出现的非易失性媒介,例如2015年,Intel和美光联合研发的3D XPoint技术,通过比DRAM更高的容量。每个CPU达到上T的带宽,使持久内存引起多方前沿关注:持久性、容量、消耗。
持久内存编程模型
如何是应用访问持久内存?和易失性内存不同,应用需要特定方法和指定的持久内容连接;持久内存不像易失性内存一样是匿名的,他需要像文件一样命名一个区域,这样应用才能找到他。应用需要具有访问持久内存的控制权限。推荐SNIA TWG编程模型是因为操作系统可以使用标准的文件语义提供持久内存的命名、权限和内存映射。
当前,多种操作系统包括Linux和Windows都支持了这种模型。
持久内存编程

DAX
Figure 1中显示的持久内存适配的文件系统,允许直接访问持久内存,而不经过系统的page cache。这样的特性称为DAX。持久内存编程模型和DAX特性表明持久内存文件可以使用mmap()或MapViewOfFile()类似的标志函数映射到内存。这种类型时图1最右边形式。应用直接通过load/store指令访问持久内存。允许直接访问持久媒介而不用进行用户态和内核态的切换。
存储持久化
Linux系统可以使用msync()或fsync()确保数据持久化,Windows可以通过FlushViewOfFile()和FlushFileBuffers()确保数据持久化。这些调用会创建一个内存栅,这个点之前的数据都已经全部持久化到持久内存。历史上,这个存储栅需要操作系统找到page cache中的脏页,然后将他们刷写到磁盘。由于持久内存不使用page cache,操作系统仅需要将CPU cache中的变动刷写到持久内存。如图2所示:
持久内存编程
图2中虚线部分显示了持久域。这种级别的架构,虚线部分的数据要么在DIMM,要么咋内存控制器的写请求队列WPQ。无论哪种返回,持久内存需要有足够的电量将虚线框中的数据刷写到持久媒介。这种特性叫做异步DRAM刷,并NVDIMM已经具备这种特性。
X86架构中,简单执行存储指令并不能确保数据持久化,因为数据可能仍然在CPU cache,一旦断电,这些数据就会丢失。需要额外的刷写指令确保数据持久化。下表描述了他们如何工作。
图2和表1中可能会使人迷惑,为什么Intel不将CPU cache弄到持久域部分。技术上可行,图2中的虚线框内包括CPU CACHEs。
扩展持久域包括CPU CACHEs的问题是x86的caches非常大,他需要的电量比电容器实际能提供的电量多的多。这就意味着平台需要电池。此时支持持久内存的服务器都配一个电池不太现实。但是对于硬件供应商来说,当然有可能在其商品中包含一个电池。这就允许跳过表1中描述读缓冲刷新指令,但是sfence指令仍然是必须的,因为存储屏障存储只有在全局可见时才被认为是持久的,这就是sfence确保的。
因为应用供应商计划使用电池以及未来期望所有平台都将CPU cache包含到持久域,所以在ACPI中添加一个属性,这样当跳过CPU刷时,BIOS可以通知操作系统。这就允许操作系统以最优的方式实现类似msync的调用。
刷写用户空间到持久域
WBINVD例外,Intel CPU以用户态模式支持表1中描述的指令。使用CLWB(CLFLUSHOT或CLFLUSH)刷写cache line并支持用用户态使用临时存储。
持久内存编程
这就允许从用户空间刷写到持久内存,而不需要经过内核,这个特性称为Optimized Flush。依赖于操作系统和硬件,各个平台选择性支持这个特性。尽管有CPU支持,但是对于应用程序来说只有操作系统说安全时才使用optimized flush。当文件系统元数据改变需要msync刷写时,操作系统需要这个控制点。
当前实现中支持安全的用户空间刷正不断演化。Windows的DAX由NTFS文件系统提供,包括无条件支持Optimized flush。Windows使用类似CLWB+SFENCE的指令保持数据持久化到持久内存。Linux中的ext4和xfs支持DAX,不需要考虑用户空间刷写安全性。作为临时解决方案,Linux提供Device-DAx,允许应用打开持久内存设备,将其映射到内存,利用用户空间刷写确保持久性。
Libpmem库提供函数告诉应用程序何时Optimized flush是安全的。强烈建议程序员使用libpmem来确定并使用用户空间刷写。Libpmem也被用来检测平台使用电池的情况,将刷写调用转换成简单的sfence指令调用。下文会详细介绍这个库。
持久内存挑战
内存中数据结构改变时原子性问题就出现了。其他线程访问这个数据结构时会不会仅考到修改到一半的数据?多线程编程时通常使用锁来保护数据结构。有时也会使用指令确保硬件中的原子性。本文中原子性也成为可见性,当修改提交时,另外一个线程才能看到这个线程的修改。
持久内存编程
Libpmemobj库提供事务保证,确保断电安全。在持久内存出现前,断电等中断写时,内存状态不会出现问题,因为是易失的。但是持久内存中,需要理解部分状态刷后就已经持久化。Intel仅使用8字节存储确保故障原子性。大于8字节的将不保证数据一致性。
其他挑战:管理空间。因为持久内存域被当做文件,文件系统可以管理这个空间,但是一旦被应用映射到内存,文件中发生的事情完全取决于应用。和malloc类似的函数分配的内存是易失的,在重启时不提供方法重连持久内存对,也不辞去任何步骤保证出现故障时数据一致性。所以持久内存编程中也需要着重处理空间分配问题。
地址独立性是另一个挑战。尽管技术上可以实现持久内映射到同一个地址上,但是当其他映射items大小改变时,这就不切实际了。一种地址空间布局随机化的特性会使操作系统随机调整库和文件映射地址。地址独立意味着持久内存中数据结构引用另一个使用指针的数据结构,即使文件映射到不同地址,这个指针也必须以某种方式使用。有几种方法实现这一点,例如在映射后重新定位指针,使用相对指针而不是绝对指针,或者使用某种类型的对象ID来应用驻存在持久内存中的数据结构。
NVM库
Intel开发的pmdk库,在GitHub上开源,开源协议BSD,使用手册可从http://pmem.io查看。
libpmem:基本库
这个库比较小,相对简单,包含探测CPU支持哪种刷写指令以及使用最佳指令进行范围拷贝。
libpmemobj:支持事务
Libpmemblk和libpmemlog:支持特定用户案例
Libmemkind:持久内存易失性使用
总结
2013年的ideas成熟了并加到了完整的编程模型中。Pmdk库被开发出来,以供持久内存编程使用。该库在GitHub上开源https://github.com/pmem
原文
https://www.snia.org/pm-summit2019
https://www.snia.org/pm-summit2017

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!