Windows Embedded Compact 7中的内存管理(下)

  • Post author:
  • Post category:其他




5.3堆

在应用程序中,以页为单位的内存分配方式会造成很多的内部碎片,而且效率很低。堆是系统为应用程序保留的一段内存空间。它允许应用程序以

1


字节或是


4


字节为单位来分配和释放内存。系统中堆的使用可以优化内存的使用,而且免去处理由


Windows Embedded CE


支持的不同微处理器的不同页面大小。一个应用程序可以简单地在堆中分配一块内存,由系统来处理分配需要的页面数。


5.3.1 Windows Embedded Compact 7中堆的特性

堆的

API


依赖于底层虚拟内存管理的


API


。堆中的内存分配不是以


64KB


为边界,而一般都小于


16KB


。如果要分配的空间超过了


16KB


,那么系统中的堆管理会直接在虚拟内存中为之创建一个独立堆。

当应用程序被创建的时候,系统会为每个程序默认分配一个堆。应用程序可以在这个堆中分配和释放内存空间,而堆的大小也随之增大和缩小。但是,如果默认堆中的内存被划分为多个片段,程序的性能会受到很大的影响。

当一个堆被划分为多个片段以后,系统需要花费更长的时间来查找内存空间以应对新的内存分配请求。为了防止这种情况发生,系统为相似的对象创建独立堆。如果堆中包含了多个相同大小的对象,堆管理器就能很容易地为新的内存分配请求分配一个空间的内存块。不过,尽管独立堆减轻了系统对片段的管理,它也增加了分配内存的开销。

Windows Embedded CE

仅支持在堆中分配不可移动的内存块。这简化了堆中内存块的处理,但会导致堆在分配和释放一段时间后会产生碎片。因为系统不能移动内存块,因此只有在堆完全空闲的时候,系统才能回收这个堆中的页面。这样的后果就是,尽管堆基本为空,但是仍需要大量的虚拟页面来满足应用程序的需求。



Windows Embedded CE


操作系统的设计中,操作系统在每个应用程序启动的时候,为之分配一个默认或本地堆。另外,应用程序可以创建多个独立堆。独立堆和本地堆的特性一样,只是它们用另一套堆的


API


进行管理。

默认情况下,

Windows Embedded CE


初始保留了


64KB


的虚拟内存空间,但是只有在它们被分配的时候才被提交。如果应用程序在本地堆中分配的内存空间超过了


64KB


,那么系统利用函数


VirtualAlloc


来满足内存分配的需求。用户可以在创建堆的时候指定最大或初始堆的大小。

为了避免造成本地堆的碎片,如果用户在一段时间需要一系列内存块的时候,那么可以使用独立堆。例如用户在编辑多个文件时,可以为每个文件创建一个独立堆。文件关闭时,独立堆也随之销毁。这样,本地堆的内存空间不受影响。


5.3.2 本地堆

本地堆是系统最初为程序保存的堆。根据

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


的返回值。


5.3.3 独立堆

为了避免本地堆的碎片,并且如果你要分配连续的内存块,较好的办法是建立分离堆,但将花费一定的时间。

跟本地堆一样,系统也为独立堆提供了一套内存空间的管理函数,下面一一对这些函数作简单介绍。

一、函数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


的样例。


5.6.1 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


引导载入,不能被自己载入。


5.6.2 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


的任务也就完成了。


5.6.3 Bootloader的结构

1.

BootLoader


的构成组件

BootLoader

主要由以下两部分组成。



1





OEM startup code

这部分代码是在

BootLoader


中最先被执行的。它的主要功能是初始化最小范围的硬件设备,比如设置


CPU


工作频率、关闭看门狗、设置


cache


、设置


RAM


的刷新率、填写内存控制寄存器(通知


CPU


有效的数据总线引脚数)等。由于系统刚刚启动,不适合使用复杂的高级语言,因此这部分代码主要由汇编程序完成。在汇编程序段设置完堆栈后,就跳转到


C


语言的


Main


函数入口(位于


<LATNAME>\eboot\main.c


);



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


的代码架构。

转载于:https://blog.51cto.com/8382359/1342189


关闭菜单