5.3堆
在应用程序中,以页为单位的内存分配方式会造成很多的内部碎片,而且效率很低。堆是系统为应用程序保留的一段内存空间。它允许应用程序以1字节或是4字节为单位来分配和释放内存。系统中堆的使用可以优化内存的使用,而且免去处理由Windows Embedded CE支持的不同微处理器的不同页面大小。一个应用程序可以简单地在堆中分配一块内存,由系统来处理分配需要的页面数。
堆的API依赖于底层虚拟内存管理的API。堆中的内存分配不是以64KB为边界,而一般都小于16KB。如果要分配的空间超过了16KB,那么系统中的堆管理会直接在虚拟内存中为之创建一个独立堆。
当应用程序被创建的时候,系统会为每个程序默认分配一个堆。应用程序可以在这个堆中分配和释放内存空间,而堆的大小也随之增大和缩小。但是,如果默认堆中的内存被划分为多个片段,程序的性能会受到很大的影响。
当一个堆被划分为多个片段以后,系统需要花费更长的时间来查找内存空间以应对新的内存分配请求。为了防止这种情况发生,系统为相似的对象创建独立堆。如果堆中包含了多个相同大小的对象,堆管理器就能很容易地为新的内存分配请求分配一个空间的内存块。不过,尽管独立堆减轻了系统对片段的管理,它也增加了分配内存的开销。
Windows Embedded CE仅支持在堆中分配不可移动的内存块。这简化了堆中内存块的处理,但会导致堆在分配和释放一段时间后会产生碎片。因为系统不能移动内存块,因此只有在堆完全空闲的时候,系统才能回收这个堆中的页面。这样的后果就是,尽管堆基本为空,但是仍需要大量的虚拟页面来满足应用程序的需求。
在Windows Embedded CE操作系统的设计中,操作系统在每个应用程序启动的时候,为之分配一个默认或本地堆。另外,应用程序可以创建多个独立堆。独立堆和本地堆的特性一样,只是它们用另一套堆的API进行管理。
默认情况下,Windows Embedded CE初始保留了64KB的虚拟内存空间,但是只有在它们被分配的时候才被提交。如果应用程序在本地堆中分配的内存空间超过了64KB,那么系统利用函数VirtualAlloc来满足内存分配的需求。用户可以在创建堆的时候指定最大或初始堆的大小。
为了避免造成本地堆的碎片,如果用户在一段时间需要一系列内存块的时候,那么可以使用独立堆。例如用户在编辑多个文件时,可以为每个文件创建一个独立堆。文件关闭时,独立堆也随之销毁。这样,本地堆的内存空间不受影响。
本地堆是系统最初为程序保存的堆。根据CPU类型的不同,本地堆的基本分配单元是4字节或是8字节。在默认情况下,Windows CE为本地堆保留192KB的空间。当在本地堆中需要分配的内存空间超过192KB,系统调用VirtualAlloc函数分配相应的虚拟内存。如果本地堆的总空间大小超过了系统预留的192KB的空间,未被保留的内存区域被扩充为本地堆的空间。但是,这部分未被保留的内存区域可能与系统默认本地堆的地址空闲不连续。
系统为本地堆提供了一系列函数供用户使用。这些函数主要包括LocalAlloc、LocalFree、LocalReAlloc和LocalSize。
一、函数LocalAlloc介绍
函数LocalAlloc的功能是在本地堆中分配内存。函数原型如下:
HLOCAL LocalAlloc(
UINT uFlags,
UINT uBytes
);
函数的返回值是一个本地内存块的句柄。由于本地堆中的内存块是固定分配的,所以返回值可以当成是一个指向块的指针。如果本地堆中没有足够的空间,函数将返回NULL。
uFlags域描述了内存块的特征。如果值为0,则默认值是LMEM_FIXED。它可选的值以及相应的描述如下:
值 |
描述 |
LMEM_FIXED |
在本地堆中分配一个固定内存块,因为本地堆分配已经固定,所以是多余的。 |
LMEM_ZEROINIT |
初始化内存内容为0。 |
LPTR |
合并LMEM_FIXED和LMEM_ZEROINIT标志。 |
uBytes域指定了要分配的内存块的大小,以字节为单位。块大小要补齐。
二、函数LocalFree介绍
函数LocalFree的功能是在释放本地堆中的内存。函数原型如下:
HLOCAL LocalFree(
HLOCAL hMem
);
函数的返回值是一个本地内存块的句柄。如果函数执行成功,则返回NULL;否则,返回内存块的句柄。
hMem域是待释放本地堆内存的句柄。
函数LocalFree能够释放被加锁的内存对象。如果应用程序处于调试状态或是运行调试版本,函数在释放之前会进入到断点中或触发提示信息。
三、函数LocalReAlloc介绍
函数LocalAlloc的功能是改变本地堆分配内存的大小。函数原型如下:
HLOCAL LocalReAlloc(
HLOCAL hMem,
UINT uBytes,
UINT fuFlags
);
函数的返回值是一个本地内存块的句柄。如果函数执行失败,则返回NULL。
hMem域指向待重分配堆的句柄,这个句柄必须是函数LocalAlloc或LocalReAlloc的返回值。
uBytes域是要重新分配的堆的大小。如果fuFlags的值是LMEM_MODIFY,那么这个域的值被忽略。
fuFlags域描述了内存块的特征。如果值为LMEM_MODIFY,这个域只是改变内存块的属性,而不改变块的大小。下面的值能和LMEM_MODIFY值联合使用:
值 |
描述 |
LMEM_MOVEABLE |
不能同时将LMEM_MODIFY和LMEM_MOVEABLE这两个值用于固定的内存块,否则会返回错误。 |
如果这个域不包含LMEM_MODIFY,那么这个参数可以是LMEM_MOVEABLE和LMEM_ZEROINT的任意组合。LMEM_MOVEABLE和LMEM_ZONEINT的描述如下:
值 |
描述 |
LMEM_MOVEABLE |
允许这个内存块被移动。 |
LMEM_ZEROINIT |
初始化内存内容为0。 |
如果函数LocalReAlloc返回失败,原来的内存块不会被释放,而且它们的句柄或指针都是有效的。如果函数LocalReAlloc的操作对象是一个固定的内存块,返回值指向内存块第一个字节的地址。
四、函数LocalSize介绍
函数LocalSize的功能在于返回当前内存块的大小(以字节为单位)。函数的原型如下:
UINT LocalSize(
HLOCAL hMem
);
在调试模式下,函数返回当前内存块的准确大小。在正常模式下,函数的返回值是以32字节对齐后的内存块大小。
hMem域指向待重分配堆的句柄,这个句柄必须是函数LocalAlloc或LocalReAlloc的返回值。
为了避免本地堆的碎片,并且如果你要分配连续的内存块,较好的办法是建立分离堆,但将花费一定的时间。
跟本地堆一样,系统也为独立堆提供了一套内存空间的管理函数,下面一一对这些函数作简单介绍。
一、函数HeapCreate介绍
函数HeapCreate的功能在于在共享内存区域保留一段内存区域建立一个独立堆。之后,函数HeapAlloc在这段保留的内存区域中分配内存。函数的原型如下:
HANDLE HeapCreate(
DWORD flOptions,
DWORD dwInitialSize,
DWORD dwMaximumSize
);
函数执行成功会返回指向新创建的独立堆的句柄,否则返回NULL。
flOptions域表明这个堆是否能被共享。它可选的值及描述如下:
值 |
描述 |
HEAP_NO_SERIALIZE |
这个参数被忽略。在Windows堆管理程序这个串行参数防止系统中一个进程的两个线程同时访问堆。但是在Windows CE中,所有的堆访问都是串行的。 |
HEAP_SHARED_READONLY |
这个堆能同时被多个进程执行读操作,但是只能被创建进程执行写操作。 |
dwInitialSize域指定了独立堆最初的大小,这个值会向上对齐到下一个页面。
dwMaximumSize域指定了预期的堆最大值。如果函数HeapAlloc和HeapReAlloc分配的空间超过了dwInitialSize值,系统会将堆的大小扩充到dwMaximumSize值。如果dwMaximumSize的值为0,独立堆的空间可以增长,只受当前可用内存空间大小的限制;否则,堆不能增长。
二、函数HeapAlloc介绍
函数HeapAlloc的功能是在已经创建的独立堆中分配内存。函数的原型如下:
LPVOID HeapAlloc(
HANDLE hHeap,
DWORD dwFlags,
DWORD dwBytes
);
函数的返回值是一个指针,指向分配的内存块。函数执行失败则返回NULL。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。这个属性将覆盖调用函数HeapCreate时指定的内存标志。它可选的值及描述如下:
值 |
描述 |
HEAP_NO_SERIALIZE |
这个参数被忽略。 |
HEAP_ZERO_MEMORY |
分配到的内存空间会被初始化为0。 |
dwBytes域指定了要分配内存的大小(以字节为单位)。如果这个独立堆是不能增长的,那么这个值必须小于0x7FFF8。
二、函数HeapFree介绍
函数HeapFree的功能是释放独立堆中已分配的内存。函数的原型如下:
BOOL HeapFree(
HANDLE hHeap,
DWORD dwFlags,
LPVOID lpMem
);
函数执行成功返回非零值,否则失败。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。它唯一的标志是HEAP_NO_SERIALIZE。
lpMem域指向要释放的内存块。
三、函数HeapReAlloc介绍
函数HeapReAlloc的功能是为独立堆中已分配的内存重新分配内存,而且分配的内存是不能移动的。函数的原型如下:
LPVOID HeapReAlloc(
HANDLE hHeap,
DWORD dwFlags,
LPVOID lpMem,
DWORD dwBytes
);
函数的返回值是一个指针,指向分配的内存块。函数执行失败则返回NULL。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。这个属性将覆盖调用函数HeapCreate时指定的内存标志。它可选的值及描述如下:
值 |
描述 |
HEAP_NO_SERIALIZE |
这个参数被忽略。 |
HEAP_REALLOC_IN_PLACE_ONLY |
重新分配大的内存块时,内存空间不能移动。如果这个参数没有设置,函数可以将原来的内存块移动到新分配大的内存块中。如果这个参数被设置,而且原来的内存块不能在相邻的空间扩充,那么函数会返回失败。 |
HEAP_ZERO_MEMORY |
分配到的内存块会被初始化为0。 |
lpMem域指向要释放的内存块。
dwBytes域指定了要分配内存的大小(以字节为单位)。
四、函数HeapSize介绍
函数HeapSize的功能在于返回当前内存块的大小(以字节为单位)。函数的原型如下:
DWORD HeapSize(
HANDLE hHeap,
DWORD dwFlags,
LPCVOID lpMem
);
在调试模式下,函数返回当前内存块的准确大小。在正常模式下,函数的返回值是以32字节对齐后的内存块大小。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。它唯一的标志是HEAP_NO_SERIALIZE。
lpMem域指向要释放的内存块。
五、函数HeapDestroy介绍
函数HeapDestroy的功能是销毁独立堆。函数的原型如下:
BOOL HeapDestroy(
HANDLE hHeap
);
函数的返回值为非零值表示成功,否则表示失败。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate调用返回的句柄。
六、函数HeapValidate介绍
函数HeapValidate的功能是验证独立堆的状态。它扫描独立堆中的所有内存块,验证系统的堆管理器所维护的堆控制结构体的状态是否一致。同时,它也能只验证独立堆中的特定内存块。函数的原型如下:
BOOL HeapValidate(
HANDLE hHeap,
DWORD dwFlags,
LPCVOID lpMem
);
函数的返回值为非零值表示整个独立堆或是指定的内存块的状态是一致的;否则,他们的状态不一致。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。标志HEAP_NO_SERIALIZE被忽略,因为独立堆永一直是串行的。
lpMem域指向要释放的内存块。如果值为NULL,函数验证整个独立堆;否则,函数只验证lpMem指向的内存块。
七、函数HeapCompact介绍
函数HeapCompact的功能是通过级联相邻的空闲内存块或是取消提交大的空闲内存块来达到压缩独立堆的目的。函数的原型如下:
UINT HeapCompact(
HANDLE hHeap,
DWORD dwFlags
);
函数执行成功则返回被取消提交的空闲内存块的最大值(以字节为单位)。如果函数执行失败,则返回0。
hHeap域是将被分配的独立堆的句柄。它只能是函数HeapCreate或GetProcessHeap调用返回的句柄。
dwFlags域是分配内存空间的属性。标志HEAP_NO_SERIALIZE被忽略,因为独立堆永一直是串行的。
八、函数GetProcessHeap介绍
函数GetProcessHeap的功能是返回当前进程的独立堆的句柄。这个句柄可以被函数HeapAlloc、HeapReAlloc、HeapFree和HeapSize使用。函数的原型如下:
HANDLE GetProcessHeap (VOID);
函数执行成功,则返回当前进程独立堆的句柄,否则返回NULL。
5.4栈
跟其它操作系统如Linux一样,Windows CE中的栈主要是用来存储函数中的临时变量。同时,函数调用过程中的返回地址以及寄存器的状态也保留在栈中。系统为每个线程都分配一个分离的栈。
不同型号的处理器所维护的栈的结构不一样。一般来说,默认情况下系统中每个栈的大小为64KB。其中,有8KB的空间被保留用于溢出错误控制等异常处理。因而,系统中实际可用的栈大小为56KB。应用程序的主线程的栈大小可以通过应用程序被链接时的链接器开关来指定。这个链接器开关主要是利用参数/STACK来进行设定的。栈增加时,提交虚拟内存页是从上至下的。当栈减小时,系统将处于的低内存环境,会回收在栈下面未使用但是仍然被提交的页。
当一个应用程序建立一个新的线程时,栈的最大尺寸可以通过建立线程时CreateThread调用来指定。同样会有一些页用作防护,但是栈的大小可以指定至1MB。注意,这个指定大小同样会被用作所有分离线程栈的默认栈大小。那就是说,如果你指定主栈为128KB,程序中所有其他的线程栈大小也限制为128KB,除非在用CreateThread建立线程时指定一个不同的大小。
一个线程首次被系统调度运行的时候,线程的栈空间只有在需要的时候提交页面,而且每次只能提交一个页面。另外,线程的栈空间页面被提交的时候,程序会因为页面的提交而影响性能。为此,对于实时处理的线程来说,它们必须至少在运行前被调度一次。
5.5静态数据块
静态数据块是Windows CE系统在加载程序时为之分配的一段内存块。这个内存块中主要存储了静态分配的字符串、缓冲区和其他一些在程序的整个生命周期中都存在的静态变量,例如全局变量,同时也包括通过静态链接到应用程序的静态库函数中的缓冲区。Windows CE分配给应用程序两块RAM中的内存块存放静态数据,一个是可读写数据(read/write data)和只读数据(read only data)。
由于Windows CE系统是基于页为这些变量分配变量的,从一页的静态数据块到下一个页面之间会存在一定的剩余空间。用户在编写应用程序的时候,应当尽量保证这部分剩余空间很少或是没有。如果你在静态数据区有空间,最好把一个或两个缓冲区放到静态数据区,避免动态分配缓冲区。
应用程序的.data数据段可以通过命令dumpbin /headers得到。如果.data数据段所在的页面没有被填满,用户可以在这段空间中存放在更多的数据,进而保证能充分利用这段剩余的空间。图5.5是一个类似的例子。
在这个例子中,数据段的虚拟大小为0x930,而它占据了一个页面共0x1000个字节。用户可以充分利用剩下的(0x1000 – 0x930)个字节而不增加内存的利用率。
5.6Windows Embedded Compact 7下的Bootloader
在Windows Embedded Compact 7的开发环境中,Bootloader是为新的硬件平台开发的第一块代码。它的主要目的是将操作系统的运行时镜像加载到硬件平台上。一般而言,操作系统的运行时镜像包括用于开发的调试版本和发行的版本。如果用户不是开发一个全新的硬件平台,Bootloader的代码不需要从头开始编写,而是可以借鉴的。
大致来说,如果硬件平台厂商系统希望在硬件上运行Windows Embedded CE的环境,他们都会提供一个与平台相关的Bootloader。Microsoft在平台开发包中也提供了一个Bootloader的样例。
对于需要运行操作系统的硬件平台来说,Bootloader负责启动和加载这个系统。不同的硬件平台所包含的Bootloader是不一样。在大多数嵌入式设备中,Bootloader是设备加电后最开始执行的代码。
不依赖Bootloader的设备也是存在的。这些设备的系统复位进程将操作系统的运行时镜像引导装入到设备的本地存储中。虽然这些设备不需要Bootloader也能运行,但是在开发阶段利用Bootloader快速地加载系统的运行时镜像到设备中。
Bootloadre对硬件平台进行初始化,将系统的软硬件环境设置成一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
在基于X86架构的设备中,系统BIOS是最先开始运行的代码。BIOS初始化系统的硬件,并搜寻系统中能启动设备的存储介质。这样看来,X86架构下的系统BIOS就可以看作是一个Bootloader。
Windows Embedded CE设备的Bootloader初始化设备的硬件,并提供函数将CE的运行时镜像从设备的本地ROM或是flash盘中加载到设备的物理内存中。
Bootloader按照Windows Embedded CE的启动方式不同,大致有以下几种方式:
BIOS Loader,或者称为x86 BIOS Loader,是用于支持x86设备的Bootloader。它从设备的本地存储中加载Windows Embedded CE的运行时镜像。BIOS读取boot.ini文件中启动的参数,分析显示配置、网络设置、调试串口以及其他选项,并将这些分析后的参数存放到Windows EmbeddedCE能够读取的内存单元。然后根据这些参数引导装入镜像。
Eboot Loader是一个基于网络的Bootloader。它通过网络将开发站中的Windows Embedded CE的运行时镜像下载到本地设备中。不过,Ebootloader只能被其他的Bootloader引导载入,而不能被自己载入。
Loadcepc是基于DOS支持x86设备的Bootloader。Loadcepc可以从设备的本地存储中加载Windows Embedded CE的运行时镜像(nk.bin),也可以用来引导Eboot Loader及Sboot Loader。
Romboot Loader用来替换x86系统的BIOS,并将数据存放在闪存存储介质中。
Sboot Loader是串行的Bootloader,通过串口将Windows Embedded CE的运行时镜像从开发站下载到本地。同Eboot Loader一样,Sboot也只能被其他Bootloader引导载入,不能被自己载入。
Bootloader是严重地依赖于硬件而实现的,特别是在嵌入式系统中。因此,在嵌入式系统里建立一个通用的Bootloader几乎是不可能的。尽管如此,Bootloader完成的工作有很多通用的类似的地方,下面以能支持大部分x86设备的BIOS Loader为例介绍Bootloader的主要功能。
类似于常用个人电脑,x86设备利用BIOS Loader按照下面的流程来加载Windows Embedded CE的运行时镜像:
在设备启动或是重启的时候,x86设备的CPU跳转到CPU重置向量地址,并执行其中BIOS代码。典型地,BIOS将对设备的硬件进行配置,包括对DRAM控制器进行配置、对设备的主桥进行配置、枚举统计PCI的设备,以及搜寻能够启动的存储介质。
如果启动介质是一块硬盘,那么加载位于硬盘第一个扇区的主引导扇区(MBR)。主引导扇区存放在一个扇区内,其中包含了实模式下的代码和数据。BIOS将主引导扇区中的数据加载到设备的内存中。之后,BIOS执行主引导扇区中的代码。
主引导扇区中的代码搜寻分区表,并在活动分区中查找启动盘。分区表是存放在主引导扇区中的数据。如果不存在活动分区,主引导扇区报错并退出;否则,找到这个分区的地址。启动扇区保存在这个活动分区的第一个扇区。主引导扇区中的代码将启动扇区中的代码和数据加载到内存中,并跳转到这片内存的起始地址。
与主引导扇区一样,启动扇区只包括一个扇区大小,且同时存放了代码和数据。启动扇区是与操作系统相关的第一块代码。启动扇区中的代码完成的功能是查找和加载BIOS Bootloader,并将找到的Bootloader加载到内存,且跳转过去。
当BIOS loader运行之后,它寻找并加载Windows Embedded CE的运行时镜像到内存中,然后跳转到镜像的起始地址。这样,操作系统的代码就开始执行了,而Bootloader的任务也就完成了。
1.BootLoader的构成组件
BootLoader主要由以下两部分组成。
(1)OEM startup code
这部分代码是在BootLoader中最先被执行的。它的主要功能是初始化最小范围的硬件设备,比如设置CPU工作频率、关闭看门狗、设置cache、设置RAM的刷新率、填写内存控制寄存器(通知CPU有效的数据总线引脚数)等。由于系统刚刚启动,不适合使用复杂的高级语言,因此这部分代码主要由汇编程序完成。在汇编程序段设置完堆栈后,就跳转到C语言的Main函数入口(位于
(2)Main code
这部分代码由C语言实现,是BLCOMMON代码的一部分,它可以用来执行比较复杂的操作。比如检测内存和Flash的有效性、检测外部设备接口、检测串口并且向已经连接的主机发送调试信息、通过串口等待命令、启动网络接口、建立内存映射等汇编无法完成的工作。
Image的下载可以通过以下接口。
① A. Parallel port I/O接口。这是通过主机和目标机的并口之间的数据传输来完成下载工作。具体实现代码,读者可以参考\kernel\hal\mdppfs.c文件;
② Ethernet port I/O接口。这是通过主机和目标机的网络接口之间的数据传输来完成下载工作。具体实现代码,读者可以参考\public\common\oak\DRIVE|RS\ETHDBG\EBOOT文件夹;
③ Debug serial I/O接口。这是通过主机和目标机的串口之间的数据传输来完成下载工作。利用串口来传输的缺点非常明显,那就是速度太慢;
通过事先Flash write代码将镜像文件固化入Flash。只要BootLoader被设计成能从Flash加载镜像文件,本选项就可以使用。
2.BootLoader控制流函数Control Flow Functions
Windows CE.NET的BootLoader的整体架构如图5.6所示。
我们来看一下Windows CE.NET的BootLoader在系统启动时所执行的函数。
图中的所有函数分为3个模块:BLCOMMON、OEM Function、FLASH Function。其中BLCOMMON模块是由微软公司提供的,执行一些逻辑上的功能,因此建议开发人员不要对其进行修改。而OEM Function、FLASH Function中的函数与硬件平台息息相关,因此对于每种硬件平台都要将函数的实现进行修改。
BLCOMMON模块的功能解释。
BLCOMMON是一个库,其实现代码位于%_WINCEROOT%\Public\Common\Oak\Drivers\
Ethdbg\Blcommon目录下。它实现了Windows CE.NET BootLoader的基本框架。这个库的工作为:将bootloader加载到RAM中执行、解压缩.bin文件、校验硬件平台的完整性、对加载的进度进行跟踪。在BLCOMMON阶段执行的过程中,主要使用OEM函数集。
BLCOMMON库的入口点为BootloaderMain函数,它有Startup汇编函数完成后跳转至该入口。BLCOMMON库将被BootLoader的程序链接在一起。
在系统启动时,CPU首先执行StartUp函数,这是个由汇编实现的函数。StartUp函数主要的功能为:设置CPU工作频率、关闭看门狗、设置cache、设置RAM的刷新率、填写内存控制寄存器(通知CPU有效的数据总线引脚数)等。在StartUp完成任务后,就跳转到BootLoaderMain函数中。这个是由C语言编程实现的函数入口点。
下面是SMDK2410中的BootLoader中的main函数实现代码:
void main(void)
{
//清空LED
OEMWriteDebugLED(0, 0xF);
//通用BootLoader (blcommon)主入口
BootloaderMain();
//注意,在此调用了BootloaderMain函数,并且没有返回值
SpinForever();
}
(1)BLCOMMON模块函数
下面列举出BLCOMMON中的控制函数并分析它们,这些函数在Blcommon.h中声明,代码实现在Blcommon.lib里:
一、函数OEMDebugInit
在运行BootloaderMain程序后,将首先调用OEMDebugInit函数,它用来初始化调试信息的I/O设备,最常见的是串口设备。由于RS232协议简单性,在系统没有启动前对串口初始化较适用。在OEMDebugInit里,又通常调用OEMInitDebugSerial函数来初始化串口。
二、函数OEMPlatformInit
OEM层的初始化函数,它主要负责目标机上的硬件初始化。在汇编阶段只是初始化了很小一部分硬件,这是由于BootLoader要求处理时间短,因此在汇编阶段的硬件初始化是十分简单的。所以有必要用高级语言完成对目标机的硬件设置,这包括具体的时钟设置、驱动和传输设备接口的初始化。
下面是此函数代码实例:
BOOL OEMPlatformInit(void)
{
BYTE BootDelay;
BYTE KeySelect;
EBOOT_CFG EbootCfg;
DWORD dwStartTime, dwPrevTime, dwCurrTime;
PCI_REG_INFO NANDInfo;
EdbgOutputDebugString("Microsoft Windows CE Bootloader for the Samsung SMDK2410 Version %d.%d Built %s\r\n\r\n", EBOOT_VERSION_MAJOR, EBOOT_VERSION_MINOR, __DATE__);
//初始化LCD显示器
InitDisplay();
// 初始化驱动全局区域
memset(pDriverGlobals, 0, sizeof(DRIVER_GLOBALS));
pDriverGlobals->MajorVer = DRVGLB_MAJOR_VER;
pDriverGlobals->MinorVer = DRVGLB_MINOR_VER;
pDriverGlobals->eth.EbootMagicNum = EBOOT_MAGIC_NUM;
// 初始化Flash,SMDK2410上的FLASH为AMD AM29LV800型。
if (!AM29LV800_Init(AMD_FLASH_START))
{
RETAILMSG(1, (TEXT("ERROR: OEMPlatformInit: Flash 初始化
failed.\r\n")));
return(FALSE);
}
........
// 让用户选择启动选项
while((dwCurrTime - dwStartTime) < EbootCfg.BootDelay)
{
KeySelect = OEMReadDebugByte();
......
}
switch(KeySelect)//判别用户命令
{
case 0x20: // 根菜单项
g_bDownloadImage = MainMenu(&EbootCfg);
break;
case 0x00: //无按键失败
case 0x0d: //用户取消了倒计时
default:
if (EbootCfg.ConfigFlags & CONFIG_FLAGS_AUTOBOOT)
{
EdbgOutputDebugString ( "\r\nLaunching flash image ... \r\n");
g_bDownloadImage = FALSE;
}
else
{
EdbgOutputDebugString ( "\r\nStarting auto-download ... \r\n");
g_bDownloadImage = TRUE;
}
break;
}
//如果用户指定了静态IP地址,那么就使用静态IP地址(不使用DHCP)
if (g_bDownloadImage && !(EbootCfg.ConfigFlags & CONFIG_FLAGS_DHCP))
{
pDriverGlobals->eth.TargetAddr.dwIP = EbootCfg.IPAddr;
pDriverGlobals->eth.SubnetMask = EbootCfg.SubnetMask;
}
//配制以太网控制器
if (!InitEthDevice(&EbootCfg))
{
DEBUGMSG(1, (TEXT("OEMPlatformInit: Failed to initialize Ethernet
controller.\r\n")));
return(FALSE);
}
return(TRUE);
}
三、函数OEMPreDownload
在下载操作系统前执行这个函数,它可以用来设置如何进行Image文件下载。例如,可以设置成从网络下载或者跳过下载直接加载Flash中的Image文件。
下面是此函数代码实例:
DWORD OEMPreDownload(void)
{
CHAR szDeviceName[EDBG_MAX_DEV_NAMELEN];
BOOL bGotJump = FALSE;
DWORD dwDHCPLeaseTime = 0;
PDWORD pdwDHCPLeaseTime = &dwDHCPLeaseTime;
DWORD dwBootFlags = 0;
//如果用户想进入已存在的映像,那么跳过下载
if (!g_bDownloadImage)
{
g_bWaitForConnect = FALSE; // 不等待宿主机连接
return(BL_JUMP);
}
//如果用户想用一个静态IP地址,那么就不要从DHCP服务器请求一个地址
//将DHCP租期时间变量设置为NULL
if (pDriverGlobals->eth.TargetAddr.dwIP &&
pDriverGlobals->eth.SubnetMask)
{
pdwDHCPLeaseTime = NULL;
RETAILMSG(1, (TEXT("INFO: Using static IP address %s.\r\n"),
inet_ntoa(pDriverGlobals->eth.TargetAddr.dwIP)));
RETAILMSG(1, (TEXT("INFO: Using subnet mask %s.\r\n"),
inet_ntoa(pDriverGlobals->eth.SubnetMask)));
}
//创建基于以太网地址的设备名称(也就是Platform Builder如何定义设备)
//
memset(szDeviceName, 0, EDBG_MAX_DEV_NAMELEN);
CreateDeviceName(&pDriverGlobals->eth.TargetAddr, szDeviceName,
PLATFORM_STRING);
EdbgOutputDebugString("INFO: Using device name: '%s'\n", szDeviceName);
//初始化TFTP传送
//
if (!EbootInitEtherTransport(&pDriverGlobals->eth.TargetAddr,
&pDriverGlobals->eth.SubnetMask,
&bGotJump,
pdwDHCPLeaseTime,
EBOOT_VERSION_MAJOR,
EBOOT_VERSION_MINOR,
PLATFORM_STRING,
szDeviceName,
EDBG_CPU_ARM720,
dwBootFlags))
{
return(BL_ERROR);
}
//保存DHCP租期时间(注意,本例中使用的是静态IP)
pDriverGlobals->eth.DHCPLeaseTime = dwDHCPLeaseTime;
return(bGotJump ? BL_JUMP : BL_DOWNLOAD);
}
四、函数DownloadImage
这个函数将执行把操作系统Image文件下载到目标机的操作。
五、函数OEMLaunch
这个函数将PC指针直接设置到Image文件的开始地址,它是启动操作系统前BootLoader的最后一个函数,没有返回值。在此之后,BootLoader就消失了。
下面分析SMDK2410的OEMLaunch的实现代码:
void OEMLaunch(DWORD dwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr,
const ROMHDR *pRomHdr)
{
DWORD dwPhysLaunchAddr;
EDBG_OS_CONFIG_DATA *pCfgData;
EDBG_ADDR EshellHostAddr;
EBOOT_CFG EbootCfg;
//从flash得到eboot配制
ReadEbootConfig(&EbootCfg);
//下载并从服务器得到IP和端口设置后,等待Platform Builder连接
//连接也发送KITL标志,稍后将用于OS(KITL)
if (g_bWaitForConnect)
{
……
}
//如果该下载未提供一个地址,那么记住kernel的启动地址或者重新调用保存的地址、、
//(也就是不下载kernel区域)
if (dwLaunchAddr && (EbootCfg.LaunchAddress != dwLaunchAddr))
{
EbootCfg.LaunchAddress = dwLaunchAddr;
WriteEbootConfig(&EbootCfg);
}
else
{
dwLaunchAddr = EbootCfg.LaunchAddress;
}
//如果用户请求一个储存在flash中的RAM映像,可以马上去请求.对于多个RAM BIN文件
//需要将RAM地址映射到flash地址
// RAM中基于地址偏移的映像在flash中是连续的
if (g_bDownloadImage && (EbootCfg.ConfigFlags & CONFIG_FLAGS_SAVETOFLASH))
{
if (!WriteRegionsToSmartMedia(&EbootCfg))
{
EdbgOutputDebugString("WARNING: OEMLaunch: Failed to store image
to Smart Media.\r\n");
}
}
//跳到下载的映像(物理地址,因为马上将关闭MMU)
dwPhysLaunchAddr = ToPhysicalAddr(dwLaunchAddr);
EdbgOutputDebugString("INFO: OEMLaunch: Jumping to Physical Address 0x%Xh
(Virtual Address 0x%Xh)...\r\n\r\n\r\n", dwPhysLaunchAddr, dwLaunchAddr);
Launch(dwPhysLaunchAddr);
//应该从不返回
SpinForever();
}
(2)下载模块函数
下载函数是由DownloadImage函数调用的。
下面列出下载模块函数并解释它们。
一、函数OEMReadData
BLCOMMON调用这个函数从文件的传输器中读取数据。读者可以参看Public\Common\Oak\Ethdbg\Eboot\Ebsimp.c文件中网络传输的例子
二、函数OEMShowProgress
BLCOMMON在下载操作系统镜像文件的时候调用这个函数。在这个函数中,可以实现通知用户下载状态的各种手段比如可以用LED灯交替闪烁或者向主机的串口发送进度信息等。
三、函数OEMMapMemAddr
如果目标系统的需求是要能支持把操作系统的镜像文件下载到FLASH中去,就必须调用本函数。由于FLASH操作速度比RAM慢,在片擦除的时候甚至会使读写操作停滞,这样在每次下载操作系统镜像文件时,由于FLASH的擦写都会使下载停滞。而OEMMapMemAddr使用了RAM缓冲操作系统镜像文件的方式,使得用户在下载操作系统镜像文件时感觉不到停滞,这个函数将FLASH地址映射到RAM地址,这样向FLASH写的数据实际上先被缓冲到RAM中,然后再写到FLASH中。
(3)FLASH编程模块
FLASH函数用于对不同的FLASH存储器进行编程。开发人员需要实现微软公司提供的框架里的函数。
一、函数OEMIsFlashAddr
判别地址是否为有效的FLASH地址。注意,这里的FLASH地址与平台相关的,如S3C2410芯片和PXA255芯片的FLASH地址是不一样的,即便是同一款CPU,由于硬件结构的不同(FLASH大小、位置等)FLASH地址也不尽相同。
二、函数OEMStartEraseFlash
BLCOMMON在获取FLASH的实际大小和开始地址后,将立即调用这个函数。这个函数将进行FLASH的擦除工作。
三、函数OEMContinueEraseFlash
BLCOMMON在下载操作系统镜像文件的过程中可以调用这个函数。当FLASH擦除发生错误的时候,可以用这个函数来重复擦除操作,并且进行校验。
四、函数OEMFinishEraseFlash
FLASH擦除完成时,BLCOMMON调用这个函数。这个函数将校验所有的擦除工作是否完成。
五、函数OEMWriteFlash
调用这个函数,将缓冲在FLASH_CACHE中的操作系统镜像文件写入FLASH中。
5.7小结
本章主要介绍了Windows Embedded Compact 7中内存相关的知识。首先,从设备使用的内存介质(ROM和RAM)的角度出发,介绍了两者不同的特性和应用场合。其次,从操作系统的角度出发,介绍了操作系统的内存管理方式-分页式虚拟存储;并介绍了应用程序的虚拟地址空间的组成,以及系统提供的对这些虚拟地址空间的操作函数。再次,从不同的内存类型出发,介绍了堆、栈、静态数据块这些内存空间类型的特征,还有相应的操作函数。最后,本章还介绍了Bootloader在设备中的作用、表现的形式;另外,以x86的BIOS loader为例,介绍了Bootloader完成的主要工作;而为了便于理解Bootloader在代码上的实现,本章还简单分析了Eboot的代码架构。