WinCE 5.0邊做邊學(xué)(5)(6)
通常,我們都是利用OutpubDebugString函數(shù)來實(shí)現(xiàn)調(diào)試信息的輸出的,但是由于系統(tǒng)底層的調(diào)試信息非常繁多,如果這樣大量的調(diào)試信息用于實(shí)時(shí)輸出的話一定會(huì)影響到系統(tǒng)的性能和實(shí)時(shí)性,也就影響到了系統(tǒng)的運(yùn)行。如果有一種方式能允許開發(fā)人員自己選擇輸出哪些調(diào)試信息,不輸出哪些調(diào)試信息的話,那么就可以讓開發(fā)人員只看到關(guān)心的調(diào)試信息,而把諸如鍵盤按鍵、鼠標(biāo)移動(dòng)等無用的調(diào)試信息隱去,則可以更好的提高開發(fā)效率,迅速找到問題所在。
調(diào)試區(qū)就是為了解決以上提出的問題的,對(duì)某一個(gè)驅(qū)動(dòng)程序,它規(guī)定好自己向外輸出的調(diào)試信息的分類,比如初始化時(shí)的信息,出錯(cuò)時(shí)的信息,釋放時(shí)的信息,激活時(shí)的信息等,然后分成幾個(gè)調(diào)試區(qū),在現(xiàn)有的CE版本中最多允許16個(gè)調(diào)試區(qū)。開發(fā)人員通過Platform Builder中Target菜單下的CE Debug Zones命令來決定想要得到哪一個(gè)或哪幾個(gè)調(diào)試區(qū)的信息,在驅(qū)動(dòng)程序中則可以根據(jù)開發(fā)人員的選擇來輸出指定調(diào)試區(qū)的信息。這就是調(diào)試區(qū)大體上的工作原理。
接下來,我們就來看一下調(diào)試區(qū)的定義,聲明,注冊(cè)及使用。
在程序中使用調(diào)試區(qū)之前必須先定義它們,一個(gè)程序的16個(gè)調(diào)試區(qū)編號(hào)分別為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版本中使用調(diào)試區(qū)了,而在RELEASE版本中則將其全部定義為0,調(diào)試信息即不再輸出。
在程序中,除了以上的定義以外,還要聲明幾個(gè)專用的調(diào)試信息輸出函數(shù),這些函數(shù)與OutputDebugString函數(shù)的區(qū)別就在于在調(diào)用時(shí)需要指定對(duì)應(yīng)的調(diào)試區(qū),這些函數(shù)以及以上用到的DEBUGZONE宏的定義都在DbgApi.h頭文件中,因此只要在源程序中包含此頭文件即可。除此以外,還需要一個(gè)全局的DEBPARAM類型的變量命名為dpCurSettings,以供集成開發(fā)環(huán)境和調(diào)試信息輸出函數(shù)使用。其代碼樣例如下:
#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調(diào)試區(qū)作為默認(rèn)被開發(fā)人員選中的調(diào)試區(qū)。
要想使用調(diào)試區(qū),還需要做的最后一件準(zhǔn)備的事情就是在程序中進(jìn)行注冊(cè),也就是在程序啟動(dòng)時(shí)通知集成開發(fā)環(huán)境本程序中要使用調(diào)試區(qū),這個(gè)注冊(cè)很簡(jiǎn)單,只要在程序的入口處使用DEBUGREGISTER宏即可,樣例如下:
DllEntry (
HANDLE hinstDLL,
DWORD Op,
LPVOID lpvReserved
)
{
switch (Op) {
case DLL_PROCESS_ATTACH :
DEBUGREGISTER((HINSTANCE)hinstDLL);
break;
……
至于調(diào)試區(qū)的使用,完全是幾個(gè)宏的使用而已,我想做程序的人都會(huì)用的,常用的宏如下:
DEBUGMSG(),DEBUGLED(),RETAILMSG(),RETAILLED(),ERRORMSG(),DEBUGCHK()
好了,調(diào)試區(qū)就概要的說了這么多,如此復(fù)雜的機(jī)制在自己的程序中寫起來是煩瑣了點(diǎn),不過如果你需要的話,可以從CE現(xiàn)有的例程序中復(fù)制過來,這樣就省了很多麻煩事,也不會(huì)出錯(cuò)。下圖是在PB中使用調(diào)試區(qū)的截圖,當(dāng)選中某一個(gè)調(diào)試區(qū)后,如果該調(diào)試區(qū)有調(diào)試信息則會(huì)在DEBUG窗口輸出的。自己試試吧!
在學(xué)習(xí)驅(qū)動(dòng)程序之前,我們還有很多東西要了解。想來想去,可能最重要的還是中斷了,所以,這次我們花點(diǎn)時(shí)間來了解一下在Windows CE中的中斷機(jī)制。
凡是學(xué)過計(jì)算機(jī)原理的人都知道中斷是什么東西,所以這些基本知識(shí)我們就不再詳述了,我們下面就先看一下CE對(duì)中斷的整體處理流程,以方便從全局上有個(gè)整體的認(rèn)識(shí)。
下圖是CE中中斷處理的流程圖示
我們分布來解釋上圖中的流程:
1、硬件設(shè)備向Kernel發(fā)送中斷異常的代碼,如果檢測(cè)到這個(gè)中斷異常,就會(huì)被Kernel層的異常處理所截獲;
2、中斷服務(wù)調(diào)度程序會(huì)調(diào)用OAL例程中的OEMInterruptDisable函數(shù),這個(gè)函數(shù)會(huì)通知硬件在處理完這一中斷前關(guān)閉特殊的中斷,但其他的中斷仍然處于開放狀態(tài);
3、中斷服務(wù)例程ISR被調(diào)用以決定如何來處理這一中斷;
4、Kernel接收到ISR的返回值以得知如何處理這一中斷。它的響應(yīng)結(jié)果之一是忽略掉這一中斷不作處理(SYSINTR_NOP),另一結(jié)果是準(zhǔn)備執(zhí)行IST。
5、Kernel引發(fā)中斷服務(wù)調(diào)度程序來喚醒中斷服務(wù)線程去工作。IST是常規(guī)的Win32線程,一旦啟動(dòng)后,它會(huì)創(chuàng)建必要的EVENT然后等待該EVENT被激發(fā)。中斷服務(wù)調(diào)度通過調(diào)用PulseEvent函數(shù)來激發(fā)EVENT,從而喚醒IST線程運(yùn)行;
6、當(dāng)喚醒以后,IST會(huì)對(duì)中斷進(jìn)行必要的處理如將數(shù)據(jù)移動(dòng)到緩沖區(qū)或其他有意義的事;
7、如果需要的話,IST會(huì)借助于I/O支持例程訪問硬件設(shè)備;
8、當(dāng)IST處理完成后,它會(huì)調(diào)用InterruptDone函數(shù)通知Kernel;
9、Kernel調(diào)用OEMInterruptDone函數(shù)完成此次中斷的處理過程,OAL例程通知硬件設(shè)備重新啟用中斷。
以上就是中斷在CE中簡(jiǎn)要的處理過程。這其中還涉及到幾個(gè)函數(shù)的使用,包括:
1、供OAL調(diào)用的ISR函數(shù)
HookInterrupt函數(shù)在OEMInit函數(shù)中被調(diào)用以關(guān)聯(lián)IRQ和ISR;
UnhookInterrupt函數(shù)用來終止IRQ和ISR的關(guān)聯(lián)。
2、供驅(qū)動(dòng)程序調(diào)用的IST函數(shù)
InterruptInitialize函數(shù)用來將EVENT對(duì)象和邏輯中斷號(hào)關(guān)聯(lián)并允許中斷;
InterruptDone函數(shù)用來通知中斷處理的結(jié)束;
InterruptDisable函數(shù)被驅(qū)動(dòng)程序調(diào)用以關(guān)閉中斷同時(shí)取消被InterruptInitialize初始化的EVENT對(duì)象。
下面我們?cè)俜謩e來看一下最重要的兩部分,ISR和IST。
ISR屬于OAL層,通常是用匯編語(yǔ)言編寫的,它可以將CPU寄存器中的數(shù)據(jù)移動(dòng)到內(nèi)存緩沖區(qū)中,但是它不能做更多的工作,其中一個(gè)原因就是它不能訪問到用戶態(tài)的存儲(chǔ)區(qū),它要把這些工作交給IST來完成。它做的另一項(xiàng)工作是進(jìn)行物理中斷號(hào)和邏輯中斷號(hào)的映射。一個(gè)物理設(shè)備比如鍵盤在一種平臺(tái)上可能產(chǎn)生4號(hào)中斷,在另一種平臺(tái)上可能產(chǎn)生15號(hào)中斷,經(jīng)過ISR以后,它就會(huì)把這一物理中斷轉(zhuǎn)換成CE中標(biāo)準(zhǔn)的SYSINTR_KEYBOARD邏輯中斷。Kernel就會(huì)根據(jù)這個(gè)邏輯中斷值找到對(duì)應(yīng)的EVENT從而喚醒IST。
ISR有兩種,一種是單ISR模式, 即全局只有一個(gè)ISR,它適用于不支持多中斷的CPU,在這種情況下,OAL會(huì)提供一個(gè)OEMInterruptHandler的命令I(lǐng)SR。另一種是多ISR模式,即CPU有多個(gè)硬件中斷的情況,OAL通過HookInterrupt函數(shù)為每一個(gè)中斷調(diào)用ISR。
IST是驅(qū)動(dòng)程序中的用戶態(tài)線程,它來執(zhí)行中斷的處理工作。在啟動(dòng)后它會(huì)空閑等待EVENT的激發(fā)狀態(tài),激發(fā)后處理真正的中斷處理過程,最后調(diào)用InterruptDone函數(shù)標(biāo)識(shí)中斷處理完成。它通常通過CeSetThreadPriority函數(shù)設(shè)置在較高的優(yōu)先級(jí)狀態(tài)。
以上是對(duì)中斷的簡(jiǎn)要了解,在WINCE5的驅(qū)動(dòng)程序中,很大的變化就是把很多過程化的東西變成了面向?qū)ο蟮姆绞?,即進(jìn)行了以類為基礎(chǔ)的封裝,這樣代碼變得非常層次化,如果你想了解以上這些中斷在具體驅(qū)動(dòng)程序中的實(shí)現(xiàn),建議還是先來看看CE4中的代碼,似乎更明顯一些。
好了,此次的內(nèi)容不多,但是較空洞,最好配合查閱驅(qū)動(dòng)程序的源程序如串口的,鍵盤的,比如鍵盤的驅(qū)動(dòng)中就有非常明顯的IST,很容易看到它是如何設(shè)置優(yōu)先級(jí)的,如果等待EVENT的,如何處理鍵盤消息的以及如何完成中斷的,代碼附后,這樣才能加強(qiáng)理解。即使自己寫驅(qū)動(dòng),也不一定完全從頭編寫,在以在別人的架構(gòu)上修改以縮短開發(fā)周期。
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;
}
評(píng)論