I have already provided two tutorials on Virtual Memory and Heap Memory Management, and this final tutorial on memory management looks at shared memory management. This is memory shared between two processes, as opposed to two threads in one process. The API calls we use to give us the ability to share memory segments between processes, may seen a little odd at first glance. I shall endeavour to explain as we progress.
One of the processes has to be designated the 'server' or 'owner' process. This process is responsible for creating the shared memory area that one or more 'client' processes can map into their address space. Now you cannot simply say I want to share this class with other processes. It simply does not work that way.
Server Side of the Operation
To create a shared memory area, we use the CreateFileMapping API call. Now you may wonder what a file has to do with sharing memory, and I will try to explain that one for you. Microsoft made a very clever decision with the Windows NT operating system, (Windows NT was the forerunner of all their operating systems since).
When you have an operating system that seamlessly pages memory onto disc and back into memory (page swapping), and you have put into place all the controlling code for locking and unlocking pages, caching of disc pages for reading and writing, why bother writing special code for sharing memory between processes. You have everything you need. So, if we take a look at the CreateFileMapping call (found on MSDN here), you will notice the first parameter supplied is a file handle. Don't worry though, we will not be using this feature during this tutorial. Setting this parameter to INVALID_HANDLE_VALUE tells the operating system we are not sharing through a file, but memory. The next parameter is the security attributes, which we will set to NULL. The third parameter tells the operating system how we want to access the shared memory. If you read my tutorial on Virtual Memory Management, you will recognize these parameters. We would normally use PAGE_READWRITE, which gives us both read and write access to the memory we are requesting. Next, we have the high and low order 32-bit parameters needed to specify how much shared memory we are after. Remember, files can be over 4GB in size, and therefore we need 64-bits to address such files. Finally we need to supply either the address of a string literal name, or NULL as the last parameter. Here, as we are not sharing a file through memory, we have to give the memory map a name, so our clients can use it. So, here is a fairly typical CreateFileMapping call.
HANDLE shared_memory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "MySharedMemory");
Client Side of the Operation
Client's need to use the OpenFileMapping API call (details may be found here on MSDN). This call requires three parameters. The first, tells the operating system what access is required for the shared memory, the second parameter specifies whether or not the handle can be inherited by a child process created using the CreateProcess API call, and the third parameter specifies the memory map name used to create the map on the server side. Below is a fairly typical OpenFileMapping call.
HANDLE shared_memory = OpenFileMapping(PAGE_READWRITE, FALSE, "MySharedMemory");
The MapViewOfFile API Call
Once the server side has created the shared memory map, and the client has opened the memory map, they both need to call MapViewOfFile (details found here on MSDN), in order that they may obtain the address of the shared memory. The MapViewOfFile call is given the handle of the map, as returned by either the CreateFileMapping or the OpenFileMapping calls. The next parameter specifies the desired access required. Now the server will need to put FILE_MAP_WRITE and the client needs FILE_MAP_READ. No other combination works. This means that the server can change the data in the shared memory, but the client can only read the data, effectively making it a one way means of communication. The third and fourth parameters specify the offset address to start the map from. It is a 64-bit value, given as two 32-bit words. For most situations, the value would be zero for both these parameters. Finally, there is the number of bytes to map into view. Putting the server side into code would typically be:
HANDLE shared_memory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "MySharedMemory"); LPVOID base_address = MapViewOfFile(shared_memory, FILE_MAP_WRITE, 0, 0, 4096);
and, putting the client side into code would typically be:
HANDLE shared_memory = OpenFileMapping(PAGE_READWRITE, FALSE, "MySharedMemory"); LPVOID base_address = MapViewOfFile(shared_memory, FILE_MAP_READ, 0, 0, 4096);
Shared Memory Synchronization
We have to be extremely careful when discussing the implication of shared memory and coherence of data, especially in a multi-processor environment. If you have read my tutorial on Thread Synchronization (found here), I introduced you to the _InterlockedCompareExchange API call which is an atomic operation that cannot be interrupted, and is far superior than using a mutex semaphore. Will this work in a multiprocessor environment and can it be used here? The simple answer is no it won't. As we do not have any way for the client to let the server know it is using the shared memory, and not to make updates, data coherence is not guaranteed.
The applications where shared memory could be used are extremely limited due to the inherent problems with data coherence. You cannot sadly, really use it as a means of inter process communication, as information has more or less to be static seen from the server perspective. It's primary application of course is for DLL's, where the code is shared between processes. Still, it is worth being aware of this operating system facility.