17.4 映射到内存的可执行文件和DLL
(1)EXE文件格式
节名 |
作用 |
.text |
.exe和.dll文件的代码 |
.data |
己经初始化的数据 |
.bss |
未初始化的数据 |
.reloc |
重定位表(装载进程的进程地址空间) |
.rdata |
运行期只读数据 |
.CRT |
C运行期只读数据 |
.debug |
调用试信 |
.xdata |
异常处理表 |
.tls |
线程本地化存储 |
.idata |
输入文件名表 |
.edata |
输出文件名表 |
.rsrc |
资源表 |
.didata |
延迟输入文件名表 |
(2)加载exe的过程
①先CreateProcess创建进程内核对象,为进程创建一个私有地址空间(页目录和页表)
②系统根据exe大小,在默认的基地址0x0040 0000上预订适当大小的区域(可以在链接程序时用/BASE 选项更改基地址,方法是在VC工程属性\链接器\高级上设置)。
③系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自于磁盘上的exe文件,而并非来自系统的页交换文件。
④读取exe文件的.idata节,此节列出exe所用到的所有dll文件。然后和exe文件一样,将dll文件映射到进程空间中。如果无法映射到基地址,系统会重新定位。(详见后面的《加载Dll过程》
⑤ 把所有的exe文件和DLL文件都映射到进程的地址空间之后,系统开始执行exe文件的启动代码,将第一页代码加载到内存,然后更新页目和页表。将第一条指令的地址交给线程指令指针。当系统执行时,会发现代码没有在内存中,就会通过页面错误机制,将exe文件中的代码加载到内存中。
(2)加载DLL的过程
系统通过LoadLibrary载入每个DLL,如果哪个DLL需要调用其他DLL,系统会同样地调用LoadLibrary来载入相应的DLL,其载入过程如下:
①预订一块足够大的地址空间来容纳DLL,并在默认的基地址(如0x10000000)预订该区域。(可以使用/BASE链接器开关来指定这个基地址)。与Windows系统的DLL都有不同的基地址,这样即使把它们载入到同一个地址空间,也不会发生重叠。
②如果系统无法在DLL文件指定的基地址处预订区域(可能是该区域被另一个DLL或EXE占用,或区域不够大),这时系统尝试在另一个地址来为DLL预订区域。但这时需要重定位,但重定位需要占用页交换文件中额外的存储空间,而且会增加加载DLL所需的时间。而如果DLL不包含重定位信息(使用链接器的/FIXED开关构建的DLL是不包含重定位信息的,这开关的好处是可以使DLL文件变得更小),那么将无法被载入。
③系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的DLL文件,而不是系统的页交换文件。如果由于Windows不能将DLL载入到指定的基地址而必须执行重定位的话,那么系统还会另外进行标注,表明DLL中有一部分物理存储器被映射到了页交换文件。
(3)第2次加载exe的过程
①建立进程、映射进程空间与第1次加载EXE是一样的,只是当系统发现这个EXE己经建立了内存映射文件对象时,它就直接映射到进程空间了。只是当系统分配物理页面时,根据节的保护属性赋予页面的保护属性,对于代码节赋予READ属性,全局变量节赋予COPY_ON_WRITE属性。
②不同的实例共享代码节和其他的节,当实例需要改变页面内容时,会拷贝页面内容到新的页面,更新页目和页表。
③对于不同进程实例需要共享的变量,EXE文件有一个默认的节,给这个节赋予SHARED属性,我们也可以创建自己的SHARED节(请后面相关的内容)
17.4.1 同一个可执行文件和DLL的多个实例不会共享静态数据
(1)多实例的启动
①如果一个应用程序己经运行,当创建该应用程序的新实例时,会根据己经创建的同一个映射文件,打开另一个内存映射视图。通过内存映射文件,同一个实例可以共享内存中的代码和数据。
②当第2个实例启动时,系统把包含应用程序代码和数据的虚拟内存页面映射到第2个实例的地址空间。(注意虚拟内存即为内存的一部分,被载入到虚拟内存中的数据或代码可理解为就是内存中的数据了,注意与进程的地址空间的区别)
(2)“写时复制”机制——保证了多实例不会共享静态数据
①任何时候,当应用程序试图写入内存映射文件的时候,系统会截获这种尝试,接着发生“写时复制”,即为应用程序(如实例2)分配一块新的内存,然后复制“数据页面2”的内容到新的页面。并重新将“实例2”地址空间中的“数据页面2”重新映射到“新页面”
②这样,“实例1”和“实例2”地址空间中的“数据页面2”就分别被映射到虚拟内存中的不同页面,从而保护数据,使得数据不会被另一个实例修改。
17.4.2 在同一个可执行文件或DLL的多个实例间共享静态数据
(1)可执行文件的常用段以及段的属性
①常用的段:如.bss、.data、.text等,详细见前面的“EXE文件格式”表格
②段的属性
属性 |
含义 |
READ |
可以从该段读取数据 |
WRITE |
可以向该段写入数据 |
EXECUTE |
可以执行该段的内容 |
SHARED |
该段的内容为多个实例所共享(本质上是关闭了写时复制机制) |
(2)多实例共享数据的方法
①创建自己的“数据段”
#pragma data_seg(“MyShareName”) // MyShareName为段的名字,可自定义
LONG g_lInstanceCount = 0; //要共享的变量必须是经过初始化的
#pragma data_seg(); //告诉编译器停止把己初始化的变量放到MyShareName段中。
② 将自定义的“数据段”属性设为“共享”
#pragma comment(linker, “/SECTION:MyShareName,RWS”) //读、写、共享
(3)allocate声明符——可将初始化或未初始化的数据放到指定的段中
//创建一个“共享段”,并将己初始化的数据放入其中
#pragma data_seg("Shared")
int a = 0; //己初始化数据,在“Shared”中
int b = 0; //未初始化的数据,不在“Shared”段中
#pragma data_seg() //让编译器停止将己初始化变量放入“Shared”段
//初始化,并将变量放入“Shared”段中
__declspec(allocate("Shared")) int c = 0;
//将未始化变量放入“Shared”段中
__declspec(allocate("Shared")) int d;
//己初始化,但不在“Shared”段中
int e = 0;
//未初始化,也不在“Shared”段中
int f;
【AppInst程序】统计共有多少个应用程序的实例正在运行
//AppInst.cpp
/************************************************************************
Module: AppInst.cpp
Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include "resource.h"
#include <tchar.h>
//////////////////////////////////////////////////////////////////////////
//系统广播消息
UINT g_uMsgAppInstCountUpdate = WM_APP + 123;
//////////////////////////////////////////////////////////////////////////
//创建“共享段”
#pragma data_seg("Shared")
volatile LONG g_lApplicationInstances = 0; //正在运行的实例的个数
#pragma data_seg()
//告诉编译器,让Shared段只有可读、可读及共享属性
#pragma comment(linker,"/Section:Shared,RWS")
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
chSETDLGICONS(hwnd, IDI_APPINST);
//强制刷新,以显示正确的实例数。
PostMessage(HWND_BROADCAST, g_uMsgAppInstCountUpdate, 0, 0);
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtrl, UINT codeNotify){
switch (id)
{
case IDCANCEL:
EndDialog(hWnd, id);
break;
}
}
//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
if (uMsg == g_uMsgAppInstCountUpdate){
SetDlgItemInt(hwnd, IDC_COUNT, g_lApplicationInstances, FALSE);
}
switch (uMsg)
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd)
{
//注删用于广播的消息,并何存ID,该消息用来通知实例个数发生变化
g_uMsgAppInstCountUpdate = RegisterWindowMessage(TEXT("MsgAppInstCountUpdate"));
//新的应用程序实例正在运行
InterlockedExchangeAdd(&g_lApplicationInstances, 1);
DialogBox(hInstance, MAKEINTRESOURCE(IDD_APPINST), NULL, Dlg_Proc);
//应用程序的结束运行
InterlockedExchangeAdd(&g_lApplicationInstances, -1);
//通知其他实例更新显示
PostMessage(HWND_BROADCAST, g_uMsgAppInstCountUpdate, 0, 0);
return 0;
}
//resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 17_AppInst.rc 使用
//
#define IDD_APPINST 1
#define IDC_COUNT 100
#define IDI_APPINST 101
#define IDI_ICON1 102
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
//AppInst.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_APPINST DIALOGEX 0, 0, 161, 21
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "多个实例的应用程序"
FONT 10, "宋体", 400, 0, 0x0
BEGIN
LTEXT "正在运行的实例数:",IDC_STATIC,25,6,93,8,SS_NOPREFIX
RTEXT "#",IDC_COUNT,113,6,16,12,SS_NOPREFIX
END
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPINST ICON "AppInst.Ico"
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_APPINST, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
原文:http://www.cnblogs.com/5iedu/p/4926309.html