Windows Embedded CE 6.0 Internals (3) Memory Continued



作者: 王克伟
出处: http://wangkewei.cnblogs.com/



对我来说写一篇博客真的不容易,我是个十足的完美主义者,但是水平很一般,所以我会花上很多时间去修补文章。也许文章并不能让你满意,如果你有任何的建议,任何的,我都非常期待你能告诉我。这篇文章仍然是继续Windows Embedded CE Internals (2)内存部分。

 

从硬件视角看内存

从硬件上看,可作为内存的大体分为RAM、ROM、Nand/Nor Flash(兼具RAM和ROM特性的混合体)。

RAM 內存可以进一步分为静态随机存取存储器(SRAM)和动态随机存取存储器(DRAM)两大类。SRAM具有快速访问的优点,但生产成本较为昂贵,一个典型的应用是高速缓存。而DRAM由于具有较低的单位容量价格,所以被大量的采用作为系统的主存储器

以下简单列举一些RAM:

DRAM
dram

SRAM
sram

VRAM(Video RAM)
vram

DDR SDRAM(Double Data Rate SDRAM)
ddr_sdram

DDRII(Double Data Rate Synchronous DRAM)
ddrII

 

那么RAM、ROM、Flash有哪些区别?我在这里简单的总结一下:

1.RAM需要供电才能保存数据,而ROM、Flash都不需要。

2.内存中的代码能够直接被执行的前提是CPU能够随机读取这个内存里面的数据,RAM满足这个条件的,还满足这个条件的是ROM和Nor Flash(也就是XIP)。CPU要执行Nand Flash里面的代码时必须把这些代码Copy到RAM里面。

3.因为ROM只能被随机的读,所以程序中可读/写的静态数据等需要Copy到RAM中,而可执行代码文本段、只读静态数据等不会被Copy到RAM。

4.关于Nand Flash和Nor Flash的具体区别请看这里

 

从操作系统视角看内存

如果你是操作系统,你如何加载模块(module,包含可执行代码的Win32文件,主要包括EXE文件和DLL文件)到内存中?

回答这个问题要了解如下内容:
1.操作系统加载EXE文件时与DLL文件的区别。
2.PE文件结构。Windows Embedded CE以及Windows Mobile的PE文件结构与Windows桌面操作系统上的相同。

我们一个个来解决。

当我们在文件浏览器中双击EXE文件或者通过CreateProcess、ShellExecuteEx等函数执行一个EXE程序时,一个EXE的实例——进程将会产生,首先系统将为这个EXE创建一个独立的进程空间,解析导入函数,此时会加载这些函数所在DLL文件到进程空间,如果这个DLL也有导入函数,那么也会加载这些函数所在DLL,然后初始化静态数据区,建立一个本地堆,然后创建一个主线程,最后跳转到EXE的入口点。可以参考源码:C:\WINCE600\PRIVATE\WINCEOS\COREOS\NK\KERNEL\loader.c

DLL不能单独执行,它只能被EXE或者其它DLL请求加载,除了上面说的那种方式被加载(隐式加载)外,还可以使用LoadLibrary和LoadLibraryEx函数加载,这样方式叫显式加载。显式加载有很多好处,首先能够让我们直接使用DLL中的函数,还有一个好处可能大多数人并不清楚。比如这段代码,你知道它不直接使用头文件的中提供的函数,而使用LoadLibrary的好处吗?

?
1
2
3
4
5
6
7
8
9
10
11
12
// Obtain a module handle to toolhelp.dll
HINSTANCE hKernel = LoadLibrary(_T( "toolhelp.dll" ));
 
if (!hKernel) {
     MessageBox(NULL, L "Toolhelp.dll not found" , L "TrayTaskList" , MB_OK);
     return 0;
}
 
// all processes currently in the system.
hProcessSnap = ( HINSTANCE )CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == ( HANDLE )-1)
     return 0;

让我们来简单看下PE文件结构:(你也许会问为什么要了解PE文件结构,因为操作系统的loader模块是按照这个格式把模块加载到内存中的。也许你发现Windows的PE文件结构不够好,你有更好的方案,让可执行文件加载的更快,当然我得说你非常的优秀,因为大部分程序员并不知道PE文件结构。)

PE文件是很古老的东西了,但是我们现在仍然在使用,而且是Windows通用的。关于详细的资料你可以参考:
PE文件格式详解(上)
PE文件格式详解(下)
Peering Inside the PE: A Tour of the Win32 Portable Executable File Format
The Common Object File Format (COFF)
An In-Depth Look into the Win32 Portable Executable File Format

PE文件结构的开始部分是一些Header,之后才是正文——由一些Section组成。

untitled

为了更方便了解,我们使用map文件,map文件是,你可以从这里获得如何从map文件定位应用程序Crash的方法,还可以阅读《Debugging Applications》获得更多相关知识。

在Visual Studio的这个位置设置是否生成map文件:
clip_image002[7]

下图是生成的map文件的截图:
clip_image002[9]

PE文件主要分为5个Section:
Section 0001 is the text segment containing the executable code of the program. (可执行代码文本)
Section 0002 contains the read-only static data. (只读静态数据)
Section 0003 contains the read/write static data. (可读/写静态数据)
Section 0004 contains the fix-up table to support calls to other DLLs.(调用其它DLL的修正表)
Section 0005 is the resource section containing the application's resources, such as menu and dialog box templates.(资源)

这5个Section还可以进一步细分,比如:.bss段表示应用程序的未初始化数据,包括所有函数或源模块中声明为static的变量。.rdata段表示只读的数据,比如字符串文字量、常量和调试目录信息。所有其它变量(除了出现在栈上的自动变量)存储在.data段之中。这些一般是应用程序或模块的全局变量。

 

现在再看一下Windows Embedded CE 5.0用户态内存分布图:

image

我们把上图从0000 0000地址到03FF FFFF地址的分布再放大一下:

image 

现在你应该大体知道EXE文件中各个Section在CE 5.0版本中大体加载到内存的哪里了。CE 6.0系统相对位置与其相似,只是单个进程不再是32M的独立空间,而是1G的独立空间。
clip_image002[15]

知道了系统如何加载EXE的,那么系统又是如何加载DLL的呢?系统怎样保证DLL的重用?

 

最后顺带着再介绍一下命令dumpbin [options] [files]

我们经常需要查看EXE、DLL或者LIB文件的导入或者导出函数,比较方便的一个方法就是在Visual Studio自带的命令行中使用dumpbin,比如查看刚生成的PortableExecute.exe的导入函数:

clip_image002[13]

C:\Program Files\Microsoft Visual Studio 9.0\VC> D:
D:> cd D:\Windows Mobile\PortableExecute\PortableExecute\Windows Mobile 6 Professional SDK (ARMV4I)\Debug
D:\Windows Mobile\PortableExecute\PortableExecute\Windows Mobile 6 Professional SDK (ARMV4I)\Debug> dumpbin /imports PortableExecute.exe

clip_image002[15]
clip_image002[17]

 

如果你是操作系统,你会怎样管理内存给应用程序使用?

这个问题主要得到下面两个问题:
1.为什么Windows Embedded CE是基于页式的虚拟内存系统?(声明:文章提到的虚拟内存指的是虚拟地址空间,不是通常说的Page File。)
2.为什么使用Page Pool?

我们一个个来解决。

虚拟内存在1959年就被发明用来隐藏计算机存储系统的分层结构,从而极大地简化后续编程任务。但是它是如此的常见,不多人会思考它为什么会存在。如果你深刻的去了解了,你会发现这项发明实在太棒了。更多请看美国乔治梅森大学计算机科学系网上教程《Virtual Memory Module》Peter J. Denning 1970年发表的论文《Virtual Memory 》(我坦白我只看了一点点)。

在文章Windows Embedded CE 6.0 Internals (2)中讲到,应用程序等使用的是虚拟地址,操作系统使用MMU处理虚拟地址到物理地址的实时转换,这样很明显的一个好吃是隔离了应用程序等与物理地址的具体实现。与Windows桌面操作系统相比,基于Windows Embedded CE系统设备的物理内存实现更多样化(文章第一部分也可以看到)。就这一点来说Windows Embedded CE更迫切的需要被实现为基于虚拟内存的系统。

当然任何事情都有两面的,带来了物理内存实现抽象化好处的同时降低了寻址的效率。为了尽一切方式去弥补牺牲的效率,微软又使用了静态地址映射、Page Pool等技术(这些技术可不是微软发明的)。这就是为什么微软一些操作系统的内存系统都比较复杂的原因。当然Windows Embedded CE内存系统的设计比Windows桌面系统(比如Windows XP)简洁了很多。毕竟人家是嵌入式系统。

另外我再列举一点页式虚拟内存带来的好处(相信你从操作系统原理课上也有接触过):
(1). 隔离了不同进程的地址空间,每个进程都有自己独立的页目录,进程切换时CPU切换现场,包括切换页目录。
(2). 提供更细腻的内存数据安全保护和管理,管理的粒度到一个页面大小,这个页面的权限可以是只读、可读可写、可执行、可读可执行、可读可写可执行、Guard(首次访问会导致STATUS_GUARD_PAGE异常)、不允许访问、不允许缓存(CPU不会高速缓存映射到这个区域的RAM页面)。
(3). 为了更有效的使用物理内存。

 

另外补充一点知识,在Windows Embedded CE 6.0中页面大小是4KB(目前的嵌入式处理器大多是4KB页面的),在早期的CE系统中这个大小是1KB。虚拟内存区域对齐的边界是64KB,也就是说最小的分配粒度是64KB,每次都是以64KB的整数倍分配。提交的粒度是一个页面大小。

可以参看C:\WINCE600\PRIVATE\WINCEOS\COREOS\NK\KERNEL\vm.c中的VMAlloc函数和DoVMAlloc函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//-----------------------------------------------------------------------------------------------------------------
//
// VMAlloc: main function to allocate VM.
//
LPVOID
VMAlloc (
     PPROCESS pprc,          // process
     LPVOID lpvaddr,         // starting address
     DWORD  cbSize,          // size, in byte, of the allocation
     DWORD  fAllocType,      // allocation type
     DWORD  fProtect         // protection
     )
{
     DWORD    dwAddr = ( DWORD ) lpvaddr;
     DWORD    dwEnd  = dwAddr + cbSize;
     DWORD    dwErr  = 0;
     LPVOID   lpRet  = NULL;
 
     // verify arguments
     if (!IsValidAllocType (dwAddr, fAllocType)              // valid fAllocType?
         || (( int ) cbSize <= 0)                              // valid size?
         || !IsValidProtect (fProtect)) {                    // valid fProtect?
         dwErr = ERROR_INVALID_PARAMETER;
         DEBUGMSG (ZONE_VIRTMEM, (L "VMAlloc failed, invalid parameter %8.8lx, %8.8lx %8.8lx %8.8lx\r\n" ,
             lpvaddr, cbSize, fAllocType, fProtect));
     } else {
         DWORD cPages;           // Number of pages
 
         // page align start address
         dwAddr &= ~VM_PAGE_OFST_MASK;
         cPages = PAGECOUNT (dwEnd - dwAddr);
         lpRet = DoVMAlloc (pprc, dwAddr, cPages, fAllocType, fProtect, PM_PT_ZEROED, &dwErr);
 
     }
     
     KSetLastError (pCurThread, dwErr);
     
     return lpRet;
}

虚拟的页面有3中状态:Free、Reserved、Committed,只有 Committed 的页面才真正映射到物理内存中,这就是为什么你使用 VirtualAlloc 函数有 MEM_COMMIT 和 MEM_RESERVE 的原因。

 

看看来是虚拟内存的机制图,因为没找到Windows Embedded CE 6.0的,我就拿《Windows Internals》(Windows XP、2003等桌面系统)中截图的说事了。

2的10次方x2的10次方x2的12次方=2的32次方=4G寻址空间。因为页面大小是4KB,所以页内索引(Byte index)需要12比特去记录,剩下的20比特索引分成一个10比特的页目录索引和一个10比特的页表索引,这样有效的降低了页目录和页表的大小(各要1KB?大小就可以了)。

image004 

下图是说明如何把虚拟内存翻译成实际的物理地址,CR3寄存器用于存放进程页目录的物理地址,进程切换时CPU会修改这个寄存器的值。
clip_image002

这个图反应了如何对每个页面进行管理和保护的,在PFN中记录每个页面是否脏了、是否可读等信息。
clip_image002[9]

这里是对每个Bit位的详细解释。
clip_image002[11]
clip_image002[13]

前面PE文件结构中涉及到的段式虚拟内存和这里的页式虚拟内存组成了段页式虚拟内存系统。

Page Pool

一些概念:

Pool is a collection of physical pages reserved for a specific purpose.

Loader is kernel function to load code pages.

File is kernel function to load and commit non-executable pages from a file.

Trimming is reducing the page pool size by discarding pages when active pool size is larger than the target size.

Target size is the normal size of the pool the pool manager will maintain.

Maximum size is the pool will never be allowed to exceed this size.

全部RAM会被分成3个部分:红色部分为Loader pool保留,黄色部分为File pool保留,剩下的为一般分配,比如:Heaps、Stacks、DLL R/W data、Page Pool use above Target level。

image

那么为什么要Page Pool呢?且听下回分解。(2010.5.26注:惭愧,到现在也没分解,不是不想分解,是分解不出来。)

 

从应用程序视角看内存

我得借鉴一下何宗键老师的图来说明一下,可以一目了然,从下到上内存管理函数使用起来会更加方便。

image 

虚拟内存API

虚拟内存是最基本的内存类型。堆、栈这样的逻辑内存其实仍然是调用底层的虚拟内存API来分配内存。而VirtualAlloc、VirtualFree、VirtualReSize等函数直接操作应用程序的虚拟内存空间中的页面。

使用虚拟内存API有一个非常注意的问题,请看如下代码:

?
1
2
3
4
for (i = 0; i < 512; i++)
{
     pMem[i] = VirualAlloc(NULL, PAGESIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
}

你能看到问题吗?

另外其它一些API有:VirtualProtect、VirtualQuery、VirtualAllocEx、VirtualProtectEx、VirtualQueryEx、VirualFreeEx,后面Ex扩展的主要多了一个HANDLE hProcess参数,为了操作另一个进程的内存,当然之前你的OpenProcess。你还可以使用 ReadProcessMemory 和 WriteProcessMemory 操作其它进程分配的内存块。


有时需要比页更小的粒度来操作内存,这时虚拟内存API明显满足不了了,你可以考虑堆API。但是使用堆会出现碎片等问题,有一些技巧可以尽量避免这个问题的出现。因为篇幅在此略过。另外注意Windows Embedded CE里面已经没全局堆了,诸如GlobalAlloc、GlobalFree、GlobalRealloc这样的函数都是通过宏映射为LocalAlloc等API了。

本地堆相关API有:LocalAlloc、LocalFree、LocalReAlloc、LocalSize等。

独立堆相关API有:HeapCreate、HeapAlloc、HeapFree、HeapReAlloc、HeapSize、HeapDestroy、CeHeapCreate等。 

线程创建时如果不指定,默认的大小是64KB,因为栈上下各需要一个页面记录是否有溢出,所以可用的大小事56KB。程序中应防止过分递归等操作造成栈溢出。

 

应用程序内存泄露问题及定位

看看维基对内存泄露的的解释:“在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。”

Application Verify是解决这个问题最常用的工具,介于这篇博客的篇幅太长,移到下篇文章中介绍了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本系统的研发具有重大的意义,在安全性方面,用户使用浏览器访问网站时,采用注册和密码等相关的保护措施,提高系统的可靠性,维护用户的个人信息和财产的安全。在方便性方面,促进了校园失物招领网站的信息化建设,极大的方便了相关的工作人员对校园失物招领网站信息进行管理。 本系统主要通过使用Java语言编码设计系统功能,MySQL数据库管理数据,AJAX技术设计简洁的、友好的网址页面,然后在IDEA开发平台中,编写相关的Java代码文件,接着通过连接语言完成与数据库的搭建工作,再通过平台提供的Tomcat插件完成信息的交互,最后在浏览器中打开系统网址便可使用本系统。本系统的使用角色可以被分为用户和管理员,用户具有注册、查看信息、留言信息等功能,管理员具有修改用户信息,发布寻物启事等功能。 管理员可以选择任一浏览器打开网址,输入信息无误后,以管理员的身份行使相关的管理权限。管理员可以通过选择失物招领管理,管理相关的失物招领信息记录,比如进行查看失物招领信息标题,修改失物招领信息来源等操作。管理员可以通过选择公告管理,管理相关的公告信息记录,比如进行查看公告详情,删除错误的公告信息,发布公告等操作。管理员可以通过选择公告类型管理,管理相关的公告类型信息,比如查看所有公告类型,删除无用公告类型,修改公告类型,添加公告类型等操作。寻物启事管理页面,此页面提供给管理员的功能有:新增寻物启事,修改寻物启事,删除寻物启事。物品类型管理页面,此页面提供给管理员的功能有:新增物品类型,修改物品类型,删除物品类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值