Windows子系统
1 Windows子系统结构
Windows子系统结构,如图:
Windows子系统有用户模式和内核模式组件。列出这些组件的职责:
a. 内核模块win32k.sys。是Windows内核的扩展。包含两大功能组成部分:
b.图形设备驱动程序。
c.Windows环境子系统进程(csrss.exe),它包含以下支持:
d.子系统DLL,如user32.dll、advapi32.dll、gdi32.dll、kernel32.dll。
Win32k.sys 注册了一个SDT(系统服务描述符表),将所提供的功能直接以系统服务的形式暴露给用户模式程序。
2 Windows子系统初始化与GUI线程
Windows的会话管理器(smss.exe) 是系统中第一个被创建的用户模式进程,是在Windows执行体和内核初始化完成以后被启动的。Smss 进程的任务之一是,启动Windows环境子系统进程csrss.exe 和Windows登录进程winlogon.exe 。smss 还通过系统服务
NtSetSystemInformation ,指示内核加载win32.sys 模块。它想NtSetSystemInformation 的
SystemInformationClass 参数传递的值位SystemExtendServiceTableInformation。NtSetSystemInformation 调用MmLoadSystemImage 加载指定的
“\SystemRoot\System32\win32k.sys”文件。
Win32k.sys 主要做了以下的初始化工作:
Win32k.sys 在入口例程中,调用KeAddSystemServiceTable ,将win32k.sys 的系统服务表加入到SDT数组KeServiceDescriptorTableShadow。
Win32k.sys 提供的系统服务的名称分类(windows server 2003)
名称 |
功能说明 |
NtUser |
窗口管理服务,如NtUserFindWindowsEx |
NtGdi |
GDI服务,如NtGdiDcObject |
NtGdiEng |
图形引擎服务,如NtGdiEngBitBlt |
NtGdiDd |
DirectDraw服务,如NtGdiDdGetDxHandle |
NtGdiDvp |
与视频端口打交道的DirectDraw服务,如NtGdiDvpCreateVideoPort |
NtGdiD3d |
Direct3D服务,如NtGdiD3dCreateSurfaceEx |
Win32k.sys 在初始化阶段通过PsEstablishWin32Callouts 向内核注册一组出调函数,数据结构WIN32_CALLOUTS_FPNS 定义如下:
typedef struct _WIN32_CALLOUTS_FPNS {
PKWIN32_PROCESS_CALLOUT ProcessCallout; //创建或删除进程时调用
PKWIN32_THREAD_CALLOUT ThreadCallout;;//转成GUI线程或删除时被调用
PKWIN32_GLOBALATOMTABLE_CALLOUT GlobalAtomTableCallout;;//内核的全局原子对象管理器调用该函数来获得当前线程的全局原子对象表的地址
PKWIN32_POWEREVENT_CALLOUT PowerEventCallout;//子系统通过它来接收电源事件
PKWIN32_POWERSTATE_CALLOUT PowerStateCallout;//子系统通过它来接收电源状态
PKWIN32_JOB_CALLOUT JobCallout;//win32k.sys 通过它参与作业管理
PVOID BatchFlushRoutine;//刷新GDI批任务处理
PKWIN32_OBJECT_CALLOUT DesktopOpenProcedure;//打开桌面
PKWIN32_OBJECT_CALLOUT DesktopOkToCloseProcedure;//是否可以关闭桌面
PKWIN32_OBJECT_CALLOUT DesktopCloseProcedure;//解除桌面映射
PKWIN32_OBJECT_CALLOUT DesktopDeleteProcedure;//释放桌面
PKWIN32_OBJECT_CALLOUT WindowStationOkToCloseProcedure;//是否可以关闭窗口站
PKWIN32_OBJECT_CALLOUT WindowStationCloseProcedure;//从全局列表中去除窗口站
PKWIN32_OBJECT_CALLOUT WindowStationDeleteProcedure;//删除窗口站,释放资源
PKWIN32_OBJECT_CALLOUT WindowStationParseProcedure;//解析一个窗口站路径
PKWIN32_OBJECT_CALLOUT WindowStationOpenProcedure;//打开一个窗口站路径
} WIN32_CALLOUTS_FPNS, *PKWIN32_CALLOUTS_FPNS;
Win32k.sys 每个会话被加载一次,驱动程序对象为“\Driver\win32k”,其他模块通过ObReferenceObjectByname 获得此驱动程序对象。
当一个非GUI线程调用的任何一个子系统系统服务(服务号为0x1000~-x1fff)时,线程的ServiceTable 指向KeServiceDescriptorTable,而KeServiceDescriptorTable[1]不包含任务系统服务,此调用导致PsConvertToGuiThread 被调用,从而转为GUI线程。
PsConvertToGuiThread 将一个线程转换为GUI线程,主要工作包括:
3 窗口管理
当用户登录到Windows系统时,winlogon 进程会创建一个交互式窗口站(interactive window station) 和三个桌面。应用程序所创建的窗口,一定属于某一个桌面。Windows中窗口站和桌面的关系,如图:
窗口站和桌面与进程和线程之间的关系:
窗口站被注册到对象管理器名字空间的\\Windows\WindowStations目录下或“\Sessions\
WinSta0
Service-0x0-3e4$ //NETWORK SERVICE 账户进程所对应的窗口站
Service-0x0-3e5$ //LOCAL SERVICE 账户
Service-0x0-3e7$ //SYSTEM 账户下的
SAWubSta //由任务调度器(Task Scheduler,一个svchost)创建的
WinSta0是登录用户的默认窗口站,Service-0x0-
任何一个进程都属于一个会话。在Windows中,会话是按编号来区分的。在系统控制台登录的用户会话为Session 0,远程桌面或终端服务登录到系统中的会话可以是Session 1、Session 2等。每个会话包含一个或多个窗口站,每个窗口站包含一个或多个桌面。
EnumWindowStations 列举当前会话的窗口站
EnumDesktops 列举指定窗口站的桌面
EnumDesktopWindows 列举指定桌面的所有顶级窗口
窗口是Windows子系统中的对象,由win32k.sys 来管理的内核对象,以句柄(HWND)的方式暴露给应用程序代码。每个会话实力都维护一个句柄表,该句柄表由两部分组成:一是该对象在句柄表中的索引,二是确保句柄唯一性的部分。
Windows子系统支持5种窗口:
Windows子系统为桌面中的所有窗口定义了一个z- 序(z-order),即深度顺序。在z-序中,最上面的窗口用户可以看到。
Windows子系统内置了7种窗口类:按钮(Button)、组合框(ComboBox)、编辑框(Edit)、列表框(ListBox)、多文档界面中的子窗口(MDIClient)、滚动条(ScrollBar)和静态文本(Static)。Windows 内部使用的窗口类: 组合框内涵列表框(ComboLBox)、DDE管理库事件(DDEMLEvent)、消息窗口(Message)、菜单(#32768)、桌面窗口(#32769)、对话框(#32770)、任务切换窗口(#32771)和图标(#32772)
CreateWindow或CreateWindowEx 创建一个窗口,对应的win32k.sys系统服务为NtUserCreateWindowEx.;
EnumThreadWindows 列举一个线程创建的窗口;
EnumChildWindows 列举某一指定窗口的所有子窗口;
FindWindow 查找指定类名称或窗口名称的窗口;
FindWindowEx 查找子窗口对象
SetProp、GetProp 设置或获取一个命名属性;
EnumProps 列举一个窗口的命名属性。
Windows 为应用程序提供了消息驱动的编程模型,负责处理用户的线程的主题逻辑通常是一个消息循环,代码如下:
for(;;)
{
if(bRet = GetMessage(&msg,NULL,0,0))//从消息队列中获得一个消息
{
if(bRet =-1) goto ErrorExit; //严重错误处理
TranslateMessage(&msg) ; //处理按键消息,将虚拟键消息转译为字符消息
DispatchMessage(&); //将消息分发到负责处理该消息的窗口
}else
{
Break; //线程接收到WM_QUIT 消息,退出循环
}
}
消息结构定义如下:
typedef struct tagMSG {
HWND hwnd; //接收该消息的窗口的句柄
UINT message; //消息标识符,WM_USER(0x400)以下的消息为系统保留
WPARAM wParam; //该消息的参数,其含义取决于message的值
LPARAM lParam; //该消息的参数,其含义取决于message的值
DWORD time; //该消息被寄到队列中的时间
POINT pt; //记录了当该消息被寄到队列中时的光标位置(按屏幕坐标)
} MSG,*PMSG;
Windows子系统为GUI线程维护了一个消息队列。消息循环不断地获取消息并交给消息的目标窗口的窗口过程来处理。
Windows子系统的消息流,如图:
在每个会话中,Windows子系统进程(csrss.exe)都会创建一个RIT(Raw Input Thread),该线程负责从设备驱动程序获得原始的输入,然后将消息寄到正确的队列中。鼠标的输入是由桌面线程(Desktop thread)的线程来接收的,然后交给RIT线程分发到应用线程中。键盘事件或其他的HID(Human Input Device)事件则直接由RIT线程从设备驱动中获得。
RIT或桌面线程获得输入事件的做法:
在一个GUI线程的消息循环中,当该线程要获取一个消息时,win32k.sys 中的系统服务NtUserGetMessage必须检查两个队列:首先检查系统队列,即输入消息队列;如果该队列中没有满足条件的消息,则在检查应用队列,即寄入消息队列。
应用程序调用DispatchMessage处理一个消息时,有两种情况是必须要进入内核的:
Windows 子系统的消息钩子(hook)机制,SetWindowsHookEx 来安装钩子,UnhookWindowsHookEx 卸载钩子。每个线程可以有多个钩子,按照不同的类型形成一个或多个钩子链(hook chain)。如下表:
钩子类型 |
说明 |
WM_CALLWNDPROC |
在系统发送一个消息到目标窗口过程之前调用的钩子函数 |
WM_CALLWNDPROCRET |
在一个消息被目标窗口过程处理之后调用的钩子函数 |
WH_CBT |
在接收CBT(Computer-Based Training)事件之前调用的钩子函数,这里CBT事件是指系统在处理窗口重要事件、同步消息队列等时引发的各种消息 |
WH_DEBUG |
用于调试其他的钩子函数 |
WH_FOREGROUNDIDLE |
当应用程序的前台线程变成空闲时调用的钩子函数 |
WH_GETMESSAGE |
当应用程序调用GetMessage或PeekMessage从消息队列种获取消息时调用的钩子函数 |
WH_JOURNALPLAYBACK |
应用程序使用此钩子函数回放一序列由WM_JOURNALRECORD钩子记录下来的鼠标和键盘消息 |
WH_JOURNALRECORD |
当子系统从系统消息队列中移除消息时,调用此钩子函数,因而该钩子函数可以记录下消息序列 |
WH_KEYBOARD |
当应用程序调用GetMessage或PeekMessage获取一个键盘消息时调用的钩子函数 |
WH_KEYBOARD_LL |
当一个键盘输入事件被插入到线程的输入队列中时调用的钩子函数 |
WM_MOUSE |
当应用程序调用GetMessage或PeekMessage获取一个鼠标消息时调用的钩子函数 |
WM_MOUSE_LL |
当一个鼠标输入事件被插入到线程的输入队列中时调用的钩子函数 |
WH_MSGFILTER |
当对话框、消息框、菜单或滚动条中的键盘或鼠标操作导致产生的消息被处理时调用的钩子函数 |
WH_SHELL |
通过此钩子函数可以接收系统的Shell事件的通知 |
WM_SYSMSGFILTER |
当对话框、消息框、菜单或滚动条中的键盘或鼠标操作导致产生的消息被处理时调用的钩子函数,是全局钩子 |
所有的钩子都是在内核模式下被激发的,钩子函数是用户模式的。Windows子系统通过KeUserModeCallBack 来调用指定的钩子函数。KeUserModeCallback 和KiCallUserMode 函数机制,他们构造一个陷阱帧,利用系统服务返回机制,指定运行ntdll.dll 的KeUserCallbackDispatcher,由它调用指定的用户钩子函数。待钩子函数返回后,KeUserCallbackDispatcher 通过NtCallbackReturn 系统服务返回内核模式,回到KeUserModeCallback。
Windows中从内核回调到用户模式在返回内核的执行过程,如图: