Code Sample: Create a C++ Persistent Memory ‘Hello World’ Program Using libpmemobj

ID 657776
Updated 12/14/2018
Version Latest
Public

author-image

By

File(s):

Download
License: 3-Clause BSD License
Optimized for...  
OS: Linux* kernel version 4.3 or higher
Hardware: Emulated: See How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM)
Software:
(programming language, tool, IDE, framework, packages)
Intel® C++ Compiler, Persistent Memory Development Kit (PMDK) libraries
Prerequisites: Familiarity with C++

Introduction

Would you like to create a “Hello World” program using the Persistent Memory Development Kit (PMDK)? This article uses basic building blocks in the PMDK’s libpmemobj library, including persistent pointers, persistent memory pools, and persistent atomic functions, to create a short code sample. The sample focuses on the library’s key functionality to write a “Hello...” message to persistent memory and to read it back with a notification to standard output. You can try this code sample using Intel® Optane™ DC persistent memory, or with emulation of persistent memory using DRAM.

Prerequisites

The article assumes that you have a basic understanding of persistent memory concepts and are familiar with the features of the PMDK. If not, visit the Intel® Developer Zone’s Persistent Memory site, where you’ll find the information you need to get started.

Code Sample Design

The sample creates a persistent memory pool where it stores the “Hello…” message. After creating the pool, the write_hello_string function is called to store the “Hello…” message to the pool. The read_hello_string function reads back the message from the pool. See the code snippet below for details. Depending on the options passed to the program (“-r” for read, and “-w” for write), the sample performs the read or write to the persistent memory pool accordingly.

Class Hello
{
	private:
 	char msg[max_msg_size] = {0};

	public:
	Hello (char* input)
	{ 
			strcpy(msg,input);
	}

	char* get_hello_msg ()
	{
		return msg;
	}
};

/* root structure  */
/****************************
 *This root structure contains all the connections the pool and persistent
 *pointer to the persistent objects. Using this root structure component to
 *access the pool and print out the message. 
 ******************************/
struct root {
	pobj::persistent_ptr hello;
};


/* Functions */

/* file_exists function  */
/****************************
 * This checks to see if the filename exists.  A boolean value (true or false)
 * will return to the calling function.
 *
 *****************************/
inline bool
file_exists (const std::string &name)
{
	std::ifstream f (name.c_str ());
	return f.good ();
}

/* show_usage function  */
/****************************
 * This function prints out the correct syntax to run the program.
 *****************************/
static void
show_usage (string name)
{
	cerr << "Usage: " << name
	     << " <-w/-r>  "
		 << endl;
}
/* End Functions */

/****************************
 * This function writes the "Hello..." string to persistent memory.
 *****************************/
void write_hello_string (char *input, char *path)
{
	pobj::pool pop;
	pobj::persistent_ptr pool;
	
	/* Create pool in persistent memory */
	// Get the root object
	pop = pobj::pool::create (path, LAYOUT, PMEMOBJ_MIN_POOL, S_IRUSR | S_IWUSR);
	// Get pool object
	pool = pop.get_root ();
	
	// Store the input into persistent memory
	pobj::make_persistent_atomic (pop, pool->hello, input);
	
	// Write to the console
	cout << endl << "\nWrite the (" << pool->hello->get_hello_msg()
	 << ") string to persistent memory." << endl;	
			
	/* Cleanup */
	/* Close persistent pool */
	pop.close ();	
	return;
}

/****************************
 * This function reads the "Hello..." string from persistent memory.
 *****************************/
void read_hello_string(char *path)
{
	pobj::pool pop;
	pobj::persistent_ptr pool;

	/* Open the pool in persistent memory */
	pop = pobj::pool::open (path, LAYOUT);
	pool = pop.get_root ();

	// Write to the console
	cout << endl    << "\nRead the ("<< pool->hello-get_hello_msg()
		<< ") string from persistent memory." << endl;		
	
	/* Cleanup */
	/* Close persistent pool */
	pop.close ();	

	return;
}


/* Main */
int main (int argc, char *argv[])
{
	pobj::pool pop;
	pobj::persistent_ptr pool;
	
	/* Reading parameters from command line */
	if (argc < 3) {
		show_usage (argv[0]);
		return 1;
	}
	
	char *path = argv[2]; //
	// Prepare the input to store into persistent memory
	char input[max_msg_size] = "Hello Persistent Memory!!!";
	
	if (strcmp (argv[1], "-w") == 0) {	
		 
		write_hello_string (input, path);
		
	} else if (strcmp (argv[1], "-r") == 0) {

		read_hello_string (path);
		
	} else { 
		show_usage (argv[0]);
		exit(1);
	}

	return 0;
}
/* End Main */

Data Structures

The main data structure for the “Hello” program is the root structure, which has a persistent pointer to a Hello class object.

main data structure for the Hello program

A Code Walk-through

In the main program, an input array containing the string “Hello Persistent Memory!!!” is defined.

// Prepare the input to store into persistent memory
char input[max_msg_size] = "Hello Persistent Memory!!!";

Next, the program parses the options passed to it and decides whether to read from or write to persistent memory.

The “write_hello_string” Function

With the option “-w” for writing to the persistent memory pool, the sample calls the write_hello_string function to carry out the task. This function completes the write to persistent memory in five steps. See the code snippet below for details:

Create the pool

pop = pobj::pool::create (path, LAYOUT,
			 PMEMOBJ_MIN_POOL, S_IRUSR | S_IWUSR);

The function create requires four parameters:

  1. Pool path – the location of the memory pool file
  2. Layout – the name for the object layout in the pool
  3. Pool size – The size, in bytes, for the pool. (In this case, we use a pre-defined minimum pool size variable in the PMDK called PMEMOBJ_MIN_POOL)
  4. Pool creation mode. In this case, we use S_IRUSR|S_IWUSR, defined as read/write permission access by the owner of the pool (equivalent to 0600).

Retrieve the root object

pool = pop.get_root ();

The get_root function helps to provide access to the root object, from which you can access the rest of the objects created on a libpmemobj pool.

Write the “Hello…” message to persistent memory

// Store the input into persistent memory
pobj::make_persistent_atomic (pop, pool->hello, input);

The pobj::make_persistent_atomic allocator guarantees that an object allocation and initialization is completed atomically (i.e., either the allocation happens, or it doesn’t).

Read the message

read the message from persistent memory and write it to standard output.

// Write to the console
cout << endl << "\nWrite the (" << pool->hello->get_hello_msg()
     << ") string to persistent memory." << endl;

Close the pool

/* Cleanup */
/* Close persistent pool */
pop.close ();

The “read_hello_string” Function

With the option “-r” for reading from persistent memory, the sample calls the read_hello_string function to carry out the task. This function completes the read from persistent memory in four steps. See the code snippet below for details:

Open pool

/* Open the pool in persistent memory */
pop = pobj::pool::open (path, LAYOUT);

Open a persistent memory pool. The open function requires two parameters:

  1. Pool path – the location of the memory-pool file
  2. Layout – the name for the object layout in the pool

Retrieve root object

pool = pop.get_root ();

Read the "Hello..." message

Read the “Hello…” message back from persistent memory and writing it to standard output

// Write to the console
cout << endl << "\nWrite the (" << pool->hello->get_hello_msg()
     << ") string to persistent memory." << endl;

Close the pool

/* Cleanup */
/* Close persistent pool */
pop.close ();

Compile and Run

A Makefile is included to help with the compilation and building of the binary. Running ‘make’ compiles it in the current working directory:

$ make

Now run the program:

$ ./hello -w t
Write the (Hello Persistent Memory!!!) string to persistent memory.
$ ./hello -r t

Read the (Hello Persistent Memory!!!) string from persistent memory.
$ ./hello -r t

Read the (Hello Persistent Memory!!!) string from persistent memory.
$ ./hello -r t

Read the (Hello Persistent Memory!!!) string from persistent memory.
$ ./hello -w t
terminate called after throwing an instance of 'pmem::pool_error'
  what():  Failed creating a pool
Aborted (core dumped)

Our last invocation fails because we’re trying to create a pool that already exists. If you want to re-run the program to persist the same string or create another string, you can simply delete the pool file. A more sophisticated approach is to either “write on top” of the previous string or to detect if the pool file exists to decide whether the program should create-and-write, or open-and-read. This is left as an exercise for the reader.

Summary

With the “Hello…” sample code presented in this article, we have demonstrated the use of persistent memory pools, persistent pointers, and the make_persistent_atomic function of the PMDK library libpmemobj. This library is simple and easy to integrate with your existing code. Use the code sample included with this article to build and run your own program, modifying it to meet your requirements. You can find more persistent memory programming examples in the PMDK examples repository on our GitHub* repo.

About the Authors

Thai Le is the software engineer focusing on cloud computing and performance computing analysis at Intel Corporation.

Steven Briscoe is an Industry Technical Specialist leading the adoption of Persistent Memory with Cloud Service Providers at Intel Corporation.

References