WinCE 编程实验(第六章 装置管理)

本文深入探讨了Windows CE操作系统中的装置管理机制,包括其特点、体系结构和关键组件的实现原理。重点分析了装置管理器、PnP管理器、电源管理器的功能与运作流程,并通过源代码解读了核心组件的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

第六章  装置管理

 

Windows CE作为一个嵌入式应用的操作系统,在装置管理(device management)方面亦具有一般嵌入式操作系统装置所支持的特点:装置管理概念简单高效;支持多种不同类别、差异极大的装置;支持随插即用(Plug and Play)的管理模式和装置省电控制;处理系统的输入输出亦具有实时响应能力。

本章主要分析Windows CE.NET操作系统的装置管理部分之系统概念、体系构成以及相关的实作程序代码,这个分析基于Microsoft Shared Source LicenseCE原始程序(source code)。主要涉及的原始程序位于Windows CE.NET根原始程序代码树(root source tree)中的 [CEROOT]/Private/Winceos/Coreos/Device目录下,主要的原始程序档案和作用在表6.1中说明。

 

6.1 装置管理部分相关的主要原始程序行表

proxy.h

要使用的系统API相关接口定义

device.h

装置管理以及装置驱动程序部分主要呼叫接口和数据结构定义

device.c

CE装置以及驱动程序管理部分的主要对外接口例程实作

iomgr.c

装置I/O资源管理的实作

pnp.c

随插即用管理器的实作

pwrmgr.h

电源管理器的主要数据结构和呼叫接口定义

pwrmgr.c

电源管理器的实作

devloadp.h

装置驱动程序运行控制接口定义

devload.c

装置驱动程序运行控制实作

celogdev.h

装置管理部分的系统日志和除错(debug)支持

 

6.1      Windows CE的装置管理模式

 

6.1.1      Windows CE装置管理部分的体系构成

一般操作系统的装置管理都采用分层式(layered)的管理模式(如图6.1);

6.1 装置管理的分层模式

 

 

CE也不例外,和图6.1相比,CE在模块组成和划分方面更加具体而实用。Windows CE和其它常见系统主要的区别在于与装置无关的系统软件这一层,Windows CE将这一层划分为四个独立的部分(在这一点上Windows CEWindows 2000/XP颇为相似):装置管理器、PnPPlug and Play)管理器、电源管理器以及支持和管理例程库构成。从概念上讲,Windows CE的装置管理和档案是密不可分的,二者共享同一个命名空间,基本上可以认为Windows CE中装置也是档案,这一点和传统的UNIX系统很相似;但同时,使用者存取档案以及装置模块时使用不完全相同的呼叫入口,装置档案在普通使用者接口上也不可见,这是和Windows 2000/XP更加类似的地方。简而言之,Windows CE装置管理部分采取了类似Windows 2000/XP的结构,但却大大的简化了中断处理、I/O存取以及内部管理的机制,例如,在Windows CE中就没有Windows 2000/XP中的软件中断和ISRInterrupt Service Routine,中断服务例程)DPCDeferred Procedure Call,延迟过程调用)及APCAsynchronous Procedure Call,异步过程调用)的复杂机制,这种简化使得系统在基本功能健全的前提下保持了较大的简洁性和高效性(这在支持实时、嵌入式应用时尤为重要)。

l          装置管理器:包括装置驱动模型的实作和I/O资源的管理。前者定义了Windows CE的装置驱动机制和一个装置驱动程序的有序工作框架,在这个框架之下,装置管理器则可以在不知道驱动程序具体细节的情况下透过注册机制存取并呼叫一个驱动程序之实体。后者则实作了I/O资源的分配、查询等管理任务。

l          PnP管理器:随插即用,顾名思义,就是当系统中硬设备发生变化时,系统可以自动配置使用新的装置。比如使用者使用USB接口把一个扩充记忆卡插在自己的数字相机上,数字相机上的操作系统立刻识别并配置了这一块扩充记忆卡,并且允许使用者把已经照好的照片传送到这块记忆卡上。CERTIFIED MAIL .NET提供了对这种特性的支持(Windows CEPnP的支持甚至好过Windows 95或者Windows NT 4),当然,PnP仅仅靠操作系统是很难实作的,一般还需要硬件的额外支持。

l          电源管理器:类似PnP,电源管理也需要硬件支持。Windows CE电源管理器管理系统的能源分配策略,它决定系统的能量消耗状态变化,当满足一定条件时,电源管理器呼叫具体装置的驱动程序以实施应变策略。

l          支持和管理例程库:一些内部支撑例程。

Windows CE的构成如图6.2所示。

 

 

6.2 Windows CE装置管理部分的结构示意

6.1.2      注册表

Windows CE并不真正的包含一个如同Windows 2000/XP那样完全的、复杂的注册表(Registry)数据库,但是类似Windows 2000/XPWindows CE的注册表也具有相似的重要作用,它记录着系统中若干重要的配置信息,例如本章所涉及到的装置之配置信息。

Windows CE透过共享函式库RegEnum.dll来实作注册表,为使用者提供由windows.h定义的存取注册表的Win32 API呼叫接口。Windows CE的注册表可以透过系统堆栈实作,用这种方法时,若实际系统中OEM厂商没有实作注册表备份函式,则系统断电时注册表信息将会遗失。

Windows CE注册表具有层次化的组织结构,由若干个机码(key)以及值(value)组成,类似档案系统;机码相当于目录,而值相当于档案。整个Windows CE注册表形成一个有三个根节点(root)的森林(forest),每个根机码(root key)下储存一类配置信息,如表6.2

 

6.2 Windows CE注册表的根机码

HKEY_LOCAL_MACHINE

硬件和驱动程序配置信息

HKEY_CURRENT_USER

使用者配置信息

HKEY_CLASSES_ROOT

OLE和档案类型配置信息

 

值就是注册表储存的数据,可以是字符串或者一个整数。在Windows CE开发中应尽可能的保持注册表的值,表6.3说明了一些对空间的限制。

 

6.3 Windows CE注册表的一些限制

机码或者值的名字

255字符

资料大小

4 KB

机码嵌套层次

最多16

 

6.1.3      HAL以及OAL支持

 

HALHardware Abstraction Layer,硬件抽象层)和OALOEM Adaptation LayerOEM适配层)都是Windows CE为了实作支持多种体系结构和平台而用来隔离操作系统和硬件的手段。HAL提供一般硬件特性的封装、隐藏,包括特定的硬件抽象机制(例如内存映像保护)和驱动程序机制(例如I/O地址映像),它使得系统和应用程序可以顺利的存取硬件特性而不必知道硬件的工作细节。一个典型的HAL对象存在于Windows CE对显示装置的支持上,系统提供DDHAL用来映像显示芯片提供的图形加速能力,当使用者试图绘制一个椭圆时,HAL会产生一个对显示驱动程序中绘制椭圆的功能呼叫,驱动程序则直接驱动硬件完成功能。HAL通常还包含HELHardware Emulation Layer,硬件仿真层),当硬件不具有某个特性的时候,HAL会自动呼叫HEL用软件仿真该特性。

OALHAL目的略有不同,它主要为操作系统的OEM开发者提供操作系统和具体硬件平台的隔离手段,试想两个厂商生产的PDA的硬件组织结构相差可能会很大。OAL主要实作硬件平台初始化、各种中断服务例程、电源管理、实时时钟、定时器、除错支持、中断开关等,这些为Windows CE的移植提供了很大的便利性,使得这个操作系统适合于嵌入式的应用。

 

6.1.4      Windows CE的装置驱动集

 

Windows CE支持多种类型的装置驱动程序,主要类别包括:


Audio Drivers

Battery Drivers

Block Drivers

Bluetooth HCI Transport Driver

Device Power Management

Direct3D Device Driver Interface

DirectDraw Display Drivers

Display Drivers

DVD-Video Renderer

IEEE 1394 Drivers

Keyboard Drivers

Network Drivers

Notification LED Drivers

Parallel Port Drivers

PC Card Drivers

Printer Drivers

Serial Port Drivers

Smart Card Drivers

Stream Interface Drivers

Touch Screen Drivers

USB Drivers


更详细的信息可以参考http://go.microsoft.com/fwlink/?linkid=5159

以下4节将结合根原始程序对Windows CE的各个主要部分做出说明。

 

6.2      装置管理器

 

装置管理器是Windows CE装置管理的核心组件机构,它主要负责追踪、维护系统的装置信息并对装置资源进行调配。它的主要实作程序代码分布在device.ciomgr.c

 

6.2.1      装置信息管理

 

装置配置信息

 

装置配置信息储存于Windows CE的注册表中,实作程序代码透过在windows.h中定义的注册表公用函式存取这些数据,主要使用到的函式举例如表6.4所示

 

6.4 部分使用到的注册表函式说明

RegOpenKeyEx

打开一个注册表机码

RegQueryValueEx

查询指定注册表项的值

RegCreateKeyEx

产生一个新的注册表机码

RegSetValueEx

设置指定的注册表项的值

RegDeleteKey

删除指定的注册表机码

RegCloseKey

关闭指定的注册表机码

 

命名

 

因为Windows CE应用程序将透过特定的档案系统接口存取装置(包含通常所说的串流式(streaming)档案的接口),所以它的装置名称与档案系统的文件名称需要共享命名空间。在Windows CE中,装置命名包含如下的规定:

1.        装置文件名要精确包含3个大写字母,一个数字,和一个冒号(:),这样档案系统才会把文件名识别为特殊装置档案。例如:COM1:PGR7:,和GPS0:是有效的装置文件名称,但是MODEM1:COM27:LPT1则不是。

2.        装置文件名称一般包含前缀,如果前缀的值不存在,那么在相关注册表机码下必须要有InitDeinit子机码(sub key)。否则,前缀包含的3个大写字母即标识了与特定名称装置相关联的档案串流存取界面(file stream interface)。前缀保存在注册表该装置子机码下名叫Prefix的值中,这个注册表值一般由装置安装程序建立。当实作串流接口的时候需要指定3字母的前缀,它能够是任何的3字母排列,不过当这个接口需要实作某个类别的装置存取时,应该用一般的前缀。例如,串行端口的驱动诸如调制解调器,能够用普通前缀COM。驱动程序能够用不同的索引把自己和其它用相同前缀的驱动程序区分开来。前缀有两种用法:第一,前缀标识了全部能透过串流接口存取的装置文件名。第二,前缀告诉装置管理器某次存取中串流接口所期望的呼叫入口。例如,当为PC卡呼叫器编写一个驱动程序时,能选择PGR作为前缀,它指定了按照串流接口方式存取装置时应用层呼叫入口的名字,例如PGR_OpenPGR_IOControl等。

3.        装置文件名索引跟在前缀后的一位,索引区分了串流接口管理的装置。在预设的情况下,索引可以为191对应到第一个装置的文件名,以此类推。第十个装置的文件名,则用0作为索引。当系统需要从不是1开始建立装置文件名的索引时,可以在注册表中用相对应装置机码下的Index值指定。如果不让装置管理器动态分配索引而使用特定索引时,系统预设该驱动程序将只支持一个装置。而进一步,如果要指定索引,同时又需要多于一个的装置文件名,可以呼叫ActivateDeviceEX函式注册新加的装置文件名;或者,当系统安装装置时,建立所需要的新注册机码,使得每个机码有不同的索引。

 

装置管理器使用的注册表名目

 

装置管理器在注册表中维护Active机码,而且只有在Init函式中才能修改该机码。装置管理提供以下装载和卸载的API函式:

l        ActivateDeviceEx

l        DeactivateDevice

Windows CE中装置驱动程序必须提供GUIDs用来标识对外的接口。装置管理器在HKEY_LOCAL_MACHINE/Drivers/RootKey机码中寻找值以决定驱动程序装载过程。RootKey预设的值是Drivers,但是通常被设为Drivers/BuiltIn

装置管理使用了HKEY_LOCAL/Drivers的子机码ActiveBuiltIn

1.        Active

HKEY_LOCAL_MACHINE/Drivers/Active机码包含了子机码,它能追踪被装置管理装载的当前活动的驱动程序。装置驱动程序不能修改注册表中由装置管理器所设定的值,一旦装置驱动程序被装载,它能在Active机码中加入新值。当装置驱动被卸载的时候,装置管理器会把相关的整个Active机码及内部的任何值删除。

当装置驱动程序被装载,装置管理器把装置驱动程序的路径传给它的Active机码作为装置驱动程序Init函式中的dwContext参数。表6.5列出了一些Active机码的可能的值。

 

6.5 Active机码的一些可用值

Name

装置名称

Key

注册装置的路径

PnpId

随插即用字符串。只为PC卡驱动程序准备

Sckt

当前插槽和PC卡的函式对。只为PC卡驱动程序准备

 

2.        BuiltIn

在引导期间,装置管理器呼叫由HKEY_LOCAL_MACHINE/Drivers/RootKey的注册机码所指定的ActivateDeviceEx。该机码预设由HKEY_LOCAL_MACHINE/Drivers/RootKey和注册表函式中的预设驱动程序装载设置,这个行为导致其它装置驱动程序最终被正确地装载。

 

核心数据结构

 

Windows CE使用简单的串行来追踪装置情况,有四锺串行节点:_fsd_tfsdev_tfsopendev_tfscandidatedev_t,分别说明如下(程序代码6.1):

 

程序代码6.1 装置管理部分重要的核心数据结构及全域变量

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/device.h

typedef struct _fsd_t {                           // 追踪档案系统驱动程序的节点类型

    struct _fsd_t * next;                       // 连结下一个节点的指标

    int       cFSDDevices;             // 档案驱动的装置标号

    HINSTANCE hFSDLib;                // 函式库实体句柄

    pInitFn   pfnFSDInit;                            // 初始化函式指标

    pDeinitFn pfnFSDDeinit;             // 解构函式指标

    pInitExFn pfnFSDInitEx;               // 扩展的初始化函式指标

    pDeinitFn pfnFSDDeinitEx;                  // 扩展的解构函式指标

    HINSTANCE hFSDLibEx;            // 扩展的函式库实体句柄

} fsd_t, * pfsd_t;

 

typedef struct fsdev_t {                         // 追踪一般装置驱动程序的节点类型

    LIST_ENTRY list;                          // 双向串行连接节点

    DWORD index;                              // 索引

    DWORD dwData;                          // 装置信息

    DWORD dwLoadOrder;               // 加载的顺序

    pInitFn fnInit;                                  // 以下为档案串流界面函式指针

    pDeinitFn fnDeinit;

    pOpenFn fnOpen;

    pCloseFn fnClose;

    pReadFn fnRead;

    pWriteFn fnWrite;

    pSeekFn fnSeek;

    pControlFn fnControl;

    pPowerupFn fnPowerup;            // 电源管理函式

    pPowerdnFn fnPowerdn;

    HINSTANCE hLib;                        // 函式库实体句柄

    DWORD dwId;                               // 驱动标识

    BOOL PwrOn;                                // 能源控制状态

    WCHAR type[3];                   // 类型名(前缀)

    WORD wFlags;                             // 驱动程序状态位模式旗标

    DWORD dwFSDData;

    DWORD dwRefCnt;                      // 驱动程序参考(reference)计数

    pfsd_t pfsd;                                    // 关联的档案装置驱动程序

} fsdev_t;

 

typedef struct fsopendev_t {                // 档案串流接口存取打开的驱动节点类型

         struct fsopendev_t *nextptr;       // 串行连接的下一个节点

         DWORD dwOpenData;

         fsdev_t *lpDev;                             // 打开的装置描述节点

         DWORD *lpdwDevRefCnt;        // 指向装置引用计数的指针

         DWORD dwOpenRefCnt;         // 开启之引用计数

         HANDLE KHandle;                     // 本结构所对应的核心资源句柄

         HPROCESS hProc;                     // 拥有这个资源的处理程序

} fsopendev_t;

 

typedef struct fscandidatedev_t {       // 追踪正在加载的驱动程序的节点类型

    LIST_ENTRY list;                          // 双串行的连接节点

    WCHAR szPrefix[3];                      // 装置名前缀

    DWORD dwPrefixLen;                 // 前缀的字符数

    DWORD dwIndex;                         // 装置索引

} fscandidatedev_t;

 

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/device.c

CRITICAL_SECTION g_devcs;          //装置管理关机码数据临界区旗标

LIST_ENTRY g_DevChain;                 //常规状态的装置列表

LIST_ENTRY g_DyingDevs;               //死亡状态的装置列表

LIST_ENTRY g_CandidateDevs;      //正在加载的装置列表

fsopendev_t *g_lpOpenDevs;            //常规状态的已开启装置列表

fsopendev_t *g_lpDyingOpens;                  //死亡状态的已开启装置列表

fsd_t *g_lpFSDChain;                          //档案系统驱动程序列表

 

系统透过这些数据结构定义了如程序代码6.1末尾部分的变量,可以看出来,系统实作装置管理共建立了6个追踪串行,追踪不同的装置对象和装置状态。每个装置节点关联一个档案节点,而每个打开的装置节点则包含一个装置节点的引用。六条串行中g_DevChain是属于较为基本性的,携带了装置描述的主要信息。它和g_DyingDevs实际的节点类型均为fsdev_t。要说明的是,这两条串行,根原始程序的定义将其类型定为LIST_ENTRY,实际上fsdev_t的起始部分正是一个这样的变量,于是类似C++中继承的实作形式,当一个指向fsdev_t结构的指针P1被赋值给一个指向LIST_ENTRY结构的指针P2时,P2指向的实际内容将和P1的第一个变量list(还记得吗,那是一个LIST_ENTRY)内容相同,那么对于LIST_ENTRY类型的任何封装好的串行操作对fsdev_t仍然有效。同样的情况也发生在g_CandidateDevs串行中。透过这种方法,程序的确在实作上减小了冗余,重复使用了更多的程序代码,另一方面,这种实作也允许两种前面提到的节点相互串行结在一起。

 

核心装置信息组织

 

Windows CE透过几条串行追踪装置以及驱动程序的信息,并在注册表里保存装置的配置和状态等。下面以RegisterDeviceEx函式为例来窥视一下装置信息的组织。这个函式的功能是向装置管理器注册一个新的装置,程序代码6.2列出了这个函式的主要程序代码,为了便于阅读,原始程序代码中的主要除错用程序代码已被删除,并补充了一些新的批注。

RegisterDeviceEx所完成的全部工作就是建构新的装置节点,并根据一定的规则检查并添加到装置表里去。整个过程分成了几个步骤:

1.        注册新装置之前先等待档案系统任何去除装置操作的完成,这主要是解决外部储存卡命名的问题

2.        分配新的装置描述节点(fsdev_t类型)

3.        根据参数从函式库(DLL或者DRV)中装载驱动程序

4.        获取装载完成的驱动程序各个导出呼叫接口的地址并填入描述节点的相应字段

5.        检查装置描述节点的唯一性(需要存取装置描述串行,有临界保护)

6.        设置初始装置状态旗标

7.        把新的节点插入装置描述串行(有临界保护)

8.        错误处理

注 1.     这段程序结构功能都比较简单,但是对错误处理有较高的要求,这段程序的错误处理并没有按照结构化方法进行编写,而使用了goto语句和结构化异常处理(__try……__except,这是Windows CE使用的C/C++语言的扩展)语句共同配合来完成的。这是一种常用的实作技巧,goto语句大大的简化了程序的逻辑结构,使它变得清晰易读。这错误处理中有一个重要的变量retval用以追踪错误的类型。

注 2.     程序有几处使用了临界区(Critical Section),目的就是保护系统装置表的一致性。要简要说明的是程序并没有在整个函式中都使用临界区,仅在必须用的地方用了,好处很明显,使核心系统的平行性尽量的好,可以提高内核被抢占的能力,这对系统的实时性能有较大影响。

 

程序代码6.2 RegisterDeviceEx函式程序代码

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/device.c

HANDLE RegisterDeviceEx(LPCWSTR lpszType, DWORD dwIndex, LPCWSTR lpszLib, DWORD dwInfo, DWORD dwLoadOrder, DWORD dwFlags, DWORD dwId, LPVOID lpvParam)

{

  HANDLE hDev = 0;

  fsdev_t *lpdev = 0, *lpTrav;

  DWORD retval = 0;

 

  retval = WaitForSingleObject(g_hCleanDoneEvt, 5000);

  //等待先前的改变结束,逾时时间为5

  ASSERT(retval != WAIT_TIMEOUT);

  retval = ERROR_SUCCESS;  // 如果后面没有对retval的修改则为成功

 

  if (dwIndex > 9) {//检查索引值是否合法

    retval = ERROR_INVALID_PARAMETER;

    goto errret;

  }

  //配置一个fsdev_t结构,用于准备新的装置描述节点

  if (!(lpdev = LocalAlloc(LPTR,sizeof(fsdev_t)))) {

    retval = ERROR_OUTOFMEMORY;

    goto errret;

  }

 

  __try {

    memset(lpdev, 0, sizeof(fsdev_t));//清空新配置的结构内容,并设置新参数

    if (lpszType != NULL)

      memcpy(lpdev->type,lpszType,sizeof(lpdev->type));

    lpdev->index = dwIndex;

    lpdev->hLib =

      (dwFlags & DEVFLAGS_LOADLIBRARY) ? LoadLibrary(lpszLib) : LoadDriver(lpszLib);//根据驱动程序类型装载可执行程序代码,并设置函式库资源句柄

    if (!lpdev->hLib) {

      retval = ERROR_FILE_NOT_FOUND;

    } else {//驱动程序码加载成功,设置各个呼叫接口的函式指针

      lpdev->fnInit = (pInitFn)FS_GetProcAddr(lpdev->type,L"Init",lpdev->hLib);

      lpdev->fnDeinit = (pDeinitFn)FS_GetProcAddr(lpdev->type,L"Deinit",lpdev->hLib);

      lpdev->fnOpen = (pOpenFn)FS_GetProcAddr(lpdev->type,L"Open",lpdev->hLib);

      lpdev->fnClose = (pCloseFn)FS_GetProcAddr(lpdev->type,L"Close",lpdev->hLib);

      lpdev->fnRead = (pReadFn)FS_GetProcAddr(lpdev->type,L"Read",lpdev->hLib);

      lpdev->fnWrite = (pWriteFn)FS_GetProcAddr(lpdev->type,L"Write",lpdev->hLib);

      lpdev->fnSeek = (pSeekFn)FS_GetProcAddr(lpdev->type,L"Seek",lpdev->hLib);

      lpdev->fnControl = (pControlFn)FS_GetProcAddr(lpdev->type,L"IOControl",lpdev->hLib);

      lpdev->fnPowerup = (pPowerupFn)FS_GetProcAddr(lpdev->type,L"PowerUp",lpdev->hLib);

      lpdev->fnPowerdn = (pPowerupFn)FS_GetProcAddr(lpdev->type,L"PowerDown",lpdev->hLib);

 

      //检查这些函式接口的合法性

      if (!(lpdev->fnInit && lpdev->fnDeinit) ||

        lpszType && (!lpdev->fnOpen || !lpdev->fnClose ||

               (!lpdev->fnRead && !lpdev->fnWrite &&

               !lpdev->fnSeek && !lpdev->fnControl))) {

        retval = ERROR_INVALID_FUNCTION;

      }

      //对部分接口没有定义处理例程的情况下设置预设处理例程

      if (!lpdev->fnOpen) lpdev->fnOpen = (pOpenFn) FS_NotSupportedBool;

      if (!lpdev->fnClose) lpdev->fnClose = (pCloseFn) FS_NotSupportedBool;

      if (!lpdev->fnControl) lpdev->fnControl = (pControlFn) FS_NotSupportedBool;

      if (!lpdev->fnRead) lpdev->fnRead = (pReadFn) FS_NotSupportedDword;

      if (!lpdev->fnWrite) lpdev->fnWrite = (pWriteFn) FS_NotSupportedDword;

      if (!lpdev->fnSeek) lpdev->fnSeek = (pSeekFn) FS_NotSupportedDword;

    }

  } __except (EXCEPTION_EXECUTE_HANDLER) {

    retval = ERROR_INVALID_PARAMETER;

  }

 

  if (retval) {//若前面的步骤不成功则转到错误处理(本函式的结尾)

    goto errret;

  }

 

  //非串流接口的驱动程序可以加载多次,所以不用检查唯一性,否则就需要检查

  if (lpdev->type[0] != L'/0') {

    //检查装置唯一性需要存取装置描述串行,所以进入临界区

    EnterCriticalSection (&g_devcs);

    __try {

      // 顺着串行逐项检查是否新节点和已有的节点有重复,从寻找结束的判别条件来看,

// 可以知道这个装置串行是循环的

      for (lpTrav = (fsdev_t *)g_DevChain.Flink;

         lpTrav != (fsdev_t *)&g_DevChain;

         lpTrav = (fsdev_t *)(lpTrav->list.Flink)) {

        if (lpTrav->type[0] != L'/0') {

          if (!memcmp(lpTrav->type,lpdev->type,sizeof(lpdev->type)) &&

            (lpTrav->index == lpdev->index)) {

            retval = ERROR_DEVICE_IN_USE;

            break;

          }

        }

      }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

      retval = ERROR_INVALID_PARAMETER;

    }

    LeaveCriticalSection(&g_devcs);

    if (retval) {

      goto errret;

    }

  }

 

  __try {

    ENTER_INSTRUM {//尝试初始化装置

      lpdev->dwData = lpdev->fnInit(dwInfo,lpvParam);

    } EXIT_INSTRUM_INIT;

 

    if (!(lpdev->dwData)) {

      retval = ERROR_OPEN_FAILED;

    } else {

      // 初始化成功,则设置电源状态为开启,并设置其它参数

      lpdev->PwrOn = TRUE;

      lpdev->dwLoadOrder = dwLoadOrder;

      lpdev->dwId = dwId;

    }

  } __except (EXCEPTION_EXECUTE_HANDLER) {

    retval = ERROR_INVALID_PARAMETER;

  }

  if (retval) {

    goto errret;

  }

  if (dwFlags & DEVFLAGS_UNLOAD) {

    // 不须装载驱动,则可以返回

    hDev = NULL;

    goto earlyret;

  }

 

  EnterCriticalSection(&g_devcs);//再次存取关机码数据结构,本次需要修改

  __try {

     // 根据LoadOrder插入欲新增的节点

     for (lpTrav = (fsdev_t *)g_DevChain.Flink;

       lpTrav != (fsdev_t *)&g_DevChain;

       lpTrav = (fsdev_t *)(lpTrav->list.Flink)) {

      if (lpTrav->dwLoadOrder >= dwLoadOrder) {

        InsertHeadList((PLIST_ENTRY)lpTrav, (PLIST_ENTRY)lpdev);

        break;

      }

    }

    if (lpTrav == (fsdev_t *)&g_DevChain) {

      // insert at the end

      InsertTailList(&g_DevChain, (PLIST_ENTRY)lpdev);

    }

    hDev = (HANDLE)lpdev;

 

  } __except (EXCEPTION_EXECUTE_HANDLER) {

    retval = ERROR_INVALID_PARAMETER;

  }

  LeaveCriticalSection(&g_devcs);

 

errret:

  // 错误处理,此处删除若干除错程序代码

  if (retval) {//未成功则释放使用的各种资源,并设置系统错误值

earlyret:

    SetLastError (retval);

    if (lpdev) {

      if (lpdev->hLib) {

        FreeLibrary(lpdev->hLib);

      }

      LocalFree (lpdev);

    }

  }

  return hDev;//返回装置句柄

}

 

其它函式的实作也是类似的,包括寻找、释放等操作,不再一一举例。原始程序代码包括比较详细的批注,读者在阅读请注意几个问题:

l        有一些函式,尤其涉及到释放资源时往往有等待的操作,包括等待特定资源、一个执行绪(Thread)结束等,请参考批注考虑为什么这些等待是必要的。

l        无用资源的释放,系统在哪些特定的地方(尤其注意错误、异常处理程序代码)释放其占用的哪些资源

l        临界区的使用,有些情况下进入或者退出临界区的呼叫会在循环中完成,检查程序代码执行时是否会多次进入或者退出同一临界区

当然,Device.c中还有大量函式不是用来组织这些装置信息的,同时,除了g_DevChain的另外几条串行目前还未涉及到,它们跟装置驱动程序以及使用者存取装置有极为密切的关系,这些内容将在装置驱动的部分加以介绍。

 

装置管理器的WinMain函式

device.c包括一个WinMain函式(需要使用TARGET_NT旗标,否则为wmain函式,这个函式实作很简单,此处不再赘述),它是device.exe的主控函式。透过这个函式可以看出Windows CE装置管理器的初始化和工作的架构,对于理解Windows CE的装置管理很有帮助。原始程序代码如程序代码6.3所示。

 

程序代码6.3 Device.cWinMain函式以及被呼叫之关机码函式

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/device.c

Int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nCmShow)

{

  if (IsAPIReady(SH_DEVMGR_APIS)) {//检查装置管理器是否已经准备好,否则不必加载

    return 0;

  }

 

  // 第一阶段:建构各种基本的数据结构

  g_BootPhase = 1;

  InitOOMSettings();                                      //初始化OOM 设置

  InitializeListHead(&g_DevChain); //建构几个装置追踪表

  InitializeListHead(&g_DyingDevs);

  InitializeListHead(&g_CandidateDevs);

  g_hCleanEvt = CreateEvent(0,0,0,0);

  g_hCleanDoneEvt = CreateEvent(0, 1, 1, 0); //重置相关事件

  //建构,并向系统注册装置管理API

  //注意,这里仍旧使用了APISet,它的相关函式并没有公开,开发时不推荐使用

  g_hDevApiHandle = CreateAPISet("WFLD", NUM_FDEV_APIS, FDevApiMethods, FDevApiSigs);

  RegisterAPISet(g_hDevApiHandle, SH_DEVMGR_APIS);

  g_hDevFileApiHandle = CreateAPISet("W32D", NUM_FAPIS, DevFileApiMethods, DevFileApiSigs);

  RegisterAPISet(g_hDevFileApiHandle, HT_FILE | REGISTER_APISET_TYPE);

  InitializeDeviceNotifications();                 //初始化装置通知机制

  InitializeCriticalSection(&g_devcs);        //初始化装置临界区

  //初始化装置管理器资源

  ResourceInitFromRegistry(TEXT("Drivers//Resources"));

  SetPowerOffHandler((FARPROC)FS_PowerAllDevices);//设置电源关闭处理函式

 

  //第二阶段:装载并初始化装置

  if (TwoPhases()) {//根据注册表的不同设置,有不同的装载方法

    HANDLE hEvent;

    // 从开机注册表(boot registry)装载装置并初始化

    DevloadInit();

    // 等待初始化完成

    hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, TEXT("SYSTEM/BootPhase2"));

    if (hEvent) {

      WaitForSingleObject(hEvent, INFINITE);

      CloseHandle(hEvent);

    }

    // 装载各种装置以及驱动程序

    g_BootPhase = 2;

    InitDevices();

    SignalStartedUsingReg();

  } else {//没有引导注册表机码

    g_BootPhase = 2;

    DevloadInit();

    SignalStarted(_wtol(lpCmdLine));

  }

  // 第三阶段

  g_BootPhase = 3;

  CELOG_DeviceFinished ();//记录日志

  while (1) {//循环以等待系统清除事件

    WaitForSingleObject(g_hCleanEvt, INFINITE);//等待系统清理(如关闭系统)事件

    // 处理自动清除的装置,和死亡状态的装置等

    ProcessAutoDeregisterDevs();

    ProcessDyingDevs();

    ProcessDyingOpens();

    //标记系统事件

    SetEvent(g_hCleanDoneEvt);

  }

  return 1;

}

VOID InitDevices(VOID)

{

    HKEY RootKey;

    DWORD status;

    DWORD ValType;

    DWORD ValLen;

    TCHAR RootKeyPath[REG_PATH_LEN];

    // 打开 HLM/Drivers key

    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, DEVLOAD_DRIVERS_KEY, 0, 0, &RootKey);

    if (status != ERROR_SUCCESS) {

        return;

    }

    // 寻找根机码的值,若没有则用default机码

    ValLen = sizeof(RootKeyPath);

    status = RegQueryValueEx(RootKey, DEVLOAD_ROOTKEY_VALNAME, NULL, &ValType, (PUCHAR)RootKeyPath, &ValLen);

    if (status != ERROR_SUCCESS) {               // 找到了根机码

        _tcscpy(RootKeyPath, DEVLOAD_DRIVERS_KEY);

    }

    RegCloseKey(RootKey);                                //关闭先前的根机码

    (void) ActivateDevice(RootKeyPath, 0);       //呼叫已注册的启动装置API函式

}   // InitDevices

 

void DevloadInit(void)

{

    // 因为没有活动(active)的装置,所以删除 HLM/Drivers/Active机码

    RegDeleteKey(HKEY_LOCAL_MACHINE, s_ActiveKey);

    v_NextDeviceNum = 1;

    v_hTapiDLL = NULL;

    v_hCoreDLL = NULL;

    g_bSystemInitd = FALSE;

    InitDevices();

}

 

6.2.2      I/O资源管理

 

I/O资源管理的任务

 

资源管理是装置管理的固有属性。资源管理追踪了在装置装载前注册表中初始化的可获得的系统资源。每个平台有唯一的IRQs集和可得到的I/O空间。这些资源全部在初始化时得到。IRQI/O空间资源一般总在OAL和注册表中预先分配或被总线驱动程序所请求,例如PCI总线驱动程序和PCMCIA总线驱动程序。内建在装置中,诸如ISA装置被系统所得知。那么装置需要的任何资源总不在开机时I/O资源管理提供的资源中。总线驱动程序,例如PCI总线驱动程序,I/O资源管理器在为找到的装置装载装置驱动程序的时候总会请求IRQI/O空间资源。同样的PC卡使用者端驱动程序要求的I/O资源和PCMCIA驱动程序也一样。当PC卡从系统中删去时PCMCIA驱动程序将释放资源。嵌入的和固定的IRQs应该映射到OAL中的SYSINTRs中而且不在可获得的资源之内。一些IRQs是共享的,典型的情况下那些IRQsPCI总线。

I/O资源管理器中定义两种资源IRQsI/O。注册机码是

l        HKEY_LOCAL_MACHINE/Drivers/Resources/IRQ

l        HKEY_LOCAL_MACHINE/Drivers/Resources/IO

这些机码提供I/O资源管理的初始状态。它们的子机码在表6.6中显示。

 

6.6 I/O资源管理的注册表

Identifier

DWORD:1

资源标识,在[CEROOT]/Public/Common/DDK/Inc/ resmgr.h中定义。

Minimum

DWORD:1

这种类型的最小有限资源。

Space

DWORD:16

资源空间里的资源数。

Ranges

"1, 3-5, 8"

用逗号分开的(?)初始的可获得的资源范围。预设是无。

Shared

"4"

用逗号分开的(?)初始的可获得的共享资源。预设是无。

注意 DWORD是作为16进位制常数说明的。

 

共享资源如果可得到,能在任何时候被请求。只有当资源低于或等于32时,才能设置共享资源。

 

重要数据结构

 

I/O资源管理在iomgr.c中实作。它提供了对I/O资源的两种管理模式:位图(bitmap)模式和稀疏表(sparse table)模式。相关数据结构如程序代码6.4

 

程序代码6.4  I/O资源管理的数据结构

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/iomgr.c

#define TABLE_THRESHOLD 32     //资源管理模式的门坎值

 

typedef struct {                                       //资源范围

    DWORD id;                                    //标识(资源起点地址)

    DWORD len;                                  //长度

} ResourceRange;

 

typedef struct SparseTableNode {    //稀疏表节点

    ResourceRange res;                            //资源范围

    struct SparseTableNode *pNext;//连结下一个节点的指标

} SparseTableNode;

 

typedef struct {                                       //稀疏表表头

    SparseTableNode *pFirst;

} SparseTable;

 

typedef struct {                                       //资源位图

    DWORD vb[1];                               //位向量

    DWORD vbs[1];                             //共享资源的位向量

} DenseTable;

 

typedef struct {                                       //资源表

    DWORD dwSize;                           //小于门坎值则用位图模式,可以设置共享,否则用稀疏模式

    union {

        SparseTable st;

        DenseTable  dt;

    };

} ResourceTable;

 

typedef struct ResourceDomain {     //资源域

    DWORD dwResId;                       //资源类型标识

    DWORD dwOffset;                        // dwId最小值

    ResourceTable table;                           //资源表

    struct ResourceDomain *pNext;         //指向下一个资源域的指标

} ResourceDomain;

 

I/O资源具有线性编址,I/O资源管理即对这个空间进行合理的分配。

 

位图模式

 

对于资源域空间小于32的情况,系统使用位模式(或称为密集表)进行管理。密集表使用位向量表示使用的空间。这样一个向量透过宏BITRANGE(bit,len)建构,宏的定义如下:

#define BITRANGE(bit,len) ((~0U >> (32 - bit - len)) & (~0 << bit))

位图中的1表示占用资源,0表示不使用。宏BITRANGE的参数bit表示1的起始位置(从低位到高位),len表示连续的1的个数。如图6.3

 

 

6.3  BITRANGE构造的位模式

 

设置密集表的输入程序代码6.5所示

 

程序代码6.5  密集表的修改

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/iomgr.c

static BOOL SetDenseResources (DenseTable *ptab, DWORD dwId, DWORD dwLen, BOOL fClaim)

{   //设置一般资源位图

    // 假设密集表定义的两个数组(实际上是指向32整数的指针)只有单元素,即32位位图

    DWORD resmask = BITRANGE(dwId, dwLen);//建构一个位图

    // 获得共享位图

    DWORD shrmask = ptab->vbs[0];

    resmask &= ~shrmask;     //去除共享的部分

    if ((ptab->vb[0] & resmask) != (fClaim ? resmask : 0))

        return FALSE;               // 资源冲突

    ptab->vb[0] ^= resmask;     //添加新的资源

    return TRUE;

}

 

static BOOL SetDenseShared (DenseTable *ptab, DWORD dwId, DWORD dwLen, BOOL fClaim)

{   //设置共享位图

    DWORD resmask = BITRANGE(dwId, dwLen);

    // Cannot un/share an unavailable resource.

    if ((ptab->vb[0] & resmask) != resmask)

        return FALSE;

    if ((ptab->vbs[0] & resmask) != (fClaim ? resmask : 0))

        return FALSE;  // share conflict

    ptab->vbs[0] ^= resmask;

    return TRUE;

}

 

稀疏表模式

 

稀疏表就是一个串行,每个节点记录一个范围,类似给予空闲表的储存管理,分配资源的时候在串行上进行节点分割、插入节点等操作。范围检验的方法很简单,实作程序代码比较简单易懂,此处就不再赘述。

 

6.2.3      装置管理接口

 

装置管理接口主要包括两个部分:档案串流存取接口和装置管理API,后者实际上是对装置管理器的功能呼叫。在程序代码6.3的第一阶段中有两个注册API的呼叫,注册的类别分别为WFLDW32D,这两个类别即为档案串流存取接口和装置管理API。程序代码6.6列出了注册的主要接口。

 

程序代码6.6  装置存取API接口

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/device.c

const PFNVOID FDevApiMethods[] = {                //注册的装置管理API

//xref ApiSetStart       

    (PFNVOID)FS_DevProcNotify,                               // 0

    (PFNVOID)0,                                                              // 1

    (PFNVOID)FS_RegisterDevice,                             // 2

    (PFNVOID)FS_DeregisterDevice,                          // 3

    (PFNVOID)FS_CloseAllDeviceHandles,              // 4

    (PFNVOID)FS_CreateDeviceHandle,                            // 5

    (PFNVOID)FS_LoadFSD,                                       // 6

    (PFNVOID)0,                                                              // 7

    (PFNVOID)FS_DeactivateDevice,                         // 8

    (PFNVOID)FS_LoadFSDEx,                                    // 9

    (PFNVOID)FS_GetDeviceByIndex,                        // 10

    (PFNVOID)FS_CeResyncFilesys,                         // 11

    (PFNVOID)FS_ActivateDeviceEx,                          // 12

    (PFNVOID)FS_RequestDeviceNotifications,      // 13

    (PFNVOID)FS_StopDeviceNotifications,             // 14

    (PFNVOID)FS_GetDevicePathFromPnp,             // 15

    (PFNVOID)FS_ResourceCreateList,                    // 16

    (PFNVOID)FS_ResourceAdjust,                                     // 17

    (PFNVOID)PM_GetSystemPowerState,               // 18

    (PFNVOID)PM_SetSystemPowerState,                // 19

    (PFNVOID)PM_SetPowerRequirement,               // 20

    (PFNVOID)PM_ReleasePowerRequirement,     // 21

    (PFNVOID)PM_RequestPowerNotifications,      // 22

    (PFNVOID)PM_StopPowerNotifications,              // 23

    (PFNVOID)0,                                                              // 24 - deprecated - VetoPowerNotification

    (PFNVOID)PM_DevicePowerNotify,                      // 25

    (PFNVOID)PM_RegisterPowerRelationship,      // 26

    (PFNVOID)PM_ReleasePowerRelationship,      // 27

    (PFNVOID)PM_SetDevicePower,                          // 28

    (PFNVOID)PM_GetDevicePower,                          // 29

    (PFNVOID)FS_AdvertiseInterface,                         // 30

//xref ApiSetEnd

};

 

const PFNVOID DevFileApiMethods[] = {                     //注册的档案串流存取界面

    (PFNVOID)FS_DevCloseFileHandle,

    (PFNVOID)0,

    (PFNVOID)FS_DevReadFile,

    (PFNVOID)FS_DevWriteFile,

    (PFNVOID)FS_DevGetFileSize,

    (PFNVOID)FS_DevSetFilePointer,

    (PFNVOID)FS_DevGetFileInformationByHandle,

    (PFNVOID)FS_DevFlushFileBuffers,

    (PFNVOID)FS_DevGetFileTime,

    (PFNVOID)FS_DevSetFileTime,

    (PFNVOID)FS_DevSetEndOfFile,

    (PFNVOID)FS_DevDeviceIoControl,

};

 

这些接口透过WIN32_DEV_CALLWIN32_FS_CALL等宏呼叫,系统在mwinbase.h中使用宏将这些呼叫封装成为标准的WIN32 API呼叫名。

 

6.3      电源管理器

 

Windows CE透过电源管理器提供电源管理功能。电源管理器增加整个系统电源效率,为每个装置提供电源管理,并且与不支持电源管理的应用程序和驱动程序共存。使用电源管理可以减少装置的电源能量消耗,同时在重新起动、运行、暂停的状态下在内存中维护和保护档案系统。

电源管理器并没有限制平台的电源要求和设计,它的主要职责是:

l        保证电源管理的具体功能可以被执行

l        处理系统的电源服务请求

l        在系统启动后或空闲状态过后立刻给装置所需之电源

l        在系统关闭和进入空闲时使装置调整电源消耗或进入睡眠

l        如果装置支持唤醒功能,唤醒装置

 

6.3.1      Windows CE电源管理体系

 

电源管理器为装置电源管理提供了机制,此机制能简单的执行并独立于Windows CE的电源管理模型。电源管理器接口在不牺牲与基本模型兼容的前提下提供OEMs和装置驱动程序开发器的弹性。在基本的Windows CE模型中,装置被通知系统暂停和重新开始。这个宣告在中断上下文中产生,所以装置通常受被暂停期间能做什么和能做多久所影响。

装置管理器管理的装置把电源状态变化说明作为IOCTLs。因为IOCTLs在执行绪上下文中执行,驱动开发器在执行电源状态变化时有更大的弹性。IOCTLs的电源管理也使得装置电源状态能从整个电源状态中分离出来。这样,当系统运行时,一些装置可被关闭,当系统暂停时,其它装置能继续运行。

作为一个直接装置电源管理的附加成分,电源管理器提供了声明的机制,此机制允许找到电源相关的事件。例如,当系统从暂停到恢复运行,电源管理器通知相关的应用程序。它还提供了进阶指定状态转移的方法。

电源管理器作为一 DLL被执行。呼叫是透过Device.exe引用Pm.dll。当电源管理APIs被呼叫时,Device.exe就会呼叫Pm.dll中的相关入口。

 

6.3.2      电源状态

 

电源管理期待所有被管理的装置支持一个或更多装置电源状态。装置电源的状态有限,装置必须通知电源管理器它们的能量消耗特征。电源管理器用OEM中定义的系统电源状态管理装置电源状态。系统电源状态在注册表中用一个字节表示,它设定了装置电源状态上限。

一些应用程序可以要求指定的装置维持在某一个装置电源层次上。例如,音频串流(audio streaming)程序可能要求当音乐播放的时候,网络卡和声音译码器在较高的电源等级上。视频串流(vedio streaming)应用程序则需要网络、显示和声音。要防止显示系统进入保护模式并保持背景灯亮。应用程序能请求电源管理器用SetPowerRequirement ReleasePowerRequirement APIs设置最低的装置电源状态请求。

 

6.3.3      电源管理接口

 

电源管理器有三种客户类型。包括:电源管理器知道的装置驱动程序、改变系统电源状态的应用程序和强迫装置执行要求的应用程序。想要被电源时间通知的应用程序,参加系统电源状态改变决策的应用程序,或两者都是。电源管理针对不同的客户用不同的编程接口。

 

装置驱动程序接口

 

电源管理器用两种机制与驱动程序沟通。电源管理器向下透过装置驱动程序之IOCTLs来获取装置能力并更新装置电源状态。而装置驱动程序也透过DevicePowerNotify API向电源管理器请求装置电源状态之改变。

电源管理器本身知道两种类型的装置。ActivateDeviceEx装载的装置面对串界面和OAL平台。ActivateDeviceEx装载的驱动程序自动向电源管理器注册。除了已安装的装置驱动程序,电源管理知道特殊装置OAL0:,它属于OEM适配层的核心界面。当管理装置的时候,电源管理器将用KernelIoControl而不是DeviceIoControl

因为电源管理器用DeviceIoControl与电源管理的装置通信,这些装置必须面对串流界面。在某些情况下,需要使用电源管理代理。打开标准的装置COM1:等,需要允许存取需要串流接口的装置。然而,电源管理器不要求可被电源管理的装置用此名称格式;装置名称可以是任何的唯一字符串。虽然平台体系结构的电源管理器只知道串流装置和OAL0:核心接口,OEMs仍旧可以自由地为平台实作附加的装置接口。

 

应用程序接口

 

电源管理器提供一些函式,让应用程序影响系统和装置的电源管理。例如:GetSystemPowerStateSetSystemPowerStateSetPowerRequirementReleasePowerRequirementGetDevicePowerSetDevicePower等。

l        设定系统电源状态

在某些情况下,应用程序可能要改变系统电源状态。应用程序给定平台上的电源状态,也不想知道可得到的系统电源状态的特征。应用程序进一位地透过设置某一传入位去触发,而不是呼叫SetSystemPowerState。电源管理器能把被设置的位转换为电源指定的状态。例如,应用程序将设置POWER_STATE_SUSPEND位请求电源状态。

下面是一些有效的电源状态位:POWER_STATE_ON,最高效能等级和最高电源消耗;POWER_STATE_SUSPEND,暂停平台,最终呼叫OEMPowerOff。如果应用程序没有设置POWER_FORCE旗标,用SetSystemPowerState请求新的电源状态,电源管理器将执行以下操作:

1)          广播PBT_QUERY讯息给需要的应用程序。如果广播被禁止,产生呼叫返回错误。

2)          查询被管理的装置来决定是否准备进入一个新的状态。如果任何装置返回ERROR_SET_POWER_STATE_VETOED, 那么电源管理器将发布 PBT_QUERYFAILED 讯息并返回错误给呼叫者。

3)          发布PBT_TRANSITION宣告。

4)          如果需要,为全部装置更新装置电源状态。如果装置已经在对新的系统电源状态可接受的状态下,电源管理器就不需要发送IOCTL_POWER_SET

应用程序能透过在SetSystemPowerState中设置POWER_FORCE绕过查询操作。在这种情况下,如果需要,电源管理器将更新全部电源状态使之符合电源状态的设置。用SetPowerRequirement强制更新而不考虑装置要求。

l        装置电源请求

在某些情况下,应用程序可能要影响电源管理器对系统电源状态的管理。例如,音频串流装置可能要网络卡或声音系统有电,甚至在系统正在用电池供电且已经空闲了一段时间后。电源管理器提供SetPowerRequirementAPI支持有特殊电源管理需要的应用程序。

SetPowerRequirement API允许应用程序让电源管理器可以很少关心装置电源状态。如果电源请求有效,电源管理器不允许装置因特殊请求设置自己的电源状态。当电源管理器改变电源状态,它强制保持系统请求而不管装置电源状态是否比系统允许的电源状态高。系统在没有强制的系统电源转移时,电源管理器根据装置电源状态的需要设置状态。在系统电源转移时,电源管理器根据OEM系统设计者设置装置电源状态。

 

通知界面

 

电源管理提供APIs集合,使得应用程序能接收电源相关的通知并且参与到改变系统状态的决策中。主要函式包括:RequestPowerNotificationsStopPowerNotificationsVetoPowerNotification。通知透过讯息传送。应用程序必须建构讯息队列并把讯息句柄透过REQuesetPowerNotificatons API传给电源管理器。应用程序将建构等待讯息队列的处理程序。

 

6.3.4      系统电源状态转换

 

在以下情况下,电源控制器引起系统电源状态的转换:

一个OEM特殊事件的发生需要一个状态转换。

一个SetSystemPowerState函式的呼叫。

OEM特殊事件可能包括从交流电源到电池电源的转换,延长系统的休眠时间,低电池能量层次等。OEM负责控制电源控制器来判断什么时候应该产生系统电源状态的转换,并在必需的时候执行这种转换。含有平台对象(platform component)的基本电源控制器可以识别这种从交流电源到电池电源的转换。

应用程序可以透过一个明确的电源状态名字或者一个它们想进入的系统电源状态的类型呼叫SetSystemPowerState函式。拥有系统电源状态的OEM信息的应用程序可以使用明确的电源状态名字。与平台无关的应用程序可以笼统地设置电源状态的信息,允许电源控制器自己确定最适合的OEM电源状态。

电源控制器可以识别三种状态转换:自发的、偶然的和强迫的。

 

自发性系统电源状态转换

 

如果电源控制器认为系统处于休止状态并且OEM定义了一个低能量、低功效的状态,它可以发动一个自发的系统电源状态转换。另一种类型的自发电源状态转换发生在应用程序呼叫SetSystemPowerState函式而没有设置POWER_PORCE旗标位的时候。

当执行一个自发的系统电源状态转换的时候,电源控制器会透过它的应用程序接口发布讯息,查询感兴趣的应用程序,或者使用IOCTL POWER QUERY查询驱动器。任何应用程序或者是驱动器都可以反对系统建议的电源状态转换。但是应用程序的开发者和装置驱动器的使用者不能无代价的反对状态转换。如果一个驱动器或者应用程序反对状态转换,电源控制器会发布一个PBT_TRANSITION的通告,并在合适的时候对所有的装置更新它们的电源状态。

当进行一个自发的系统电源状态转换的时候,电源控制器将会优先满足要求使用SetPowerRequirement应用程序接口的最小装置电源状态。

 

偶然性系统电源状态转换

 

电源控制器总是初始化为自发系统电源状态转换。当一个OEM定义了一个和一些外部事件相关的系统电源状态时,OEM必须控制电源控制器,使其能够识别这一事件。

一个OEM可以是一个电源控制器以外的应用在识别时间和进行状态转换之间转变,但同时必须禁止基本电源控制器负责识别交流电源状态转换的程序代码。

当初始化一个偶然的系统电源状态转换时,电源控制器不会查询应用程序和装置,但是会立即更新装置电源状态。当执行一个偶然的电源状态转换的时候,电源控制器通常会透过呼叫SetPowerRequirement函式继续对装置电源请求优先,然而,OEM可能会选择创造一个诸如VeryLowBattery或者CriticalSuspend的系统电源状态,并且控制电源控制器,使其忽略装置电源对这些状态的请求。

 

强迫性系统电源状态转换

 

当一个应用程序呼叫了SetSystemPowerState函式,并设置了POWER_FORCE旗标位的时候,强迫性的系统电源状态转换发生了。在初始化为一个强迫的系统电源状态转换的时候,电源控制器并不在状态变换前要求应用程序或者装置驱动器。它立即为新的系统电源状态刷新(?)装置电源状态。

如果电源控制器知道目标电源状态对于驱动器环境是不合适的,它可以选择不响应系统电源状态刷新的要求。例如,如果一个电源控制器知道系统用完了电池,它就不会响应一个要求进入MaxPowerAC状态的应用程序请求。电源控制器并没有被要求要增强对电源状态变换的敏感度,但是,应用程序不应当认为POWER_FORCE必然会保证电源控制器确实改变了系统电源状态。

 

6.3.5      驱动程序电源管理

 

可控制电源的装置驱动程序能够在被ActivateDeviceEx装载的时候自动通知电源控制器它们的存在。如果REG_MULTI_SZ是驱动程序的IClass值的一部分,电源控制器在其装载的时候会收到一个驱动器通知。一旦电源控制器收到这个驱动器通知,它将透过使用一个驱动器IOCLT确定驱动器的能量。接着,它会根据当前电源系统状态和应用程序对于驱动器的需求开始控制驱动器的能量。如果一个OEM选择应用一个在串流接口之外的驱动器接口程序,他们需要控制电源控制器,使其能够获悉驱动器的存在。

在初始化过程中,驱动器装置必须是驱动器处于D0状态。当电源控制器透过IOCTL_POWER_CAPABILITIES发出查询时,他们必须尽可能详细的报告驱动器的能力。

电源控制器透过使用IOCTL_POWER_SET调整驱动器的电源状态,在执行对于IOCTL的支持的时候,驱动器装置发展商们应该注意以下几条:

l        驱动器并不是被要求执行所有五种驱动器电源状态的,他们只要求工作在电源状态D0下。

l        电源控制器可能会要求驱动器进入任何驱动器电源状态,而并不仅仅是驱动器在IOCTL_POWER_CAPABILITIES句柄中宣告支援的几个。

l        如果一个驱动器被要求进入一个它并不支持的电源状态,它会被期待进入另一个它并不支持的更高功效的状态。例如,如果一个驱动器并不支持D2,它会被要求进入D1。其中D3状态需要一些特殊的句柄。

l        电源控制器可能会透过发出IOCTL_POWER_SET,本质上将一个驱动器放入它已经存在的当前状态。在这种情况下,驱动器装置简单的返回成功,什么也不执行。

l        电源控制器并不被要求设置驱动器的电源状态为其被系统电源状态制定的默认值。例如,如果一个驱动器的电源状态落入了被系统电源状态和应用程序需求所限制的范围内,电源控制器可以选择不改变驱动器的电源状态。

可控制电源的装置驱动程序透过XXX_PowerDownXXX_PowerUp可以继续接受系统暂停和重起的通告。这些通告在系统核心呼叫OEMPowerOff之前的中断上下文中被发出。

一些装置可能有熟练管理自己的能力。装置驱动程序开发者典型地要在装置不运行的时候减少电量开销。减少电能开销使得减慢装置的活动,所以装置使用时想提高电能。增加活动提高了电能消耗。装置能根据运作情况动态提升或降低电源状态。实际的排程准则因实际装置而异。电源管理器的API允许驱动程序开发者请求电源管理器调整电源的状态。如果装置电源状态落在系统电源状态和应用程序呼叫SetPowerRequirement请求的状态之间,则电源管理器允许电源调整。当电源管理器决定接收装置的电源状态调整请求,它能用装置API更新装置电源状态,一般用IOCTL_POWER_SET实作。

 

6.3.6      电源管理器的实作

 

电源管理器主要由 [CEROOT]/Private/Winceos/Coreos/Device/Lib/pwrmgr.c实作,通知机制则由同一目录下的device.c实作。Pwrmgr.c的主要程序代码只是一个高层封装,它呼叫Windows平台公用组件实作,该组件位于[ceroot[/public/common/oak/drivers/pm/drv/pm.c中。

 

6.4      随插即用管理器

 

和电源管理器相比,PnP管理器简单很多,它主要实作了装置管理的通知与广告机制。

 

6.4.1      数据结构

 

 PnP管理数据入口为一全域的静态变量g_Notifications,这实际上是一个表头,包含指向两个基本串行的表头的指标,还包括一个临界区变量,用以实作系统对自身的保护性存取。如程序代码6.7所示。

 

程序代码6.7  PnP管理器数据结构

// 摘自 [CEROOT]/Private/Winceos/Coreos/Device/Lib/PnP.c

//      [CEROOT]/Private/Winceos/Coreos/Device/Lib/devloadp.h

//      [CEROOT]/Public/common/sdk/inc/pnp.h

 

static struct {

    NotifyListElement *phNotify;                                   //通知串行

    AdvertisementListElement *phAdvertisement;   //广播串行

    CRITICAL_SECTION cs;                                         //临界区存取控制变量

} g_Notifications;       //静态变量,管理表头

 

typedef struct AdvertisementListElement_ {      //广告串行的基本节点类型

    AdvertisementListElement *pNext;              //下一个节点

    HPROCESS hProc;                                         //广告处理过程控制码

    DEVDETAIL devDetail;                                   //装置接口信息

} AdvertisementListElement;

 

typedef struct NotifyListElement_ {             //通知串行的基本节点类型

    GUID guidDevClass;                             //装置类别标识

    HANDLE hSend;                                    //讯息队列句柄

    NotifyListElement *pNext;                     //下一个通知节点

} NotifyListElement;

 

typedef struct {                                       //装置接口信息描述节点

    GUID guidDevClass;                   //装置类别标识

    DWORD dwReserved;                //保留字段

    BOOL fAttached;                            //若装置接口可用,则为真,否则为假

    int cbName;                                   //接口名的字节计数

    TCHAR szName[1];                      //界面名的起始处

} DEVDETAIL, *PDEVDETAIL;

 

从程序代码6.7可以看出它们之间的相互关系:通知和广告节点分别形成两个单串行,追踪在系统管理器注册过的通知和广告事件,透过它们记录的信息可以找到注册这些事件的处理程序,并透过系统的通信机制——讯息队列——将讯息发送给这些处理程序。DEVDETAIL结构提供了装置接口的信息,在广告串行中包含这一信息。在系统中装置接口的变更是透过广告的方式在系统内传播的,数据结构的定义正是为了实作这种机制。

每一个广告节点记录了一个注册的接口变化事件,而每一个通知节点则记录了一个注册过的希望知道系统变更的实体——它透过一个讯息队列来代表:hSend变量记录了这一点。使用者可以透过注册函式告诉PnP管理器自己有广告信息或者自己需要知道某个装置的通知信息。PnP管理器将讯息队列的存取限制在了三个函式中:RegisterDeviceNotifications,注册通知并构造一个讯息队列;UnregisterDeviceNotifications,反注册通知并关闭讯息队列;SendToMaybe,写讯息队列,主要是把一个装置接口描述信息DEVDETAIL类型的变量传送给注册通知者。这几个函式构成了PnP讯息传递的基本能力,其它的函式基本上是扫描两条串行、存取注册表并用SendToMaybe建构。

 

6.4.2      通知

 

PnP管理器支持注册以及反注册一个通知,当一个此类呼叫发生时,系统将在phNotify串行上增删节点,并且管理相对应的讯息队列资源。SendNotifications函式可以将一个装置接口描述信息发给通知串行上的每一个讯息队列。NotifyAll函式查询注册表中的装置接口的类别信息以及Active机码,并呼叫ProcessInterface函式将查到的每一个接口信息发送给特定的或者所有的通知注册者。

 

6.4.3      广告

 

广告是和一个装置接口信息变更相关的,类似通知,广告也允许注册和反注册,和通知不同,系统更改的是phAdvertisement串行。一个广告的注册或者反注册事件总是伴随一次SendNotifications函式的呼叫,它将新的装置接口信息发送给所有已注册的讯息队列。PnP管理器也允许寻找并修改特定的广告。

 

6.5      装置驱动程序

 

6.5.1      装置驱动程序接口

 

Windows CE使用注册表记录可以存取的装置驱动程序信息,并透过装置管理的几个部分互相协调合作使得装置驱动程序得以正常工作。装置驱动程序总是与某个装置相关联的,前面已经知道在装置描述串行的每一个节点中有若干函式指标,实际上,这些指针指向的函式实体都是由相关联的具体装置驱动程序实作的。装置驱动程序的实例请参考第十四章。

使用者程序存取一个装置时,透过注册表中的相关项目查询一个装置驱动程序所能提供的接口。类似Windows XPWindows CE透过装置接口提醒应用程序、服务和装置驱动器注意装置接口的出现和消失。AdvertiseInterface提醒装置管理器注意所有装置接口的有效性和移动的相关部分,它自身并不装载或卸载驱动程序。使用AdvertiseInterface的驱动器在取消接口的时候也必须给与广播。驱动程序接口透过AdvertiseInterface发布为装置接口类别IClass形式,装置接口类别提供了可以被应用程序用来获得装置驱动器特性的方法。每一个IClass包含了一个GUID和一个名字,其中GUID描述了通常的装置接口,名字定义了一个接口的实例,如COM1DSK1。一个装置驱动可以同时拥有一个或多个接口,也可以没有,装置接口同GUIDs相关联,由注册表中的一个IClass值所指定。与GUID相关联的接口一般定义在定义接口的表头档案中。透过适当地设置IClass中的值,可以用ActivateDeviceEx函式在Device.exe中把接口直接公开化。相反,程序设计师也可以透过StopDeviceNotification停止装置接口。AdversitiseInterface是装置管理器提供的一个API接口,在程序代码6.6中可以看到它呼叫接口实际上为FS_AdversitiseInterface,这个函式透过PnP管理器的UpdateAdvertisedInterface函式将装置驱动程序的接口注册到系统中。

Windows CE实作了通告机制向感兴趣的应用程序或驱动程序提示系统接口的变化,通告机制提供了多种不同的方法,如:接口被定义在用来启动装置的注册表的IClass值中;IClass值被定义在一个装置驱动器的初始函式的活动机码中;IClass值透过ActivateDeviceEx中的参数REGINI传递。上述所有行为都使得驱动程序在装载、初始化和卸载时导致装置管理API送出关于接口的通告。可以使用RequestDeviceNotificationStopDeviceNotification来注册和反注册装置接口通告。例如,当不需要接收通告,可以呼叫StopDeviceNotification,然后关闭带有CloseMsgQueue的讯息队列。

信息队列是对装置通告操作的系统对象,是共享储存空间里的一个先进先出队列。这个资源的存取,可以使用WaitForSingleObject或者WaitForMultipleObject进行同步。除此之外,讯息队列只能被讯息队列函式控制。例如,对一个队列句柄呼叫CloseHandle是不允许的。

 

6.5.2      装置驱动程序结构

 

广义上来讲,在Windows CE中装置可以指任何需要控制、资源的物理或者逻辑的系统实体(软件或者硬件),装置驱动程序则可以是一个管理装置、服务或者协议的软件模块。下面如果没有特殊说明,装置驱动程序特指驱动、管理物理装置的软件模块。

装置驱动程序要实作特定装置的中断响应、中断引发的数据传送和处理、装置的档案串流接口以及装置专用接口。中断响应一般透过中断服务例程(ISR, Interrupt Service Routine)实作,它随装置的不同而不同,例如对于每一种品牌型号的显示卡,这段程序都可能会不同,它一般在OAL中提供;中断的实际处理一般被放在一个叫做中断服务执行绪(IST, Interrupt Service Thread)的机构中实作,这段程序的内容和架构一般是装置类型特异(?)的,例如所有的音频装置这种框架具有类似性,它可以是同类装置共享的程序代码。装置接口则透过通知机制由device.exe导出到系统中,同时具有串流接口的驱动程序在装入的时候自己实作的xxx_ 形式的接口函式指针将被挂接到装置描述串行中的相对应位置。

从复杂性角度,装置驱动程序可以分为两种:分层结构驱动程序(Layered Driver)和单体结构驱动程序(Monolithic Driver)。绝大部分实际的Windows CE装置驱动程序都是分层结构的驱动程序。

单体的装置驱动程序一般只用在非常关机码的系统特性上,因为它可以把前面提到的主要功能都集合在一起实作为中断处理例程,这对于实时要求很高的装置来说,是可以得到好处的。但是,这种结构的使用会严重影响系统整体的效率,例如机码盘驱动,如果用这种方式作的话,在我们频繁使用机码盘的时候,会有大量时间处理这个中断,其它装置中断以及系统排程的总时间就减少了很多。此外,一般单体的驱动程序去除了大部分分层驱动程序层之间交互的通信代价,就单个装置而言效率确实可以得到提高。

分层的驱动程序可以适应绝大多数的装置,Windows CE针对多个类别的装置做个不同的类型驱动程序的体系结构,使得同类装置可以共享大部分程序代码。这件事的可能性完全建立在分层的模式下,在分层的通用模型中,装置驱动程序分成了两个层次:平台相关驱动程序(PDD, Platform Dependent Driver)和模型装置驱动程序(MDD, Model Device Driver)。前者在OAL中实作,和具体装置紧密相关,后者针对驱动类型设计,目标是类型驱动程序程序代码的重用,主要由Windows CE的系统支持所提供,当然第三方开发商也可以提供这样的类型驱动程序。MDD透过呼叫PDD中的特定例程存取硬件的具体特性。MDD一般提供如下的处理:定义装置驱动服务提供接口(DDSI, Device Driver Service Provider Interface),这些接口是MDD对相应PDD的需求;向系统范围提供装置驱动接口(DDI, Device Driver Interface);处理一些复杂任务,例如中断的处理。

 

6.5.3      中断处理

 

各种装置透过中断和Windows CE的核心通信,在系统发现中断之后,处理的过程将分为两个部分实作:核心的ISR和使用者执行绪IST

ISR的实作一般要求短小精悍、效率要求很严格,它们只实作最简单及重要的功能:响应装置并返回一个中断标识给操作系统核心。Windows CE支持两种类型的ISR:静态ISR和可安装ISR

l        静态ISR。只能静态地编译进核心,运行时不能改变。与IST通信时,只是单向的,由ISRIST。静态ISR支持嵌套(nested)中断,并且使用核心堆栈。

l        可安装ISR。由核心管理程序从DLL中动态加载,和静态的ISR不同,它和IST通信可以是从ISTISR。多个ISR可以与同一个中断请求相关联,系统按照加载驱动程序的顺序依次排程。在可安装ISR中,共享内存的使用也比较灵活。

IST则处理中断的一般事务性工作,当核心接到ISR传给自己的中断标识之后就发出一个中断事件,启动一个正等待在该事件的事件队列上的IST,一段时间之后,排程器(scheduler)就会排程这个执行绪工作,处理中断的事务。Windows CE中断处理包括的各种组件如图6.4所示

 

 

 

 

 

 

 

 

 

 

 

 

 

6.4  中断处理组件

 

Windows CE处理中断的过程在中可以透过图6.5反映出来。

 

 

6.5  中断处理过程

 

需要做几点批注:1)系统对的中断响应是分阶段区别的,当核心的处理程序捕获中断之后,将禁止所有同级或者级别较低的中断发生,当这一过程有高等级的中断发生,将会引起巢式(nested)中断,稍后会有说明;当中断事件被设置以后,所有ISR的任务基本上就完成了,处理过程完成了第一阶段的任务,此时系统没必要限制除同种中断之外的其它装置事件的发生,为了不遗失这些装置中断,系统需要开放这些中断;最后中断处理完毕,系统恢复常态。这种分阶段限制中断的做法对于系统的中断响应时间是有利的。2OALISR可以排程可安装ISR的运行,因为在系统中允许中断被共享——例如可能存在多个鼠标装置的驱动程序——对于这种情况,系统将依据各个ISR注册加载的顺序排程这些中断服务例程工作,对于实时性要求很高的装置来说,这种共享并不是一个好主意。3)设置事件之后到IST被排程(schedule)实际上有一个短暂的时间差,设置事件并不直接将控制转移到IST,这一点是透过系统的排程程序完成的:排程程序会发现这个事件,并检测是否有执行绪等待在这个事件的队列上,如果有,则它们被放进就绪队列,紧接着的一次时钟中断,系统将根据优先级排程各个执行绪的运行。IST的优先级一般高于普通执行绪,这使得它们可以抢占普通执行绪的时间片段完成优先中断处理。4)在Windows Windows CE中没有类似Windows 2000/XP的复杂的DPC/APC中断排程机制,因而在处理中断完成的数据拷贝等善后工作没有设立专门机制,一般可以在IST以及相应的串流接口中实作相应的缓存和资料迁移。

 

6.5.4      DMA

 

Windows CE提供了一些接口存取硬件系统的DMA能力,这允许使用者编写的驱动程序可以有效的在装置和内存之间作数据数据传送。使用DMA的最大问题就是地址的变换:使用者常常需要数据在某个特定装置的储存区(I/O可能是一系列地址)和内存中某个地址区域之间迁移,这两个地址在系统中分别表示为I/O资源以及一个缓冲区(虚拟)地址,支持DMA的核心例程将实作两个地址的变换。I/O资源将映像为实际装置储存区,而缓冲区地址将被变换成一个实际的物理地址传送给DMA控制器。

 

6.5.5      实时特性

 

Windows CE是一个具有一定实时能力的操作系统,这不仅体现在系统采用了基于优先级的多级队列实时排程(同级队列采用时间片段轮转),更在于它的中断处理的时间特性。实时系统对中断处理的一般性要求就是需要中断被快速的响应,Windows CE并非专门为实时应用而设计(从某种意义上讲它可以看成通用的嵌入式操作系统),但是它为这方面的应用开发留了一定余地,奥妙就在于中断处理的ISR-IST体系上。

l        ISR的精巧实作有效的减小了中断屏蔽的时间

l        系统提供了256个优先级运行各种系统组件和应用程序,ISR-IST运行在较高的优先级上,这样它们总是可以抢占较低优先级的组件区的时间片段。关机码的实时任务可以设置较高的优先级。

l        对于一些实时要求很高的应用,可以实作一个较复杂的ISR,直接在里面处理关机码装置的工作。有一些较强的实时应用正是这么做的。

 

6.5.6      装置驱动程序的加载

 

装置能被接口描述。最简单的情况是,有一个对下面向硬件的接口,对上面向应用程序的接口。面向(?)硬件的接口能呼叫硬件或总线接口,面向应用程序的接口能呼叫客户功能。装置驱动程序能够被其它驱动程序、装置管理器或直接被应用程序操纵。他们的行为由界面描述。接口中定义了一系列的功能,一般包括初始化和卸载(硬件亦然)。透过这些接口的定义,系统可以方便的实作装置驱动程序以及装置的加载工作。

总线负责在装载总线上透过硬件联机挂接装置,系统中检查和装载合适装置的程序叫总线列举器。在Windows CE中根(root)的总线列举器或者总线驱动程序是由注册表列举器(RegEnum.dll)实作的,还记得CE中的装置信息都是记录在系统注册表中的吗?装置管理器透过呼叫ActivateDeviceEx来呼叫注册表,总线驱动程序负责驱动装载以及装载的次序。

一般来说,应用程序和驱动程序不直接受连接到系统的装置的影响,它们和装置事件之间是透过系统通知机制实作的,这一机制使得它们可以轻而易举得知装置驱动程序所提供的指定接口的变化。例如,档案管理器不必得知由于PC卡的插入而产生新的装置的信息,但当档案系统安装新装置之后它需要知道有一个新的卷(volume)可以使用了。

系统初始化的时候会呼叫OEMInit初始化例程,完成诸如以太网络除错或串行端口除错等底层服务。首先,OAL需要设置和列举总线去操作除错服务和相关装置,例如。以太网络除错服务需要驱动PCI总线上的网络卡。OAL负责把资源信息放到注册机码中,并使它能被驱动程序存取。接着,CE核心呼叫KernelIoControl,允许帮助函式PCIReg被驱动程序呼叫去组织注册表。然后,Device.exe被装载执行,它负责启动I/O Resource Manager,并且从注册表中查出可以得到的资源列表。

注册表列举器实作了InitDeinit函式。它透过读注册表项发现新装置。装置管理透过检查HKEY_LOCAL_MACHINE/Drivers/RootKey装载注册表列举器,此机码决定了处理程序开始装载驱动程序的地方,预设的值是Drivers。注册表列举器检查注册表传给它的根机码,用来找描述装载装置的子机码。RegEnum.dll按照顺序检查传给它机码下面的第一层机码。在每个找到的子机码上呼叫ActivateDeviceEx,每一个子机码可能有被ActivateDeviceEx或最终呼叫的驱动程序解释的任何值。另外,每个子机码有一个从0255的顺序值。最小的次序值最先被装载。如果没有次序值,驱动将在被定义之后装载。

如果驱动程序实作了串流接口,驱动表项的指针将从注册表相应的Prefix值中建立,若没有这个值,将使用Init。例如,等于DSKPrefix的串行端口驱动程序将有一个COM_Init的表项指标。如果没有Prefix值,那么表项指针为Init例程。当注册表列举器初始化系统的时候,它一个接一个扫描HKEY_LOCAL_MACHINE/Drivers/BuiltIn子机码和RootKey的值指向的地方并且根据每个表项的内容初始化装置。它用DLL值装载相应的动态链接库,然后在Active下为驱动程序建立子机码,然后呼叫初始化例程并把子机码名称作为参数传递过去。XXX_Init例程能用所得到的字符串存取注册表获得装置描述信息。通常装置驱动程序的编写者在注册机码中包含其它的值。这些值指定类似I/O端口地址,IRQ设置,DMA设置,或其它附加的信息。

RootKey可能拥有PCI子机码,该机码下列举的DLL正是PCI总线驱动。PCI总线的注册表项包括:1)可得到的资源;2Order值,默认值一般保证它最后被装载;3SysIntr值,一般由OAL指定的,映像到特定的IRQ。另外,一些总线驱动,可能要附加的值。此处不再赘述若干细节问题。

有关装置驱动程序加载过程的详细情况,请参考第12章的内容。

 

6.5.7      Windows CE的类型装置驱动程序

 

Windows CE支持多种类型的装置驱动程序,每一种类别实际上代表了一种类型的MDD,具有特定的DDIDDSI,下面以显示卡驱动程序为例,简单分析一种具体的MDD结构。

 

界面

 

Windows CE显示装置接口的DDIWindows 2000/XP显示DDI的一个子集,它只包括Windows 2000/XP显示DDI功能的基本部分。与定位在桌面应用为主的Windows 2000/XP不同,在Windows CE中,GDI(图形装置接口,Graphics Device Interface)不需要图形驱动程序提供自己能力的描述,因为它们都要实作相同的功能;图形驱动程序被编译成为使用者模式的动态连结函式库,而不是集合在系统层中;GDI在呼叫图形驱动程序显示之前,将GUI中的复杂图形操作分解为图形引擎可以支持的基本操作。

 

结构

 

整个显示驱动程序的MDD被嵌入系统的GUI环境——图形、窗口以及事件子系统(GWES, Graphics, Windowing and Event Subsystem)中,任何CE的显示卡驱动程序必须遵循这一规范。显示卡驱动程序直接被GWES.exe呼叫,在GWE中包括基本图形引擎(GPE, Graphics Primitive Engine),它实作基本的绘图功能,构成了图形驱动MDD的主要部分。与硬件具体相关的,例如操作显示卡绘制等,由OEMPDD中实作。如图6.6所示。

 

 

6.6  图形驱动体系结构

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值