技术与生活 性能之重 持久内存编程指南—乱译连载 (6. libpmem: 低层次持久内存支持)

持久内存编程指南—乱译连载 (6. libpmem: 低层次持久内存支持)

本章演示了libpmem提供的API的一个很小集。该库不会为你改变,不会提供电源失效-安全事务,不提供分配器。像libpmemobj库(下章描述)提供了所有这些任务,并内部使用libpmem以简单化flush和copy操作。

6  libpmem: 低层次持久内存支持

本章介绍libpmem,它是PMDK中最小的库。该C库非常低层,像CPU指令一样处理持久内存相关的事情,以最优方式copy数据到持久内存,以及进行文件映射。只想完全以原始方式访问持久内存的程序员,不需要提供分配器或者事务的库,只使用libpmem作为他们开发的基石。

Libpmem中检测CPU指令有效性的代码,是单调的样板代码,你无需在每个应用中都重写一遍。利用这些来自libpmem中的少量代码可以节省时间,这些代码还是经过充分测试和调优的。

对于大多数程序员来说,libpmem太低层,你可以快速浏览此章(或者跳过),把精力移向更高层,PMDK中更友好的库。处理持久化的所有PMDK库,如:libpmemobj,都构建在满足低层需求的libpmem之上。

像所有PMDK库一样,都提供在线手册。对于libpmem,地址:http://pmem.io/pmdk/libpmem/,该页上有Linux和Windows版本的手册链接。尽管PMDK项目的目的是提供跨操作系统的统一接口,但还是会有些小的不同。本章中的C代码样例可以在Linux和Windows上构建和运行。

本章样例有:

  1. simple_copy.c 是一个小程序,从源文件中copy 4k的块到持久内存上的文件中;
  2. full_copy.c 是一个更完整的copy程序,copy整个文件;
  3. manpage.c 是libpmem手册页中使用的简单示例。

6.1  使用库

为了使用 libpmem, 首先要包括所需的头文件,如列表Listing 6-1所示:

Listing 6-1. Including the libpmem headers
32    
33  /*    
34   * simple_copy.c    
35   *    
36   * usage: simple_copy src-file dst-file    
37   *    
38   * Reads 4KiB from src-file and writes it to dst-file.    
39   */    
40    
41  #include <sys/types.h>    
42  #include <sys/stat.h>    
43  #include <fcntl.h>    
44  #include <stdio.h>    
45  #include <errno.h>    
46  #include <stdlib.h>    
47  #ifndef _WIN32    
48  #include <unistd.h>    
49  #else    
50  #include <io.h>    
51  #endif    
52  #include <string.h>    
53  #include <libpmem.h>

注意53行,使用libpmem,要使用此头文件,然后在linux下构建link 此C程序时还要使用 -lpmem 选项。

6.1  映射文件

Libpmem包含了一些内存文件映射的简便函数。当然,应用可以在linux上直接调用mmap() 或者在windows上直接调用 MapViewOfFile(), 但使用libpmem有以下优势:

  1. Libpmem知道操作系统调用映射时的正确参数。例如,在Linux上,直接使用CPU指令来将变更flush到持久内存是不安全的,除非映射创建时指定了MAP_SYNC标志;
  2. libpmem 检测映射是否真的映射到持久内存,以及直接使用CPU指令来flush是否是安全的。
Listing 6-2 展示了在持久内存-感知的文件系统上,如何内存映射文件到应用中

Listing 6-2. Mapping a persistent memory file
80      /* create a pmem file and memory map it */    
81      if ((pmemaddr = pmem_map_file(argv[2], BUF_LEN,    
82              PMEM_FILE_CREATE|PMEM_FILE_EXCL,    
83              0666, &mapped_len, &is_pmem)) == NULL) {    
84          perror("pmem_map_file");    
85          exit(1);    
86      }

对于前面提到的检测是否是映射到持久内存上,可以通过下面的方式来知晓:在调用pmem_map_file后,is_pmem标志会被返回;是否是真的映射到持久内存上,可以通过is_pmem来判断。使用该标志来决定如何flush变更到持久化,就是调用者的责任了。当使一个内存范围持久化时,只有当is_pmem标志被设置时,调用者才可以使用libpmem、pmem_persist提供的优化的flush操作。从手册中摘录的例子Listing 6-3说明了此点。

Listing 6-3. manpage.c: Using the is_pmem flag
74      /* Flush above strcpy to persistence */    
75      if (is_pmem)    
76          pmem_persist(pmemaddr, mapped_len);    
77      else    
78          pmem_msync(pmemaddr, mapped_len);

Listing 6-3 展示了pmem_msync()函数的方便性,它只是对msync()的小小包装,Windows也一样。所以你在Linux和Windows上的程序,不需要以不同的逻辑来构建,因为libpmem处理了它们之间的不同。

6.3  copy到持久内存

在libpmem中有几个接口,用来以最优方式copy或者清空一段持久内存。最简单的接口如清单Listing 6-4所示,从源文件copy一个数据块到持久内存中并且flush持久化。

Listing 6-4. simple_copy.c: Copying to persistent memory
88      /* read up to BUF_LEN from srcfd */    
89      if ((cc = read(srcfd, buf, BUF_LEN)) < 0) {    
90          pmem_unmap(pmemaddr, mapped_len);    
91          perror("read");    
92          exit(1);    
93      }    
94    
95      /* write it to the pmem */    
96      if (is_pmem) {    
97          pmem_memcpy_persist(pmemaddr, buf, cc);    
98      } else {    
99          memcpy(pmemaddr, buf, cc);   
100          pmem_msync(pmemaddr, cc);   
101      }

注意96行is_pmem如何使用,本应调用pmem_persist(),却调用了pmem_memcpy_persist(),这是因为pmem_memcpy_persist()包含了flush持久化操作。

pmem_memcpy_persist()包含了持久化是因为它确定使用non-temporal存储更优,绕过CPU cache并且不需要为持久化执行后续的cache flush指令。通过使用提供的包括copy和flush的API,libpmem使用最优的方式来执行这些步骤。

6.4  分离Flush步骤

Flush到持久化设备包含两步:

  1. Flush CPU cache或者向前面例子一样整个绕过他们;
  2. 等待所有硬件缓存完成flush,确保都已经写到媒体。

当调用pmem_persist()时,这些步骤一起执行,或者第一步调用pmem_flush(),然后第二步调用pmem_drain()。注意这些步骤在所给的平台上不一定都需要,但该库知道如何检测并正确执行。例如,在Intel平台,pmem_drain是一个空函数。

什么时候分成多步?例子清单Listing 6-5 演示了这么做的一个理由。因为例子多次调用memcpy()来copy数据,它使用libpmem copy(pmem_memcpy_nodrain()),该方法只执行flush,将最终的drain抽干步骤延到最后。不像flush步骤,抽干步骤不能获取地址范围,所以它是一个系统范围内的抽干操作,发生在copy各数据块循环结尾处。

Listing 6-5. full_copy.c: Separating the flush steps
    
58  /*    
59   * do_copy_to_pmem    
60   */    
61  static void    
62  do_copy_to_pmem(char *pmemaddr, int srcfd, off_t len) 
63  {    
64      char buf[BUF_LEN];    
65      int cc;    
66    
67      /*    
68       * Copy the file,    
69       * saving the last flush & drain step to the end    
70       */    
71      while ((cc = read(srcfd, buf, BUF_LEN)) > 0) {    
72          pmem_memcpy_nodrain(pmemaddr, buf, cc);    
73          pmemaddr += cc;    
74      }    
75    
76      if (cc < 0) {    
77          perror("read");    
78          exit(1);    
79      }    
80    
81      /* Perform final flush step */    
82      pmem_drain();    
83  }

在清单Listing 6-5中,pmem_memcpy_nodrain()是专门为持久内存设计的。当使用其他库和标准函数如memcpy()时,记住这些函数在持久内存存在之前就被实现完成了,所以不会执行任何flush操作到持久化设备。特别是通过C运行时环境提供的memcpy(),经常会在常规存储(需要flushing)和非临时性存储(不需要flushing)之间选择。这个选择基于性能,而不是持久化。因为你不知道它会选择哪个指令,所以你需要自己使用pmem_persist() 或者msync()来执行flush持久化。

当copy多个范围到持久内存时,在许多应用中,选择所用指令时更看中性能。持久内存范围的置零也是一样的。为了满足这些需求,libpmem 提供了 pmem_memmove()、pmem_memcpy(), 以及pmem_memset(),这些函数提供了一个标志参数,以使调用者可以对所使用的指令有更多的控制。例如,传给PMEM_F_MEM_NONTEMPORAL 标志,将告诉这些函数使用非临时存储代替选择那些基于范围大小的指令。标志完整清单见这些函数的手册。

6.5  本章小结

本章演示了libpmem提供的API的一个很小集。该库不会为你改变,不会提供电源失效-安全事务,不提供分配器。像libpmemobj库(下章描述)提供了所有这些任务,并内部使用libpmem以简单化flush和copy操作。

作者: charlie_chen

编程是一生最爱: >> 架构与设计; >> 软件工程; >> 项目管理; >> 产品研发。
联系我们

联系我们

022-XXXXXXXX

在线咨询: QQ交谈

邮箱: 1549889473@qq.com

欢迎交流。
关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部