File(s): | Download |
License: | BSD 3-clause License |
Optimized for... | |
---|---|
OS: | Linux* kernel version 4.3 or higher |
Hardware: | Intel® Optane™ DC persistent memory and 2nd Gen Intel® Xeon® Scalable processor Emulated: See How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM) |
Software: (Programming Language, tool, IDE, Framework) |
C Compiler, Persistent Memory Development Kit (PMDK) libraries |
Prerequisites: | Familiarity with C |
Introduction
In this article and accompanying code sample, we show how to create a “Hello World” program using the Persistent Memory Development Kit (PMDK) libpmemobj library. libpmemobj is a C language library that provides an interface designed to make it easy to allocate and access objects in persistent memory files, which are called pools. We demonstrate how to use libpmemobj API functions to create, open, close, and manage data in a pool. The pool can reside on any media (such as a regular hard disk drive or a solid-state drive), but the best performance is obtained when it is located in byte-addressable persistent memory. In this example, we use Intel® Optane™ DC persistent memory modules, but if you can’t get your hands on those or another persistent memory product, you can emulate a persistent memory region using dynamic random-access memory (DRAM).
Prerequisites
The article assumes that you have a basic understanding of persistent memory concepts and are familiar with features of PMDK. If not, visit Persistent Memory site on Intel® Developer Zone, where you will find the information you need to get started.
Code Sample Design
This code sample creates a persistent memory pool where it stores a “Hello…” message. Depending on user input, the program performs a read from or a write to the pool. In the write_hello_string() function, the program creates a pool and writes the “Hello…” message to it. In the read_hello_string() function, the program reads the message back from the pool.
/*
* hello_libpmemobj.c -- an example for libpmemobj library
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libpmemobj.h>
// Name of our layout in the pool
#define LAYOUT "hello_layout"
// Maximum length of our buffer
#define MAX_BUF_LEN 30
// Root structure
struct my_root {
size_t len;
char buf[MAX_BUF_LEN];
};
/****************************
* This function writes the "Hello..." string to persistent-memory.
*****************************/
void write_hello_string (char *buf, char *path)
{
PMEMobjpool *pop;
// Create the pmemobj pool or open it if it already exists
pop = pmemobj_create(path, LAYOUT, PMEMOBJ_MIN_POOL, 0666);
// Check if create failed
if (pop == NULL)
{
perror(path);
exit(1);
}
// Get the PMEMObj root
PMEMoid root = pmemobj_root(pop, sizeof (struct my_root));
// Pointer for structure at the root
struct my_root *rootp = pmemobj_direct(root);
// Write the string to persistent memory
// Assign the string length and persist it
rootp->len = strlen(buf);
pmemobj_persist(pop, &rootp->len, sizeof (rootp->len));
// Copy string and persist it
pmemobj_memcpy_persist(pop, rootp->buf, buf, rootp->len);
// Write the string from persistent memory to console
printf("\nWrite the (%s) string to persistent-memory.\n", rootp->buf);
// Close PMEM object pool
pmemobj_close(pop);
return;
}
/****************************
* This function reads the "Hello..." string from persistent-memory.
*****************************/
void read_hello_string(char *path)
{
PMEMobjpool *pop;
//Attempt open instead
pop = pmemobj_open(path, LAYOUT);
// Check if open failed
if (pop == NULL) {
perror(path);
exit(1);
}
// Get the PMEMObj root
PMEMoid root = pmemobj_root(pop, sizeof (struct my_root));
// Pointer for structure at the root
struct my_root *rootp = pmemobj_direct(root);
// Read the string from persistent memory and write to the console
printf("\nRead the (%s) string from persistent-memory.\n", rootp->buf);
// Close PMEM object pool
pmemobj_close(pop);
return;
}
/****************************
* This main function gather from the command line and call the appropriate
* function to perform read and write persistently to memory.
*****************************/
int main(int argc, char *argv[])
{
char *path = argv[2];
// Create the string to save to persistent memory
char buf[MAX_BUF_LEN] = "Hello Persistent Memory!!!";
if (strcmp (argv[1], "-w") == 0) {
write_hello_string(buf, path);
} else if (strcmp (argv[1], "-r") == 0) {
read_hello_string(path);
} else {
fprintf(stderr, "Usage: %s <-w/-r> <filename>\n", argv[0]);
exit(1);
}
}
Code Walk-through
In the main program, we define an input array containing the text “Hello Persistent Memory!!!”. The program will pass the buf variable into the write_hello_string() function for writing into persistent memory.
// Create the string to save to persistent memory
char buf[MAX_BUF_LEN] = "Hello Persistent Memory!!!";
Next, the program parses the options passed to it, either -r or -w, and decides whether to read from or write to persistent memory.
The write_hello_string Function
For option -w, the program calls the write_hello_string function. This function completes the write to persistent memory in the five steps shown in the following code snippet:
Step 1 – Create a pool
// Create the pmemobj pool or open it if it already exists
pop = pmemobj_create(path, LAYOUT, PMEMOBJ_MIN_POOL, 0666);
Use pmemobj_create() to create a persistent memory pool.
PMEMobjpool *pmemobj_create(const char *path, const char *layout,
size_t poolsize, mode_t mode);
const char *path
- Location of memory pool fileconst char *layout
- Name for the object layout in the poolsize_t poolsize
- Minimum pool size allowed by the library is 6 MB. We pass the predefined macro “PMEMOBJ_MIN_POOL” here.mode_t mode
– Memory pool file permissions
Step 2 – Get Root Object
// Get the PMEMObj root
PMEMoid root = pmemobj_root(pop, sizeof (struct my_root));
Once the pool has been created, get a pool entry point. Call pmemobj_root(), which provides an entry point for all other persistent objects allocated using the libpmemobj API. There is exactly one root object in each pool, and we recommend that you use only one pool in your application.
pmemobj_root() has the following syntax:
PMEMoid pmemobj_root(PMEMobjpool *pop, size_t size);
PMEMobjpool *pop
- Handle to persistent memory poolsize_t size
- Size of root object
Step 3 – Write the “Hello…” message to persistent memory
// Pointer for structure at the root
struct my_root *rootp = pmemobj_direct(root);
Now you have an entry point to the pool. Get a handle to its memory location by using pmemobj_direct(). It returns a pointer to a PMEMoid object with a handle Object Identifier (OID ).
It has the following syntax:
void *pmemobj_direct(PMEMoid oid);
PMEMoid OID
- An object handle of type PMEMoid
// Write the string to persistent memory
// Assign the string length and persist it
rootp->len = strlen(buf);
pmemobj_persist(pop, &rootp->len, sizeof (rootp->len));
After getting the root handle, make the pool persistent using the pmemobj_persist() function. This function forces any changes in the range [addr, addr+len] to be stored durably in persistent memory. There are no alignment restrictions on this range, but it may be expanded as necessary to meet platform alignment requirements. pmemobj_persist() has the following syntax:
void pmemobj_persist (PMEMobjpool *pop, const void *addr, size_t len);
PMEMobjpool *pop
- Handle to persistent memory pool popconst void *addr
- Address location of persistent memorysize_t len
- Size of persistent memory to persist
// Copy string and persist it
pmemobj_memcpy_persist(pop, rootp->buf, buf, rootp->len);
You can now copy the “Hello…” string to the pool by using the pmemobj_memcpy_persist() function, which combines a regular memcpy with a pmemobj_persist () call. It has the following syntax:
void *pmemobj_memcpy_persist(PMEMobjpool *pop, void *dest,
const void *src, size_t len);
PMEMobjpool *pop
- Handle to persistent memory pool popvoid *dest
- Destination address location of persistent memoryconst void *src
- Source address location that contains the “Hello…” string to be copied to persistent memory
size_t len - Size of memory to be copied
Step 4 – Display the “Hello…” string on screen
// Write the string from persistent memory to console
printf("\nWrite the (%s) string to persistent-memory.\n", rootp->buf);
Step 5 – Close the pool
// Close PMEM object pool
pmemobj_close(pop);
Use the pmemobj_close() function to close the pool. This function closes the persistent memory pool and deletes the pool handle pop. The object store lives on in the file we created. It can be re-opened at a later time using pmemobj_open() as described in the read_hello_string() function that we’ll create next.
pmemobj_close() has the following syntax:
void pmemobj_close(PMEMobjpool *pop);
PMEMobjpool *pop - Handle to persistent memory pool pop
The read_hello_string() Function
For the -r option, we’ll implement the read_hello_string() function, which will read from persistent memory in five steps. See the following code snippet for more details:
Step 1 – Open the pool
//Attempt open instead
pop = pmemobj_open(path, LAYOUT);
To read the “Hello…” string from persistent memory, we open our persistent memory pool with the pmemobj_open () function. It has the following syntax:
PMEMobjpool *pmemobj_open(const char *path, const char *layout);
const char *path
- Location of persistent memory pool fileconst char *layout
- Name for the object layout in the pool
Step 2 – Get root object
// Get the PMEMObj root
PMEMoid root = pmemobj_root(pop, sizeof (struct my_root));
The usage here is the same as in the write_hello_string function.
Step 3 – Get a pointer to the root object
// Pointer for structure at the root
struct my_root *rootp = pmemobj_direct(root);
The usage here is the same as in the write_hello_string function.
However, here the program uses the pointer to read the “Hello…” string function and uses printf () to display it on screen.
Step 4 – Use regular C printf () to display the “Hello…” string on screen.
// Read the string from persistent memory and write to the console
printf("\nRead the (%s) string from persistent-memory.\n", rootp->buf);
Step 5 – Close the pool
// Close PMEM object pool
pmemobj_close(pop);
The usage here is the same as in the write_hello_string() function.
Compile and Run
Use the included Makefile to compile and build the binary. Running make will compile it in the current working directory:
$make
Now run the program, where the file name is “t”:
Write the (Hello Persistent Memory!!!) string to persistent memory.
$./hello_lpmemobj -w t
Read the (Hello Persistent Memory!!!) string from persistent memory.
$ ./hello_lpmemobj -r t
Hello Persistent Memory!!!
Read the (Hello Persistent Memory!!!) string from persistent-memory.
$ ./hello_lpmemobj -q t
Usage: ./hello_lpmemobj <-w/-r> <filename>
Summary
In this “Hello…” sample code, we showed how to write and read a simple string to or from persistent memory. You can find this code sample and more persistent memory programming examples in the PMDK examples repository on our GitHub* repo.
About the Author
Thai Le is a software engineer focusing on cloud computing and performance computing analysis at Intel Corporation.
References
- “libpmemobj” library
- How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM)
- Create a C++ Persistent Memory “Hello World” Program Using libpmemobj
- Create a “Hello World” Program Using Persistent Collections for Java* (PCJ)
- Create a “Hello World” Program Using the Low Level Persistence Library (LLPL) for Java*
- Introduction to Programming with Persistent Memory from Intel
- Persistent Memory -- Get Started
Notices
Twitter*: Allocate memory files and access objects in persistent memory files, called pools, in the PMDK. Creating, opening, closing, and copying is done using libpmemobj API functions.
Summary: Allocate memory files and access objects in persistent memory files, called pools, in the PMDK. Creating, opening, closing, and copying is done using libpmemobj API functions.