Interprocess Communication: Mapped memory

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

Interprocess communication involves processes communicating with each other in this article we cover mapped memory and how it aids in this communication.

Table of contents.

  1. Introduction.
  2. Mapping a file.
  3. Shared access.
  4. Private mappings.
  5. Summary.
  6. References.

Prerequisites.

  1. Shared memory
  2. Semaphores

Introduction.

IPC(Interprocess communication) involves the transfer of data between processes, these processes can be either related or unrelated.

In the prerequisite articles we have discussed two types of interprocess communication namely shared memory and semaphores. Other types of of IPCs are,

  • Pipes, these are communication devices permitting unidirectional communication between processes.
  • Sockets, these support interprocess communication between unrelated processes even on different systems.

Mapped memory allows different process to communicate via a shared file in the file system.
It can be used as an easier way to access file contents.
For mapped memory to support interprocess communication, it develops an association between a file and a process' memory.

This file is split into page-sized chunks which are copied to virtual memory so that they can be available to a process via its address space and therefore the process will be able to read this file with an ordinary memory access.
The process is also able to modify this file by writing to memory and thus faster access to files.

Basically we allocate a buffer to hold all file contents and read the file into the buffer and writing the modified buffer back to the file.

Mapping a file.

The mmap(Memory Mapped) system call is used to map an ordinary file into a process' memory. It takes an address where we would like Linux to map a file into the process' address as its first argument, if NULL, the system chooses an available start address.

The second argument specifies the length of the map in bytes.
The third argument is the protection on the mapped address range.
Protection consists of a bitwise OR between PROT_READ(read), PROT_WRITE(write) and PROT_EXEC(execute).
The fourth arguments a flag value which specifies additional options.

  • A flag value is also a bitwise OR of the following constraints,
  • MAP_FIXED which when specified, Linux will use the page-aligned address we request to map the file rather than treating it as a hint.
  • MAP_PRIVATE when specified it means that the writes to the memory range shouldn't be written to the attached file instead be written to a private copy of the file.
  • MAP_SHARED, meaning writes are instantly reflected in the underlying file rather than buffering writes. We use this when using mapped memory for IPC. Note that it should not be used with the previous flag(MAP_PRIVATE).
  • The fifth a file descriptor opened to the file to be mapped.
  • The sixth an offset from the beginning of the file from which the map will start.

If the call is successful a pointer to the beginning of the memory is returned otherwise we get MAP_FAILED.

The munmap call is used to release a mapping, it takes the start address and length of the mapped region as its arguments.
Normally Linux will unmap mapped regions when a process ends.

An example of writing a random number to a memory-mapped file write.c.

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>

#define FILE_LENGTH 0X100

// get random number between low and high
int getRandom(unsigned const low, unsigned const high){
    unsigned const range = high - low + 1;
    return low + (int)(((double) range) * rand() / (RAND_MAX + 1.0));
}

int main(int argc, char* argv[]){
    int fd;
    void* fileMemory;

    // seed random number generator
    srand(time(NULL));

    // prepare large enough file to hold int
    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    lseek(fd, FILE_LENGTH + 1, SEEK_SET);

    write(fd, "", 1);
    lseek(fd, 0, SEEK_SET);

    // create mapping
    fileMemory = mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    // write random integer to mapped area
    sprintf((char*)fileMemory, "%d\n", getRandom(-100, 100));

    // deallocate - not necessary since done when program terminates.
    munmap(fileMemory, FILE_LENGTH);

    return 0;
}

Compilation:

$ gcc write.c -o write

First we generate a random number and write it to memory-mapped file.
mmap-write opens the file defined in the command line, if not defined it will be created.

The third argument to open it states that the file is open for reading and writing.
Assuming we don't know the file's length, lseek can be used to ensure it is large enough to store an integer then move back the file position to its beginning.
After mapping the file, we close the file descriptor since we no longer need it, then write a random integer to the mapped memory and also the file and unmap memory. For this we call munmap or not since after the program terminates Linux will automatically unmap the file.

An example of reading an integer from a memory-mapped file read.c and doubling it.

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>

#define FILE_LENGTH 0X100

int main(int argc, char* const argv[]){
    int fd;
    void* fileMemory;
    int integer;

    // open file
    fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR);

    // create memory mapping
    fileMemory = mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);
    
    // read integer from file and print its double
    scanf(fileMemory, "%d", &integer);
    printf("Read integer: %d\n", integer);
    sprintf((char*)fileMemory, "%d\n", 2 * integer);

    // deallocate memory - not necessary, when program terminates it is removed
    munmap(fileMemory, FILE_LENGTH);

    return 0;
}

Compilation:

$ gcc read.c -o read

We read the number, print it and replace it in the memory-mapped file with a double value.
mmap-read will read the number out of the specified file and write its doubled value to the file.

It first opens the file and maps it for reading and writing. We assume the file is large enough and therefore we don't use lseek
scanf reads and parses the value out of memory, formats it and the double is written using sprintf.

Execution,

$ ./write mapFile
$ ./read mapFile 

Read integer: 22064

$ cat mapFile 
44128

We pass a command line argument 'mapFile' which is a file to map.
NUMBER is written to disk file without calling write and read without calling read.

Here we have used an integer as an example however we can store/retrieve an arbitrary binary in a memory-mapped file.

Shared access.

Processes can communicate using memory-mapped regions associated with the same file.
We specify the MAP_SHARED flag so that any writes to memory-mapped are transferred to the underlying file and made available to other processes otherwise if we don't specify this flag Linux may buffer writes before transferring them to the file.
Another approach is to force Linux to incorporate buffered writes into the disk file by using the msync whose first two parameters specify a memory-mapped region. The third parameter is a flag whose value can be,

  • MS_ASYNC, where an update is scheduled but not necessarily run before a call returns.
  • MS_SYNC where the update is instant. Call to msync blocks until update is done.
  • Both these flags may not be used together.
  • MS_INVALIDATE where all other file mappings are invalidated so they can see updated values.

An example
To flush a shared file mapped at address mem_addr with the length mem_length bytes, we write,

msync(mem_addr, mem_length, MS_SYNC | MS_INVALIDATE);

Memory-mapped regions also follow a protocol to avoid race conditions that is, semaphores can be used to prevent multiple processes from accessing a region at a time.
fcntl* call can also be used to place read and write lock on a file.

Private mappings.

MAP_PRIVATE flag specified in mmap will create a copy-on-write region and therefore any write to this region will be reflected in the process' memory and other processes mapping the same file will not be able to see these changes.
That is, instead of writing to a shared page by all processes, the process will write to a private copy of this page and all subsequent readings or writings by this process will use this page.

Summary.

mmap can also be used for other uses, one is as a replacement for read and write, that is, instead of explicitly reading a file's contents into memory, a program may map this file into memory and scan it using reads to the memory. Depending on a program this can be faster compared ti explicit I/O operations.

References.

  1. Execute man mmap, man munmap man msync for their manual pages.
  2. pipes

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.