admin 管理员组

文章数量: 887019

 Windows子系统

1  Windows子系统结构

Windows子系统结构,如图:

Windows子系统有用户模式和内核模式组件。列出这些组件的职责:

a. 内核模块win32k.sys。是Windows内核的扩展。包含两大功能组成部分:

  1. 窗口管理器(window manager): 负责控制窗口显示、管理屏幕输出、手机来自键盘鼠标和其他设备的输入,以及将用户信息传递给应用程序。
  2. GDI:图形输出设备的函数库。

b.图形设备驱动程序。

c.Windows环境子系统进程(csrss.exe),它包含以下支持:

  1. 控制台窗口
  2. 创建和删除进程和线程
  3. 支持16位虚拟DOS 机(VDM,Virtual DOS Machine)进程
  4. 其他一些函数,如GetTempFile,DefinedDosDevice、ExitWindowsEx等。

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 主要做了以下的初始化工作:

  1. 调用KeAddSystemServiceTable ,加入一个新的SDT(服务描述表)。
  2. 调用PsEstablishWin32Callouts ,向内核注册一组出掉函数,以便内核在适当时候调用这些出调函数。
  3. 调用MmPageEntireDriver,使得win32k.sys 的所有代码页面和数据页面可被换出内存(pageable)。
  4. 调用InitializeGre ,初始化windows 子系统的图形引擎。
  5. 调用Win32UserInitialize ,初始化Windows 子系统的窗口管理器。

Win32k.sys 在入口例程中,调用KeAddSystemServiceTable ,将win32k.sys 的系统服务表加入到SDT数组KeServiceDescriptorTableShadow。

Win32k.sys 提供的系统服务的名称分类(windows server 2003)

名称

功能说明

NtUser<xxx>

窗口管理服务,如NtUserFindWindowsEx

NtGdi<xxx>

GDI服务,如NtGdiDcObject

NtGdiEng<xxx>

图形引擎服务,如NtGdiEngBitBlt

NtGdiDd<xxx>

DirectDraw服务,如NtGdiDdGetDxHandle

NtGdiDvp<xxx>

与视频端口打交道的DirectDraw服务,如NtGdiDvpCreateVideoPort

NtGdiD3d<xxx>

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线程,主要工作包括:

  1. 将线程的内核栈转换为一个大内核栈。先调用MmCreateKernelStack 创建一个新的内核栈,然后调用KeSwitchKernelStack 替换内核栈。
  2. 调用win32k.sys 注册的出调函数PspW32ProcessCallout.
  3. 将线程的ServiceTable 切换到KeServiceDescriptorTableShadow,从而使该线程可以使用win32k.sys 提供的系统服务。
  4. 调用win32k.sys 的PspW32ThreadCallout。

3  窗口管理

当用户登录到Windows系统时,winlogon 进程会创建一个交互式窗口站(interactive window station) 和三个桌面。应用程序所创建的窗口,一定属于某一个桌面。Windows中窗口站和桌面的关系,如图:

 

窗口站和桌面与进程和线程之间的关系:

  1. 窗口站可通过Windows子系统的系统服务NtUserCreateWindowStation 来创建,当一个进程调用该系统服务来创建一个窗口站时,此窗口站与该调度进程相关联,并且属于该进程所在的会话。
  2. 桌面可通过Windows子系统的系统服务NtUserCreateDesktop来创建,当一个进程调用该系统服务来创建一个桌面时,所创建的桌面属于该进程所属的窗口站,并且与调用线程相关联。
  3. 当一个进程第一次调用Windows子系统的系统服务(非窗口站和桌面的系统服务)时,它自动与一个窗口站和桌面建立连接。连接哪个窗口站,则依据以下规则:
  • a. 该进程已调用过SetProcessWindowStation ,指定了窗口站。
  • b. 从父进程继承一个窗口站。
  • c. 如果该进程没有调用过SetProcessWindowStation,也不能从父进程获得窗口站,那么Windows子系统试图按以下顺序来选择窗口站:
  1. 进程创建函数CreateProcess 的STARTUPINFO参数指定的窗口站名称。
  2. 若进程运行在交互式用户的登录会话中,则连接到交互式窗口站。
  3. 若运行在非交互式的登录会话中,则根据登录会话的标识信息,构造一个窗口站名称,并试图打开该窗口站。如果打开操作不成功,则创建此名称的窗口站已经一个默认桌面。
  4. 当进程连接到一个窗口站以后,Windows子系统给调用线程分配一个桌面。分配桌面规则如下:
  • a . 该线程已调用过SetThreadDesktop指定了桌面。
  • b.  从父进程继承一个桌面。
  • c.  如果该线程没有调用SetThreadDesktop,也不能从父进程获得桌面,那么,Windows子系统按以下顺序选择桌面:
  1. 进程创建函数CreateProcess 的STARTUPINFO参数指定的桌面名称。
  2. 否则,连接到该进程所连接的窗口站的默认桌面。

窗口站被注册到对象管理器名字空间的\\Windows\WindowStations目录下或“\Sessions\<X>\Windows\WindowStations”目录下,X表示会话ID。如Windows Server 2003 sp1 ,”Windows\WindowStations”目录包含以下5 个窗口站:

WinSta0

Service-0x0-3e4$ //NETWORK SERVICE 账户进程所对应的窗口站

Service-0x0-3e5$ //LOCAL SERVICE 账户

Service-0x0-3e7$  //SYSTEM 账户下的

SAWubSta       //由任务调度器(Task Scheduler,一个svchost)创建的

WinSta0是登录用户的默认窗口站,Service-0x0-<xxx>$是非交互式窗口站。

任何一个进程都属于一个会话。在Windows中,会话是按编号来区分的。在系统控制台登录的用户会话为Session 0,远程桌面或终端服务登录到系统中的会话可以是Session 1、Session 2等。每个会话包含一个或多个窗口站,每个窗口站包含一个或多个桌面。

EnumWindowStations 列举当前会话的窗口站

EnumDesktops 列举指定窗口站的桌面

EnumDesktopWindows 列举指定桌面的所有顶级窗口

窗口是Windows子系统中的对象,由win32k.sys 来管理的内核对象,以句柄(HWND)的方式暴露给应用程序代码。每个会话实力都维护一个句柄表,该句柄表由两部分组成:一是该对象在句柄表中的索引,二是确保句柄唯一性的部分。

Windows子系统支持5种窗口:

  1. 可重叠窗口(overlapped window),是桌面上的顶级窗口,通常有标题、边框和客户区域。
  2. 弹出式窗口(pop-up window),是一种特殊的可重叠窗口,往往用做对话框、消息框或者临时跳出应用程序主窗口的窗口。
  3. 子窗口(child window),他们有父窗口,并且受限于父窗口的客户区域。
  4. 层次窗口(layered window),用于实现不规则形状的窗口,或者窗口的形状有动画效果,或者窗口有半透明效果。
  5. 消息窗口(message-only window),仅仅用于发送和接收消息,不可见,不参与桌面上窗口的层次交叠,也不会被列举到。

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或桌面线程获得输入事件的做法:

  1. 向设备发起一个异步操作,在读操作中指定一个APC例程,当设备驱动程序由数据可提供时,I/O管理器在完成时将此APC例程插入到原线程中。
  2. 此APC例程在获得了当前的输入事件后,再次发起一个异步读操作,然后返回。
  3. 当设备驱动程序由输入数据时,会再次出发APC例程。
  4. 以此类推,可通过APC例程不停地接收输入设备的数据。

在一个GUI线程的消息循环中,当该线程要获取一个消息时,win32k.sys 中的系统服务NtUserGetMessage必须检查两个队列:首先检查系统队列,即输入消息队列;如果该队列中没有满足条件的消息,则在检查应用队列,即寄入消息队列。

应用程序调用DispatchMessage处理一个消息时,有两种情况是必须要进入内核的:

  1. 这是一个系统定时器消息
  2. 消息的窗口类著名了这是一个内核窗口类型。

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中从内核回调到用户模式在返回内核的执行过程,如图:

 

本文标签: 子系统 Windows gui