进程-->消息队列
消息队列-->鼠标
消息队列-->键盘
消息队列-->其他进程的消息
这个模式怎么区分消息属于谁,是通过一个专门进程处理消息,这样问题是有跨进程通信太费时。Windows不是这样,Linux是这样。微软是把消息队列存到了0环。
怎么找到消息结构体,是KThread(任何一个线程都有个内核结构体EThread,EThread第一个成员有是Kthread,Kthread有个结构体是Win32Thread,这个数据结构比较特殊,只有图形界面才有这个结构,这个结构体有消息队列)
<1> 当线程刚创建的时候,都是普通线程:
Win32Thread是空的。
graph LR
Thread.ServiceTable-->KeServiceDescriptorTable
<2> 当线程第一次调用Win32k.sys时,操作系统线程会调用一个函数:PsConvertToGuiThread
主要做几件事:
1. 扩充内核栈,必须换成64KB的大内核栈,因为普通内核栈只有12KB大小。
2. 创建一个包含消息队列的结构体,并挂到KTHREAD上。(Win32Thread指向新结构体,里有个结构体ThreadInfo,里面有个变量是MessageQueue)
3.
ServiceTable服务表改变指向
graph LR
Thread.ServiceTable-->KeServiceDescriptorTableShadow
<1> 消息队列存储在0环,通过KTHREAD.Win32Thread可以找到
<2> 并不是所有线程都要消息队列,只有GUI线程才有消息队列
<3> 一个GUI线程对应1个消息队列
消息队列与线程的关系:一个GUI线程对应一个消息队列
去了当前窗口所对应的消息队列,
win32wk启动时,会启动两个线程,监控鼠标,和监控键盘的线程。
graph LR
w32k.sys中2个线程鼠标监控线程键盘监控线程-->储存到对应的消息队列中
那么鼠标监控线程怎么通过窗口,找到它的线程,然后找到消息队列呢?
通过CreateWindow跟踪,发现在0环画的。
窗口对象结构图有个成员,这个结构体在0环。
_WINDOW_OBJECT的结构:
...
PTHREADINFO pti; //所属线程
...
所以在某个窗口点击,鼠标监控线程知道哪个窗口,根据窗口成员,找到线程,把这个点击事件放到这个线程的消息队列里。
窗口句柄是全局的,hwnd不是地址,是0环表的索引,就是窗口句柄表。是公共的一张表,不是私有的。
窗口是在0环创建的
窗口句柄是全局的
一个线程可以用多个窗口,但每个窗口只能属于一个线程
一个GUI线程有一个消息队列
graph LR
普通线程-->GUI线程
GUI线程-->THREAD.W32THREAD
THREAD.W32THREAD-->THREADINFO
THREADINFO-->消息队列
一个线程可以有多个窗口,所有窗口共享一个消息队列
graph LR
_WINDOW_OBJECT-->PTHREADINFO_pti_//所属线程
_WINDOW_OBJECT-->WNDPROC_lpfnWndProc_//窗口过程即窗口回调函数
消息队列其实是一组,有7个
<1> SentMessagesListHead //接到SendMessage发来的消息
<2> PostedMessagesListHead //接到PostMessage发来的消息
<3> HardwareMessagesListHead //接到鼠标、键盘的消息
...略
GetMessage( LPMSG lpMsg, //返回从队列中摘下来的消息,把取出消息放在这
HWND hWnd, //过滤条件一:发个这个窗口的消息,取哪个窗口的消息
UNIT wMsgFilterMin, //过滤条件
UNIT wMsgFilterMax //过滤条件
);
GetMessage先看SentMessagesListHead有没有消息,有的话立马处理。即使没有Trans和Dispatch也可以处理。
循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除。
User32!GetMessage 调用 w32k!NtUserGetMessage
do
{
//先判断SentMessagesListHead是否有消息 如果有处理掉
do
{
....
KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
Arguments,
ArgumentLength,
&ResultPointer,
&ResultLength);
....
}while(SentMessagesListHead != NULL)
//以此判断其他的6个队列,里面如果有消息 返回 没有继续
}while(其他队列!=NULL)
getMessage处理完才发送SendMessage接到值,接受到的通过共享内存返回结果发送sendmessage的程序才继续,所以是同步。
postmessage被处理是在Transmessage
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //这个是处理键盘按键的码,转换成字符,比如有这个处理,可以用消息码WM_CHAR得到是字符,没有的话就只能用WM_KEYDOWN,得到的是asiic码。
DispatchMessage(&msg);
}
SentMessagesListHead由GetMessage()处理。
User32!DispatchMessage 调用 w32k!NtUserDispatchMessage
<1> 根据窗口句柄找到窗口对象
<2> 根据窗口对象得到窗口过程函数,由0环发起调用
<1> GetMessage()在处理SentMessagesListHead中消息时,在0环通过KeUserCallback,实现了一个回调,调用了3换的函数。
<2> DispatchMessage()在处理其他队列中的消息时
<3> 内核代码,比如CreateWindow通过0环函数调用了KeUserCallback,KeUserCallback直接从0环调用3环的窗口回调。
这三个都是从0环发起调用3环的函数。
1、从0环调用3环函数的几种方式:
APC、异常、内核回调
2、凡是有窗口的程序就有可能0环直接调用3环的程序。回调机制中0环调用3环的的代码是函数:KeUserModeCallback
3、回到3环的落脚点:
APC:ntdll!KiUserApcDispatcher
异常:ntdll!KiUserExceptionDispatcher
4、内核回调在3环的落脚点:
PEB
+0x2C 回调函数地址表(由User32.dll提供)
KeUserModeCallback函数的第一个参数就是索引
[1] 滴水视频