内存映射文件

——Windows核心编程学习手札之十七

与虚拟内存一样,内存映射文件保留地址空间,并将物理存储器提交给该区域,差别在于所提交的物理存储器是磁盘上有文件存在的空间,而非系统的页文件,一旦文件被映射,就可以访问它,如同整个文件被加载到内存一样。内存映射文件用于三个不同目的:

1)系统使用内存映射文件,以便加载和执行.exe和DLL文件,可以节省页文件空间和应用程序启动运行所需的时间;

2)可以使用内存映射文件来访问磁盘上的数据文件,可以不必对文件执行I/O操作,并且可以不用对文件内容进行缓存;

3)使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。

当线程调用CreateProcess时,系统会执行下列步骤:

1)系统找出在调用CreateProcess时设定的.exe文件,如找不到这个文件,进程无法创建,CreateProcess将返回FALSE;

2)系统创建一个新进程内核对象;

3)系统为新进程创建一个私有地址空间;

4)系统保留一个足够大的地址空间区域,用于存放.exe文件,按照默认设置,.exe文件的基地址是0X00400000,也可以在创建应用程序的.exe文件时重载这个地址,方法是在链接应用程序时使用链接程序的/BASE选项。

5)系统知道已保留区域的物理存储器是在磁盘上的.exe文件,而不是在系统的页文件;

当.exe文件被映射到进程的地址空间后,系统将访问.exe文件一个部分,该部分列出了包含.exe文件中的代码要调用的函数的DLL文件,进而系统为每个DLL文件调用LoadLibrary函数。通过LoadLibrary函数加载DLL,系统有如下步骤:

1)系统保留一个足够大的地址空间区域,用于存放DLL文件,区域位置由DLL文件本身决定,默认设置,Microsoft的Visual C++建立的DLL文件基地址是0X10000000,不过,你可以在创建DLL文件重载这个地址,方法是使用链接程序的/BASE选项。Windows提供的所有标准系统DLL都拥有不同的基地址,这样,如果加载到单个地址空间,区域不会重叠;

2)系统可能因为区域为其他.exe或DLL文件占用和区域不够大的原因,无法在该DLL的首选基地址;

3)系统知道支持已保留区域的物理存储器位于磁盘上的DLL文件中,而不是在系统的页文件中;

如果由于某种原因,系统无法映射.exe和所有必要的DLL文件,那系统会通过消息框提示,并且释放进程的地址空间和进程对象。CreateProcess函数将返回FALSE,可通过GetLastError函数了解失败原因。当.exe文件和所有必要DLL文件都成功映射到进程的地址空间后,系统开始执行.exe文件的启动代码,同时,系统将负责所有的分页、缓冲和高速缓存的处理,如.exe文件中的代码需要去访问尚未加载到内存的指令地址,将出现错误,系统发现错误后,将自动将该页代码从该文件的映象加载到一个RAM页面,之后系统将这个RAM页面映射到进程的地址空间中的相应位置,并且让线程继续运行。

知道应用程序实例数代码:

// AppInst.cpp : Defines the entry point for the application.

//

#include "stdafx.h"

#include "resource.h"

#define MAX_LOADSTRING 100

// Global Variables:

HINSTANCE hInst;                                                        // current instance

TCHAR szTitle[MAX_LOADSTRING];                                                        // The title bar text

TCHAR szWindowClass[MAX_LOADSTRING];                                                  // The title bar text

// Foward declarations of functions included in this code module:

ATOM                          MyRegisterClass(HINSTANCE hInstance);

BOOL                          InitInstance(HINSTANCE, int);

LRESULT CALLBACK  WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

//define here for demo

UINT g_uMsgAppInstCountUpdate=INVALID_ATOM;//the system-wide unique window message

//==========================================

//tell the compiler to put this initialized variable in tis own shared section

//so it is shared by all instances of this application.

#pragma data_seg("Shared")

volatile LONG g_lApplicationInstances=0;

#pragma data_seg()

//tell the linker to make the Shared section readable/writable,and shared.

#pragma comment(linker,"/Section:Shared,RWS")

//=============================================

//end of demo

int APIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR     lpCmdLine,

int       nCmdShow)

{

//get the numberic value of the systemwide window message used to notify

//all top-level windows when the module's usage count has changed.

g_uMsgAppInstCountUpdate=RegisterWindowMessage(TEXT("MsgAppInstCountUpdate"));

//there is another instance of this application running.

InterlockedExchangeAdd((PLONG)&g_lApplicationInstances,1);

// TODO: Place code here.

MSG msg;

HACCEL hAccelTable;

// Initialize global strings

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

LoadString(hInstance, IDC_APPINST, szWindowClass, MAX_LOADSTRING);

MyRegisterClass(hInstance);

// Perform application initialization:

if (!InitInstance (hInstance, nCmdShow))

{

return FALSE;

}

hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_APPINST);

// Main message loop:

while (GetMessage(&msg, NULL, 0, 0))

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

InterlockedExchangeAdd((PLONG)&g_lApplicationInstances,-1);

//have all other instances update their display.

PostMessage(HWND_BROADCAST,g_uMsgAppInstCountUpdate,0,0);

return msg.wParam;

}

//

//  FUNCTION: MyRegisterClass()

//

//  PURPOSE: Registers the window class.

//

//  COMMENTS:

//

//    This function and its usage is only necessary if you want this code

//    to be compatible with Win32 systems prior to the 'RegisterClassEx'

//    function that was added to Windows 95. It is important to call this function

//    so that the application will get 'well formed' small icons associated

//    with it.

//

ATOM MyRegisterClass(HINSTANCE hInstance)

{

WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style                    = CS_HREDRAW | CS_VREDRAW;

wcex.lpfnWndProc       = (WNDPROC)WndProc;

wcex.cbClsExtra           = 0;

wcex.cbWndExtra         = 0;

wcex.hInstance             = hInstance;

wcex.hIcon                  = LoadIcon(hInstance, (LPCTSTR)IDI_APPINST);

wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);

wcex.hbrBackground     = (HBRUSH)(COLOR_WINDOW+1);

wcex.lpszMenuName     = (LPCSTR)IDC_APPINST;

wcex.lpszClassName     = szWindowClass;

wcex.hIconSm             = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

return RegisterClassEx(&wcex);

}

//

//   FUNCTION: InitInstance(HANDLE, int)

//

//   PURPOSE: Saves instance handle and creates main window

//

//   COMMENTS:

//

//        In this function, we save the instance handle in a global variable and

//        create and display the main program window.

//

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

{

HWND hWnd;

hInst = hInstance; // Store instance handle in our global variable

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

if (!hWnd)

{

return FALSE;

}

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

//Force the static control to be initializede correctly.

PostMessage(HWND_BROADCAST,g_uMsgAppInstCountUpdate,0,0);

return TRUE;

}

//

//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)

//

//  PURPOSE:  Processes messages for the main window.

//

//  WM_COMMAND    - process the application menu

//  WM_PAINT     - Paint the main window

//  WM_DESTROY      - post a quit message and return

//

//

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

int wmId, wmEvent;

PAINTSTRUCT ps;

HDC hdc;

TCHAR szHello[MAX_LOADSTRING];

LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

if(message==g_uMsgAppInstCountUpdate)

{

TCHAR chNum[4];

itoa(g_uMsgAppInstCountUpdate,chNum,4);

strcat(szHello,chNum);

}

switch (message)

{

case WM_COMMAND:

wmId    = LOWORD(wParam);

wmEvent = HIWORD(wParam);

// Parse the menu selections:

switch (wmId)

{

case IDM_ABOUT:

DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);

break;

case IDM_EXIT:

DestroyWindow(hWnd);

break;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

break;

case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

// TODO: Add any drawing code here...

RECT rt;

GetClientRect(hWnd, &rt);

DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);

EndPaint(hWnd, &ps);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

return 0;

}

// Mesage handler for about box.

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_INITDIALOG:

return TRUE;

case WM_COMMAND:

if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)

{

EndDialog(hDlg, LOWORD(wParam));

return TRUE;

}

break;

}

return FALSE;

}

操作系统使内存能够将一个数据文件映射到进程的地址空间中,方便大量数据的操作。下面以将文件中的所有字节顺序进行倒序,来理解内存映射文件的功能。

步骤一:创建或打开文件内核对象

若要创建或打开一个文件内核对象,调用CreateFile函数:

HANDLE CreateFile(

PCSTR pszFileName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

PSECURITY_ATTRIBUTES psa,

DWORD dwCreationDisposition,

DWORD dwFlagsAndAttributes,

HANDLE hTemplateFile);

参数pszFileName用于指明要创建或打开的文件的名字(包括严格选项路径),参数dwDesiredAccess用于设定如何访问该文件的内容,为0表不能读取或写入文件的内容(用于只想获取文件的属性时);为GENERIC_READ可以从文件中读取数据;为GENERIC_WRITE可以将数据写入文件;为GENERIC_READ|GENERIC_WRITE可以从文件中读取数据,也可以将数据写入文件。参数dwShareMode告诉系统将如何共享文件,为0表打开文件的任何尝试均将失败;为FILE_SHARE_READ表示使用GENERIC_WRITE打开文件的其他尝试将会失败;为FILE_SHARE_WRITE表示使用GENERIC_READ打开文件的其他尝试将会失败;为FILE_SHARE_READ|FILE_SHARE_WRITE表示打开文件的其他尝试将会取得成功。如果CreateFile函数成功地创建或打开指定的文件,便返回个亿 文件内核对象的句柄,否则返回INVALID_HANDLE_VALUE。

步骤二:创建一个文件映射内核对象

调用CreateFile函数,就可将文件映象的物理存储器的位置告诉系统,所传递的路径名用于指明支持文件映象的物理存储器所在磁盘上的确切位置,此时,还需要告诉系统,文件映射对象需要多少物理存储器,需调用CreateFileMapping函数:

HANDLE CreateFileMapping(

HANDLE hFile,

PSECURITY_ATTRIBUTES psa,

DWORD fdwProtect,

DWORD dwMaximumSizeHigh,

DWORD dwMaximumSizeLow,

PCTSTR pszName);

参数hFile用于标识映射到进程地址空间中的文件句柄,是CreateFile返回的值;psa参数是指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针,通常传递NULL值。系统创建文件映射对象,并将用于标识该对象的句柄返回该调用线程,如系统无法创建文件映射对象,便返回一个NULL句柄值。

步骤三:将文件数据映射到进程的地址空间

当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。可通过MapViewOfFile函数来实现:

PVOID MapViewOfFile(

HNADLE hFileMappingObject,

DWORD dwDesiredAccess,

DWORD dwFileOffsetHigh,

DWORD dwFileOffsetLow,

SIZE_T dwNumberOfBytesToMap);

参数hFileMappingObject用于标识文件映射对象的句柄,该句柄上调用CreateFileMapping和OpenFileMapping函数返回的。参数dwDesiredAccess用于标识如何访问该数据。

步骤四:从进程的地址空间中撤消文件数据的映象

当不再需要保留映射到你的进程地址空间区域中的文件数据时,可调用下面函数:

BOOL UnmapViewOfFile(PVOID pvBaseAddress);

参数pvBaseAddress用于设定返回区域的基地址,该值必须与调用MapViewOfFile函数返回的值相同。在进程终止运行前,如没有调用该函数,保留区域将不会被释放。为提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映象,如果要确保更新被写入磁盘,可调用函数FlushViewOfFile强制系统将修改过的数据的一部分或全部重新写入磁盘映像中:

BOOL FlushViewOfFile(

PVOID pvAddress,

SIZE_T dwNumberOfBytesToFlush);

第一个参数pvAddress包含在内存映射文件中的视图的一个字节的地址,传递地址圆整为一个页面边界值,第二个参数指明要刷新的字节数。

步骤五和步骤六:关闭文件映射对象和文件对象

如不关闭打开了的内核对象,在进程继续运行中会导致资源泄露。关闭文件映射对象和文件对象,需两次调用CloseHandle函数,每个句柄调用一次。

使用内存映射文件在进程之间共享数据。Windows提供各种机制,使应用程序能共享数据和信息,这些机制包括:RPC、COM、OLE、DDE、窗口消息(尤其是WM_COPYDATA)、剪贴板、邮箱、管道和套接字等。在Windows中,单机共享数据的最底层机制是内存映射文件。如果互相进行通信的所有进程都在同一台计算机上,上面提到的所有机制均使用内存映射文件从事,如要求达到较高性能和较小开销,内存映射文件是最佳机制。数据共享方法是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面,因此,当一个进程将数据写入一个共享文件映射对象的视图时,其他进程可以立即看到它们视图中的数据变更情况。注意,如果多个进程共享单个文件映射对象,那所有进程必须使用相同的名字来表示该文件映射对象。

内存映射文件——Windows核心编程学习手札之十七相关推荐

  1. 插入DLL和挂接API——Windows核心编程学习手札之二十二

    插入DLL和挂接API --Windows核心编程学习手札之二十二 如下情况,可能要打破进程的界限,访问另一个进程的地址空间: 1)为另一个进程创建的窗口建立子类时: 2)需要调试帮助时,如需要确定另 ...

  2. DLL基础——Windows核心编程学习手札之十九

    DLL基础 --Windows核心编程学习手札之十九 Windows API中的所有函数都包含在DLL中,3个最重要的DLL是Kernel32.dll,它包含用于管理内存.进程和线程的各个函数:Use ...

  3. 在应用程序中使用虚拟内存——Windows核心编程学习手札之十五

    在应用程序中使用虚拟内存 --Windows核心编程学习手札之十五 Windows提供了3种进行内存管理的方法: 1)  虚拟内存,最适合用来管理大量对象或结构数组: 2)  内存映射文件,最适合用来 ...

  4. 虚拟内存——Windows核心编程学习手札之十四

    虚拟内存 --Windows核心编程学习手札之十四 系统信息 有些操作系统的值是根据主机而定的,如页面大小.分配粒度大小等,这些值不用硬编码形式,进程初始化时应检索这些值以使用.函数GetSystem ...

  5. 内核对象——Windows核心编程学习手札系列之三

    内核对象 --Windows核心编程学习手札系列之三 内核对象可供系统和应用程序使用来管理各种各样的资源,如进程.线程.文件等,是内核分配的一个内存块,只能又内核访问,该内存块是一种数据结构,它的成员 ...

  6. 窗口消息——Windows核心编程学习手札之二十六

    窗口消息 --Windows核心编程学习手札之二十六 Windows允许一个进程至多建立10000个不同类型的用户对象(user object):图符.光标.窗口类.菜单.加速键表等,当一个线程调用一 ...

  7. 未处理异常和C++异常——Windows核心编程学习手札之二十五

    未处理异常和C++异常 --Windows核心编程学习手札之二十五 当一个异常过滤器返回EXCEPTION_CONTINUE_SEARCH标识符时是告诉系统继续上溯调用树,寻找另外的异常过滤器,但当每 ...

  8. 异常处理程序和软件异常——Windows核心编程学习手札之二十四

    异常处理程序和软件异常 --Windows核心编程学习手札之二十四 CPU负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异常作为对错误的反应,CPU引发的异常称为硬件异常(hardwar ...

  9. DLL的高级操作技术——Windows核心编程学习手札之二十

    DLL的高级操作技术 --Windows核心编程学习手札之二十 显示加载DLL模块: HINSTANCE LoadLibrary(PCTSTR pszDLLPathName); HINSTANCE L ...

最新文章

  1. SSO 单点登录会话管理
  2. Guava包学习--Multiset
  3. angularJS 自定义元素和属性
  4. mysql字符串区分大小写么_mysql字符串区分大小写的问题-阿里云开发者社区
  5. vue自定义指令截取图片中心显示
  6. [云炬商业计划书阅读分享]
  7. 代码检查规则:Python语言案例详解
  8. 使用Thumbnailator压缩照片
  9. NDPIC极简昼夜瀑布流图片WordPress主题
  10. 蚂蚁集团CEO胡晓明宣布辞任
  11. 老生长谈的$.extend()方法
  12. 【51单片机】利用烧录软件生成延时函数 入门学习
  13. 幅相频率特性曲线的绘制(1)
  14. 大学生期末考试Java复习题
  15. php简历掌握技能,php简历技能怎么写
  16. php 判断是否为字符串,php怎么判断是不是字符串
  17. 禁止微信中长按出现 复制文本和在浏览器中打开
  18. java英语apple_apple是什么意思_apple在线翻译_英语_读音_用法_例句_海词词典
  19. 计算机毕业设计JAVA人民医院体检预约mybatis+源码+调试部署+系统+数据库+lw
  20. 学习笔记(2):A110测试-测试课程申请22

热门文章

  1. 声音对比处理_厨房垃圾处理器 | 厨余垃圾分类的正确打开姿势
  2. 网站无法响应HTTP/1.1
  3. Cron 表达式详解
  4. 数据挖掘介绍以及模型参数详解
  5. 这是一个测试rss的内容哦
  6. USB基础---OHCI、UHCI、EHCI和XHCI控制器简介
  7. WinCE 应用程序开机自动方法
  8. 区域显示触发_天涯明月刀手游五行藏珍天机奇遇怎么触发?五行藏珍天机奇遇攻略...
  9. 一键装机linux_linux系统安装,怎样安装linux系统制作步骤
  10. a = 3 中3在栈还是堆_56.堆、栈、方法区(3.0)