wince驱动开发二

6、驱动中注意的要点

1:CE下同名设备不能大于10

CE5.0中已经没有这个问题了,以前的版本可以这样做:只给上层输出一个设备,然后用一个IOCTL去打开一个个的物理设备这样就可以做到不受任何限制了。

 

2:MDD与PDD
一个驱动程序通常会被分成硬件相关(PDD)与硬件无关(MDD)层两部分。
当然,这种分层不是必须的,只是采用这种分层以后可以少写很多代码,因为微软提供了很多驱动程序的MDD。即使CE中没有我们所写的驱动程序的样例,采用这种结构以后,当需要写第二个程序时,就可以重用它的代码,就可以提高开发效率。

 

MDD是提供同类型的设备(比如串口)都会有的功能,这样PDD基本上就只有寄存器操作了。

像串口的中断处理,Read/Write函数,其大部分代码都是在MDD中实现的,不同的串口实现中只需要提供一些实际操作寄存器的函数。不同的驱动程序,其MDD与PDD的接口不尽相同,

 

3:XXX_Init函数的返回句柄
通常,这个句柄是驱动程序自己保存数据的一个指针,我们在Init返回时告诉上层程序,以后上层调用其它函数(例如Open)时,会将这个值传入,这样,我们就可以访问自己的一些私有数据。

当然,也可以返回一个任意的非0值对于一个设备驱动程序,系统不用的层会有不同的句柄。我们在XXX_Init中返回的句柄保存在设备管理器中,别的程序中应该是看不到的,而用CreateFile也会得到一个文件句柄,这个保存在哪我不知道,但和前者是不一样的。也就是说不同层的软件所关心的句柄也会不一样
4:DEBUGMSG与RETAILMSG的区别
它们都是输出调试信息用的,区别是:
DEBUGMSG只在DEBUG版中有效,RELEASE版中它被定义成了NULL
RETAILMSG在DEBUG和RELEASE版中都可以输出,
而且DEBUGMSG可以在运行时刻用DEBUZONE控制要不要输出信息。

 

在ship build 时,RETAILMSG 和DEBUGMSG都无效。

 

5:调试区与dpCurSettings

我们都是利用OutpubDebugString函数来实现调试信息的输出的,但是由于系统底层的调试信息非常繁多,如果这样大量的调试信息用于实时输出的话一定会影响到系统的性能和实时性,也就影响到了系统的运行。如果有一种方式能允许开发人员自己选择输出哪些调试信息,不输出哪些调试信息的话,那么就可以让开发人员只看到关心的调试信息,而把诸如键盘按键、鼠标移动等无用的调试信息隐去,则可以更好的提高开发效率。

 

调试区就是为了解决以上提出的问题的,对某一个驱动程序,它规定好自己向外输出的调试信息的分类,比如初始化时的信息,出错时的信息,释放时的信息,激活时的信息等,然后分成几个调试区,在现有的CE版本中最多允许16个调试区。

 

开发人员通过Platform Builder中Target菜单下的CE Debug Zones命令来决定想要得到哪一个或哪几个调试区的信息,在驱动程序中则可以根据开发人员的选择来输出指定调试区的信息。这就是调试区大体上的工作原理。

 

调试区的定义,声明,注册及使用。
在程序中使用调试区之前必须先定义它们,一个程序的16个调试区编号分别为0-15。代码样例如下所示:
#ifdef DEBUG
//
// For debug builds, use the real zones.
//
#define ZONE_TEST DEBUGZONE(0)
#define ZONE_PARAMS DEBUGZONE(1)
#define ZONE_VERBOSE DEBUGZONE(2)
……
#define ZONE_WARN DEBUGZONE(14)
#define ZONE_ERROR DEBUGZONE(15)

#else
//
// For retail builds, use forced messages based on the zones turned on below.
//
#define ZONE_TEST 0
#define ZONE_PARAMS 0
#define ZONE_VERBOSE 0
……
#define ZONE_WARN 0
#define ZONE_ERROR 0

#endif
这样,就可以程序的DEBUG版本中使用调试区了,而在RELEASE版本中则将其全部定义为0,调试信息即不再输出。
在程序中,除了以上的定义以外,还要声明几个专用的调试信息输出函数,这些函数与OutputDebugString函数的区别就在于在调用时需要指定对应的调试区,这些函数以及以上用到的DEBUGZONE宏的定义都在DbgApi.h头文件中,因此只要在源程序中包含此头文件即可。除此以外,还需要一个全局的DEBPARAM类型的变量命名为dpCurSettings,以供集成开发环境和调试信息输出函数使用。其代码样例如下:
#ifdef DEBUG
DBGPARAM dpCurSettings = {
   TEXT("WaveDriver"), {
   TEXT("Test") // 0
   ,TEXT("Params") // 1
   ,TEXT("Verbose") // 2
   ,TEXT("Interrupt") // 3
   ,TEXT("WODM") // 4
   ,TEXT("WIDM") // 5
   ,TEXT("PDD") // 6
   ,TEXT("MDD") // 7
   ,TEXT("Regs") // 8
   ,TEXT("Misc") // 9
   ,TEXT("Init") // 10
   ,TEXT("IOcontrol") // 11
   ,TEXT("Alloc") // 12
   ,TEXT("Function") // 13
   ,TEXT("Warning") // 14
   ,TEXT("Error") // 15
  }
  ,
  (1 << 15) // Errors
  | (1 << 14) // Warnings
};
#endif
此例中还把ERROR和WARN调试区作为默认被开发人员选中的调试区。
要想使用调试区,还需要做的最后一件准备的事情就是在程序中进行注册,也就是在程序启动时通知集成开发环境本程序中要使用调试区,这个注册很简单,只要在程序的入口处使用DEBUGREGISTER宏即可,样例如下:
DllEntry (
HANDLE hinstDLL,
DWORD Op,
LPVOID lpvReserved
)
{
switch (Op) {

case DLL_PROCESS_ATTACH :
DEBUGREGISTER((HINSTANCE)hinstDLL);
break;
……

6:内核对象的传递

 

许多情况下,在不同进程中运行的线程需要共享内核对象。一般来说对于操作系统的内核对象句柄,比如线程句柄等等它们是不能在进程中进行共享的,也就是说这些句柄只能属于进程私有,句柄本身只是一个32位值,不同的句柄有不同的含义,HBRUSH

、HINSTANCE、HRESULT 都是句柄,但其含义大相径庭,但一般情况下句柄都是标识某对象的(HRESULT就不是),所以一般的句柄是内存空间相关的,而不同的exe其内存空间是无关的,所以共享句柄的值是无意义的,实际上返回的这些句柄的值是进程句柄表中的索引值。

 

那么如何在不同进程间共享内核对象句柄呢?一般有三种方法:

1 利用对象句柄的继承性

只有当进程具有父子关系时,才能使用对象句柄的继承性。在这种情况下,父进程可以使用一个或多个内核对象句柄,并且该父进程可以决定生成一个子进程,为子进程赋予对父进程的内核对象的访问权。。若要使这种类型的继承性能够实现,父进程必须执行若干个操作步骤。

 

首先,当父进程创建内核对象时,必须向系统指明,它希望对象的句柄是个可继承的句柄。这要求进程必须指定一个S E C U R I T Y _ AT T R I B U T E S结构并对它进行初始化,然后将该结构的地址传递给特定的C r e a t e函数。

 

然后,是让父进程生成子进程。这要使用C r e a t eP r o c e s s函数来完成。

BOOL CreateProcess( 

LPCTSTR lpApplicationName,                 // name of executable module

LPTSTR lpCommandLine,                      // command line string

LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD 

LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD 

BOOL bInheritHandles,                     // handle inheritance option 

DWORD dwCreationFlags,               // creation flags 

LPVOID lpEnvironment,                      // new environment block 

LPCTSTR lpCurrentDirectory,                // current directory name

LPSTARTUPINFO lpStartupInfo,               // startup information 

LPPROCESS_INFORMATION lpProcessInformation // process information);

可通过将bInheritHandles设置为True来实现。接下来通过这种方法生成的子进程就可以拥有父进程内核句柄的继承权了。

 

2 命名对象

共享跨越进程边界的内核对象的第二种方法是给对象命名。许多(虽然不是全部)内核对

象都是可以命名的。但当创建一个未命名的对象时,可以通过使用继承性(如上一方法介绍的那样)或DuplicateHandle (下一方法将要介绍)共享跨越进程的对象。若要按名字共享对象,必须为对象赋予一个名字。

 

3 复制对象句柄

共享跨越进程边界的内核对象的最后一个方法是使用DuplicateHandle函数

 

BOOL DuplicateHandle( 

HANDLE hSourceProcessHandle,  // handle to source process 

HANDLE hSourceHandle,         // handle to duplicate 

HANDLE hTargetProcessHandle,  // handle to target process 

LPHANDLE lpTargetHandle,      // duplicate handle 

DWORD dwDesiredAccess,        // requested access 

BOOL bInheritHandle,          // handle inheritance option 

DWORD dwOptions               // optional actions);

 

简单说来,该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。DuplicateHandle 函数配有若干个参数,但是实际上它是非常简单的。DuplicateHandle函数最普通的用法要涉及系统中运行的3个不同进程。

 

当调用DuplicateHandle函数时,第一和第三个参数hSourceProcessHandle和hTargetProcessHandle是内核对象句柄。这些句柄本身必须与调用DuplicateHandle函数的进程相关。此外,这两个参数必须标识进程的内核对象。如果将句柄传递给任何其他类型的内核对象,那么该函数运行就会失败。

 

第二个参数hSourceHandlee是任何类型的内核对象的句柄。但是该句柄值与调用DuplicateHandle的进程并无关系。相反,该句柄必须与hSourceProcessHandle句柄标识的进程相关。

 

第四个参数phTargetHandle是HANDLE变量的地址,它将接收获取源进程句柄信息拷贝的项目索引。返回的句柄值与hTargetProcessHandle标识的进程相关。DuplicateHandle的最后3个参数用于指明该目标进程的内核对象句柄表项目中使用的访问屏蔽值和继承性标志。DwOptions参数可以是0(零),也可以是下面两个标志的任何组合:DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE。如果设定了DUPLICATE_SAME_ACCESS标志,则告诉DuplicateHandle函数,你希望目标进程的句柄拥有与源进程句柄相同的访问屏蔽。使用该标志将使DuplicateHandle忽略它的dwDesiredAccess参数。如果设定了DUPLICATE_CLOSE_SOURCE标志,则可以关闭源进程中的句柄。该标志使得一个进程能够很容易地将内核对象传递给另一个进程。当使用该标志时,内核对象的使用计数不会受到影响。

 

上述三种方法的使用都有自己需要的条件,使用之前要先确定自己所具备的条件以及自己所要求达到的目的,以免使用时遇到不必要的错误。

 

7、同步

在多数情况下,线程之间难免要相互通信、相互协调才能完成任务。比如,当有多个线程共同访问同一个资源时,就必须保证一个线程正读取这个资源数据的时候,其它线程不能够修改它。这就需要线程之间相互通信,了解对方的行为。再有当一个线程要准备执行下一个任务之前,它必须等待另一个线程终止才能运行,这也需要彼此相互通信。实际开发过程中,线程间需要同步的情况非常多。Windows CE.NET给我们提供了很多的同步机制,熟练的掌握这些机制并合理运用会使线程之间的同步更合理、更高效。
Windows CE.NET具有两种运行模式:用户模式和内核模式。并且允许一个运行于用户模式的应用程序随时切换为内核模式,或切换回来。线程同步的有些解决办法运行在用户模式,有些运行在内核模式。《Windows核心编程》上说从用户模式切换到内核模式再切换回来至少要1000个CPU周期。查看CE下API函数SetKMode的源码,这个函数用于在两种模式间切换,改变模式只需修改一些标志,至于需要多少个CPU周期很难确定。但至少可以肯定来回切换是需要一定时间的。所以在选择同步机制上应该优先考虑运行在用户模式的同步解决办法。

 

1、互锁函数
互锁函数运行在用户模式。它能保证当一个线程访问一个变量时,其它线程无法访问此变量,以确保变量值的唯一性。这种访问方式被称为原子访问。互锁函数及其功能见如下列表:
 

函数

参数和功能

InterlockedIncrement

参数为PLONG类型。此函数使一个LONG变量增1

InterlockedDecrement

参数为PLONG类型。此函数使一个LONG变量减1

InterlockedExchangeAdd

参数1为PLONG类型,参数2为LONG类型。此函数将参数2赋给参数1指向的值

InterlockedExchange

参数1为PLONG类型,参数2为LONG类型。此函数将参数2的值赋给参数1指向的值

InterlockedExchangePointer

参数为PVOID* 类型,参数2为PVOID类型。此函数功能同上。具体参见帮助

InterlockedCompareExchange

参数1为PLONG类型,参数2为LONG类型,参数3为LONG类型。此函数将参数1指向的值与参数3比较,相同则把参数2的值赋给参数1指向的值。不相同则不变

InterlockedCompareExchangePointer

参数1为PVOID* 类型,参数2为PVOID类型,参数3为PVOID。此函数功能同上。具体参见帮助

2、临界区
临界区对象运行在用户模式。它能保证在临界区内所有被访问的资源不被其它线程访问,直到当前线程执行完临界区代码。除了API外,MFC也对临界区函数进行了封装。临界区相关函数:

        void  InitializeCriticalSection ( LPCRITICAL_SECTION );
        void  EnterCriticalSection ( LPCRITICAL_SECTION );
        void  LeaveCriticalSection ( LPCRITICAL_SECTION );
        void  DeleteCriticalSection ( LPCRITICAL_SECTION );       

举例如下:

        void CriticalSectionExample (void)
        {
            CRITICAL_SECTION  csMyCriticalSection;
        InitializeCriticalSection (&csMyCriticalSection);  ///初始化临界区变量
  
            __try
           {
           EnterCriticalSection (&csMyCriticalSection);   ///开始保护机制
           ///此处编写代码
            }
  
            __finally   ///异常处理,无论是否异常都执行此段代码
           {
           LeaveCriticalSection (&csMyCriticalSection);  ///撤销保护机制
           }
        }
       

MFC类使用更简单:

CCriticalSection  cs;
  
cs.Lock();
///编写代码
cs.Unlock();       
  

使用临界区要注意的是避免死锁。当有两个线程,每个线程都有临界区,而且临界区保护的资源有相同的时候,这时就要在编写代码时多加考虑。

3、事件对象
  事件对象运行在内核模式。与用户模式不同,内核模式下线程利用等待函数来等待所需要的事件、信号,这个等待过程由操作系统内核来完成,而线程处于睡眠状态,当接收到信号后,内核恢复线程的运行。内核模式的优点是线程在等待过程中并不浪费CPU时间,缺点是从用户模式切换到内核模式需要一定的时间,而且还要切换回来。在讲解事件对象前应该先谈谈等待函数。等待函数有四个。具体参数和功能见下表:

 

函数

参数和功能

WaitForSingleObject

参数1为HANDLE类型,参数2为DWORD类型。此函数等待参数1标识的事件,等待时间为参数2的值,单位ms。如果不超时,当事件成为有信号状态时,线程唤醒继续运行。

WaitForMultipleObjects

参数1为DWORD类型,参数2为HANDLE * 类型,参数3为BOOL类型,参数4为DWORD类型。此函数等待参数2指向的数组中包含的所有事件。如果不超时,当参数3为FALSE时,只要有一个事件处于有信号状态,函数就返回这个事件的索引。参数3为TRUE时,等待所有事件都处于有信号状态时才返回。

MsgWaitForMultipleObjects

参数1为DWORD类型,参数2为LPHANDLE类型,参数3为BOOL类型,参数4为DWORD类型,参数5为DWORD类型。此函数功能上同WaitForMultipleObjects函数相似,只是多了一个唤醒掩码。唤醒掩码都是和消息有关的。此函数不但能够为事件等待,还能为特定的消息等待。其实这个函数就是专为等待消息而定义的。

MsgWaitForMultipleObjectsEx

参数1为DWORD类型,参数2为LPHANDLE类型,参数3为DWORD类型,参数4为DWORD类型,参数5为DWORD类型。此函数是MsgWaitForMultipleObjects函数的扩展。将原来函数的参数3除掉,添加参数5为标志。标志有两个值:0或MWMO_INPUTAVAILABLE。

如果一个线程既要执行大量任务同时又要响应用户的按键消息,这两个专用于等待消息的函数将非常有用。

和事件有关的函数有:

HANDLE  CreateEvent(LPSECURITY_ATTRIBUTES  lpEventAttributes,
                                    BOOL bManualReset,
                                    BOOL  bInitialState,
                                    LPTSTR  lpName);
  
BOOL  SetEvent(HANDLE  hEvent );
BOOL  PulseEvent(HANDLE  hEvent);
BOOL  ResetEvent(HANDLE  hEvent);
  
HANDLE  OpenEvent(DWORD  dwDesiredAccess,
                               BOOL  bInheritHandle,
                               LPCTSTR  lpName );
  

事件对象是最常用的内核模式同步方法。它包含一个使用计数和两个BOOL变量。其中一个BOOL变量指定这个事件对象是自动重置还是手工重置。另一个BOOL变量指定当前事件对象处于有信号状态还是无信号状态。


函数CreateEvent创建一个事件对象,参数1必须为NULL,参数2指定是否手工重新设置事件对象的状态。如果为FALSE,当等待函数接到信号并返回后此事件对象被自动置为无信号状态。这时等待此事件对象的其它线程就不会被唤醒,因为事件对象已经被置为无信号状态。如果参数2设置为TRUE,当等待函数接到信号并返回后事件对象不会被自动置于无信号状态,其它等待此事件对象的线程都能够被唤醒。用ResetEvent函数可以手工将事件对象置为无信号状态。相反SetEvent函数将事件对象置为有信号状态。PulseEvent函数将事件对象置为有信号状态,然后立即置为无信号状态,在实际开发中这个函数很少使用。OpenEvent函数打开已经创建的事件对象,一般用于不同进程内的线程同步。在调用CreateEvent创建一个事件对象时,传递一个名字给参数4,这样在其它进程中的线程就可以调用OpenEvent函数并指定事件对象的名字,来访问这个事件对象。

4、互斥对象
  互斥对象运行在内核模式。它的行为特性同临界区非常相似,在一个线程访问某个共享资源时,它能够保证其它线程不能访问这个资源。不同的是,互斥对象运行在内核模式,从时间上比临界区要慢。由于内核对象具有全局性,不同的进程都能够访问,这样利用互斥对象就可以让不同的进程中的线程互斥访问一个共享资源。而临界区只能在一个进程内有效。

和互斥相关的函数有:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES  lpMutexAttributes,
                                BOOL  bInitialOwner,
                                LPCTSTR  lpName);
  
BOOL  ReleaseMutex(HANDLE  hMutex);       
  

互斥对象包含一个引用计数,一个线程ID和一个递归计数。引用计数是所有内核对象都含有的。线程ID表示哪个线程正在使用互斥资源,当ID为0时,互斥对象发出信号。递归计数用于一个线程多次等待同一个互斥对象。函数CreateMutex创建一个互斥对象,参数1必须设置为NULL,参数2如果设置为FALSE,表示当前线程并不占有互斥资源,互斥对象的线程ID和递归计数都被设置为0,互斥对象处于有信号状态。如果设置为TRUE,表示当前线程将占有互斥资源,互斥对象的线程ID被设置为当前线程ID,递归计数被设置为1,互斥对象处于无信号状态。当调用等待函数时,等待函数检验互斥对象的线程ID是否为0,如果为0,说明当前没有线程访问互斥资源,内核将线程唤醒,并且将互斥对象的递归计数加1。当一个线程被唤醒后,必须调用函数ReleaseMutex将互斥对象的递归计数减1。如果一个线程多次调用等待函数,就必须以同样的次数调用ReleaseMutex函数。与其它Windows不同的是,和互斥相关的函数中没有OpenMutex函数。要在不同进程中访问同一互斥对象,调用CreateMutex函数,参数传递互斥对象的名称,返回这个互斥对象的句柄。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值