Wednesday, February 15, 2017

Oracle Linux – Working with Memory Mapped Files

When working with Oracle Linux and developing your own solutions which make a more direct use of the underlying operating system you will, at one point in time, encounter the need to have multiple processes interact with the same file. As an example, you might have a processes that writes actions to an action-queue file while another process is reading this file and updates the file when the action is completed.

When you encounter such a situation you can work around this by fseek() and code via this method however there are more elegant ways of doing it. You could map the file to memory and use a pointer to the memory map to interact with it.

Using mmap()
To do so you can use the map maker mmap() to map a file to memory.  mmap() creates a new mapping in the virtual address space of the calling process. The mmap() system call can be called as shown below:

void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

addr : This is the address we want the file mapped into. The best way to use this is to set it to (caddr_t)0 and let the OS choose it for you. If you tell it to use an address the OS doesn't like (for instance, if it's not a multiple of the virtual memory page size), it'll give you an error.

len : This parameter is the length of the data we want to map into memory. This can be any length you want. (Aside: if len not a multiple of the virtual memory page size, you will get a blocksize that is rounded up to that size. The extra bytes will be 0, and any changes you make to them will not modify the file.)

prot : The "protection" argument allows you to specify what kind of access this process has to the memory mapped region. This can be a bitwise-ORd mixture of the following values: PROT_READ, PROT_WRITE, and PROT_EXEC, for read, write, and execute permissions, respectively. The value specified here must be equivalent to the mode specified in the open() system call that is used to get the file descriptor.

flags : There are just miscellaneous flags that can be set for the system call. You'll want to set it to MAP_SHARED if you're planning to share your changes to the file with other processes, or MAP_PRIVATE otherwise. If you set it to the latter, your process will get a copy of the mapped region, so any changes you make to it will not be reflected in the original file—thus, other processes will not be able to see them. We won't talk about MAP_PRIVATE here at all, since it doesn't have much to do with IPC.

fildes : This is where you put that file descriptor you opened earlier.

off : This is the offset in the file that you want to start mapping from. A restriction: this must be a multiple of the virtual memory page size. This page size can be obtained with a call to getpagesize().

Example code
As an example you can review the below code example. Here you see we need to include sys/mman.h explicitly. In the example we use the file  “somefile”

#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>

int filedesc, pagesize;
char *data;

filedesc = open("somefile", O_RDONLY);
pagesize = getpagesize();
data = mmap((caddr_t)0, pagesize, PROT_READ, MAP_SHARED, filedesc,pagesize);

By making use of such as an approach you will have much more control over files and will much more control and ease of development in cases where you will need to interact with a file from multiple processes at the same time.

For users who use Oracle Linux as pure the operating system and do not develop custom code or only code on a higher level this might not directly be of interest. However, everyone who is building custom code on Oracle Linux that needs to interact more directly with the operating system using this approach can be very beneficial in some cases.

No comments: