新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > WinCE 5.0边做边学(5)(6)

WinCE 5.0边做边学(5)(6)

作者:时间:2011-02-25来源:网络收藏
对于实时系统,如何调试是一个很难解决的问题,包括对系统内核的调试,对驱动程序的调试,对应用程序的调试等。对于应用程序,通常各集成开发环境都提供了常规的单步跟踪等调试手段。而对于另两个的调试,Platform Builder除了为我们提供了常规的断点跟踪等手段以外,还和系统的源程序配合使用了一种新的调试手段——Debug Zones调试区。
通常,我们都是利用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;
……
至于调试区的使用,完全是几个宏的使用而已,我想做程序的人都会用的,常用的宏如下:
DEBUGMSG(),DEBUGLED(),RETAILMSG(),RETAILLED(),ERRORMSG(),DEBUGCHK()

好了,调试区就概要的说了这么多,如此复杂的机制在自己的程序中写起来是烦琐了点,不过如果你需要的话,可以从CE现有的例程序中复制过来,这样就省了很多麻烦事,也不会出错。下图是在PB中使用调试区的截图,当选中某一个调试区后,如果该调试区有调试信息则会在DEBUG窗口输出的。自己试试吧!


在学习驱动程序之前,我们还有很多东西要了解。想来想去,可能最重要的还是中断了,所以,这次我们花点时间来了解一下在Windows CE中的中断机制。
凡是学过计算机原理的人都知道中断是什么东西,所以这些基本知识我们就不再详述了,我们下面就先看一下CE对中断的整体处理流程,以方便从全局上有个整体的认识。
下图是CE中中断处理的流程图示


我们分布来解释上图中的流程:
1、硬件设备向Kernel发送中断异常的代码,如果检测到这个中断异常,就会被Kernel层的异常处理所截获;
2、中断服务调度程序会调用OAL例程中的OEMInterruptDisable函数,这个函数会通知硬件在处理完这一中断前关闭特殊的中断,但其他的中断仍然处于开放状态;
3、中断服务例程ISR被调用以决定如何来处理这一中断;
4、Kernel接收到ISR的返回值以得知如何处理这一中断。它的响应结果之一是忽略掉这一中断不作处理(SYSINTR_NOP),另一结果是准备执行IST。
5、Kernel引发中断服务调度程序来唤醒中断服务线程去工作。IST是常规的Win32线程,一旦启动后,它会创建必要的EVENT然后等待该EVENT被激发。中断服务调度通过调用PulseEvent函数来激发EVENT,从而唤醒IST线程运行;
6、当唤醒以后,IST会对中断进行必要的处理如将数据移动到缓冲区或其他有意义的事;
7、如果需要的话,IST会借助于I/O支持例程访问硬件设备;
8、当IST处理完成后,它会调用InterruptDone函数通知Kernel;
9、Kernel调用OEMInterruptDone函数完成此次中断的处理过程,OAL例程通知硬件设备重新启用中断。
以上就是中断在CE中简要的处理过程。这其中还涉及到几个函数的使用,包括:
1、供OAL调用的ISR函数
HookInterrupt函数在OEMInit函数中被调用以关联IRQ和ISR;
UnhookInterrupt函数用来终止IRQ和ISR的关联。
2、供驱动程序调用的IST函数
InterruptInitialize函数用来将EVENT对象和逻辑中断号关联并允许中断;
InterruptDone函数用来通知中断处理的结束;
InterruptDisable函数被驱动程序调用以关闭中断同时取消被InterruptInitialize初始化的EVENT对象。
下面我们再分别来看一下最重要的两部分,ISR和IST。
ISR属于OAL层,通常是用汇编语言编写的,它可以将CPU寄存器中的数据移动到内存缓冲区中,但是它不能做更多的工作,其中一个原因就是它不能访问到用户态的存储区,它要把这些工作交给IST来完成。它做的另一项工作是进行物理中断号和逻辑中断号的映射。一个物理设备比如键盘在一种平台上可能产生4号中断,在另一种平台上可能产生15号中断,经过ISR以后,它就会把这一物理中断转换成CE中标准的SYSINTR_KEYBOARD逻辑中断。Kernel就会根据这个逻辑中断值找到对应的EVENT从而唤醒IST。
ISR有两种,一种是单ISR模式, 即全局只有一个ISR,它适用于不支持多中断的CPU,在这种情况下,OAL会提供一个OEMInterruptHandler的命令ISR。另一种是多ISR模式,即CPU有多个硬件中断的情况,OAL通过HookInterrupt函数为每一个中断调用ISR。
IST是驱动程序中的用户态线程,它来执行中断的处理工作。在启动后它会空闲等待EVENT的激发状态,激发后处理真正的中断处理过程,最后调用InterruptDone函数标识中断处理完成。它通常通过CeSetThreadPriority函数设置在较高的优先级状态。
以上是对中断的简要了解,在WINCE5的驱动程序中,很大的变化就是把很多过程化的东西变成了面向对象的方式,即进行了以类为基础的封装,这样代码变得非常层次化,如果你想了解以上这些中断在具体驱动程序中的实现,建议还是先来看看CE4中的代码,似乎更明显一些。
好了,此次的内容不多,但是较空洞,最好配合查阅驱动程序的源程序如串口的,键盘的,比如键盘的驱动中就有非常明显的IST,很容易看到它是如何设置优先级的,如果等待EVENT的,如何处理键盘消息的以及如何完成中断的,代码附后,这样才能加强理解。即使自己写驱动,也不一定完全从头编写,在以在别人的架构上修改以缩短开发周期。
BOOL
KeybdIstLoop(
PKEYBD_IST pKeybdIst
)
{
SETFNAME(_T("KeybdIstLoop"));

UINT32 rguiScanCode[16];
BOOL rgfKeyUp[16];
UINT cEvents;

DEBUGCHK(pKeybdIst->hevInterrupt != NULL);
DEBUGCHK(pKeybdIst->pfnGetKeybdEvent != NULL);
DEBUGCHK(pKeybdIst->pfnKeybdEvent != NULL);

SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

wait_for_keybd_interrupt:
if (WaitForSingleObject(pKeybdIst->hevInterrupt, INFINITE) == WAIT_OBJECT_0)
{
cEvents = (*pKeybdIst->pfnGetKeybdEvent)
(pKeybdIst->uiPddId, rguiScanCode, rgfKeyUp);

for (UINT iEvent = 0; iEvent cEvents; iEvent) {
(*pKeybdIst->pfnKeybdEvent)(pKeybdIst->uiPddId,
rguiScanCode[iEvent], rgfKeyUp[iEvent]);
}
// cEvents could be 0 if this was a partial scan code, like 0xE0

InterruptDone(pKeybdIst->dwSysIntr_Keybd);
}

goto wait_for_keybd_interrupt;

ERRORMSG(1, (TEXT("KeybdIstLoop: Keyboard driver thread terminating.rn")));
return TRUE;
}

linux操作系统文章专题:linux操作系统详解(linux不再难懂)


评论


相关推荐

技术专区

关闭