线程堆栈and内存映射文件

原文地址::http://blog.163.com/xiaolonghong123123@126/blog/static/94939062201003110616964/


相关文章

1、进程线程及堆栈关系的总结----http://hi.baidu.com/nash635/item/658aa9f645bf2011d6ff8c72


每当创建一个线程时,系统就会为线程的堆栈(每个线程有它自己的堆栈)保留一个堆栈空间区域,并将一些物理存储器提交给这个已保留的区域。按照默认设置,系统保留1 MB的地址空间并提交两个页面的内存。但是,这些默认值是可以修改的,方法是在你链接应用程序时设定M i c r o s o f t的链接程序的/ S TA C K选项:
/STACK:reserve[,commit]
当创建一个线程的堆栈时,系统将会保留一个链接程序的/ S TA C K开关指明的地址空间区域。但是,当调用C r e a t e T h r e a d或_ b e g i n t h r e a d e x函数时,可以重载原先提交的内存数量。这两个函数都有一个参数,可以用来重载原先提交给堆栈的地址空间的内存数量。如果设定这个参数为0,那么系统将使用/ S TA C K开关指明的已提交的堆栈大小值。
当保留了这个区域后,系统将物理存储器提交给区域的顶部的两个页面。在允许线程启动运行之前,系统将线程的堆栈指针寄存器设置为指向堆栈区域的最高页面的结尾处(一个非常接近0 x 0 8 1 0 0 0 0 0的地址)。

C/C++运行期库的堆栈检查函数
C / C + +运行期库包含一个堆栈检查函数。当编译源代码时,编译器将在必要时自动生成对该函数的调用。堆栈检查函数的作用是确保页面被适当地提交给线程的堆栈。下面让我们来看一个例子。
当编译器遇到程序中的每个函数时,它能确定该函数需要的堆栈空间的数量。如果该函数需要的堆栈空间大于目标系统的页面大小,编译器将自动插入对堆栈检查函数的调用。

内存映射文件
与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。它们之间的差别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件(磁盘文件与系统页文件区别大大????找一下google)。一旦该文件被映射,就可以访问它,就像整个文件已经加载内存一样。
内存映射文件可以用于3个不同的目的:
1 系统使用内存映射文件,以便加载和执行. e x e和D L L文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。
2 可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I / O操作(因为虚拟内存或内存映射,是地址映射操作,直接用内存地址访问,速度快),并且可以不必对文件内容进行缓存。
3 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Wi n d o w s确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。

《???页文件(虚拟内存) 被映射的磁盘文件之间区别??? 以及物理存储器 和进程空间的虚拟地址有区别

~~~~~当阅读了上一节后,你必定会认为,如果同时运行许多文件的话,页文件就可能变得非常大,而且你会认为,每当你运行一个程序时,系统必须为进程的代码和数据保留地址空间的一些区域,将物理存储器提交给这些区域,然后将代码和数据从硬盘上的程序文件拷贝到页文件中已提交的物理存储器中。
实际上系统并不进行上面所说的这些操作。如果它进行这些操作的话,就要花费很长的时间来加载程序并启动它运行。相反,当启动一个应用程序的时候,系统将打开该应用程序的. e x e文件,确定该应用程序的代码和数据的大小。然后系统要保留一个地址空间的区域,并指明与该区域相关联的物理存储器是在. e x e文件本身中。即系统并不是从页文件中分配地址空间,而是将. e x e文件的实际内容即映像用作程序的保留地址空间区域。当然,这使应用程序的加载非常迅速,并使页文件能够保持得非常小


内存映射的可执行文件和DLL文件
1) 系统找出在调用C r e a t e P r o c e s s时设定的. e x e文件。如果找不到这个. e x e文件,进程将无法创建,C r e a t e P r o c e s s将返回FA L S E。
2) 系统创建一个新进程内核对象。
3) 系统为这个新进程创建一个私有地址空间。
4) 系统保留一个足够大的地址空间区域,用于存放该. e x e文件。该区域需要的位置在. e x e文件本身中设定。按照默认设置, . e x e文件的基地址是0 x 0 0 4 0 0 0 0 0(这个地址可能不同于在6 4位Windows 2000上运行的6 4位应用程序的地址),但是,可以在创建应用程序的. e x e文件时重载这个地址,方法是在链接应用程序时使用链接程序的/ B A S E选项。
5) ~~~~系统注意到支持已保留区域的物理存储器是在磁盘上的. e x e文件中,而不是在系统的页文件中。
~~~~当所有的. e x e和D L L文件都被映射到进程的地址空间之后,系统就可以开始执行. e x e文件的启动代码。当. e x e文件被映射后,系统将负责所有的分页、缓冲和高速缓存的处理。例如,如果. e x e文件中的代码使它跳到一个尚未加载到内存的指令地址,那么就会出现一个错误。系统能够发现这个错误,并且自动将这页代码从该文件的映像加载到一个R A M页面。然后,系统将这个R A M页面映射到进程的地址空间中的相应位置,并且让线程继续运行,就像这页代码已经加载了一样。当然,这一切是应用程序看不见的。当进程中的线程每次试图访问尚未加载到R A M的代码或数据时,该进程就会重复执行。

可执行文件或DLL的多个实例不能共享静态数据(图17-3)
~~~~系统运用内存管理系统的c o p y - o n - w r i t e(写入时拷贝)特性来防止进行这种改变。每当应用程序尝试将数据写入它的内存映射文件时,系统就会抓住这种尝试,为包含应用程序尝试写入数据的内存页面分配一个新内存块,再拷贝该页面的内容,并允许该应用程序将数据写入这个新分配的内存块。结果,同一个应用程序的所有其他实例的运行都不会受到影响。图1 7 - 3显示了当应用程序的第一个实例尝试改变数据页面2时出现的情况。
系统分配一个新的虚拟内存页面,并且将数据页面2的内容拷贝到新页面中。第一个实例的地址空间发生了变更,这样,新数据页面就被映射到与原始地址页面相同位置上的地址空间中。这时系统就可以让进程修改全局变量,而不必担心改变同一个应用程序的另一个实例的数据。

在可执行文件或DLL的多个实例之间共享静态数据
  结的概念
    每个. e x e或D L L文件的映像都由许多节组成。按照规定,每个标准节的名字均以圆点开头。例如,当编译你的程序时,编译器会将所有代码放入一个名叫. t e x t的节中。该编译器还将所有未经初始化的数据放入一个. b s s节,而已经初始化的所有数据则放入. d a t a节中。
      每一节都拥有与其相关的一组属性,这些属性如表1 7 - 1所示。
      表17-1 .exe或D L L文件各节的属性 

      属性 含义 
      R E A D 该节中的字节可以读取 
      W R I T E 该节中的字节可以写入 
      E X E C U T E 该节中的字节可以执行 
      S H A R E D 该节中的字节可以被多个实例共享(本属性能够有效地关闭c o p y - o n - w r i t e机制) 
自定义一个节,用来共享多个进程实例或DLL之间的数据
#pragma data_seg("Shared")
  LONG g_lInstanceCount = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:Shared,RWS")

内存映射数据文件
一个文件,零缓存
当使用内存映射文件对文件内容进行倒序时,你打开该文件,然后告诉系统将虚拟地址空间的一个区域进行倒序。你告诉系统将文件的第一个字节映射到该保留区域的第一个字节。然后可以访问该虚拟内存的区域,就像它包含了这个文件一样。实际上,如果在文件的结尾处有一个单个0字节,那么只需要调用C运行期函数_ s t r r e v,就可以对文件中的数据进行倒序操作。
这种方法的最大优点是,系统能够为你管理所有的文件缓存操作。不必分配任何内存,或者将文件数据加载到内存,也不必将数据重新写入该文件,或者释放任何内存块。但是,内存映射文件仍然可能出现因为电源故障之类的进程中断而造成数据被破坏的问题。

步骤1,2:创建一个文件映射内核对象
调用C r e a t e F i l e函数,就可以将文件映像的物理存储器的位置告诉操作系统。
HANDLE CreateFileMapping(
   HANDLE hFile,//将文件映像的物理存储器的位置告诉操作系统
   PSECURITY_ATTRIBUTES psa,//NULL
   DWORD fdwProtect,
   DWORD dwMaximumSizeHigh,//主要作用是保证文件映射对象能够得到足够的物理存储器,对于4 GB 或小于4 GB的文件来说,d w M a x i m u m S i z e H i g h的值将始终是0。
   DWORD dwMaximumSizeLow,//主要作用是保证文件映射对象能够得到足够的物理存储器
   PCTSTR pszName);
本章开头讲过,创建内存映射文件就像保留一个地址空间区域然后将物理存储器提交给该区域一样。
但是,当系统将存储器映射到进程的地址空间中去时,系统必须知道应该将什么保护属性赋予物理存储器的页面。
系统创建文件映射对象,并将用于标识该对象的句柄返回该调用线程。如果系统无法创建文件映射对象,便返回一个N U L L句柄值。记住,当C r e a t e F i l e运行失败时,它将返回I N VA L I D _H A N D L E _ VA L U E(定义为-1),当C r e a t e F i l e M a p p i n g运行失败时,它返回N U L L。请不要混淆这些错误值。

步骤3:将文件数据映射到进程的地址空间
当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。可以通过调用M a p Vi e w O f F i l e函数来进行这项操作:
PVOID MapViewOfFile(
   HANDLE hFileMappingObject,
   DWORD dwDesiredAccess,
   DWORD dwFileOffsetHigh,
   DWORD dwFileOffsetLow,
   SIZE_T dwNumberOfBytesToMap);
剩下的3个参数与保留地址空间区域及将物理存储器映射到该区域有关。当你将一个文件映射到你的进程的地址空间中时,你不必一次性地映射整个文件。相反,可以只将文件的一小部分映射到地址空间。被映射到进程的地址空间的这部分文件称为一个视图,这可以说明M a p Vi e w O f F i l e是如何而得名的。

~~~~~总结:进程有自己的进程地址空间,磁盘有虚拟内存空间或文件映射地址空间,将后者提交给前者,称作提交物理存储器。内存映射文件以及视图与磁盘映射文件,每当你为文件创建一个文件映射对象并且映射该文件映射对象的视图时
当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面的函数将它释放
BOOL UnmapViewOfFile(PVOID pvBaseAddress);

~~~~~为了提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映像。如果需要确保你的更新被写入磁盘,可以强制系统将修改过的数据的一部分或全部重新写入磁盘映像中,方法是调用F l u s h Vi e w O f F i l e函数:
BOOL FlushViewOfFile(
   PVOID pvAddress,
   SIZE_T dwNumberOfBytesToFlush);

{HANDLE hFile = CreateFile(...);
HANDLE hFileMapping = CreateFileMapping(hFile, ...);
PVOID pvFile = MapViewOfFile(hFileMapping, ...);

// Use the memory-mapped file.

UnmapViewOfFile(pvFile);
CloseHandle(hFileMapping);
CloseHandle(hFile);
}
当对内存映射文件进行操作时,通常要打开文件,创建文件映射对象,然后使用文件映射对象将文件的数据视图映射到进程的地址空间。
~~~~~~总结:打开文件,创建文件映射对象(相当于虚拟内存,此时还没未提交物理存储器),使用文件映射对象将文件的数据视图映射到进程的地址空间(相当于提交物理存储器)
    ,然后Use memory-mapped file。。。在对文件映射对象视图进行操作时,系统并不立即更新磁盘文件,而使用了FlushViewOfFile()可以强制磁盘写入。
插曲:与所有C字符串一样,字符串的最后一个字符必须是个0结束符。由于文本文件不以0为结束符,因此F i l e R e v必须给文件附加一个0。

PSTR pchANSI = (PSTR) pvFile;
pchANSI[dwFileSize / sizeof(CHAR)] = 0;
while(pchANSI != NULL) 
{
   //We have found an occurrence....
   *pchANSI++ = '\r';     //Change '\n' to '\r'.
   *pchANSI++ = '\n';     //Change '\r' to '\n'.
   pchANSI = strchr(pchANSI, '\n'); //Find the next occurrence.
}
~~~~~文件映射是多么的强大,哈哈,当你观察这样一个简单的代码时,可能很容易忘记你实际上是在对磁盘驱动器上的文件内容进行操作(这显示出内存映射文件的功能是多么大)
 
使用内存映射文件来处理大文件
首先映射一个文件的开头的视图。当完成对文件的第一个视图的访问时,可以取消它的映像,然后映射一个从文件中的一个更深的位移开始的新视图。必须重复这一操作,直到访问了整个文件。这使得大型内存映射文件的处理不太方便,但是,幸好大多数文件都比较小,因此不会出现这个问题。
内存映射文件与数据视图的相关性
系统就会确保映射的视图数据的相关性。例如,如果你的应用程序改变了一个视图中的文件内容,那么所有其他视图均被更新以反映这个变化。
注意Wi n d o w s允许创建若干个由单个数据文件支持的文件映射对象。Wi n d o w s不能保证这些不同的文件映射对象的视图具有相关性。它只能保证单个文件映射对象的多个视图具有相关性。
~~~~~~问题就可能产生:一个进程可以调用R e a d F i l e函数来读取文件的一个部分,并修改它的数据,然后使用Wr i t e F i l e函数将数据重新写入文件,而第二个进程的文件映射对象却不知道第一个进程执行的这些操作。由于这个原因,当你为将被内存映射的文件调用C r e a t e F i l e函数时,最好将d w S h a r e M o d e参数的值设置为0。这样就可以告诉系统,你想要单独访问这个文件,而其他进程都不能打开它。

设定内存映射文件的基地址
PVOID MapViewOfFileEx(
   HANDLE hFileMappingObject,
   DWORD dwDesiredAccess,
   DWORD dwFileOffsetHigh,
   DWORD dwFileOffsetLow,
   SIZE_T dwNumberOfBytesToMap,
   PVOID pvBaseAddress);
该函数的所有参数和返回值均与M a p Vi e w O f F i l e函数相同,唯一的差别是最后一个参数p v B a s e A d d r e s s有所不同。在这个参数中,你为要映射的文件设定一个目标地址。与Vi r t u a l A l l o c一样,你设定的目标地址应该是分配粒度边界( 64 KB)的倍数,否则M a p Vi e w O f F i l e E x将返回N U L L,表示出现了错误。
 
 使用内存映射文件在进程之间共享数据
数据共享方法是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面。因此,当一个进程将数据写入一个共享文件映射对象的视图时,其他进程可以立即看到它们视图中的数据变更情况。注意,如果多个进程共享单个文件映射对象,那么所有进程必须使用相同的名字来表示该文件映射对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值