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 |
初始化内存内容为 |
LPTR |
合并 |
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_ZEROINT
的任意组合。
LMEM_MOVEABLE
和
LMEM_ZONEINT
的描述如下:
描述 |
|
LMEM_MOVEABLE |
允许这个内存块被移动。 |
LMEM_ZEROINIT |
初始化内存内容为 |
如果函数
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 |
这个参数被忽略。在 |
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 |
分配到的内存空间会被初始化为 |
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 |
分配到的内存块会被初始化为 |
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