[笔记]Windows核心编程《一》错误处理、字符编码
[笔记]Windows核心编程《二》内核对象
[笔记]Windows核心编程《三》进程
[笔记]Windows核心编程《四》作业
[笔记]快乐的LInux命令行《五》什么是shell
[笔记]Windows核心编程《五》线程基础
[笔记]Windows核心编程《六》线程调度、优先级和关联性
[笔记]Windows核心编程《七》用户模式下的线程同步
[笔记]Windows核心编程《八》用内核对象进行线程同步
[笔记]Windows核心编程《九》同步设备I/O和异步设备I/O
[笔记]Windows核心编程《十一》Windows线程池
[笔记]Windows核心编程《十二》纤程
[笔记]Windows核心编程《十三》windows内存体系结构
[笔记]Windows核心编程《十四》探索虚拟内存
[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存
[笔记]Windows核心编程《十六》线程栈
[笔记]Windows核心编程《十七》内存映射文件
[笔记]Windows核心编程《十八》堆栈
[笔记]Windows核心编程《十九》DLL基础
[笔记]Windows核心编程《二十》DLL的高级操作技术
[笔记]Windows核心编程《二十一》线程本地存储器TLS
[笔记]Windows核心编程《二十二》注入DLL和拦截API
[笔记]Windows核心编程《二十三》结构化异常处理
相关:
参考1
参考2
内存映射文件 与虚拟内存相似,内存映射文件允许开发人员预定一块地址空间区域并给区域调拨物理存储器。不同之处在于内存映射文件的物理存储器来自磁盘上已有的文件,而不 是来自系统的页交换文件。
一旦把文件映射到地址空间,我们就可以对它进行访问,就好像整个文件都已经在被载入内存一样。
页交换文件,简单讲就是系统用于做虚拟内存的一个磁盘文件
页交换文件相关可参考windows内存体系结构
共享内存是一种特殊的文件映射
内存映射主要用于以下三种情况:
当一个线程在调用CreateProcess的时候,系统会执行以下步骤:
OEP PE可执行程序入口点地址
)。但是,只需在构建应用程序的.exe文件时使用/BASE连接器开关,我们就可以给自己的应用程序指定一个不同的地址。当系统把.exe文件映射到进程的地址空间之后,会访问.exe文件中一个段,这个段列出了一些DLL文件,它们包含该.exe文件调用到的函数。然后系统会调用LoadLibrary来载入每个DLL,如果哪个DLL需要用到其它DLL,那么系统同样会调用其它DLL,那么系统同样会调用 LoadLibrary来载入相应的DLL。
每当调用LoadLibrary来加载一个DLL时,系统将执行下列操作步骤,它们均类似上面的第4和第5个步骤:
系统会预定一块足够大的地址空间区域来容纳DLL文件。待预定的的地址空间区域的具体位置已经在DLL文件中指定。
按照默认设置, Microsoft的Visual C++建立的 D L L文件基地址是0 x10000000(这个地址可能不同于在 64位Windows 2000上运行的6 4位D L L的地址)但是,你可以在创建DLL文件时重载这个地址,方法是使用链接程序的 /BASE选项。所有与Windows一起发布的系统DLL都有不同的基地址,这样即使把它们载入到同一个地址空间,也不会发生重叠。
如果系统无法在DLL文件指定的基地址处预定区域,这可能是因为该区域已经被另一个DLL或.exe占用,也可能是区域不够大,这时系统会尝试在另一个地址 来为DLL预定地址空间区域。
- 如果DLL不包含重定位信息(当使用连接器的/FIXED开关来构建DLL),这意味着DLL必须被载入到指定的基地址,否则无法被载入。
- 如果对DLL执行重定位,重定位不仅需要占用页交换文件中额外的存储空间,而且会增加载入DLL所需的时间。
系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的DLL文件,而并非来自页交换文件。如果由于Windows不能将DLL载入到指定的基地址而必须重定位的话,那么系统还会另外进行标注,表明DLL中有一部分物理存储器映射到了页交换文件。
注意:如果由于某个原因系统无法映射 . e x e和所有必要的D L L文件,那么系统就会向用户显示一个消息框,并且释放进程的地址空间和进程对象。CreateProcess函数将向调用者返回 FALSE,调用者可以调用GetLastError函数,以便更好地了解为什么无法创建该进程。
把所有的.exe文件和DLL文件都映射到进程的地址空间之后,系统会开始执行.exe文件的启动代码。当完成对.exe文件的映射后,系统会负责所有的换页(paging)、缓存(buffering)、以及高速缓存(caching)操作。
例如,
如果.exe文件中的代码使它跳到一个尚未加载到内存的指令地址,那么就会出现一个错误。
系统能够发现这个错误,并且自动将这页代码从该文件的映像加载到一个 RAM页面。
然后,系统将这个RAM页面映射到进程的地址空间中的相应位置,并且让线程继续运行,就像这页代码已经加载了一样。
当然,这一切是应用程序看不见的。当进程中的线程每次试图访问尚未加载到RAM的代码或数据时,该进程就会重复执行
如果一个应用程序已经在运行,那么当我们为这个应用程序创建一个新的进程时,系统只不过是打开另一个内存映射试图(memory-mapped view),创建一个新的进程对象,并为主线程创建一个新的线程对象。这个新打开的内存映射视图隶属一个文件映射对象(file-mapping object),后者用来标识可执行文件的映像。系统同时给进程对象和线程对象分别制定新的进程ID和线程ID。通过使用内存映射文件,同一个应用程序的多个实例可以共享内存中的代码和数据。
如下图,它描述了如何把可执行程序的代码和数据载入到虚拟内存,并映射到地址空间中。可执行页面---->载入到虚拟内存—>映射到应用程序地址空间
假设应用第二个实例程序,这时系统不过是把包含应用程序的代码和数据映射到第二个实例的地址空间中,如下图所示:
注意:
实际上,文件的内容被分为段,代码在一个段中,而全局变量再另一个段中,段是对齐到页面大小的整数倍,应用程序可以通过调用GetSystemInfo来检测页面大小,在.exe或DLL文件中,代码段通常在数据段的前面。
操作系统通过内存管理系统的写时复制特性来防止这种情况的发生。当写入内存映射文件时,系统会截获此尝试,接着为应用程序分配一块新内存,然后复制 页面内容,最终的结果是其它实例不会受到影响。
下图描绘了当应用程序的第一个实例试图修改数据页面2虽的一个全局变量时,会产生的结果。
每个exe文件和dll文件映象都有许多段组成。
比如:
自定义自己的段
#pragma data_seg("sectionname")
例如我们可以用下面代码来合建一个名为Share的段,它只包含一个LONG的变量
#pragma data_seg("Shared")
LONG g_lInstanceCount = 0;
#pragma data_set();
注意:
编译器只会将已经初始化的变量放入自己定义的段当中,如果上面代码中g_lInstanceCount 没有初始化,则不会放到我们指定的段之中。
但是Vc++ 编译器提供了一个allocate声明符,它允许我们将未经初始化的数据放到任何我们想要放入的段中。
多个实例共享数据的方法:
之所以将变量放在一个单独的段中,最常见的原因就是为了共享exe或dll多个实例中共享数据。
注意:
但是MricroSoft并不鼓励使用共享段:
1.有潜在安全漏洞
2.意味着一个应用程序中的错误可能影响到另一应用程序。
Windows系统可以使我们能够把数据文件映射到进程地址空间中,这样一来,对大型数据流进行操作就非常容易。
以颠倒文件内容为例子。四种方法实现。
实现方法具体步骤:
缺点:
实现方法具体步骤:
优缺点:
实现方法具体步骤:
优缺点:
实现方法具体步骤:
优缺点:
使用内存映射文件,需要执行下面三个步骤:
使用完后清理工作,需要执行以下三个步骤:
略,暂时不看 需要实践代码。https://www.cnblogs.com/zhangdongsheng/p/3269515.html
如何将一个16EB的文件映射到一个较小的地址空间中?
一开始,我们只映射文件开头的部分到视图中,完成对文件的第一个视图的访问后,我们可以撤销对文件这一部分的远射,然后把文件的另一部分映射到视图中。我们一直重复这个过程,直到完成对整个文件的访问。
结论: 系统允许我们把同一个文件中的数据映射到多个视图中。只要我们映射的是同一个文件映射对象,那么系统会确保各视图中的数据是一致的。
原因: 这是因为即使该页面被多次映射到进程的虚拟地址空间中,系统也还是在同一个内存页面中保存被映射的数据。如果多个进程把同一个数据文件映射到多个视图中,那么数据也仍然会是一致 的,这是因为数据文件中的每个页面在内存中只有一份——但这些内存页面会被映射到多个进程的地址空间中。
说明: Windows允许我们以同一个数据文件为后备存储器来创建多个文件映射对象。Windows并不保证这些不同的文件映射对象的各个视图是一致的。系统只保证在同一文件映射对象的多个视图间保持一致。
简而言之,就是多个程序读取映射文件时,各个程序是读取的是一致的,因为映射文件只有一份,其他程序的地址空间都是指向同一份映射文件。
在调用VirtualAlloc的时候,我们可以建议系统在指定的基地址预定地址空间。同样,也可以用MapViewOfFileEx函数来代替MapViewOfFile函数,这样就能建议系统把文件映射到指定的地址:
PVOID MapViewOfFileEx(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
PVOID pvBaseAddress
);
除了最后一个参数 pvBaseAddress ,这个函数的所有参数和返回值都与MapViewOfFile函数完全相同。我们可以用这个参数来给要映射的文件制定一个目标地址。同VirtualAlloc函数一样,指定的目标地址必须是分配粒度的整数倍。 使用内存映射文件跨进程共享数据的时候,MapViewOfFileEx非常有用。给共享的数据文件在不同的进程指定相同的基地址。
在进程能够从自己的地址空间中访问内存映射文件的数据之前,Windows要求进程先调用MapViewOfFile。不同进程调用MapViewOfFile时返回的内存地址,很可能是不同的。
Windows提供了多种机制,使得应用程序之间能够快速、方便地共享数据和信息。在Windows中,在同一台机器上共享数据的最底层的机制就是内存映射文件。
让 我们来看一个例子:启动应用程序。当一个应用程序启动时,系统会先调用CreateFile来打开磁盘上的.exe文件。接着系统会调用 CreateFileMapping来创建文件映射对象。最后系统会以新创建的进程的名义调用MapViewOfFileEx(并传入SEC_IMAGE 标志),这样就把.exe文件映射到了进程的地址空间中。值所以调用MapViewOfFileEx而不是MapViewOfFile,是为了把文件映射 到指定的基地址,这个基地址保存在.exe的PE文件头中。系统然后创建进程的主线程,在映射得到的视图中取得可执行代码的第一个字节的地址,把该地址放 到线程的指令指针中,最后让CPU开始执行其中的代码。
如 果用户启动同一个应用程序的第二个实例,那么系统会发现该.exe文件已经有一个文件映射对象,因此就不会再创建一个新的文件对象或文件映射对象。取而代 之的是,系统会再次映射.exe文件的一个视图,但这次是在新创建的进程的地址空间中。至此,系统已经把同一个文件同时映射到了两个地址空间中。显然,由 于物理内存中包含.exe文件可执行代码的那些页面为两个进程所共享,因此内存的使用率更高。
Microsoft加入了相应的支持,让系统能够创建以页交换文件为后备存储器的内存映射文件,这样就不需要用磁盘上专门的文件来作为后备存储器了。这种方法和为磁盘文件创建内存映射文件的方法几乎完全相同,甚至更简单。 一方面,由于不必创建或打开一个专门的磁盘文件,因此不需要调用CreateFile。
我们只需要像原来那样调用CreateFileMapping,并将INVALID_HANDLE_VALUE作为hFile参数传入。这告诉系统我们创建的文件映射对象的物理存储器不是磁盘上的文件,而是希望系统从页交换文件中调拨物理存储器
。
所需分配的存储器大小由CreateFileMapping的dwMaximumSizeHigh和dwMaximumSizeLow参数决定。
CreateFileMapping为我们提供了一种方法,即在fdwProtect参数中指定SEC_RESERVE或SEC_COMMIT标志。这样我们把先前的那个电子的数据作为一个文件映射对象来共享,但又不希望在一开始就给它调拨所有物理存储器。
SEC_RESERVE:系统不会从页交换文件中调拨物理存储器,它只返回文件映射对象的句柄。现在我们可以调用MapViewOfFile来给这个文件映射对象创建一个视图。MapViewOfFile会预订地址空间,但不会调任何物理存储器。设计图访问会违规。通过使用SEC_RERVER和VirtualAlloc我们不仅能与其它进程共享电子表格的数组,而且还能高效的使用物理存储器。如果内存映射文件是通过SEC_RESERVE标志得到的,便不能用VirtualFree来撤销调拨给它的存储
NT文件系统(NTFS)提供了对稀疏文件的支持。这是一项非常棒的特征。我们可以用这项特性来创建和处理稀疏内存映射文件,这样一来,存储器就不必总是在页交换文件中,而可以在普通文件中。
假设我们要创建一个内存映射文件来存储录音数据。当用户说话时,我们希望把数字音频数据写入到内存缓存中,并以磁盘文件为内存缓存的后备存储器。一个部分调拨的内存映射文件当然是最简单高效的办法。问题是我们不知道用户在单击停止按钮前会说多久,可能是五分钟,但也可以是5小时。差距不可谓不大!我们需要一个足够大的文件来保存这些数据。但是,在使用稀疏调拨的内存映射文件时,大小并没有多大关系。
不同进程对信号量Count进行累加
#include
#include
using namespace std;
int main()
{
HANDLE hMap;
PINT lpMapAddr;
// get file mapping's handle
hMap = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // 权限:所有
FALSE, // 不继承句柄
"TEST_Map" // 名字
);
if (NULL == hMap) {
// init
hMap = CreateFileMapping(
INVALID_HANDLE_VALUE, // 不是真实的文件,所以写 INVALID_HANDLE_VALUE
NULL, // 同上
PAGE_READWRITE, // 该句柄对文件映射可读可写
0, // 与下一个参数连用,表示文件映射的大小
1024, // 与上一个参数连用,表示文件映射的大小
"TEST_Map" // 名字
);
if (NULL == hMap) {
goto end;
}
lpMapAddr = (PINT)MapViewOfFile(
hMap, // 文件映射句柄
FILE_MAP_ALL_ACCESS, // 权限:所有
0, // 与下一个参数连用,表示文件映射起始地址偏移
0, // 与上一个参数连用,表示文件映射起始地址偏移
0 // 映射整个文件映射对象
);
*lpMapAddr = 1;
}
// get file mapping's address
lpMapAddr = (PINT)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
// read
cout << "instance's count : " << *lpMapAddr << endl;
// write
*lpMapAddr = *lpMapAddr + 1;
cout << "finished." << endl;
getchar();
end:
if (hMap)
CloseHandle(hMap);
getchar();
return 0;
}
运行可执行模块时,操作系统的加载程序执行步骤
windows 内存管理总结
共享内存时一种特殊的文件映射,使用的是磁盘页交换文件为内存交换文件