获得帮助

获取有关持久内存编程问题的解答。

论坛

概念

什么是 SNIA* NVM 编程模型?

这个存储联网行业协会(SNIA)*规范定义支持非易失性内存 (non-volatile memory,或 NVM) 的各种用户空间和操作系统内核组件之间的建议的行为。 此规范并不描述特定的 API。 反之,其意图是使共同的 NVM 行为能够被多种操作系统特定的界面所公开。 该模型中使用的一些技术包括内存映射文件、直接访问 (DAX) 及其他。 要了解更多信息,请参阅 SNIA NVM 编程模型

什么是直接访问 (DAX)?

DAX 允许直接访问存储在持久内存中或存储块设备上的文件。 如果文件系统中没有 DAX 支持,通常使用页面缓存来缓冲对文件的读出和写入,并要求额外的复制操作。

DAX 通过直接对存储设备执行读出和写入而消除了额外的复制操作。 DAX 还被用于提供由对 mmap 的调用而映射至用户空间的页面。 要了解更多信息,请参阅 文件的直接访问

持久内存感知的文件系统有什么功能?

持久内存文件系统可检测内核中是否具有 DAX 支持。 如果具有 DAX 支持,当应用程序打开此文件系统上的一个内存映射文件时,它可直接访问持久区域。 持久内存感知的文件系统的实例包括 EXT4、Linux* 上的 XFS 和 Microsoft Windows Server* 上的 NTFS。

要获得 DAX 支持,必须用 dax mount 选项来挂载文件系统。 例如,在 EXT4 文件系统上,可如下挂载:

mkfs –t ext4 /dev/pmem0
mount –o dax /dev/pmem0 /dev/pmem

在字节编址的持久内存上,文件的内存映射有何不同?

文件的内存映射是一项老技术,它在持久内存编程中发挥重要作用。

使用文件的内存映射时,即告知操作系统将该文件映射至内存,然后将此内存区域公开至应用程序的虚拟地址空间。

对于采用块存储的应用程序,当使用内存映射时,此区域被当作字节编址的存储。 在后台,页面缓存发生,正是在此处操作系统暂停应用程序以执行 I/O 操作,但底层存储只能以块的形式读写。 因此,即使只改变了一个字节,整个 4K 块被移至存储,这种方式效率低下。

对于采用持久内存的应用程序,使用内存映射的文件的区域被当作字节编址(缓存行)存储,而页面缓存被取消。

什么是原子性?

在可见性的语境中,原子性即是其他线程可见到的。 在断电原子性的语境中,原子性是断电或其他故障无法破坏的存储大小。 在 x86 处理器中,任何至内存的存储只保证 8 字节的原子性。 在实际应用程序中,数据更新可包括大于 8 字节的组块。 任何大于 8 字节的存储均不具备断电原子性,并可能导致写入损坏。

为何需要块转换表 (BTT) 来管理扇区原子性?

块转换表(Block Translation Table,或 BTT)为持久内存设备提供原子性扇区更新语义。 BTT 防止依赖扇区写入的应用程序发生写入损坏。 BTT 自身表达为层叠的块设备,并保留底层存储的一部分用于其元数据。 BTT 是一个间接寻址表,它重新映射该卷上的所有块。 BTT 可被视为一种极端简单的文件系统,其唯一目的是提供原子性扇区更新。

使软件适用于持久内存会面临哪些挑战?

实施持久内存支持的主要挑战是:

  • 确保数据的持久性和一致性
  • 检测并处理持久内存错误
清空(+fence)有何重要性?

当应用程序写入持久内存时,在其处于断电保护的域之前,并不能保证其持久性。 为确保写入处于断电保护的域中,必须在写入后清空(+fence)。

如何从用户空间中清空 CPU 缓存?

有三种方法可从用户空间中清空 CPU 缓存:

  • 使用  CLFLUSH 一次清空一个缓存行。 因历史原因,CLFLUSH 是一条序列化指令,因此,如果要清空一个区域的持久内存,就需循环执行此指令,因此,使用 CLFLUSH  意味着清空操作一个接一个发生。
  • 使用 CLFLUSHOPT  并行清空多个缓存行。 在此指令后执行  SFENCE,因其排序性微弱。 要了解这些指令的更详细信息,请在文档英特尔® 64 和 IA-32 架构 - 软件开发人员手册 - 合卷 中搜索主题 CLFLUSH—Flush Cache Line。
  • 使用 CLWB,该指令的行为类似 CLFLUSHOPT,但是缓存行可能在缓存中仍有效。
事务为何重要?

事务可用于更新大组块数据。 如果一个事务的执行中断,事务性语义的实施为应用程序提供保证:即保证代码中一个带注释节段的断电原子性。

英特尔® 事务同步扩展指令能否用于持久内存?

不能。就处理器而言,持久内存只是内存而已,而处理器能在持久内存上执行任何类型的指令。 这里的问题是原子性。 英特尔® 事务同步扩展在缓存层中实施,因此缓存的任何清空当然必须中止该事务。 如果在事务成功之后不发生清空,故障原子性和可见性原子性可能会不同步。

 
持久内存开发套件基础

什么是持久内存开发套件?

永久性内存开发套件 (PMDK) 前称非易失性内存库 (NVML),是旨在支持永久性内存感知应用程序开发的库和工具的汇集。 开源 PMDK 项目目前支持十个针对持久内存的各种使用案例的库,这些库支持 C、C++、Java* 和 Python* 语言。 PMDK 还包括用于开源工具集的 pmemcheck 插件之类的工具,valgrind,以及日益增多的文档、代码样本、教程和博客条目汇集。 这些库经过调优及符合生产质量要求的验证,并通过许可证发布,以允许在开源产品和闭源产品二者中使用。 随着新使用案例的发现,该项目不断扩大。

为何使用 PMDK?

PMDK 旨在解决永久性内存的挑战并促进永久性内存编程的采用。 它为开发人员提供以存储联网行业协会非易失性内存 (SNIA NVM) 编程模型全面实施的、历经测试并即刻可用于生产的库和工具。

存储性能开发套件(Storage Performance Development Kit,或 SPDK)与 PMDK 之间有何不同?

PMDK 针对字节编址的持久内存设计并优化。 这些库除了可配合英特尔® 傲腾™ DC 内存模块使用外,还可配合非易失双列直插式内存模块 (NVDIMM)(如 NVDIMM-N)使用。

  • SPDK 是一组库,用于写入使用块 I/O 的高性能存储应用程序。
  • PMDK 的重点是持久内存,而 SPDK 的重点是存储,不过若需要,这两组库能良好地配合。
对于 PMDK 提供哪些语言绑定?

所有库均以 C 实施,并针对 C++ 语言的 libpmemobj 库提供定制绑定。

PMDK 是否具有访问持久内存的库?

可以。 Libpmem 是一个简单的库,它检测处理器支持的清空指令的类型。 它使用针对该平台的最佳指令,以创建性能调优的例程,用于复制一定范围的持久内存。

哪些库支持事务?

可以。 有三个库:libpmemobjlibpmemblklibpmemlog

  • Libpmemobj 提供一个事务性对象存储,该对象存储提供用于持久内存编程的内存分配、事务和通用设施。
  • Libpmemlog 提供一个 pmem 驻留的日志文件。 这对于频繁追加至日志文件的程序(如数据库)非常有用。
  • Libpmemblk 支持 pmem 驻留块的阵列,这些块的大小都相同并原子性地更新。 例如,对于在 pmem 中保留一个固定大小对象的缓存的程序,该库可能会很有用。
是否可使用 malloc 分配持久内存?

不能。PMDK 提供一个接口,用于分配和管理持久内存。

PMDK 库如何测试?

这些库的功能曾在使用 DRAM 模拟的持久内存上经过验证。 在实际硬件上的测试正在进行中。

有没有使用 PMDK 的实际应用程序的实例?

可以。 例如,我们为 Redis* 增添了持久内存支持,由此允许额外的配置选项,用于管理持久性。 特别是,当以“只可追加的文件”模式运行 Redis 时,会将所有命令保存至持久内存驻留的日志文件中,而不是保存至存储在常规硬盘驱动器上的纯文本只可追加的文件中。 持久内存驻留的日志文件在 libpmemlog 库中实施。

有关 Redis 的实施和构建指令,请参阅 Libraries.io 文档

 
PMDK—libpmem

何时应使用 libpmem,何时应使用 libpmemobj?

Libpmem 提供低级持久内存支持。 如果打算自行处理跨程序中断的持久内存分配和一致性,应使用 libpmen。

大多数开发人员使用 libpmemobj,它提供:

  • 一个事务性对象存储
  • 内存分配
  • 事务
  • 用于持久内存编程的通用设施

使用 libpmem 来实施 libpmemobj。

pmem_memcpy_persist 与 pmem_persist 之间有何区别?

区别在于 pmem_persist 不复制任何东西,而仅仅清空数据至持久性(脱离 CPU 缓存)。 换言之:

pmem_memcpy_persist(dst, src, len) == memcpy(dst, src, len) + pmem_persist(dst, len)

 
PMDK—libpmemobj

术语“对象存储”、“内存池”和“布局”是何意思?
  • 对象存储:将持久性的 blob(二进制大对象)作为可变大小的对象(而非文件或块)处理
  • 内存池:公开的内存映射的文件
  • 布局:为标识一个内存池所选的字符串
什么原因导致 libpmemobj 在固态盘上运行缓慢?

PMDK 针对字节编址的持久内存设计并优化,而固态盘以块为基础。 在固态盘上运行 libpmemobj 要求事务从块寻址转换为字节寻址。 这在事务中增加了额外的时间。 而且,为读入及清空写入内容,要求将整个块从固态盘移到内存并移回。

应用程序崩溃并重启后,如何在内存映射文件中找到对象?

Libpmemobj 将内存映射区域定义为内存池,而内存池由一个布局来标识。 每个内存池有一个称为的已知位置,所有数据结构都与根挂钩。 当一个应用程序崩溃后重启时,它寻找根对象,从根对象可检索其余数据。

libpmemobj 是否支持本地或远程复制?

支持的。libpmemobj 通过在 pmempool 命令上使用 sync 选项,或者使用来自 libpmempool(3) 库的 pmempool_sync() API 而支持本地和远程复制。

对于跨多个不同内存池的事务有什么支持?

对于跨多个不同内存池(其中,每个内存池的类型相同或不同)的事务没有支持。

在 libpmemobj 中如何处理并行性?

Libpmemobj 保持一个世代数,该数字在每次打开一个 pmemobj 池时递增。 当获得一个 pmem 感知的锁(例如一个 PMEM 互斥锁)时,会将该锁与该池的当前世代数比较,以考察这是否是该池打开以来的第一次使用。 如果是第一次使用,该锁被初始化。 因此,如果持有 1000 个锁,而计算机崩溃了,所有那些锁都被丢弃,因为世代数在池打开时递增,在池关闭时递减。 这可避免必须找到并遍历所有的锁。

 
线程安全性

内存池管理函数是否线程安全?

不是。内存池管理函数不是线程安全的,因为由于运行时性能的原因,无法将共享的全域状态放入一个锁之下。

pmem_persist 是否线程安全?

不是。pmem_persis 的作用是确保传递的内存区脱离处理器缓存,而不论该内存区中存储的是什么。 存储和清空是不同的操作。 要原子性地存储及持久,在这两种操作周围都手动执行加锁。

 
PMDK—内存池管理

如果要分配 N 个特定大小的对象,如何确定 pmemobj 池的大小?

Libpmemobj 为每个池使用约 4 千字节,再加上每 16 千兆字节静态元数据的 512 千字节。 例如,一个 100 千兆字节的池需要 3588 千字节用于静态元数据。 此外,用于小型分配(小于或等于 2 兆字节)的kilobytes) 每一个内存组块(256 千字节)使用 320 字节的元数据。 而且,每一个分配的对象具有 64 字节的头。

创建大型池(大于 100 千兆字节)时如何使用 pmempool?

确保在使用 pmempool 之前保留有持久内存的一种方法是使用命令 create。 要了解更多详细信息,可键入命令 man pmempool-create

创建一个 110 GB blk 池文件。

$ pmempool create blk --size=110G pool.blk

创建最大允许的日志池文件。

$ pmempool create log -M pool.log
对单一文件中的多个池是否有支持?

没有。不支持单一文件中有多个池。 我们的库支持串接多个文件以创建单个池。

如何扩展持久内存池?

持久内存池创建后不会自动扩大。 可以使用多孔文件 (holey file) 创建一个大池,然后依赖文件系统执行其他一切操作。 然而,通常认为这种做法不能令人满意,因为这违背了传统存储解决方案的工作方式。 要了解详情,请参阅运行时可扩展区

扩展 libpmemobj 池有什么好方法?

PMDK 库依赖于文件系统的能力来支持稀疏文件 (sparse file)。 这意味着创建尽可能大的文件,而实际存储内存的使用将仅仅是实际分配的量。

是否能使用 libpmemobj 删除内存池?

不能。pmemobj_close() 函数关闭内存池,但并不删除内存池句柄。 对象存储本身仍存留在包含它的文件中,以后可重新打开。

要删除内存池,使用以下选项之一:

  • 从内存映射的文件系统(对象池)删除该文件。
  • 使用 pmempool rm 命令。

 
PMDK - 硬件和软件

PMDK 是否可配合其他 NVDIMM 使用?

可以。 PMDK 属于平台中性和供应商中性,尽管这些库经过优化,在英特尔® 傲腾™ DC 永久性内存模块上性能最佳。

访问 NVDIMM 是否需要 PMDK?

PMDK 并非必须,但对于采用持久内存编程是一种便利。 可以将 PMDK 库作为二进制使用,或者,如果是从头开始实施持久内存访问代码,可以选择参考库中的代码。

PMDK 是不是任何 Linux* 或 Microsoft Windows* 分发版的组成部分?

可以。 Suse*、Red Hat Enterprise Linux* 和 Ubuntu* 的 Linux 分发版中包括 PMDK 库,但不包括工具。

至于 Microsoft Windows,PMDK 库(但不是工具)包括在 Windows Server* 2016 和 Windows® 10 内。要了解详情,请参阅 pmem.io 博客 PMDK for Windows

要获取完整的 PMDK,请从 PMDK GitHub 储存库下载之。

PMDK 是否支持 ARM64*?

目前只支持 x86 上的 64 位 Linux* 和 Windows*。

基于英特尔® 傲腾™ 技术(如英特尔® 傲腾™ 内存和英特尔® 傲腾™ 固态盘)的块存储设备是否支持 libpmem?

不支持。PMDK 仅针对字节编址的持久内存设备设计并优化。

 
结构之上的持久内存(Persistent Memory Over Fabric,或 PMOF)

什么是 PMOF?

PMOF 允许在配备持久内存的计算机之间远程复制数据。

librpmem 和 rpmemd 的目的为何?

Librpmem 和 rpmemd 实施结构之上的持久内存。 Librpmem 是 PMDK 中的一个库,它在起始节点上运行;rpmemd 是一个新的远程 PMDK 守护进程,它将在数据被复制至的每一个远程节点上运行。 该设计利用 OpenFabrics Alliance (OFA) libfabric 应用程序级别 API,用于后端“远程直接内存访问”联网基础设施。

 
调试

如何为我的应用程序启用 libpmemlog 调试日志?

使用 -lpmemlog 选项链接该应用程序。 此选项针对性能而优化,跳过影响性能的检查,绝不在日志中记录任何跟踪信息,也不执行任何运行时断言。

包括以下各项:

  • 位于 /usr/lib/PMDK_debug 之下的、包含运行时断言和跟踪点的库
  • 环境变量 LD_LIBRARY_PATH,取决于系统上安装的调试库,该环境变量设置为 /usr/lib/PMDK_debug/usr/lib64/PMDK_debug

该库的调试版中的跟踪点使用环境变量 PMEMLOG_LOG_LEVEL 来启用。