📄 jiurl键盘驱动 5.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0058)http://jiurl.nease.net/cn/document/KbdDriver/JiurlKbd5.htm -->
<HTML><HEAD><TITLE>JIURL键盘驱动 5</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键盘驱动 </FONT><FONT
face=宋体>5</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>7 键盘驱动的运作</B><BR><BR>7.1 输入数据队列简介<BR><BR>i8042prt 和 kbdclass
各有一个输入数据队列,他们是循环使用的缓冲区。他们的每个单元是一个 KEYBOARD_INPUT_DATA 结构。<BR><BR>i8042prt
的自定义的设备扩展中,保存着一些指针和计数值,用来使用它的那个输入数据队列。<BR>PKEYBOARD_INPUT_DATA 类型的
InputData , DataIn , DataOut , DataEnd。ULONG 类型的
InputCount。KEYBOARD_INPUT_DATA 类型的 CurrentInput 。<BR>InputData
这个指针,指向输入数据队列的开头。<BR>DataEnd 这个指针,指向输入数据队列的结尾。<BR>DataIn
这个指针,指向要进入队列的新数据,将要被放在队列中的位置。<BR>DataOut
这个指针,指向要出队列的数据,在队列中开始的位置。<BR>InputCount 这个值为输入数据队列中,数据的个数。<BR>CurrentInput
存放当前从i8042芯片中获得的数据。<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>KEYBOARD_INPUT_DATA 结构在 ntddkbd.h
中定义<BR><BR>typedef struct _KEYBOARD_INPUT_DATA { // 0xC<BR>USHORT
UnitId;<BR>USHORT MakeCode;<BR>USHORT Flags;<BR>USHORT Reserved;<BR>ULONG
ExtraInformation;<BR>} KEYBOARD_INPUT_DATA,
*PKEYBOARD_INPUT_DATA;<BR><BR><BR>7.2
键盘驱动对读请求的处理<BR><BR> 键盘驱动的应用层线程
win32k!RawInputThread,总是会发一个 IRP_MJ_READ 的 IRP
给键盘设备栈的栈顶设备对象,要求读入数据。键盘设备栈栈顶设备对象所在的驱动 kbdclass 的
kbdclass!KeyboardClassRead 处理这个 IRP_MJ_READ 的 IRP。在初始化过程中
win32k!RawInputThread 发第一个 IRP_MJ_READ 的 IRP。之后,每当这个 IRP_MJ_READ 的 IRP
得到数据结束返回给应用层之后,应用层又会再发一个 IRP_MJ_READ 的 IRP。<BR><BR>我们使用WinDbg的!irp命令,看看
kbdclass!KeyboardClassRead 传入的参数 Irp<BR><BR>kd> !irp fe43e008<BR>Irp is
active with 6 stacks 6 is current (= 0xfe43e12c)<BR>No Mdl System buffer =
fe42d868 Thread fe42ed60: Irp stack trace. <BR>cmd flg cl Device File
Completion-Context<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000 00000000
00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000 00000000
00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000 00000000
00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000 00000000
00000000<BR>[ 0, 0] 0 0 00000000 00000000
00000000-00000000 <BR><BR>Args: 00000000 00000000 00000000
00000000<BR>>[ 3, 0] 0 0 fe4f5df0 fe42d908
00000000-00000000 <BR>\Driver\Kbdclass<BR>Args: 00000078 00000000
00000000 00000000<BR><BR>查看当前的 IO_STACK_LOCATION 中的详细内容<BR><BR>kd>
!strct io_stack_location fe43e12c<BR>struct _IO_STACK_LOCATION
(sizeof=36)<BR>+00 byte MajorFunction = 03 .<BR>+01 byte MinorFunction =
00 .<BR>+02 byte Flags = 00 .<BR>+03 byte Control = 00 .<BR>+04 union
__unnamed19 Parameters<BR>+04 struct __unnamed23 Read<BR>+04 uint32 Length
= 00000078<BR>+08 uint32 Key = 00000000<BR>+0c union _LARGE_INTEGER
ByteOffset<BR>+0c uint32 LowPart = 00000000<BR>+10 int32 HighPart =
00000000<BR>+0c struct __unnamed3 u<BR>+0c uint32 LowPart =
00000000<BR>+10 int32 HighPart = 00000000<BR>+0c int64 QuadPart =
0000000000000000<BR>+14 struct _DEVICE_OBJECT *DeviceObject =
FE4F5DF0<BR>+18 struct _FILE_OBJECT *FileObject = FE42D908<BR>+1c function
*CompletionRoutine = 00000000<BR>+20 void *Context =
00000000<BR><BR>我们看到当前 IO_STACK_LOCATION 的 MajorFunction 为 0x03,即
IRP_MJ_READ。Parameters.Read.Length 为 0x78,表明
Irp->AssociatedIrp.SystemBuffer 指向0x78字节大小的空间,一个 KEYBOARD_INPUT_DATA
的大小为 0xc,所以 Irp->AssociatedIrp.SystemBuffer
中最多可以放10(十进制)个KEYBOARD_INPUT_DATA。<BR><BR>我这里 kbdclass 和 i8042prt
的输入数据队列的大小都为 0x64*sizeof(KEYBOARD_INPUT_DATA),即100(十进制)个
KEYBOARD_INPUT_DATA 的大小。<BR><BR>kbdclass!KeyboardClassRead 中,<BR>调用
IoMarkIrpPending(Irp) ,使这个 IRP pending,然后调用 IoStartPacket 。IoStartPacket
将调用驱动的 StartIo 例程,kbdclass 的 StartIo 例程是
KeyboardClassStartIo。<BR><BR>kbdclass!KeyboardClassStartIo 的作用是,检查
kbdclass 的输入数据队列中是否有数据,如果有的话,直接从 kbdclass
的输入数据队列中,取出数据,满足读请求。<BR><BR>kbdclass 的输入数据队列中的数据是从哪里来的呢?如果从 i8042prt
的输入数据队列中来的输入数据太多,一个 IRP_MJ_READ 的 IRP 读不完的话,则 IRP 读走它所能读的 10 个
KEYBOARD_INPUT_DATA,而剩余的数据就会被放入 kbdclass 的输入数据队列中。通常情况下,从 i8042prt
的输入数据队列中来的输入数据都能被一个 IRP_MJ_READ 的 IRP
全部取走,不会有剩余。<BR><BR>kbdclass!KeyboardClassStartIo 中,<BR>判断
deviceExtension->InputCount 是否为0。deviceExtension->InputCount 中保存着
kbdclass 的输入数据队列的中数据的个数。也就是判断 kbdclass 的输入数据队列中是否有数据。<BR>没有数据的话,通常是这种情况,设置
deviceExtension->RequestIsPending 为 TRUE,返回。<BR>有数据的话,调用 RtlMoveMemory
把kbdclass的输入数据队列中的数据复制到 Irp->AssociatedIrp.SystemBuffer 中,设置
deviceExtension->RequestIsPending 为 FALSE,调用 IoCompleteRequest 结束这个
IRP_MJ_READ 的 IRP。<BR><BR>简单的说,应用层发来一个 IRP_MJ_READ 的 IRP 要求读数据。<BR>如果
kbdclass 的输入数据队列中没有数据的话,这个 IRP_MJ_READ 的 IRP 就 pending
在那里,等待键盘上的键被按下,产生输入数据。通常都是这种情况。<BR>如果 kbdclass
的输入数据队列中有数据的话,也就是说键盘的输入数据太多,上一个 IRP_MJ_READ 的 IRP 没有取完,剩下的放在了 kbdclass
的输入数据队列中,那么现在这个 IRP_MJ_READ 的 IRP 从 kbdclass
的输入数据队列中直接取出数据,然后被完成。当我们按住一个键不放的时候,就能看到这种情况。<BR><BR>7.3
键盘驱动的中断<BR><BR>键盘驱动在初始化的过程中,调用 IoConnectInterrupt 连接了键盘的驱动。其中调用时的参数
ServiceRoutine 为 I8042KeyboardInterruptService ,参数 Vector 为
0xb3。<BR><BR>我们使用 WinDbg 的 !idt 命令,查看这时的中断描述符表<BR><BR>kd>
!idt<BR><BR>IDT for processor #0<BR><BR>00: 804625e6
(nt!KiTrap00)<BR>...<BR>b3: fe4cf264
(Vector:b3,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042KeyboardInterruptService(fe1c1750))<BR>b4:
80461138 (nt!KiUnexpectedInterrupt132)<BR>...<BR>ff: 804613f0
(nt!KiUnexpectedInterrupt207)<BR><BR>可以看到中断向量为 b3 的中断服务例程的入口地址为
fe4cf264,而并不是 i8042prt!I8042KeyboardInterruptService 的入口地址
fe1c1750。那么这个入口地址是什么呢?原来,<BR><BR>调用 IoConnectInterrupt ,将产生一个 KINTERRUPT
结构,中断描述符表中保存的中断服务例程的入口地址就是这个 KINTERRUPT 结构的 DispatchCode 的地址。中断时,执行这个
DispatchCode ,DispatchCode 进行些处理后,会调用 nt!KiInterruptDispatch ,而
nt!KiInterruptDispatch 根据 KINTERRUPT 结构中的 ServiceRoutine ,也就是
IoConnectInterrupt 的参数 ServiceRoutine 的入口地址,调用驱动中的服务例程。<BR><BR>我们从
http://www.insidewindows.info/ntifs.h 可以找到 KINTERRUPT 的结构定义<BR><BR>typedef
struct _KINTERRUPT { // Size: 0x1E4<BR>/*000*/ CSHORT Type;<BR>/*002*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -