PMDK based Persistent Memory Programming

基于libpmemobj库的C-style编程

本文主要用于总结对PMDK库中libpmemobj中相关内容的学习,通过简单学习libpmemobj可以对基于PMDK的持久内存编程有个大概的了解。PMDK包含了libpmemobj在内的多个库,其中libpmemobj是上层的封装,一般用户可以直接调用这个库提供的API进行持久内存编程。不过libpmemobj主要还是C的实现,相对而言也是偏向底层的,所以如果有更高抽象层次的需求,PMDK提供了对应的C++绑定,实现了C++ style的一些封装,更加有高级语言风格。目前先介绍libpmemobj,这样也有助于后面学习C++ binding。

开始编程之前的准备

Layout

在进行持久编程之前首先需要定义一个layout名,因为PMDK在打开一个持久内存资源池的时候需要制定一个layout,通过layout来标识一个内存池,这是一个字符串。

定义的方式有两种,一种就是直接定义:(其实感觉类似文件名的作用,但是不是文件名)

#define LAYOUT_NAME "my_layout"

或者直接在参数中传入一个字符串就行

另一种通过宏的方式

这种方式下提供了一系列的宏,除了定义layout外还涉及其他的定义主要有

POBJ_LAYOUT_BEGIN(layout_name)
POBJ_LAYOUT_TOID(layout_name, type)
POBJ_LAYOUT_ROOT(layout_name, root)
POBJ_LAYOUT_END(layout_name)

其中TIOD宏用于定义程序中所需要的类型,而ROOT宏用于定义根对象,关于根对象后面会有解释,这两个定义一定要在BEGIN与END之间

除此之外,使用这种方法定义了layout之后,在使用layout_name的时候就需要通过POBJ_LAYOUT_NAME宏来获取layout name

Persistent Pointer

Raw type

对于易失性内存上的编程,一个指针本质上是一个指向对象在虚拟内存地址空间的起始地址,而持久内存中的指针作用也是类似,同样是相当于一个记录了当前对象在内存池中的位置的结构,也有不一样的地方。

最明显的不同就是,持久内存指针包含了两个值。持久内存指针通过一个结构体PMEMoid来定义,其原始定义为:

typedef struct PMEMoid{
   uint64_t pool_uuid_lo;
   uint64_t off;
}

两个值分别为资源池pool的id,以及该对象在pool中offset,由于一个应用可能有多个资源池,所以通过这两个值就能定位一个持久对象的位置。

当我们需要对一个持久对象进行操作的时候,我们可以需要其在虚拟地址空间中的地址,这个时候可以通过pmembj_direct函数将一个PMEMoid指针转换为我们所需要的结构的指针,比如:

struct root = pmemobj_direct(my_root);//my_root is a PMEMoid

typed persistent pointer

有了PMEMoid我们可以对持久类型对象进行操作了,但是有一个问题,PMEMoid相当于直接通过一个地址去访问数据,但是这个数据的含义是不知道的,就相当于使用一个void*来对数据进行操作(这个时候就可能会出现不同类型的指针之间相互赋值这种情况,但是编译器并不会认为他是错的)。所以我们需要引入类型系统。PMDK中使用较多的类型系统是基于Named Union实现的TIOD类型。

TOID类型的实现定义为:

#define TOID(type)\
union _toid_##type##_toid

#define TOID_DECLARE(type)\
TOID(type)\
{\
    PMEMoid oid;\
    type *_type;\
}

可以看到这里定义了一个\_toid\_##type##\_tiod类型的union,好吧这不是重点,背后的实现机理大致就是这样,所以当我们需要一个类型化的持久指针的时候就可以这样声明:

struct my_root{
    int a;
    char name[10];
}
TOID(struct my_root) root;

这里我们就获得了一个my_root类型的持久指针root,在这种情况下,不同类型的TOID之间的赋值则会被编译器拒绝。同时由于我们有了类型,所以当需要访问类型的对象的时候,不再需要pmemobj_direct的转换而是通过两个API:D_RO以及D_RW,分别实现读以及写操作

printf("%d\n", D_RO(root)->a);

D_RW(root)->a = 10;

而且两个API内部是自动持久化的,所以我们不用显式地再去调用persist,同时更加符合我们的编程习惯。

根对象

其实最开始一直没有弄懂根对象是干什么用的以及怎么用的,直到看到了一个实际的例子之后才大致理解了根对象的作用。(这个例子是PMDK包中提供的一个使用PMDK编程的用例,是一个基于持久内存的小游戏,https://github.com/pmem/pmdk/blob/master/src/examples/libpmemobj/pminvaders/pminvaders2.c,非常有意思,完美地体现了持久内存的魅力)

首先是根对象(root object)是干什么的。根据官方的说法,根对象的作用就是一个访问持久内存对象的入口点,是一个锚的作用。设想一下程序的所有对象都放到你定义的一个内存池中,那么当我们再次打开这个内存池的时候,如何去访问你之前存放在你内存池中的对象?这就需要根对象,因为根对象是你的内存池中唯一可寻址的对象,其他所有对象都需要从根对象开始访问,所以从根对象开始,就可以获取你在这个内存池中存放的所有持久对象了。

根对象示意

那么有了根对象怎么去使用,根据我的理解以及根对象的定义,当你定义一个新的持久对象的时候就需要将其添加到根对象中,比如:

struct root{
    int a;
    char buf[10];
    struct node n;
};

这个根对象表示我们会用到一个整数a、一个字符数组buf和一个node结构体,我们把它们全部放到根对象里面,那么下次需要访问这些对象的时候就可以从根对象开始访问。

根对象的创建

根对象的创建API有两个,主要区别在于是否是类型化的

首先是非类型化的原始API:pmemobj_root(PMEMobjpool* pop, size_t size)

涉及到的参数主要是内存池指针以及所需要的大小。pmemobj_root函数的主要作用是create或者resize根对象,根据官方文档的描述,当你初次调用这个函数的时候,如果size大于0并且没有根对象存在,则会分配空间并创建一个根对象。当size大于当前根对象的size的时候会进行重分配并resize。

另一个接口是类型化的API:POBJ_ROOT(PMEMobjpool* pop, TYPE)

这是一个宏,传入的TYPE是根对象的类型,并且最后返回值类型是一个TOID指针,其余的用法与pmemobj_root一致

需要注意的是一个资源池的根对象是唯一的

如何通过PMDK访问Persistent Memory资源

PMDK是通过将持久内存抽象成资源池的方式进行访问,对应的API主要有三个分别是create、open以及close

PMEMobjpool *pmemobj_create(const char *path, const char *layout, size_t poolsize, mode_t mode)

create函数用于创建一个资源池,通过传入的路径、指定的layout以及大小创建一个持久内存资源池,返回一个PMEMobjpool类型的指针

PMEMobjpool *pmemobj_open(const char *path, const char *layout)

open函数用于给定路径以及layout然后打开一个资源池(二次使用的时候)

void pmemobj_close(PMEMobjpool *pop)

close用于关闭,每次程序结束之前一定要调用close关闭资源池

事务

在没有事务机制的情况下,当我们想要写一段数据的时候,需要先写数据的长度,然后在写数据,通过这样的方式保证数据在崩溃的时候的一致性,比如:

rootp->len = strlen(buf);
pmemobj_persist(pop, &rootp->len, sizeof (rootp->len));
pmemobj_memcpy_persist(pop, rootp->buf, my_buf, rootp->len);

为了简化这一过程引入了事务机制,首先是最基础的事务机制:

TX_BEGIN(pop){

}TX_ONCOMMIT{

}TX_ONABORT{

}TX_FINALY{

}TX_END

整个事务的流程可以通过这几个宏以及代码块来定义,并且将事务分成了多个阶段,中间的三个阶段为可选的,最基本的一个事务流程是TX_BEGIN-TX_END,这也是最常用的部分,其他的几个部分在嵌套事务中使用较多。

除了基本的事务代码块,libpmemobj还提供了相应的事务操作API。

一个是事务性数据写入API:pmemobj_tx_add_range&pmemobj_tx_add_range_direct,add_range函数主要有三个参数:root object、offset以及size,该函数表示我们将会操作[offset, offset+size)这段内存空间,PMDK将会自动在undo log中分配一个新的对象,然后将这段空间的内容记录到undo log中,这样我们就能随机去修改这段空间的内容并且保证一致性。带上direct标志的函数用法一致,区别在于direct函数直接操作的是一段虚拟地址空间。

TX_BEGIN(pop){
    pmemobj_tx_add_range(root, 0, sizeof(my_root));
    memcpy(rootp->buf, buf, strlen(buf));
}

注意事务性的API一定要在事务内执行

除了这两个函数还有一个TX_MEMCPY,用法与memcpy类似,只不过是针对事务性持久内存。

另一个是事务性的数据分配与回收,分别是TX_NEW以及TX_FREE,TX_NEW以待分配的结构作为参数,返回一个对应类型的TOID指针。需要注意的是NEW和FREE使用之前都需要调用TX_ADD函数,其作用与pmemobj_tx_add_range类似,都是表示我们将会修改这段内存空间。

TOID(my_root) root = POBJ_ROOT(pop);
TX_BEGIN(pop){
    TX_ADD(root);
    TOID(my_class) classp = TX_NEW(my_class);
    D_RO(root)->class = classp;
}TX_END

TX_BEGIN(pop){
    TX_ADD(root);
    TX_FREE(D_RO(root)->class);
    D_RO(root)->class = TOID_NULL(my_class);
}TX_END

这里FREE之后还要给class赋值一个NULL指针,防止访问到无效值

数据持久化

数据持久化的事务性API前面已经提到,以及两个非事务的基本API:

void pmemobj_persist(PMEMobjpool *pop, const void *addr, size_t len);:用于将[addr, add+size)区间内的数据持久化

void *pmemobj_memcpy_persist(PMEMobjpool *pop, void *dest, const void *src, size_t len);:作用类似memcpy

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容