首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

WinCE线程和内存管理之内存管理 02

WinCE线程和内存管理之内存管理 02

3、进程地址空间结构

  进程地址空间结构如图2所示。这个图源至MSDN。Windows CE.NET同以前版本的Windows CE操作系统在进程地址空间上有所不同,以前的Windows CE把XIP DLL也加载到进程的32MB地址空间中,而Windows CE.NET把XIP DLL单独加载到Slot 1中,这样对于每个进程来说,它总的地址空间就大了一倍,也就是64MB。这个问题我在讲解进程的时候提到过。

  当一个应用程序启动时,内核为这个程序选择一个空闲的槽(Slot),并且加载所有的代码、资源,并分配堆栈,加载DLL等。当这个进程得到CPU使用权时,它的整个地址空间被内核映射到Slot 0,也就是当前进程使用的地址空间,然后开始运行。图中给出的地址实际上是经过映射到Slot 0之后的结构。从图中可以看出,进程首先加载代码段,因为每个进程最低部64KB作为保留区域,所以代码段从0x0001 0000开始,内核为代码段分配足够的虚拟地址空间后,接着分配空间为只读数据和可读/可写数据,接着分配空间为资源数据,之后分配空间为默认堆和栈。非XIP DLL从进程最高地址向下开始加载。非XIP DLL的加载按如下规则:内核先检查要加载的DLL是否被其它进程加载过,如果加载过,就做一个地址的重定位。这样就避免了整个系统内多次加载相同DLL。如果没有加载过,就按照从槽的高地址到槽的低地址的顺序查找空闲的地址空间。然后分配足够的地址空间用于加载DLL。因为每个进程在执行前都要映射到Slot 0,而且进程使用的所有DLL可能来自不同的槽(Slot),为避免所有使用的DLL在映射到Slot 0中出现地址空间冲突的现象,内核的加载器(Loader)在加载DLL时会查找所有槽中加载的DLL的地址,保证在映射到Slot 0时不会发生地址冲突现象。假如系统内有两个进程,进程A只加载了DLL A,进程B需要加载DLL A和DLL B,那么进程B会留出DLL A的地址空间,然后加载DLL B,也就是说进程B映射到Slot 0时,DLL A的地址空间和DLL B的地址空间是相邻的,不会发生冲突。好在Windows CE下DLL都很小,而且一个应用程序使用的DLL多数是系统的DLL(存在于Slot 1)。所以目前来看进程的地址空间还够用。




图2 进程地址空间结构



  4、堆和栈

  堆是一段连续的较大的虚拟地址空间。应用程序在堆中可以动态地分配、释放所需大小的内存块。利用堆的优点是在一定范围内减小了内存碎块。而且开发者分配内存块前不必去了解CPU的类型。因为不同的CPU分页大小不相同,每个内存页可能是1KB、4KB或更多。在堆内分配内存块可以是任意大小的,而直接分配内存就必须以内存页为单位。当一个应用程序启动时,内核在进程所在的地址空间中为进程分配一个默认192KB大小的虚拟地址空间,但是并不立刻提交物理内存。如果在运行当中192KB不能满足需求,那么内核会在进程地址空间中重新查找一个足够大小的空闲的地址空间,然后复制原来堆的数据,最后释放原来的堆所占的地址空间。这是因为默认的堆的高地址处还有栈,所以必须重新分配一个。Windows CE.NET的堆有明显的缺点,不同于其它Windows操作系统下的堆管理,在Windows CE.NET创建的堆中创建的内存块不能够移动,多次创建内存块、释放内存块会产生内存碎块,这样的话当需要分配一个大一点的连续的内存块时,本来空闲的内存块加起来足够用,但是这些内存块是分隔的,不符合要求。像Windows 2000或98的内核会频繁的移动分散的正使用的内存块,使它们聚集在一起。这也是为什么有时需要句柄而不用指针的原因。由于Windows CE.NET的堆的缺点,开发者如果要频繁的在堆中创建、释放内存块的话,最好自己创建一个单独的堆,而不用默认的堆。而且我还建议最好直接在全局地址空间中(0x4200 0000到0x7FFF FFFF)分配所需地址空间。因为进程地址空间可用的实在太小了。关于堆函数我在这就不多说了,和其它Windows操作系统堆API基本一致。请参考帮助文档。

  栈也是一段连续的虚拟地址空间,和堆相比空间要小的多,它是专为函数使用的。当调用一个函数时(包括线程),内核会产生一个默认的栈,并且内核会立刻提交少量的物理内存(也可以禁止内核立刻提交物理内存)。栈的大小和CPU有关,一般为64KB,并且保留顶部2KB为了防止溢出。可以修改栈的大小,具体修改方法在讲解线程的时候已经说过了,这里就不再重复了。修改栈的大小一般时候不会发生,如果采用在编译链接时修改大小,那么所有栈的大小都会改变,这不太合理。实际开发中最好不要在栈中分配很大、很多的内存块,如果分配的内存块超过了默认栈的限制,那么会引起访问非法并且内核会立刻终止进程。最好在进程的堆中分配大的内存块并且在函数返回前释放,或者在创建线程时指定栈的大小。

  5、内存映射文件

 与虚拟内存一样,内存映射文件用来保留一个地址空间,并提交物理存储器。早期的内存映射文件并不是提交物理内存供调用者使用,而是提交永久存储器上的文件数据。当然操作系统会为永久存储器保留一个读缓冲区,这样读取文件数据就快多了。内存映射文件的特点使它很适合于加载EXE或DLL文件。这样可以节省内存又减少了加载所需时间。还可以使用它来映射大容量的文件,这样就不必在读取文件数据前设置很大的缓冲区。另外内存映射文件常用于进程间通信,也是进程间通信的主要手段,其它进程之间通信机制都是基于内存映射文件来实现。为了更快的在进程之间通信,现在的内存映射文件也可以提交物理内存,这样内存映射文件既可以提交物理内存又可以提交文件。
  Windows CE.NET同样支持无名和有名的内存映射文件。我建议在开发软件的过程中,如果需要读写大容量的文件,或者需要在不同进程内的线程之间通信,最好采用内存映射文件,而且最好在全局地址空间内(0x4200 0000到0x7FFF FFFF)分配。这会使我们事半功倍。
返回列表