[Main]

Shared Memory

In Win32, memory can be shared amongst several processes on the same machine using file mapping.  Mechanical devices such as hard drives have very high access latencies and low transfer rates as compared to electrical devices such as memory.  Operating systems alleviate these discrepancies by using buffering techniques.  A buffer is nothing more than memory allocation that is used to hold information from the slower mechanical device.  The buffer caches all the data that is read from or written to the disk.   For example, a hundred bytes written to a file would be buffered with out actual writing to the file containing device.  If the same 100 bytes need to be read, they would be read from the buffer.  Reading from the buffer is far faster than, if the data was written to the disk, followed by a read of the disk drive.  If several processes need access to the same file at the same time, they all access it through the same buffer(to keep consistent reads and writes).  File mapping in win32 offers direct access to the user for this file buffer.  The file buffer space is mapped to the local process space of each of the processes.  Since memory is broken into pages, the buffer space is always rounded up to the next page size.

The CreateFileMapping function is used to create a file-mapping kernel object which is used to refer to the file buffer of a given file.  The function takes a handle to a file object as an argument.  File mapping kernel objects can either anonymous or named.  The CreateFileMapping function takes a name argument.  If the argument is NULL, than the file mapping object is anonymous.   If the argument is a non-NULL string, then the object is named.  If the object already exists, a reference to the existing file mapping object is returned. The OpenFileMapping function can be used to get access to the a named file mapping object.  The function MapViewOfFile is used to map the file buffer to the process space of the current process. It takes an argument of a handle to the file mapping object.  The return value of the function is a pointer to the beginning of the buffer in the processes own memory space.  If the process modifies memory locations in the buffer, when the file is finally closed and buffers flushed(written) to the disk, the file would be changed.  The function UnmapViewOfFile is used to remove the buffer from the current processes memory space.  The CloseHandle function can be used to close the a handle to the file-mapping kernel object.

If the CreateFileMapping function is passed a value 0xFFFFFFFF as an argument for the file handle(file whose buffer is going to be shared), the function will return a handle to a buffer that is not associated with any file.  This buffer can be used amongst several processes as shared memory.  Using OpenFileMapping is more difficult, due to writing code for creation and opening of file mapping kernels, as compared to that of just using CreateFileMapping function alone.

The CreateFileMapping takes arguments to specify the memory access rights.  If a file is opened for reading only and the handle is passed to CreateFileMapping, the associated buffer can only be read from.  It cannot be written to.  If an attempt is made to write to that memory, the process will receive a protection fault.   If a file is opened for both reading and writing, then the file mapping object can be created with a subset of the permissions.  The file mapping object can be made for reading and writing or only reading.

Using shared memory in conjunction with either interlocked functions or critical section objects is very efficient as compared to creating and using kernel objects such as semaphores, mutexes, and event objects.

Proto-Type: HANDLE CreateFileMapping

(

HANDLE hFile, // handle to file to map -- 0xFFFFFFF, if only shared memory req.
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes
DWORD flProtect, // protection for mapping object
DWORD dwMaximumSizeHigh, // high-order 32 bits of object size  - use 0, for file size
DWORD dwMaximumSizeLow, // low-order 32 bits of object size  - use 0, for file size
LPCTSTR lpName // name of file-mapping object
);
Info: Function return a handle to either a new file mapping object or to an existing one.  The return value is NULL, if function fails.  The flProtect argument can be one of the following.
  • PAGE_READONLY   -  memory  is readonly
  • PAGE_READWRITE  - memory is readable and writable
  • PAGE_WRITECOPY  - memory is readable, after memory has been written too.

The dwMaximumSizeHigh and dwMaximuSizeLow arguments are used to specify the size of the object.

 

Proto-Type: LPVOID MapViewOfFile

(

HANDLE hFileMappingObject, // file-mapping object to map into address space
DWORD dwDesiredAccess, // access mode
DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
DWORD dwFileOffsetLow, // low-order 32 bits of file offset
DWORD dwNumberOfBytesToMap // number of bytes to map
);
Info: Function maps a buffer referred to by the file mapping object to the local process space of the current process. The function returns NULL, if  there was an error.  Otherwise, it returns a legal address.  The dwDesiredAccess has the following values:
  • FILE_MAP_READ -   mapping can only be read only
  • FILE_MAP_WRITE  - mapping can either be read or write

 

Proto-Type: BOOL UnmapViewOfFile

(

LPCVOID lpBaseAddress // address where mapped view begins
);
Info: A file buffer that was mapped to the current process space is realeased with this function.  The lpBaseAddress argument takes the return value from the   MapViewOfFile function.  The return value of this function is TRUE on success.

 

The next program demonstrates the conversion of a file to upper case by first creating a file mapping for the ReadMe.txt text file.

//  Program converts a file to upper case

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <windows.h>


static const char szFileName[]="ReadMe.txt";

int main(void)
    {
    HANDLE hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE
        ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
    assert(hFile!=INVALID_HANDLE_VALUE);

        // create anonymous file mapping
    HANDLE hMap=CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,NULL);
    assert(hMap!=NULL);

    DWORD dwFileSize=GetFileSize(hFile,NULL); // get file size
   
        // map buffer to local process space
    char *memory=(char *)MapViewOfFile(hMap,FILE_MAP_WRITE,0,0,dwFileSize);
    assert(memory!=NULL);

    for(DWORD i=0;i<dwFileSize;++i)
        {
        if(islower(memory[i])!=0)
            { toupper(memory[i]); }
        }
   
    UnmapViewOfFile(memory); // unmap file buffer

    CloseHandle(hMap); CloseHandle(hFile);
    return 0;
    }

 

The next program demonstrates access to shared memory.  Two processes access a ten buffers.  Each buffer has a maximum size of 100 bytes.  The first process is writer process that write information to the buffers in ascending order.  The second process is a reader that reads the buffers.  This is the standard reader-writer problem found in operating system courses.

/* Writer Code */

#include <windows.h>
#include <assert.h>
#include <stdio.h>


static const LONG BufferSize=100;
static const LONG Buffers=10;
static const char MemoryName[]="Share Memory Name";
static char (*Memory)[Buffers][BufferSize];
static const char WriterSemaphoreName[]="Writer Semaphore";
static const char ReaderSemaphoreName[]="Reader Semaphore";

static HANDLE hWriterSemaphore;
static HANDLE hReaderSemaphore;
static HANDLE hMemory;

int main(void)
    {
    hWriterSemaphore=CreateSemaphore(NULL,Buffers,Buffers,WriterSemaphoreName);
    assert(hWriterSemaphore!=NULL);

    hReaderSemaphore=CreateSemaphore(NULL,0,Buffers,ReaderSemaphoreName);
    assert(hReaderSemaphore!=NULL);

    hMemory=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,0
        ,sizeof(char [Buffers][BufferSize]),MemoryName);
    assert(hMemory!=NULL);

    Memory=(char (*)[Buffers][BufferSize])MapViewOfFile(hMemory
        ,FILE_MAP_WRITE,0,0,sizeof(char [Buffers][BufferSize]));
    assert(Memory!=NULL);

    for(int i=0;;++i)
        {
        WaitForSingleObject(hWriterSemaphore,INFINITE);
        printf("Writing to Buffer %i\n",i);
        wsprintf((*Memory)[i%Buffers],"This is the writer - %i\n",i);
   
        Sleep(1000);

        ReleaseSemaphore(hReaderSemaphore,1,NULL);
        }

    UnmapViewOfFile(Memory);
    CloseHandle(hWriterSemaphore); CloseHandle(hReaderSemaphore);
    return 0;
    }

// Reader Code

#include <windows.h>
#include <assert.h>
#include <stdio.h>


static const LONG BufferSize=100;
static const LONG Buffers=10;
static const char MemoryName[]="Share Memory Name";
static char (*Memory)[Buffers][BufferSize];
static const char WriterSemaphoreName[]="Writer Semaphore";
static const char ReaderSemaphoreName[]="Reader Semaphore";

static HANDLE hWriterSemaphore;
static HANDLE hReaderSemaphore;
static HANDLE hMemory;

int main(void)
    {
    hWriterSemaphore=CreateSemaphore(NULL,Buffers,Buffers,WriterSemaphoreName);
    assert(hWriterSemaphore!=NULL);

    hReaderSemaphore=CreateSemaphore(NULL,0,Buffers,ReaderSemaphoreName);
    assert(hReaderSemaphore!=NULL);

    hMemory=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,0
        ,sizeof(char [Buffers][BufferSize]),MemoryName);
    assert(hMemory!=NULL);

    Memory=(char (*)[Buffers][BufferSize])MapViewOfFile(hMemory
        ,FILE_MAP_WRITE,0,0,sizeof(char [Buffers][BufferSize]));
    assert(Memory!=NULL);

    for(int i=0;;++i)
        {
        WaitForSingleObject(hReaderSemaphore,INFINITE);
        puts((*Memory)[i%10]); // Reader reads data
        ReleaseSemaphore(hWriterSemaphore,1,NULL);
        }

    UnmapViewOfFile(Memory);
    CloseHandle(hWriterSemaphore); CloseHandle(hReaderSemaphore);
    return 0;
    }
copyrightę2001 Prof Devi - mdevi@comine.com, mdevi@liu.edu. All rights reserved