持久内存文件系统中通过硬件事务性内存为 Free 提供强一致性

原文链接

摘要   文件系统设计通常是性能和一致性之间的折衷。一种常见的做法是牺牲数据一致性来换取更好的性能,就好像高性能和强一致性无法同时实现一样。在本文中, 我们重新审视权衡并提出了一种轻量级的软硬件协作机制 HOP,为了展示在持久化内存(PM)文件系统中借助硬件事务性内存(HTM)达到高性能和强一致性的可行性。HOP 的关键思想是选择文件系统界面可见的更新并将它们转换为 HTM。 HOP 采用类似 FS 的乐观并发控制 (OCC) 机制来克服 HTM 容量限制,并利用协作锁作为回退来保证进度。我们应用 HOP 来构建 HTMFS,一个具有强一致性的用户空间 PM 文件系统。在评估中,与最先进的 PM 文件系统相比,HTMFS 的性能提升高达 8.4 倍,表明可以在高性能持久内存中实现强一致性。

1 简介

  文件系统是许多存储服务的关键基石,例如键值存储和数据库以及持久存储数据的应用程序。 在早期,文件系统是为性能而设计的,具有松散的一致性保证。 例如,FFS [47] 依赖于文件系统的干净卸载来避免一致性问题。 在崩溃或电力短缺的情况下,文件系统用户必须调用并等待冗长的文件系统一致性检查程序,即 fsck,它将检测一致性问题并尝试恢复但不能保证 [26]。

  如今,随着存储设备的加速及其在应用程序中的广泛使用,性能不再是应用程序需要的唯一特性。应用程序还需要强一致性才能提供可靠的服务。例如,键值存储和数据库需要强崩溃一致性来保证所有返回的写入都被持久化并且在系统崩溃后可以被正确读取。在没有或弱一致性保证的文件系统上,这些应用程序要么在一致性级别上妥协,要么使用复杂的机制来提供可靠的存储。如果文件系统可以提供强一致性,编程工作也可以被减少。

  文件系统的强一致性意味着每个请求的顺序一致性,它包括两个方面。首先,对于任意文件系统请求,并发任务观察到的文件系统状态的修改应该是原子的。大多数文件系统使用 inode 级别的锁,这样可以保证不同请求的修改顺序,保证顺序的一致性。其次,每当系统崩溃,重启后,所有先前的文件系统请求都应该满足 all-or-nothing 语义,即,对于单个文件系统所有文件系统状态修改请求,应该都被应用或都不被应用。文件系统不一定保证强崩溃一致性。例如,ZoFS [16] 不提供数据修改的原子性。假设一个写入者写到一半就崩溃了;在系统恢复后读取者可能读取部分更新的值。

  与此同时,现代存储设备变得更快、更与众不同。新兴的持久内存 (PM) 强制内存具有持久性。因此,文件系统可以使用加载/存储指令来访问性能接近 DRAM 的 PM 存储。提出了几个 PM 文件系统 [11、13、16–18、37、42、74、80] 来利用 PM 特性;其中许多提供了很强的一致性。
  然而,现有的 PM 文件系统仍然需要复杂且昂贵的机制,例如日志记录 [6, 10] 和影子分页 [7,64],以实现强大的崩溃一致性。日记有双写问题,而影子分页需要将更改传播到原子更新,因此它只适合专用数据结构。写放大与其使用的数据结构和写入的模式有关。

  以前的方法仅限于 CPU 写入的原子性单元。英特尔的受限事务内存 (RTM) [32] 可以提供多个更新的原子性。然而,文件系统自然与 RTM 不兼容。基于块设备的文件系统通过 IO 访问数据,这将中止 RTM。虽然 CPU 加载/存储指令可以直接访问持久内存,但 PM 写入操作需要借助 cache line (缓存行)下刷指令(例如 clflush、clflushopt 和 clwb)来持久化,这将中止 RTM。

  最近,英特尔推出了其第二代 Optane 持久内存产品,该产品与新的 Xeon 平台合作,支持增强的异步 DRAM 刷新 (eADR) 技术,该技术在崩溃的情况下将 CPU cache (缓存) 包含在持久性域中 [29]。特别是,一旦内存写入变得全局可见,平台就会保证内存写入的持久性,这意味着对持久内存的数据修改不再需要缓存行刷新来实现持久性。这使我们有可能将 RTM 和持久内存相结合,以同时提供原子性、并发性和持久性。

  尽管 RTM 可以与 PM 一起使用,但一些挑战阻止了 RTM-PM 直接用于 PM 文件系统。起初,用户使用文件系统来处理大数据的存储和检索。然而,RTM 在读写集大小上都有限制,因此很容易因文件数据复制而中止。其次,FS 相关系统调用的代码路径存在一定的依赖关系。例如,与路径相关的操作(如 open 和 mkdir)必须先进行路径查找,文件索引必须在读写文件之前完成。这些操作可能很长,并且可能包括不需要由 RTM 跟踪的内存访问。简单地将整个操作包装在一个 RTM 中不仅容易导致容量中止,而且还会增加冲突中止的可能性。

  在本文中,我们提出了 HOP,一种轻量级的硬件软件协作机制,用于在 PM 文件系统中提供强一致性。 HOP 建立在最新的 eADR 兼容平台之上,并利用硬件事务内存 (HTM) 来保证文件系统更新的原子持久性。为了解决 HTM 的容量限制,HOP 采用类似 OCC2 [41] 的机制将大型文件系统请求切成更小的部分,同时在执行期间保持并发一致性和崩溃一致性。为了保证文件系统的进程,HOP 设计了协作锁作为 HTM 的回退。 HOP与其他崩溃一致性机制的比较如表1所示。

  为了说明 HOP,我们实现了 HTMFS,一个基于 ZoFS 的用户空间 PM 文件系统。 在 SQLite [70] 上使用 FxMark [52]、Filebench [72]、LevelDB [24] 和 TPC-C [15] 进行的评估表明,HOP 优于最先进的 PM 文件系统,在提供强一致性的同时实现与弱一致性 FS 相似的性能。通过精心设计的细粒度并发控制,HTMFS 在竞争案例中提供了更好的性能。

论文的贡献包括:

  • HOP 的设计,一种在持久内存文件系统中提供强一致性的轻量级软硬件协作机制(§3);
  • HTMFS 的实施,它使用 HOP(§4)提供强一致性和性能;
  • 综合评估表明 HTMFS 优于最先进的持久内存文件系统,证明了 HOP 的有效性 (§5)。

2 背景和动机

在本节中,我们介绍了我们工作的背景知识和动机。

2.1 文件系统一致性和性能

  原始文件系统并不是以一致性为优先级构建的,例如,如果在干净卸载之前发生崩溃,FFS 无法保证一致性 [26]。

  一个古老的 fsck 工具只是让文件系统可以挂载 [26],但没有任何数据一致性或持久性的保证。 fsck 工具有助于恢复、修复和刷新系统。

  如果没有提供一致性的文件系统,应用程序需要负责保证一致性。例如,为了保证数据以原子方式持久化到文件 A,应用程序需要依次执行以下操作。

  1. 创建与文件 A 内容相同的文件 B;
  2. 向文件 B 写入新数据;
  3. Flush文件 B,保证新数据持久化存储;
  4. 将文件 B 重命名为文件 A;
  5. 同步目录变化;

  这显然对应用程序来说是昂贵的。因此,一些文件系统,例如 Ext4 和 NOVA [80],提供强一致性作为可选功能。

  然而,强一致性并不是免费的。 Ext4 使用 data journal 为数据提供原子更新,因此存在如表 1 所示的双写问题。NOVA 可以使用 CoW(copy-on-write)方式来原子更新数据。然而,在我们的评估部分,CoW 可能会使 NOVA 的性能降低多达 60% 以上。

2.2 持久内存和 PM 文件系统

  持久内存 (PM) 是一种新兴的存储技术,它强制执行持久化字节寻址内存。使用与易失性存储器(即 DRAM)相同的接口,写入 PM 的数据保证在电源循环期间保留。因此,存储层次结构发生了变化。

  基于这些变化,提出了几种 PM 文件系统以更好地利用 PM 特性来获得更好的性能。这些文件系统重新审视在 PM 带来的新场景中现有的崩溃一致性机制,而不是探索文件系统完全不同(且更高效)的崩溃一致性机制。唯一的区别是利用原子指令为单个缓存行提供小更新。 BPFS [13] 以树状结构组织整个文件系统,并通过影子分页和原子指令提供强一致性。 PMFS [18] 引入了细粒度的日志记录,并结合了原子指令和可选的影子分页以实现数据一致性。 NOVA [80] 是为 PM 设计的日志结构文件系统,它结合了所有原子指令、影子分页和日志记录以实现强一致性。 SoupFS [17] 重新审视 PM 软更新技术,不提供强一致性保证。

  传统文件系统,如 Ext4 和 XFS,引入直接访问模式 (DAX) 以绕过数据路径中的页面缓存,从而优化其在 PM 上运行时的性能。但是,这不会改变这些文件系统的崩溃一致性级别。

  PM 的字节寻址性和持久性也激发了几个用户空间文件系统,例如 Aerie [74]、Strata [42]、SplitFS [37]、ZoFS [16] 和 Libnvmmio [11]。 Strata 和 Libnvmmio 使用日志来保证一致性。 SplitFS 依赖底层的 Ext4 进行元数据处理。 ZoFS 采用软更新方式来保护数据修改,因此仅提供弱一致性。它首先就地更新数据,然后修改文件的大小来完成操作。但是,如果在文件大小更改之前系统崩溃,下一个读取操作可能会读取部分更新。

  总之,PM 为文件系统的设计带来了新的机遇;虽然现有的新文件系统仍然坚持现有的崩溃一致性机制,但性能和强一致性之间的权衡持续成为快速和可靠文件系统的障碍。

2.3 硬件事务内存

  事务内存为程序员提供了一种简单(有时是高效)的方法来实现并发应用程序。硬件事务内存技术,例如英特尔在 TSX [1] 中的受限事务内存 (RTM) 和 ARM 的 TME [46],为事务内存提供硬件支持。程序员只需要识别出包裹共享内存资源的临界区,并用 xbegin 和 xend 指令进行标记即可。事务内存机制将保证临界区的执行可以串行化,从而不会发生数据竞争。执行不满足可序列化要求的事务将被硬件中止,这是通过缓存一致性协议检测到的。具体来说,未提交事务的数据写入保存在该 CPU 核心的私有缓存中,并且只有在事务成功提交时才变得全局可见。

  由于与缓存实现的强关联,以下限制将导致 HTM 中止,用户应注意这一点。

  冲突中止。 HTM 使用读/写集来跟踪对内存的访问。在 HTM 中读取的缓存行被添加到读取集中,写入的缓存行被添加到写入集中。在HTM提交成功之前,如果读集中的一个缓存行被修改或者写集被另一个核访问,这个事务会因为冲突而中止。这种类型的中止可能会通过重试事务而成功。

  容量中止。 CPU 的私有缓存大小是有限的;因此,HTM 具有有限的读写集。任何超过读取或写入设置的事务都将不可避免地中止,无论事务重试多少次。

  其他中止。 除了冲突和容量中止外,其他一些来源也可能中止事务,例如中断和 HTM 不兼容指令。简单的重试是否能让交易成功,要看具体的场景。例如,如果在事务处理过程中发生页面错误,中断将导致事务中止。在这种情况下,在重试事务之前需要进行预故障(提前触发页面错误)。

2.4 PM 文件系统中的 HTM

  在基于块的存储设备时代,HTM 从来都不是文件系统一致性的选择。字节可寻址持久内存的出现为在文件系统中使用 HTM 提供了机会。然而,CPU 缓存的易失性迫使使用缓存行刷新指令来实现持久性,这在本质上与将正在运行的事务数据存储在 CPU 缓存中的 HTM 机制相冲突。直到2021年1月,Intel新平台将CPU缓存纳入了持久化域,这意味着到达CPU缓存的数据即使在死机、断电的情况下也能保证持久耐用,在持久内存上使用HTM成为可能。 它不仅限于此。 HTM 成为与持久内存一起使用的好伙伴。根据 Intel [29] 的说法,如果发生电力短缺,只有全局可见的数据才会持久。换句话说,HTM 进行中的数据修改将被丢弃,使 HTM 成为一种很好的替代方法,可以强制执行原子更新以实现文件系统中的崩溃一致性。 HTM 似乎有望用于文件系统,以同时提供崩溃一致性和并发性保证。

3 设计

  乍一看,为文件系统配备 HTM 似乎很简单:只需将每个文件系统请求包装在一对 xbegin 和 xend 中即可保证文件系统请求的 ACID。然而,现实证明这还远远不够。由于文件系统请求中的代码路径较长且操作复杂,将整个文件系统请求直接包装在硬件事务中将经常(如果不是总是)导致事务中止。在文件系统中直接采用 HTM 会导致以下三个问题:

  1. 长代码路径可能会永久导致容量中止;
  2. 长代码路径使事务更容易因数据冲突而中止;
  3. abort 事务的重试需要重复更多的工作。

  为了解决上述问题,我们设计了一种轻量级的软硬件协同机制 HOP。下面我们先介绍什么是 HOP,然后介绍我们如何使用 HOP 来构建 RTM 兼容的文件系统,即 HTMFS。

3.1 HOP

  为了缩短 HTM 中的代码路径,我们将单个文件系统操作拆分为多个小块。当结合在一起时,它们相当于将执行单个大型事务。这个想法类似于事务分片[68]。

  文件系统操作中的所有内存访问可以分为三种类型:

  1. Reads;
  2. Invisible writes:无法通过文件系统接口观察到的更新(如内存分配和影子页面的更新);
  3. Visible writes:文件系统接口可以观察到的更新(如时间戳修改、就地更新、文件大小的变化)。

   为了减轻复杂文件系统操作导致的容量中止,HOP 仅在事务中包装可见写入。不可见的更新旨在能够以最小的开销回滚,而关键读取由序列计数保护。

  更详细地说,我们首先在 RTM 之外执行所有读取和不可见写入。然后我们使用 RTM 将可见写入包装到持久内存中,以原子方式完成它们。但是,不对第一部分应用任何保护可能会导致并发错误。 HOP通过序列计数保护可能导致并发错误的字段来保证并发一致性。如图1所示,当我们要访问 RTM 外部的受保护字段时,我们会先记录对应的序列计数,然后再访问持久内存。这些序列号在进入 RTM 保护区域时会被验证,以确保只要 RTM 提交成功,其余部分在整个过程中保持不变。 如果验证失败(即有序列计数发生变化),HOP 将回滚到第一个变化点重新开始事务。比如我们发现A的seqcount没有变化,但是B的seqcount被修改了,我们就拿红色虚线“B is changed”重新记录B的seqcount,重新读取B。

  除了修改的 seqcount 之外,许多原因(在 §2.3 中介绍)也可能导致中止。如果是中断或其他原因导致的意外中止,再次重试 RTM 事务(图 1 中的“RTM 中止”)就足够了,因为回到最开始会造成不必要的开销。

  并发正确性的讨论。 接下来我们将讨论 HOP 中的所有并发场景(read-read,write-write,writeread,read-write)。 读读不会随时带来问题。 由于 HOP 中潜在的冲突写入受到 RTM 的保护,因此两个冲突的写入将导致彼此冲突中止,直到其中一个成功(或者继续相互中止,使其无法继续前进,我们将通过其他方法避免)。

  但是,写读/读写如果发生冲突,将导致 RTM 中止,因为潜在的冲突写操作都在 RTM 内部执行。任何中止都将触发图 1 中的重做,因此成功提交的事务保证不存在并发错误。

  对于读写场景,如图 2 所示,线程 1 首先读取一些受序列计数保护的变量,然后开始 RTM,验证序列号,并执行所有可见的写操作。线程 2 被简化为在某个时间点修改冲突变量。线程 2 修改的时间点可以分为三个范围,导致三种后果。

  • 结果A:如果线程 2 在线程 1 读取 seqcount 之前或线程 1 完成所有操作后修改了它,则它不会影响线程 1 的结果。
  • 结果 B:如果线程 2 在线程 1 读取 seqcount 和验证 seqcount 之间修改了 seqcount,则会导致线程 1 验证失败,从而重做整个任务。
  • 结果 C:如果线程 2 在线程 1 验证成功后(同时在 RTM 结束之前)修改了 seqcount,它会导致线程 1 中的 RTM 中止,因为它修改了线程 1 的读取集,从而重做整个任务。

  使用 HOP,我们可以打破 RTM 容量限制。然后我们将介绍 HOP 是如何通过文件系统中的一些具体操作来帮助构建 HTMFS 的。

3.2 文件操作

3.2.1 数据读取

  对于数据读取,我们使用 seqcount-based 的方法使其具有原子性。具体来说,一个文件的结构如图 3 所示,对于每一页,我们先记录最后一页的持久指针(带有序号),然后读取其内容。读取完成后,我们验证指向所有记录的指针及其序列计数没有被改变。我们会重新读取发生变化的页面,然后再次验证所有序列计数,直到此过程中的所有记录都保持稳定。 由于只有数据写入修改序列计数或持久指针,我们可以确保在整个读取操作中我们读取的页面没有变化。

3.2.2 数据写入

  数据更新是文件系统运行的基础。 HTMFS 面临的主要挑战之一是 RTM 的容量限制与文件系统操作涉及的大量数据之间的冲突。因此,直接将整个文件系统操作包装在一个 RTM 事务中将不可避免地导致容量中止,从而阻止操作完成。

  为了解决这个问题,我们提出了一种混合方法,结合了写时复制和日志记录,将数据更新转换为可以嵌入 RTM 事务中的元数据更新。

  适合单个 PM 页面的小型写入直接包装在 RTM 中。 对于大数据写入,如图 3 所示,我们的策略首先将数据写入持久内存,这样大量数据就可以用指针表示,从而可以轻松地将其嵌入到有限的 RTM 事务中。为了详细说明,我们首先分配 PM 空间来存储数据。请注意,分配信息在 DRAM 中,不会在崩溃后保留。但是数据在 PM。然后我们启动一个 RTM 事务,在这个事务中修改文件系统元数据,包括分配元数据的修改。 持久点是 RTM 提交。成功提交后,文件数据和元数据将以原子方式持久保存。事务中止后,文件系统在重新启动后不会发生任何更改,唯一的例外是文件数据被写入未分配的 PM, 这在大多数情况下是良性的。但是这些块可能在系统崩溃后泄漏了。对整个持久内存进行耗时的扫描可以帮助检索泄漏的空间。为了消除回收过程,我们设计了一个基于空闲列表的新分配器(如图 4 所示)以防止内存泄漏。

3.2.3 分配

  我们将分配分为两部分:首先,我们将分配的块移动到临时分配列表中,该列表与未分配空间列表具有相同的结构。然后我们简单地将临时分配列表中的一个事务扔到持久化分配列表。如果发生崩溃,我们将临时列表添加到未分配空间列表中以防止内存泄漏。

  为了确保文件系统不引用任何未分配的数据块,通常文件系统会在释放块之前修改(或删除)对数据块的引用。如果文件系统在对数据块的引用被删除后(当该内存块尚未被释放时)崩溃,也可能会发生内存泄漏。当我们需要释放多个数据块时,我们也可能会在释放到一半的时候崩溃。确保原子性的一种更简单的方法是将所有这些操作包装在一个事务中, 但 RTM 很可能有一个容量中止。为了解决这个问题,我们采用了类似于自由操作分配的方法。只有必须以原子方式完成的操作才放在 RTM 中,从而避免了容量中止的可能性。

3.3 目录操作

3.3.1 路径行走

  文件系统通常使用树结构来维护目录层次结构。路径遍历是文件系统中非常常见的场景。很多文件系统相关的系统调用都需要路径遍历,比如 open、mkdir、unlink 等,这些函数会先进行路径遍历,文件系统会用斜线分割完整的路径。然后它依次查找每一级路径名,从根目录开始,直到到达最后一级。然后对最后一个目录中的最后一个文件名执行指定的操作(例如打开)。

  所有需要遍历该路径的操作都会记录它访问的目录条目 (dentry) 的序列号,并在与数据写入相同的 RTM 中验证这些序列号(如图 1 所示)。以touch /a/b 为例,这个操作会先在根目录(/)下查找 dentry a。当它找到匹配的 dentry 时,会先记录该 dentry 的序号(dentry a 的 Dseq),然后读取目录 a 的 inode 号。

  然后它将开始一个事务,验证之前读取的序列号,将新的 dentry b 插入目录 /a,最后提交事务。

3.3.2 目录更新

  我们不在目录 inode 上使用锁来保护对同一目录的更新(添加/删除目录)。相反,我们在每个桶中使用一个单独的 seqcount,所有插入操作都需要修改哈希表中相应桶的 seqcount(图 5 中的 Bseq)。当同时向目录中插入多个不同的目录条目时,竞争将导致只有一个目录插入操作成功。 同时,另一个将不得不重做整个操作,因为序列号已被修改。在重做操作的过程中,操作会发现目录中已经存在同名的目录项,返回错误码EEXIST。

  在文件系统中,可以通过系统调用 rmdir 删除目录。但是,只能删除空目录,以免意外删除有用的数据。实用程序 rm 可用于删除带有参数 -r 的非空目录,这将递归地删除目录及其内容。在实现中,它会先删除目录的所有子目录,然后通过 rmdir 从文件系统树中删除空目录。此过程不会打破文件系统中只能删除空目录的限制。

  我们需要考虑这样一种情况,进程 A 试图将一个新文件 /a/b/c touch 到一个空目录 /a/b 中,而进程 B 试图删除这个空目录 /a/b。

  如图1所示,A 会先走一遍路径,记录 /a/b 的 dentry 的序列计数 Dseq。然后它将通过插入 /a/b/c 验证同一 RTM 中的序列号。如果序列号在验证之前已经改变,那么 A 将验证失败并回滚(重新查找路径)。如果 A 验证成功后改变了序号,修改这个序号(B 在另一个事务中改变)会导致 A 的事务中止。然后 A 会回滚再做。在新一轮的路径查找中,会发现目录 /a/b 不存在,这与 B 的整个操作在 A 之前完成的结果相同,不会造成任何问题。

  B 还需要在与删除此空目录的操作相同的 RTM 中验证和修改此序列号。一旦成功提交,没有走完路径的 insert 操作将无法找到这个目录(/a/b),其他的将无法验证 Dseq 或被 Dseq 的修改中止,从而保护了本案的正确性。如果 B 因为冲突被 A 中止,B 在重试时会发现目录不为空,从而返回 ENOTEMPTY,就好像是在尝试删除一个非空目录,这和 A 操作完成是一样的原子地在 B 之前。

  这个 Dseq 保证了本例中两个操作的结果与串行执行是一致的。所以不再需要使用锁来保护它的并发正确性。

3.4 其他文件类型符号链接。

  符号链接首先扩展为正常路径,新路径将返回给调度程序,调度程序将重新调度文件请求。其余的操作就像普通文件一样。

3.5 时间戳

  文件系统中有几个时间戳来记录一个文件的一些信息。

  • 访问时间戳(atime):上次访问文件的时间。
  • 修改时间戳 (mtime):上次修改文件内容的时间。
  • 更改时间戳(ctime):文件元数据最后一次更改的时间。

  很多文件系统操作(甚至是read、stat等读操作)都会修改部分时间戳。修改时间戳理论上应该与访问文件同时发生,因此它们需要以原子方式完成。我们需要在与其他操作相同的事务中修改时间戳。在这里,我们观察到在事务结束时放置对关键变量的访问和修改显着降低了由于冲突而中止的可能性。

3.6 特例:重命名

   unlink 和 rmdir 都只能从文件系统中删除叶节点(文件和空目录)。然而,重命名没有这样的限制,并且可以将文件系统子树移动到另一个位置。

  重命名是一种特殊的操作,需要从文件系统树中原子地删除一个目录或一个文件,并将其添加到另一个目录中。通常我们会对这两个目录都持有锁来保证正确性。但是,可能会发生两个重命名操作都持有锁并等待拿取对方的锁,从而导致死锁。这个问题可以通过比较两个锁,按照一定的顺序取锁来解决。 但是只取修改后的两个目录的锁,并不能防止环路的发生。如图 6 所示,目录树中有两条路径 /A/B/C 和 /X/Y/Z。有两个重命名操作;一个人想将 /A/B 重命名为 /X/Y/Z/B,另一个人想将 /X/Y 重命名为 /A/B/C/Y。

  以第一个操作为例。 1. 首先它会遍历路径并找到源目录 A/B(锁定父 inode A)和目标 /X/Y/Z(锁定 inode Z)。 2. 然后它尝试从目录A 中删除目录条目B,并在目录Z 中插入一个新的目录条目B。 3. 最后它会释放两个持有的锁。但是,在步骤 1 和步骤 2 之间,另一个操作也可能完成路径遍历并获得两个 inode(源 Y 和目标 C)。 如果没有其他保护,这两个操作都可以成功,从而导致重命名循环。所以我们需要采取额外的措施来避免循环,例如,通过添加全局重命名锁来序列化所有重命名操作。

  对于重命名过程,我们仍然采用无锁设计(HOP)。在路径行走(namex)中,我们记录了我们遍历的所有目录的序列计数(如第 3.3.1 节所述),最后检查所有序列计数是否在一个 RTM 中发生了变化。如果有变化,则从变化点重新执行 namex 操作;如果没有变化,则继续删除和添加目录项的操作。 由于上述所有操作(检查路径更改和修改目录条目)都是在同一 RTM 中完成的,因此成功的 RTM 提交可保证整个重命名操作自动完成。在前面的例子中,如果两个操作都完成路径遍历并进入目录修改步骤(步骤 2),那么当一个操作完成时,另一个操作将中止,因为它的读取集被修改,从而重新验证序列号并失败因为序列计数已被修改。然后回滚,重新走路径,发现目录树终于变了。

4 实施

  为了说明 HTMFS 的有效性,我们实施了一个新的文件系统。在比较了几个文件系统之后,我们决定基于 ZoFS [16] 实现 HTMFS,因为 ZoFS 中的所有操作都在用户空间,从而避免了由于系统调用而导致事务中止的可能性。

  总体架构如图 7 所示。ZoFS 由一个内核状态 KernFS 和一个(或多个)用户空间文件系统库组成。 HTMFS 也由两部分组成,ZoFS原来的KernFS和新的LibFS。在 ZoFS 中,整个文件系统树根据权限划分为多个区域。

4.1 KernFS

  KernFS 负责维护整个文件系统中所有区域的信息,以及所有持久内存页的属性。每个区域都有一个根页面,用于存储该区域的元数据。 KernFS 使用一个持久的哈希表来存储所有的区域,其中键是每个区域的路径前缀,值是每个区域根页面的相对地址。当一个用户空间的文件系统库需要访问一个路径时,KernFS 使用这个哈希表来找到该区域的根页面并进一步访问该区域。

  KernFS 以页面粒度全局管理所有 PM 空间。 ZoFS 使用两级分配。 KernFS 将 PM 页面批量分配给区域,每个区域进一步分配其页面以存储数据和元数据。 KernFS 跟踪每个页面的分配状态,即每个页面属于哪个区域以及哪些页面是空闲的并且可以分配。在这个过程中,ZoFS 使用一棵全局易失性红黑树来跟踪分配表中的所有空闲空间,并使用另一棵红黑树[5] 来跟踪所有已分配的空间和相应区域的根页地址。这些易失性数据结构可以在系统崩溃后轻松重建。

4.2 LibFS

  LibFS 负责管理区域内的所有元数据和数据,主要包括文件和目录。它包含§3中的所有设计。文件结构如图3所示,是一个类似于页表的三级结构,支持最大512GB的文件。当然,它可以轻松扩展以支持更大的文件。目录结构是一个哈希表,如图5所示。

  由于KernFS使用空闲列表来管理空闲空间,当一个区域向内核发出系统调用以获取更多空闲空间(Coffer_enlarge)时,KernFS返回一个空闲列表。因此,我们的 LibFS 需要将空闲列表转换为 HTMFS 可识别的版本(如图 4 所示)。这可以防止修改内核端。

  回退路径。当 RTM 失败时,我们根据返回值选择重试或回退路径。当失败的重试次数超过阈值时,我们也会走回退路径(我们在实现中选择 60,因为当最大重试次数从 10 到 100 时,它提供最佳性能。)。在回退路径中,我们使用 inode 级别的读/写锁来进行并发控制,并使用 RTM 来实现崩溃一致性。当 RTM 在回退路径中仍然失败时,我们使用日志作为最后的手段。

  正常路径上的操作会在RTM开始后首先检查写锁是否有人持有。如果写锁被另一个任务持有,操作将回滚到回退路径并尝试持有写锁。如果检查时锁没有被其他人持有,但在RTM提交前别人拿到了写锁,操作会因为读集被修改而中止,然后重试RTM操作,重新检查锁状态。

4.3 防止 RTM 中止

  RTM 中止的原因有很多,首先是 RTM 容量中止。最简单的实现是将整个文件系统调用包装在一个 RTM 中,经过实验我们发现大多数目录和文件操作都会中止容量。使用HOP之后,HTMFS解决了这类问题。

  在我们的实现中,我们发现 RTM 中止的一个常见原因是页面错误,这是无法预测的。因此,我们通过首先访问需要访问的内存并预加载要在 RTM 中止后执行的代码来防止页面错误失败。

  最后,失败是由于冲突,它返回一个特定的值。在那种情况下,HTMFS 会尝试先重试,如果没有太多竞争,这可以解决这些冲突。如果重试失败一定次数,则 HTMFS 回退到回退路径,即使用锁来保护并发控制的关键代码。在回退路径中,我们将首先采取锁定来防止并发访问,然后使用 HOP 来确保其崩溃一致性。所以还是可以满足强一致性要求的。

  还有一些其他原因,例如在 RTM 中混用 AVX 和 SSE 指令、REP-MOV* 指令中的长字符串等,这些都可能导致 RTM 中止 [31]。在实践中,我们发现memcpy使用的REP-MOV指令大概率会导致RTM abort。所以我们使用循环赋值(SSE2-MOV)来替换RTM里面的memcpy。

个人总结:通过一套机制 (HOP) 在 PM 上高效使用 RTM 来高效的实现强一致性。HOP 则是通过降低 RTM 锁范围,通过 seqCount 的验证来降低读在事务中的占用。

Fast 22 HTMFS: Strong Consistency Comes for Free with Hardware Transactional Memory in Persistent...相关推荐

  1. 论文悦读(5)——NVM文件系统之CtFS(FAST‘22)文件系统

    CtFS(FAST'22) 1. 背景(Background) 1.1 NVM 1.2 NVM文件系统 1.3 快速索引方案 2. 观察与动机(Observation & Motivation ...

  2. Strong Consistency, 强一致性技术概述

    http://horicky.blogspot.com/2009/11/nosql-patterns.html A brief history of Consensus_ 2PC and Transa ...

  3. Meltdown:Reading Kernel Memory from User Space 论文中英对照

    Meltdown:Reading Kernel Memory from User Space 翻译目录 摘要(Abstract) 一.简介(Introduction) 二.背景介绍(Backgroun ...

  4. Meltdown: Reading Kernel Memory from User Space论文翻译

    Meltdown: Reading Kernel Memory from User Space翻译 摘要(Abstract) The security of computer systems fund ...

  5.  Meltdown论文翻译【转】

    转自:http://www.wowotech.net/basic_subject/meltdown.html#6596 摘要(Abstract) The security of computer sy ...

  6. 宇宙最强,meltdown论文中英文对照版(三)

    本文由郭健郭大侠翻译,将分为三次连载完成,这是第三部分.郭大侠是蜗窝科技(http://www.wowotech.net/)的创始人,倡导"慢下来,享受技术"的健康理念,侠之大者, ...

  7. Meltdown 论文翻译

    摘要(Abstract) The security of computer systems fundamentally relies on memory isolation, e.g., kernel ...

  8. (转)一些经典的计算机书籍

    以下列表中的计算机书籍(中文版)来自微博:@程序员的那些事 粉丝的推荐.按推荐次数,从高到低往下排.如果大家还有其他计算机相关的经典书籍推荐,请在评论中留言,或者在这条微博的评论中留言,我们将继续扩充 ...

  9. Intel SGX相关文章

    Intel收集的SGX论文 https://software.intel.com/content/www/us/en/develop/topics/software-guard-extensions/ ...

最新文章

  1. Injection of @Reference dependencies failed;
  2. 小论Java类变量的隐私泄露
  3. 【小游戏】Random实现猜数字小游戏
  4. 置顶 | 2020学习单/读书单(顺境做事,逆境读书)
  5. JDBC获取新增记录的自增主键
  6. ASP.NET Core 3.x - 为什么采用新的 Endpoint Routing 路由系统
  7. linux的内置的账户_6 款面向 Linux 用户的开源绘图应用程序
  8. 前端构建工具 Gulp.js 上手实例
  9. linux职业_对Linux的好奇心导致了意外的职业
  10. 【Flink】Flink自带的测试类 AbstractStreamOperatorTestHarness
  11. wordpress静态文件加速,整合CDN
  12. 叮~AutoML自动化机器学习入门指南,来了
  13. hdu--1073--字符串处理
  14. HTML中文字携带拼音的方法
  15. Tensorflow函数学习笔记2---tf.multipy和tf.matmul
  16. 分片(primary shard replica shard)
  17. python爬取微博热搜显示到折线图_Python爬取新浪微博热搜榜-Go语言中文社区
  18. 怎么更改當前的USERENV(#39;LANG#39;)返回值 oracle
  19. 《渴望生活——梵高传》读后感
  20. 观美剧《傲骨之战》,了解诉讼融资行业现状

热门文章

  1. 独角数卡PHP自动化售货发卡网源码下载
  2. 搭建机器人电控系统——通信协议——CAN通信及其实例
  3. 《数学之美》选章精读
  4. 谷歌浏览器打不开原型图解决办法
  5. Shiro的 rememberMe 功能使用指导(为什么rememberMe设置了没作用?)
  6. 链路层、网络层、传输层、应用层长度
  7. 浅析Draw Call
  8. 多线程并发或线程安全问题如何解决?
  9. 树莓派3 之 USB摄像头安装和使用
  10. Coursera| Mathematics for maching learning | Linear Algebra