Code Sample: Create a C Persistent Memory “Hello World” Program Using libpmemobj

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 file
  • const char *layout - Name for the object layout in the pool
  • size_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 pool
  • size_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 pop
  • const void *addr - Address location of persistent memory
  • size_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 pop
  • void *dest - Destination address location of persistent memory
  • const 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 file
  • const 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

 

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.

For more complete information about compiler optimizations, see our Optimization Notice.