?? jiurl鍵盤驅動 3.htm
字號:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0058)http://jiurl.nease.net/cn/document/KbdDriver/JiurlKbd3.htm -->
<HTML><HEAD><TITLE>JIURL鍵盤驅動 3</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<STYLE type=text/css>.title {
FONT-WEIGHT: bold; FONT-SIZE: 21px; LINE-HEIGHT: 48px; FONT-FAMILY: "黑體", Arial, sans-serif; TEXT-DECORATION: none
}
.author {
FONT-SIZE: 12px; LINE-HEIGHT: 16px; FONT-FAMILY: "宋體"
}
.content {
FONT-SIZE: 14px; LINE-HEIGHT: 20px
}
</STYLE>
<META content="MSHTML 6.00.2900.2668" name=GENERATOR></HEAD>
<BODY bgColor=#f7f7f7 topMargin=5>
<DIV align=center>
<CENTER>
<TABLE height=29 cellSpacing=0 cellPadding=0 width="96%" border=0>
<TBODY>
<TR>
<TD class=title width="100%" height=41>
<P align=center><FONT face=宋體>JIURL鍵盤驅動 3</FONT></P></TD></TR></CENTER>
<TR>
<TD class=author width="100%" height=9>
<P align=center><FONT face=宋體>作者: <A
href="mailto:jiurl@mail.china.com">JIURL</A> </FONT></P></TD></TR>
<TR>
<TD class=author width="100%" height=6>
<P align=center><FONT
face=宋體>
主頁: <A href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A>
</FONT></P></TD></TR>
<TR>
<TD class=author width="100%" height=2>
<P align=center><FONT face=宋體> 日期: 2003-12-13</FONT>
</P></TD></TR></TBODY></TABLE></DIV>
<DIV align=center>
<CENTER>
<TABLE height=1 cellSpacing=0 cellPadding=0 width="96%" border=0>
<TBODY>
<TR>
<TD width="100%" height=1>
<HR color=#396da5 SIZE=3>
</TD></TR></TBODY></TABLE></CENTER></DIV>
<DIV align=center>
<TABLE class=content height=200 cellSpacing=0 cellPadding=0 width="96%"
border=0>
<TBODY>
<TR>
<TD vAlign=top width="131%" height=200>
<P><B>4 編譯與調試環境簡介</B><BR><BR>4.1 源碼<BR><BR>
ps/2鍵盤驅動的設備棧有3層,最底層設備對象的驅動是 acpi,中間層設備對象的驅動是 i8042prt,最高層設備對象的驅動是
kbdclass。<BR><BR> DDK 所附的源碼中有 i8042prt 和 kbdclass
的源碼,分別位于 ...\NTDDK\src\input\pnpi8042 ,...\NTDDK\src\input\kbdclass
。<BR><BR> 注意,在目前DDK所附的源碼中沒有 acpi 的源碼,不過 acpi
對于鍵盤驅動幾乎沒有起什么作用。在DDK中可以找到一個叫acpi的目錄,但那個下面并不是acpi.sys的源碼,而是acpiec.sys的源碼,沒有用處。<BR><BR>4.2
關閉文件保護替換系統中的驅動<BR><BR> 我們要用我們自己編譯的 debug 版本的
i8042prt.sys 和 kbdclass.sys 替換在 ...\WINNT\system32\drivers
下系統原來的這兩個sys文件。<BR><BR>不過由于有文件保護,我們替換之后,系統會立刻自動替換會原來的。因此我們關閉了win2k的文件保護。<BR><BR>替換驅動文件還有其他的方法,不過在這里關閉文件保護,直接替換驅動文件,對我們最合適。<BR><BR>4.3
改動源碼以產生調試信息<BR><BR>
為了得到調試信息,我們修改了少量的源碼。修改很多地方可能都可以達到這個目的。這里介紹的只是一種方法。<BR><BR>4.3.1 改動
kbdclass <BR><BR>kbdclass.h 中<BR><BR>#define DEFAULT_DEBUG_LEVEL
0 <BR><BR>改為 <BR><BR>#define DEFAULT_DEBUG_LEVEL 3 //#define
DEFAULT_DEBUG_LEVEL 0<BR><BR>4.3.2 改動 i8042prt<BR><BR>i8042prt.h
中<BR><BR>#define Print(_flags_, _x_) \<BR>if (Globals.DebugFlags &
(_flags_) || !(_flags_)) { \<BR>DbgPrint (pDriverName); \<BR>DbgPrint _x_;
\<BR>}<BR>#define IsrPrint(_flags_, _x_) \<BR>if (Globals.IsrDebugFlags
& (_flags_) || !(_flags_)) { \<BR>DbgPrint (((ULONG)(_flags_)) >=
0x0001000 ? pIsrMou : pIsrKb); \<BR>DbgPrint _x_;
\<BR>}<BR><BR>改為<BR><BR>#define Print(_flags_, _x_) \<BR>{ \<BR>DbgPrint
(pDriverName); \<BR>DbgPrint _x_; \<BR>}<BR>#define IsrPrint(_flags_, _x_)
\<BR>{ \<BR>DbgPrint (((ULONG)(_flags_)) >= 0x0001000 ? pIsrMou :
pIsrKb); \<BR>DbgPrint _x_; \<BR>}<BR><BR>由于我使用 WinDbg
單機調試,當我要獲得按一個鍵的調試信息時,發現鼠標導致的調試信息不停的出現,使我沒有辦法在目標系統中按一個鍵,所以我去掉了一些打印調試信息的語句,來獲得在目標系統中按一個鍵的機會。<BR><BR>注釋掉
I8042MouseInterruptService,I8042MouseIsrDpc,I8xWriteDataToMouseQueue,I8xGetByteAsynchronous
中的所有調試打印語句。<BR><BR>4.4 源碼級調試<BR><BR> 使用 WinDbg
進行源碼級調試。單機使用 WinDbg
,可以參考文章《借助VMware實現單機使用WinDbg》,這篇文章可以在我的主頁上找到。<BR><BR><BR><B>5
鍵盤驅動概述</B><BR><BR>
鍵盤驅動位于應用層win32k!RawInputThread和硬件i8042之間。win32k!RawInputThread 總是發一個
IRP_MJ_READ 的 IRP
到鍵盤設備棧的頂端,等待著來自鍵盤的數據。當i8042有數據要鍵盤驅動取走的時候,就會觸發中斷,這個中斷的中斷服務例程是鍵盤驅動中的函數,于是鍵盤驅動就可以從i8042讀取數據,經過一系列處理最終完成那個等待的
IRP。<BR><BR>5.1 鍵盤設備棧<BR><BR>
我們首先來看看鍵盤驅動的設備棧。對于初始化完成之后,處于運行狀態,并且插有 ps/2
鍵盤的系統,我們看看它的鍵盤設備棧是什么樣子。<BR><BR>我們通過 WinObj 之類的工具查看對象管理器命名空間 (Object
Manager Namespace),可以看到在 \driver\ 下的名叫 kbdclass
的驅動對象。在處于運行狀態下的系統中,打斷進入WinDbg,我們使用WinDbg 的 !drvobj
命令,可以得到它的設備對象列表。<BR><BR>kd> !drvobj \driver\kbdclass<BR>Driver object
(fe4f6330) is for:<BR>\Driver\Kbdclass<BR>Driver Extension List: (id ,
addr)<BR><BR>Device Object list:<BR>fe4f5df0 <BR><BR>設備對象只有一個,地址為
fe4f5df0,使用WinDbg 的 !devobj 命令,來獲得一些這個設備對象的信息。<BR><BR>kd> !devobj
fe4f5df0<BR>Device object (fe4f5df0) is for:<BR>KeyboardClass0
\Driver\Kbdclass DriverObject fe4f6330<BR>Current Irp fe43e6c8 RefCount 0
Type 0000000b Flags 00002044<BR>DevExt fe4f5ea8 DevObjExt
fe4f5fd8 <BR>ExtensionFlags (0000000000) <BR>AttachedTo (Lower)
fe4f5020 \Driver\i8042prt<BR>Device queue is busy -- Queue
empty.<BR><BR>看到這個設備對象有個名字叫 KeyboardClass0,我們用 WinObj 也可以在 \Device\
下看到這個設備對象。<BR><BR>使用 WinDbg 的 !devstack 命令,來看看設備對象 fe4f5df0 也就是
\Device\KeyboardClass0 所在的設備棧<BR><BR>kd> !devstack fe4f5df0<BR>!DevObj
!DrvObj !DevExt ObjectName<BR>> fe4f5df0 \Driver\Kbdclass fe4f5ea8
KeyboardClass0<BR>fe4f5020 \Driver\i8042prt fe4f50d8 <BR>fe4dd730
\Driver\ACPI fe507468 0000000e<BR>!DevNode fe4fed68 :<BR>DeviceInst is
"ACPI\PNP0303\4&5289e18&0"<BR>ServiceName is
"i8042prt"<BR><BR>我們看到<BR>設備棧最頂層的設備對象是 fe4f5df0,有名字,叫做 KeyboardClass0,屬于驅動
Kbdclass。<BR>設備棧中間層的設備對象是 fe4f5020,沒有名字,屬于驅動 i8042prt。<BR>設備棧最底層的設備對象是
fe4dd730,有名字,0000000e,屬于驅動 ACPI。<BR><BR>!DevNode
部分的內容和注冊表有關,我們會在后面討論。<BR>要注意的是,ACPI用于鍵盤驅動的那個設備對象的名字,和你計算機上安了多少外設有關,在我這里是
0000000e,在你那里很可能不是。<BR>我們看到的
\Driver\Kbdclass,\Driver\i8042prt,\Driver\ACPI
是驅動對象的名字。一個驅動對象就對應著一個驅動程序文件。<BR><BR>我們再看一下 \Driver\i8042prt
的設備對象列表。<BR><BR>kd> !drvobj \driver\i8042prt<BR>Driver object
(fe4f69f0) is for:<BR>\Driver\i8042prt<BR>Driver Extension List: (id ,
addr)<BR><BR>Device Object list:<BR>fe4d3ba0
fe4f5020 <BR><BR>可以看到有兩個設備對象。使用 !devobj
我們可以知道,一個是用于ps/2鍵盤驅動的設備對象,一個是用于ps/2鼠標驅動的設備對象。<BR><BR>acpi
更是有多個設備對象。<BR><BR>最早我就是看到 kbdclass 的名字,猜測這就是鍵盤的驅動程序,進而看到了它的設備棧,看到了
i8042prt,acpi,后來實踐也證明了 kbdclass ,i8042prt 就是鍵盤驅動。<BR><BR>我們這里看到的 ps/2
鍵盤的設備棧,是沒有自己另外安裝其他鍵盤過濾程序的情況。<BR><BR>最頂層的設備對象是 驅動 Kbdclass
的設備對象。<BR>中間層的設備對象是 驅動 i8042prt 的一個設備對象。<BR>最底層的設備對象是 驅動 ACPI
的一個設備對象。<BR><BR>5.2 各層簡介<BR><BR>Kbdclass<BR><BR>鍵盤設備棧的棧頂,應用層發出的所有
IRP,都首先發到這里。對于那個等待鍵盤數據的 IRP_MJ_READ 的
IRP,完全由這一層處理,不向下傳。<BR><BR>i8042prt<BR><BR>對硬件i8042的所有操作都在這一層完成。鍵盤中斷的中斷服務例程也是這一層的函數。使用
WinDbg 的 !idt 命令可以看到這一點。<BR><BR>kd> !idt<BR><BR>IDT for processor
#0<BR><BR>00: 804625e6 (nt!KiTrap00)<BR>01: 80462736
(nt!KiTrap01)<BR>...<BR>b3: fe4cf264
(Vector:b3,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042KeyboardInterruptService(fe1c1750))<BR>...<BR>ff:
804613f0 (nt!KiUnexpectedInterrupt207)<BR><BR>可以看到中斷向量為 b3 的中斷服務例程最終為
i8042prt!I8042KeyboardInterruptService,即 i8042prt 中的函數
I8042KeyboardInterruptService
。<BR><BR>驅動的中斷和一般的中斷有所不同,我們將在后面做討論。<BR><BR>ACPI<BR><BR>這一層幾乎不起什么作用。<BR><BR>5.3
鍵盤驅動的運作<BR><BR>
鍵盤驅動的主要工作就是,當鍵盤上有鍵按下引發中斷時,鍵盤驅動從端口讀出按鍵的掃描碼,最終順利的將它交給在鍵盤設備棧棧頂等待的那個
IRP_MJ_READ 的 IRP。為了完成這個任務,鍵盤驅動使用了兩個循環使用的緩沖區。<BR><BR>i8042prt 和 kbdclass
各有自己的一個,循環使用的緩沖區。他們的每個單元是一個 KEYBOARD_INPUT_DATA
結構,用來存放一個掃描碼及其相關信息。在鍵盤驅動的注釋中,把這個循環使用的緩沖區叫做輸入數據隊列(input data
queue),i8042prt 的那個緩沖區被叫做 port keyboard input data queue,kbdclass
的那個緩沖區被叫做 class input data queue。輸入數據隊列更形象一些,我們也會使用這種叫法。<BR><BR>i8042prt
的自定義的設備擴展中,保存著一些指針和計數值,用來使用它的那個輸入數據隊列。<BR>PKEYBOARD_INPUT_DATA 類型的
InputData , DataIn , DataOut , DataEnd。ULONG 類型的 InputCount。<BR>InputData
這個指針,指向輸入數據隊列的開頭。<BR>DataEnd 這個指針,指向輸入數據隊列的結尾。<BR>DataIn
這個指針,指向要進入隊列的新數據,將要被放在隊列中的位置。<BR>DataOut
這個指針,指向要出隊列的數據,在隊列中開始的位置。<BR>InputCount 這個值為輸入數據隊列中,數據的個數。<BR><BR>kbdclass
的自定義的設備擴展中,保存著一些指針和計數值,用來使用它的那個輸入數據隊列。<BR>PKEYBOARD_INPUT_DATA 類型的
InputData , DataIn , DataOut , DataEnd。ULONG 類型的 InputCount。<BR>InputData
這個指針,指向輸入數據隊列的開頭。<BR>DataEnd 這個指針,指向輸入數據隊列的結尾。<BR>DataIn
這個指針,指向要進入隊列的新數據,將要被放在隊列中的位置。<BR>DataOut
這個指針,指向要出隊列的數據,在隊列中開始的位置。<BR>InputCount
這個值為輸入數據隊列中,數據的個數。<BR><BR>對于這兩個輸入數據隊列,<BR>放入一個數據,這個數據應該被放在 DataIn
處,然后DataIn 后移一格,InputCount 加 1。當 DataIn
移到隊列的結尾時,將從隊列的開頭重新開始。<BR>取出一個數據,這個數據應該從 DataOut 處取出,然后 DataOut
后移一格,InputCount 減 1。當 DataOut 移到隊列的結尾時,將從隊列的開頭重新開始。<BR><BR>對于 i8042prt
的輸入數據隊列。鍵盤中斷服務例程中,從i8042讀出的按鍵信息,放入i8042prt的輸入數據隊列。上層處理輸入的回調函數中,取出i8042prt的輸入數據隊列中的數據。<BR><BR>對于
kbdclass 的輸入數據隊列。只有當那個等待的 IRP_MJ_READ IRP
要求讀的大小,小于i8042prt的輸入隊列中放入的數據時,才被使用。也就是說,只有當那個等著讀的 IRP 讀不完輸入數據時,才使用
kbdclass 的輸入數據隊列。當那個等著讀的 IRP
讀完要求的數據后,還有剩余時,剩余的數據放入kbdclass的輸入數據隊列。下一次的應用層發來的 IRP_MJ_READ
IRP,當發現kbdclass的輸入數據隊列中有數據時,直接從kbdclass的輸入數據隊列中讀出數據。<BR><BR>下面我們對鍵盤驅動處理鍵盤按鍵的過程做一個概括介紹<BR><BR>當鍵盤上一個鍵被按下時,產生一個
Make Code ,引發鍵盤中斷。<BR><BR>當鍵盤上一個鍵被松開時,產生一個 Break
Code,引發鍵盤中斷。<BR><BR>鍵盤中斷導致鍵盤中斷服務例程被執行。導致最終 i8042prt
的I8042KeyboardInterruptService 被執行。I8042KeyboardInterruptService
中,從端口讀出按鍵的掃描碼,放在一個 KEYBOARD_INPUT_DATA 中。將這個 KEYBOARD_INPUT_DATA 放入
i8042prt 的輸入數據隊列中,一個中斷放入一個數據,DataIn 后移一格,InputCount 加 1。最后會
KeInsertQueueDpc 一個進行更多處理的 DPC(延遲過程調用)。<BR><BR>DPC 中,調用上層處理輸入的回調函數(也就是
kbdclass
處理輸入數據的函數),取走i8042prt的輸入數據隊列里的數據。因為設備擴展中保存著上層處理輸入數據的回調函數的入口地址,所以他知道該調用誰。上層處理輸入的回調函數(也就是
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -