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

Published: 07/18/2019  

Last Updated: 07/18/2019

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 second-generation 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: (knowledge, skills) 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) libpmemblk library. This library provides persistent memory support for memory mapping and for reading and writing at the memory block level, which makes low-level persistent memory programming much easier. We demonstrate how to use libpmemblk 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. We recommend using Intel® Optane™ DC persistent memory modules. If you cannot get your hands on those or other persistent memory products, you can emulate a persistent memory region using Dynamic Random-access Memory (DRAM).

Prerequisites

This article assumes that you have a basic understanding of persistent memory concepts and are familiar with features of the Persistent Memory Development Kit (PMDK). If not, visit the Intel® Developer Zone (Intel® DZ) Persistent Memory site, where you will find the information you need to get started.

Code Sample Design

This program creates a persistent memory pool to store the “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 it.

 /*
 * hello_libpmemblk.c -- simple example for the libpmemblk library
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libpmemblk.h>


/* size of each element in the pmem pool */
#define ELEMENT_SIZE 1024


/****************************
 * This function writes the "Hello..." string to persistent-memory.
 *****************************/
void write_hello_string (char *input, char *path)
{
	PMEMblkpool *pbp;
	
	/* create the pmemblk pool or open it if it already exists */
	pbp = pmemblk_create(path, ELEMENT_SIZE, PMEMBLK_MIN_POOL, 0666);

	/* Check if create failed */
	if (pbp == NULL) {
		perror(path);
		exit(1); 
	}

	/* store a block at index 0 */
	if (pmemblk_write(pbp, input, 0) < 0) {
		perror("pmemblk_write");
		exit(1);
	}

	/* output a string from the persistent memory to console */
	printf("\nWrite the (%s) string to persistent memory.\n",input);
	
	/* Cleanup */
	/* Close to the pmemblock */
	pmemblk_close(pbp);
}



/****************************
 * This function reads the "Hello..." string from persistent-memory.
 *****************************/
void read_hello_string(char *path)
{
	PMEMblkpool *pbp;
	/* Define input and output buffers */
	char output[ELEMENT_SIZE];
	
	// Open the pool
	pbp = pmemblk_open(path, ELEMENT_SIZE);
	
	/* Check if open failed */
	if (pbp == NULL) {
		perror(path);
		exit(1);
	}
	
	/* read the block at index 0 */
	if (pmemblk_read(pbp, output, 0) < 0) {
		perror("pmemblk_read");
		exit(1);
	}
	
	/* Reading the string from persistent-memory and write to console */
	printf("\nRead the (%s) string from persistent memory.\n", output);	
	
	/* Cleanup */
	/* Close to the pmemblock */
	pmemblk_close(pbp);
}


/****************************
 * This main function gathers from the command line and calls the appropriate
 * functions to perform read from and write persistently to memory.
 *****************************/
int main(int argc, char *argv[])
{
	char *path = argv[2];
	
	/* Define input buffers */
	char input[ELEMENT_SIZE];

	/* store a block at index 0 */
	strcpy(input, "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 {
		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!!!” using strcpy() from the standard C language library.

/* store a block at index 0 */
strcpy(input, "Hello Persistent Memory!!!");	

Next, the program parses input from the command line and decides whether to read from or write to persistent memory.

The “write_hello_string” Function

For option -w, the sample calls the write_hello_string function. It completes the write to persistent memory in the three steps shown in the following code snippets:

Step 1. Create the pool

/* create the pmemblk pool or open it if it already exists */
pbp = pmemblk_create(path, ELEMENT_SIZE, PMEMBLK_MIN_POOL, 0666);

To create a persistent memory pool, use the pmemblk_create() function, which returns a PMEMblkpool handle:

PMEMblkpool *pmemblk_create(const char *path, size_t bsize, size_t poolsize, mode_t mode);
  • const char *path - Location of the memory pool file.
  • size_t bsize - Block size for blocks created in the pool.
  • size_t poolsize - Pool size. We pass t predefined macro PMEMBLK_MIN_POOL here.
  • mode_t mode - Permission mode for the pool file.

Step 2. Write the “Hello…” string to persistent memory

/* store a block at index 0 */
if (pmemblk_write(pbp, input, 0) < 0) { 
    perror("pmemblk_write"); 
    exit(1); 
}

After creating the pool, we write the “Hello…” string to it using pmemblk_write() with the following syntax:

int pmemblk_write(PMEMblkpool *pbp, const void *buf, long long blockno);
  • PMEMblkpool *pbp - Pointer to the handler of our persistent memory pool.
  • const void *buf - Source pointer to a block where the size should be the size we used with pmemblk_create (in this case ELEMENT_SIZE) to be copied to the memory pool.
  • long long blockno - Block number zero is the destination block in the memory pool pbp where the data will be written.

Step 3. Close the pool

/* Cleanup */
/* Close to the pmemblock */
pmemblk_close(pbp);

After writing the block containing the “Hello…” string, close the pool using pmemblk_close() with the following syntax:

void pmemblk_close(PMEMblkpool *pbp);
  • PMEMblkpool *pbp - Pointer to the handler of our persistent memory pool.

The “read_hello_string” Function

Use the -r option to read from persistent memory. The code sample calls read_hello_string to carry out the task. This function completes the read from persistent memory in three steps. See the following code snippets for details:

Step 1. Open the pool

// Open the pool

pbp = pmemblk_open(path, ELEMENT_SIZE);
PMEMblkpool *pmemblk_open(const char *path, size_t bsize);
  • const char *path - Location of the memory pool.
  • size_t bsize - Pool block size. We use a predefined macro ELEMENT_SIZE here.

Step 2. Read the “Hello…” message and write to console

/* read the block at index 0 */
if (pmemblk_read(pbp, output, 0) < 0) {
	perror("pmemblk_read");
	exit(1);
}

After opening the pool, we start reading the “Hello…” string from it at block number zero using pmemblk_read() with the following syntax:

int pmemblk_read(PMEMblkpool *pbp, void *buf, long long blockno);
  • PMEMblkpool *pbp - Pointer to the handler of our persistent memory pool.
  • void *buf - Destination pointer to a block of memory where the persistent memory block containing the “Hello…” string will be copied.
  • long long blockno - Source block number in the memory pool to be copied to the buffer.

Step 3. Close the pool

/* Cleanup */
/* Close to the pmemblock */
pmemblk_close(pbp);

See pmemblk_close() as described earlier in the “write_hello_string” function, as it performs the same task.

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”:

$ ./hello_lpmemblk -w t

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

Read the (Hello Persistent Memory!!!) string from persistent memory.
$ ./hello_lpmemblk -rw t
Usage: ./hello_lpmemblk <-w/-r> <filename>
$ ./hello_lpmemblk -w t
t: File exists
$

Summary

In this “Hello…” sample code, we showed how to write a simple string to and read it from persistent memory using the PMDK libpmemblk library. If you are interested in C++ and Java* sample code using other libraries or APIs from PMDK, you can find links to additional “Hello World” code samples in the References section below. You can find the code sample we used in this article 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

Product and Performance Information

1

Performance varies by use, configuration and other factors. Learn more at www.Intel.com/PerformanceIndex.