WinCE 5.0邊做邊學(5)(6)
通常,我們都是利用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;
}
評論