When you create a new process for an application that is already running, the system simply opens another memory-mapped view of the file-mapping object that identifies the executable file’s image and creates a new process object and a new thread object (for the primary thread). The system also assigns new process and thread IDs to these objects. By using memory-mapped files, multiple running instances of the same application can share the same code and data in RAM.
Note one small problem here. Processes use a flat address space. When you compile and link your program, all the code and data are thrown together as one large entity. The data is separated from the code but only to the extent that it follows the code in the .exe file. (See the following note for more detail.) The following illustration shows a simplified view of how the code and data for an application are loaded into virtual memory and then mapped into an application’s address space
As an example, let’s say that a second instance of an application is run. The system simply maps the pages of virtual memory containing the file’s code and data into the second application’s address space, as shown next
The system allocated a new page of virtual memory (labeled as "New page" in the image above) and copied the contents of data page 2 into it. The first instance’s address space is changed so that the new data page is mapped into the address space at the same location as the original address page. Now the system can let the process alter the global variable without fear of altering the data for another instance of the same application.
A similar sequence of events occurs when an application is being debugged. Let’s say that you’re running multiple instances of an application and want to debug only one instance. You access your debugger and set a breakpoint in a line of source code. The debugger modifies your code by changing one of your assembly language instructions to an instruction that causes the debugger to activate itself. So you have the same problem again. When the debugger modifies the code, it causes all instances of the application to activate the debugger when the changed assembly instruction is executed. To fix this situation, the system again uses copy-on-write memory. When the system senses that the debugger is attempting to change the code, it allocates a new block of memory, copies the page containing the instruction into the new page, and allows the debugger to modify the code in the page copy.
Sharing Static Data across Multiple Instances of an Executable or DLL
The fact that global and static data is not shared by multiple mappings of the same .exe or DLL is a safe default. However, on some occasions it is useful and convenient for multiple mappings of an .exe to share a single instance of a variable. For example, Windows offers no easy way to determine whether the user is running multiple instances of an application. But if you could get all the instances to share a single global variable, this global variable could reflect the number of instances running. When the user invoked an instance of the application, the new instance’s thread could simply check the value of the global variable (which had been updated by another instance), and if the count were greater than 1, the second instance could notify the user that only one instance of the application is allowed to run and the second instance would terminate.
Every .exe or DLL file image is composed of a collection of sections. By convention, each standard section name begins with a period. For example, when you compile your program, the compiler places all the code in a section called .text. The compiler also places all the uninitialized data in a .bss section and all the initialized data in a .data section.
Executable Common Sections
In addition to using the standard sections created by the compiler and the linker, you can create your own sections when you compile using the following directive:
So, for example, I can create a section called "Shared" that contains a single LONG value, as follows:
LONG g_lInstanceCount = 0;
When the compiler compiles this code, it creates a new section called Shared and places all the initialized data variables that it sees after the pragma in this new section. In the preceding example, the variable is placed in the Shared section. Following the variable, the #pragma data_seg() line tells the compiler to stop putting initialized variables in the Shared section and to start putting them back in the default data section. It is extremely important to remember that the compiler will store only initialized variables in the new section
The Microsoft Visual C++ compiler offers an allocate declaration specifier, however, that does allow you to place uninitialized data in any section you desire. Take a look at the following code:
// Create Shared section & have compiler place initialized data in it.
// Initialized, in Shared section
int a = 0;
// Uninitialized, not in Shared section
// Have compiler stop placing initialized data in Shared section.
// Initialized, in Shared section
__declspec(allocate("Shared")) int c = 0;
// Uninitialized, in Shared section
__declspec(allocate("Shared")) int d;
// Initialized, not in Shared section
int e = 0;
// Uninitialized, not in Shared section
Simply telling the compiler to place certain variables in their own section is not enough to share those variables.
You must also tell the linker that the variables in a particular section are to be shared. You can do this by using the
/SECTION switch on the linker's command line:
Following the colon, type the name of the section for which you want to alter attributes. In our example, we want to change the attributes of the Shared section. So we’d construct our linker switch as follows:
After the comma, we specify the desired attributes: use R for READ, W for WRITE, E for EXECUTE, and S for SHARED. The switch shown indicates that the data in the Shared section is readable, writable, and shared. If you want to change the attributes of more than one section, you must specify the /SECTION switch multiple times—once for each section for which you want to change attributes.
You can also embed linker switches right inside your source code using this syntax:
#pragma comment(linker, "/SECTION:Shared,RWS")
This line tells the compiler to embed the preceding string inside a special section of the generated .obj file named ".drectve". When the linker combines all the .obj modules together, the linker examines each .obj module’s ".drectve" section and pretends that all the strings were passed to the linker as command-line arguments. this technique should be used all the time because it is so convenient—if you move a source code file into a new project, you don’t have to remember to set linker switches in the Visual C++ Project Properties dialog box
Although you can create shared sections, Microsoft discourages the use of shared sections for two reasons. First, sharing memory in this way can potentially violate security. Second, sharing variables means that an error in one application can affect the operation of another application because there is no way to protect a block of data from being randomly written to by an application.