第十六章 项目制作
本章提供了五个不同性质的项目,来帮助读者更进一步的了解Windows CE嵌入式操作系统。这五个项目包括了同步与互斥的程序设计、内存管理、储存系统管理、驱动程序撰写、和图形系统的使用。借着完成这些项目,读者可以从实做中来学习如何使用Windows CE所提供的发展环境及侦错工具。
16.1 同步与互斥专案
项目任务
考虑在一个三岔路口,每条路都是双向的,车辆有机会从某一条路开向其它路线。编写一个处理程序,来仿真此三岔路口的交通状况,并参考实际生活中的经验,提出一些合理的假设。仿真的时候要先作一个模块,仿真车辆的到达,并可输入参数以调节车流量,在这个问题中,我们要掌控的资源就是路口的车道,应注意有些路线是不能同时使用的。
在模型中可以把车辆当作一个点来看,但需设定车辆的合理间距、考虑车辆的行驶速度、车辆行经路口的时间、路口能容纳的车辆数、……等,对于这些参数,我们可以用常数来表示。在上述合理的假设下,考虑怎样才能让这个交通系统有最大的车流量?程序要尽可能公平,不能出现死结或某条路线“饥饿”的情况,且要尽量避免交通拥塞。
请先用Windows CE提供的号志(Semaphore)机制 (包括同步和互斥两类) 解决上述问题。再考虑选择下列几种Windows CE的同步机制中,解决上述问题的实作。可使用的方式包括事件、临界区间以及互斥(mutex)的变量计算。程序仿真的结果应显示车流量平常和高峰期两种情况之下,十分钟内各条路线的车流量与时间、路口车流量与时间、各路线等待车辆的队列长度与时间等各分部情况,以及全部车辆通过路口的情况 (包括方向、等待时间、车辆序号以及第几个通过路口等信息) 。
相关内容提示
Windows CE的同步互斥与号志机制,请参考第2章和第3章的内容。
16.2 内存管理项目
16.2.1 专案一:虚拟内存
项目任务
此项目的目的是从不同角度,了解Windows CE对虚拟内存空间的管理、分配方法。同时需要了解追踪程序的编写方式 (使用Windows CE提供的号志功能,与被追踪程序保持同步)。并且对Windows CE分配虚拟内存、改变内存状态,以及对物理内存 和页 (page) 状态查询的API函式功能、参数限制、使用规则有进一步了解。
本实验要求使用Windows CE的API函式,编写一个包含两个执行绪的处理程序,一个执行绪用于仿真内存分配工作,另一个执行绪则用于追踪第一个执行绪的内存动作,两个执行绪之间透过号志同步。仿真内存动作的执行绪可从档案中读取记忆体操作,而记忆体操作的内容包括如下:
l 时间:操作及等待时间。
l 块数:分配内存的块数。
l 大小:内存块的大小。
l 操作:包括保留 (reserve)、提交 (commit)、释放 (release)、回收 (decommit) 一个区域。可以将这些操作编号存放于档案中。
l 保留 (reserve):指保留处理程序的虚拟地址空间,而不分配实体储存空间。
l 提交 (commit):在内存中分配实体储存空间,从高地址往低地址扫描 (MEM-TOPDOWN)。
l 回收 (decommit):指释放物理内存空间,但在虚拟地址空间仍然保留。与提交 (commit) 相对应,即可以回收已经提交的内存块。
l 释放 (release): 指将实体储存和虚拟地址空间全部释放。与保留 (reserve) 相对应,即可以释放已经保留的内存块 (提交页和保留页的概念见实习目的) 。
l 取存权限:Windows CE .NET有12种取存控制权限,可以将这些权限编号存放于档案中。
追踪执行绪要将被追踪执行绪的页面中已使用的地址范围、物理内存总量,以及虚拟内存总量……等信息显示出来。
相关内容介绍
关于Windows CE虚拟内存的操作请参考MSDN的相关部分。
16.2.2 专案二:逻辑内存管理性能
项目任务
写一个具有两个执行绪的程序,一个仿真Win32堆积(heap)和堆积内部存储器块的分配与释放;另一个则监视前者的内存信息。需要记录堆积中各种内存相关的数据结构和统计信息,可设计一个算法,统计堆积内的已分配空间、可用空间、碎片空间 (碎片视为小于指定大小的空闲内存块,这个可以透过程序参数指定) 。程序的两个执行绪间需要有一定的同步,比如,统计堆积内存信息的过程中,不能有分配和回收内存的动作,建议使用临界区间以达到同步。分配与回收内存的动作应该为随机指派,不过要注意控制得比较“合理”,使得这个程序可以比较长时间地运行。
相关内容提示
关于Windows CE堆积的操作,请参考MSDN关于lmem组件的相关函式。
要进行分配算法和试验结果的对比,请参考第4章的内容。
16.3 储存系统项目
16.3.1 专案一:RAMDISK I/O
在前面的档案存取部分,以安装RamDisk为例,讲解了如何将FAT档案系统加载到Windows CE中去。本节将继续使用RamDisk完成以下实验任务:
l 了解RamDisk在对象空间 (Object Store) 中的物理结构;
l 如何直接对RamDisk进行I/O操作。
简介
这里所使用的RamDisk实际上是在对象空间中创造出的一个虚拟磁盘,因为该RamDisk使用FAT档案系统驱动程序,所以其物理结构与同样以FAT为档案系统驱动的实际储存装置没有差别。在进行I/O操作之前,首先必须对其进行格式化操作。格式化操作会在磁盘上预先保留的一些区域中,记录下重要信息,例如在磁盘的0号扇区 (Boot Sector,又称启动扇区),就属于这样的预留区域。
Boot Sector为磁盘的第一个扇区,包含MBR (Master Boot Record)、DPT (Disk Partition Table )和Boot Record ID三部分。以下为个别的内容:
l MBR占用Boot Sector的前446个字节 (0到0x1BD),用于存放系统的主开机程序。
l DPT占用64个字节 (0x1BE到0x1FD),记录了磁盘的基本分割区信息。磁盘分割区表内有四个分割区项,每项各占用16字节,分别记录了每个主分割区的信息。
l Boot Record ID占用两个字节 (0x1FE和0x1FF),对于合法开机区,其值等于0xAA55,这是用来判断开机区是否合法的标志。
一般来说,MBR起始处是一条跳跃指令 (Jump instruction)。这样安排的目的在于,系统启动时会先将磁盘的Boot Sector内容加载内存,并由其第一条指令开始执行。由于在启动阶段需要知道一些系统的基本参数,所以这些参数的值被放在该跳跃指令之后。而排在这些参数之后的就是该跳跃指令的目的地。这样一来,在系统开始执行启动扇区的程序时,会立即跳过这段系统参数区。MBR的结构如下所示:
A jump instruction to the Bootstrap program
Parameter sector
Bootstrap program
对于FAT档案系统来说,在参数区中储存着很多与其相关的信息,例如FAT表的个数、根目录所允许的最大表格项目、每个FAT表 所占用的扇区数、……等。
项目任务
编写程序从RamDisk中读入Boot Sector的参数内容,并对其值进行解析,最后显示在标准输出上。
相关内容提示
为了对RamDisk这块虚拟的磁盘装置进行I/O操作,首先需要打开其装置档案以获得相对应的装置句柄,这可以透过CreateFile来实作。
接下来读出Boot Sector的内容有两种方法,一种可由SetFilePointer和ReadFile来完成;另一种可透过DeviceIoControl直接实作,其参数dwIoControlCode为IOCTL_DISK_READ,lpInBuffer指向一个SG_REQ结构。
最后要完成的工作就是对刚刚读到的参数信息进行解析,这里可能需要读者做一些数据整理的工作。
16.3.2 专案二:档案系统与目录结构
档案系统的功用是将磁盘上散乱的磁盘区块,组织成档案的形式,以方便使用者的使用,即在使用者观点下的磁盘操作,是以档案为单位进行的。透过本次实验,读者将了解以下内容:
l FAT档案系统如何组织磁盘结构。
l 如何实作基本的目录操作。
FAT档案系统是Microsoft早期为MS-DOS开发的一套档案系统,随着磁盘容量的不断增大,FAT为满足其需要而不断发展,由早期的FAT12、FAT16,演变为今天的FAT32。FAT档案系统的磁盘结构如图16-1所示。使用FAT作为档案系统的磁盘,均被划分为以下几个部分:
l 保留区域。
l FAT,即档案分配表。
l 根目录。
l 用于储存档案的空间。
图16-1 FAT档案系统的磁盘结构
通常磁盘上会同时存放两份FAT表,以便在系统当机时进行恢复。在FAT表中,每个表格项目都对应磁盘上的一个扇区。在FAT档案系统中,档案分配的空间是以链接串行 (linked list) 结构表示。通常一个档案由一组扇区构成,该档案的起始扇区对应的FAT表格项目指向该档案的第二个扇区,而第二个扇区对应的FAT表格项目则指向该档案的第三个扇区,……依此类推。而该档案的最后一个扇区对应的FAT表格项目,其内容以EOF的结束符号。
随着磁盘容量的不断增大,FAT档案系统开始以簇 (Cluster) 来代替扇区的概念。簇是一组连续的扇区集合,可以将其看作是一个虚拟的扇区。在现代的FAT档案系统中,每个FAT表格项目不再对应一个寻址扇区,而是对应一个簇。透过簇的概念,便可以使用FAT档案系统来管理容量更大的磁盘。
档案的所有信息都保存在目录中,使用者可以透过文件名称在目录中寻找该档案的相关信息。这种档案寻找是透过在目录列表中,对目录项目进行线性搜寻完成的。通常每个目录项目都包含该档案的文件名称,以及其在磁盘上的储存位置,另外还包括档案大小以及一些时间信息。FAT12档案系统的目录项目结构如表16-1所示。
表16-1 FAT12档案系统的目录项目结构
偏移 | 长度 | 内容 |
0x00 | 8 | 文件名称 |
0x08 | 3 | 副文件名称 |
0x0B | 1 | 属性 |
0x 0C | 10 | 保留区碱 |
0x16 | 2 | 时间 |
0x18 | 2 | 日期 |
0x 1A | 2 | 真实簇号 |
0x 1C | 4 | 文件大小 |
项目任务
l 编写一个打印目录列表的函式 (就像MS-DOS中的dir或UNIX中的ls命令)。
l 编写一个函式可以由目前目录返回上一层目录,或进入一个子目录。
l 编写一个用来从目前目录中删除一个档案的函式。
l 编写一个函式完成拷贝档案的功能。
16.4 驱动程序专案
项目任务
参考第14章的原始码,编写一个核心驱动程序,当某个应用程序读取这个装置的时候,可以读取到连续的随机数或者是0,装置可以通过IoControl函式配置成回传随机数或者回传0。
修改上述驱动程序,使得它可以被多个处理程序同时使用,做到某次产生的随机值不会被读多次,也不会有一个随机值产生了,但还没有读取就被丢弃。透过IoControl函式对驱动程序的设置也要考虑同样的问题。读者可以自己设计存取策略,例如提供排他的存取模式。修改前面的驱动程序,实现管道:一个应用程序可以向这个设备一个字节一个字节地写入数据,而另一个程序则可以从设备里按顺序读取前面的程序写入的每一个字节,装置应该设有一个小型缓冲区,当缓冲区满了,写入端应该停滞,而程序试图读具有空缓冲的该种装置时也会被停滞。
相关内容提示
关于驱动程序的撰写请参考第14章。
关于档案串流接口的信息请参考MSDN的相关内容。
16.5 图形系统项目
背景知识介绍
在Windows CE中,显示驱动程序属于独立驱动程序,它们可以被其父处理程序(GWE子系统模块或设备管理器)直接加载和呼叫。
Windows CE的大部分显示驱动程序使用一套称为图形原语引擎(GPE,Graphics Primitive Engine)的C++类别,将其作为基础类别,用来产生显示器硬件的显示驱动程序。使用GPE类别可以节省大量的开发和侦错时间。Platform Builder 以二进制的形式提供了GPE库, 位于[CEROOT]/Public/Common/OAK/Lib目录下。
Windows CE的显示驱动程序通常使用分层的体系结构来编写:微软提供的GPE库作为显示驱动程序的模型装置驱动程序(MDD,model device driver)层,负责所有的精简绘图操作;OEM或独立硬件厂商负责撰写硬件特定的原始码,这些原始码对应于显示驱动程序分层体系结构中依赖平台的驱动程序(PDD,platform-dependent driver)层。
Platform Builder中提供了包括S3 Trio64、CT6555X等在内,多个显示驱动程序的样本,代表了各种各样的显示器硬件装置。这些显示驱动程序的样本均利用GPE来提供精简的绘图操作功能。显示驱动程序的开发人员可以通过修改这些样本,从而用最少的时间和精力来建立显示驱动程序。显示驱动程序的样本位于[CEROOT]/Public/Common/OAK/Drivers/Display/Samples目录下。
项目任务
显示驱动程序的大部分工作是通过少数的基本操作来完成,因此如果能够加快这些操作的速度,就能大幅度的改善整个显示驱动程序的性能。最基本的操作是位块传送(bit block transfers,blits)和画线。从主要储存媒体到显存的任何像素矩形块传送操作都是blits,这些操作的例子包括显示图标、显示光标以及画颜色填充的矩形。由于加速的目的,画线操作被局限于画直线。
这些类型的加速既可以用硬件也可以用软件来实现。位块传送和画线的硬件加速通常很快,但并非对所有的显示器硬件都可行。然而,即使显示器不提供硬件加速功能,也可以透过软件来得到比GPE类别提供的精简情况要好的性能。
对于位块传送,显示驱动程序可以使用三个层次的处理:由GPE提供的位块传送模拟、由模拟库或程序员自行定义的原始码提供的软件加速仿真、以及显示装置硬件支持的硬件加速。显示驱动程序将位块传送转给GPE作为替代,显示驱动程序可以将位块传送转给仿真库或直接给硬件。
S3 Trio64驱动程序显示了如何支持硬件加速和使用软件仿真,该驱动程序位于[CEROOT]/Public/Common/OAK/Drivers/Display/Samples/CEPC/S3Trio64目录下。
下面的原始码取自S3 Trio64驱动程序,它说明了当GDI呼叫驱动程序的BltPrepare函式时如何开始位块传送处理。驱动程序初始化位块传送的参数,并决定使用哪个函式来完成位块传送,下面的原始码说明了初始化过程。
SCODE S3Trio64::BltPrepare(
GPEBltParms *pBltParms )
{
. . . . . . / /此处是侦错信息和可选的定时参数
pBltParms->pBlt = EmulatedBlt;
在初始化原始码的后面,显示驱动程序可以包含软件或硬件的加速原始码,样本驱动程序可以在编辑时包含或删除这些原始码。ENABLE_ACCELERATION指定要包含硬件加速原始码,ENABLE_EMULATION指定要包含软件加速的仿真原始码。下面的原始码取自S3 Trio64驱动程序,它说明了驱动程序如何评估栅格操作(raster operation,ROP)原始码,并在可能的情况下将位块传送指定给支持的硬件加速。
#ifdef ENABLE_ACCELERATION
#define FUNCNAME(basename)(SCODE(GPE::*)(struct GPEBltParms * )) Emulator::Emulated##basename
if ( pBltParms->pDst->InVideoMemory() ) {
switch( pBltParms->rop4 )
{
case 0x0000: // BLACKNESS
DEBUGMSG(GPE_ZONE_BLT_LO,(TEXT("S3Trio64::Blt - BLACKNESS/r/n")));
SelectSolidColor( 0 );
pBltParms->pBlt = (SCODE (GPE::*)(struct GPEBltParms *))AcceleratedFillRect;
break;
......
阅读并修改S3 Trio64驱动程序的原始码,测试显示驱动程序使用GPE精简仿真处理、软件加速仿真和硬件加速时的性能。
Platform Builder包含一个样本应用程序VideoApp,表示驱动程序的开发人员可利用对显示驱动程序进行测试。VideoApp透过画直线、折线、矩形、显示文字、光标等操作测试显示驱动程序的功能, 证明其可正确执行一组通用的图形操作, 该程序位于[CEROOT]/Public/Common/OAK/Drivers/Display/Samples/CEPC/Test目录。
Platform Builder包含一个样本测量工具DispPerf,显示驱动程序的开发人员可以利用它来测量显示驱动程序的性能。DispPerf建立一张表格,针对被测量的每种R O P原始码(即由G P E仿真处理、由软件仿真库处理、和由硬件加速处理),分别列出频率、花费的时间(单位:微秒)和平均花费的时间(微秒)。DispPerf原始码位于[CEROOT]/Public/Common/OAK/Drivers/Display/DispPerf目录。
专安步骤
1) 为实验项目建立目录。
2) 将样本驱动程序目录(如S3 Trio64)中的档案复制到自己的项目中。
3) 修改config.cpp文件,将显示装置定位在线性帧缓冲区模式(这是因为GPE要求显示器硬件使用线性帧缓冲区,即显示器内存必须存在于连续的内存区域)。
4) 针对由GPE精简仿真处理的、由软件仿真库处理的和由硬件加速处理的三种ROP原始码,分别修改显示驱动程序。
5) 利用VideoApp测试每种情况驱动程序的功能。
6) 利用DispPerf测试每种情况驱动程序的性能。
有关GPE类和仿真库的信息,请参考Windows CE的在线说明文件。