前言
学习Windows内核的基础(感觉真的好复杂)
基于<<加密与解密>>
Chap7以及网上的多个blog
理论基础
权限级别
CPU设计者将CPU的运行级别从内到外分为4个,R0,R1,R2,R3,权限依次降低
设计之初是想让R0运行内核,R1,R2运行设备驱动,R3运行应用程序
但是为了工作简单,并没有使用R1,R2,而是将设备驱动运行在内核同一级别的R0
分级的目的是为了保护系统的稳定性和安全性。通过限制某些操作只能在高权限级别下执行,操作系统可以防止用户级应用程序意外或恶意地修改关键系统资源,进而导致系统崩溃或安全漏洞。
- Ring0 内核模式
HAL
是一个可加载的核心模块HAL.DLL
,它为运行在WindowsXP
上的硬件平台提供低级接口.WINDOWSXP的执行体是NTOSKRNL.EXE
的上层,内核是其下层。用户层导出并且可以调用的函数接口在NTDLL.DLL
中,通过使用Win32API
或其他环境子系统对它们进行访问 - Ring3 用户模式 程序的代码只能运行在用户模式下,每当它需要使用到系统内核或内核的扩展模块(内核驱动程序)所提供的服务时,应用程序通过硬件指令从用户模式切换到内核模式中;当系统内核完成了所请求的服务以后,控制权又回到用户模式代码。
内存空间布局
Windows中,用户代码和内核代码有各自的运行环境,并且它们可以访问的内存空间也不同。
以x86为例,支持32位寻址,因此支持4GB虚拟内存空间(可以通过PAE技术,增加到36位寻址将寻址空间扩大到64GB)
如图,在4GB的虚拟地址空间中,Windows
系统的内存主要分为内核空间和应用层空间,各占约2GB,其中还包括64KB的NULL空间和非法区域
Windows内存的逻辑地址分为两部分:段选择符
和偏移地址
CPU进行地址翻译的时候,先通过分页机制计算出一个线性地址,再通过页表机制将线性地址映射到物理地址,从而存取物理内存中的数据和指令
x64与x86类似,但是x64的寻址空间太大了,会存在很多空洞,有很多空间并不可用
Windows与内核启动过程
-
启动自检阶段
打开电源,开始自检,从BIOS载入必要指令,进行硬件的初始化检查
-
初始化启动阶段
自检完成,根据CMOS的设置,BIOS加载启动盘,将主引导记录(MBR)中的引导代码载入内存,接着,启动过程由MBR来执行
启动代码会搜索MBR中的分区表,找出活动分区,将第一个扇区中的引导代码载入内存。引导代码检测当前使用的文件系统,查找
ntldr
文件找到后启动该文件
BIOS
将控制权转交给NTLDR
,NTLDR
完成操作系统的启动工作,Windows7
使用的是bootmgr
-
Boot加载
先从启动分区加载
ntldr
,然后对ntldr
进行如下设置- 设置内存模式(如果是x86处理器,且是32位系统)设置为
32-bit flat memory
- 启动一个简单的文件系统,以定位
boot.ini ntoskrnl Hal
等启动文件 - 读取
boot.ini
- 设置内存模式(如果是x86处理器,且是32位系统)设置为
-
检测和配置硬件阶段
这个阶段会检查和配置硬件设备
-
内核加载过程
-
NTLDR
首先加载Windows
内核Ntoskrnl.exe
和硬件抽象层HAL
-
HAL
会对硬件底层的特性进行隔离,然后为操作系统提供统一的调用接口 -
ntldr
从注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet
键下读取这台机器安装的驱动程序,然后依次加载驱动程序,初始化底层设备驱动,在注册表的HEKY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
键下查找Start
键的值为0和1的设备驱动其中
Start
值可以为0,1,2,3,4 数值越小,启动越早SERVICE_BOOT_START(0)
表示内核刚刚初始化,此时加载的都是与系统核心有关的重要驱动程序,如磁盘驱动SERVICE_BOOT_START(1)
晚一点,稍微不那么重要的驱动程序SERVICE_AUTO_START(2)
是从登录界面出现的时候开始加载,如果登录速度比较快,很可能驱动还没加载就已经登录了SERVICE_DEMAND_START(3)
表示在需要的时候手动加载SERVICE_DISABLED(4)
来表示禁止加载
-
-
Windows的会话管理启动
驱动程序加载完成,内核会启动会话管理器。这是一个名为
smss.exe
的程序,在Windows
中第一个创建的用户模式进程,起作用如下- 创建系统环境变量
- 加载
win32k.sys
,这是Windows
子系统的内核模式部分 - 启动
csrss.exe
,(Client/server Runtime SubSystem)这是Windows
子系统的用户模式部分 - 启动
winlogon.exe
,管理用户登录和注销,验证用户的登录凭据,并且初始化图形用户界面
-
Windows登录阶段
Windows
子系统启动的winlogon.exe
系统服务提供对Windows
用户的登录和注销的支持,可以完成以下的注册工作- 启动服务器子系统(Services.exe),也称服务控制管理器(SCM)
- 启动本地安全授权(LSA)过程(lsass.exe)
- 显示登录界面
登录组件将用户的账号和密码安全地传送到
LSA
进行认证处理。如果用户提供的信息正确就能通过认证允许对系统的访问 -
Windows7 与 windowsxp启动过程的区别
BIOS
通过自检后,将MBR载入内存并执行,引导代码会找到启动管理器Bootmgr
bootmgr
寻找活动分区boot
文件夹中的启动配置数据BCD
文件,读取并组成相应语言的启动菜单,在屏幕显示多操作系统选择画面bootmgr
读取系统文件windows\system32\winload.exe
并将控制权交给winload.exe
- 然后
winload.exe
加载win7
的内核,然后过程与xp类似 xp
的启动过程在2024
已经淘汰。。。
-
系统引导方式
UEFI
与GPT
UEFI
j结合GPT
会成为今后系统引导的主要解决方案- UEF[(统一可扩展Finnware接口,统一的可扩展固件接口)的出现主要用于替换BIOS.在UEFT中,用于表示lba的地址是64位的,突破了在BIOS与mbr技术方案中分区容量2TB的限制.UEFI本身已经相当于一个微型操作系统了.UEFI具备文件系统的支持能力,能够直接读取FAT分区中的文件.开发人员可以开发出直接在UEFI下运行的应用程序,这类程序文件通常以“EFI”结尾.我们可以将安装程序做成en类型的应用程序,然后把它放到任意分区中直接运行这使安装操作系统变得简单.而在UEFI下,这些统统都不需要–不需要主引导记录不需要活动分区,不需要任何工具.只要将安装文件复制到一个FAT 32(主)分区或U盘中,通过)这个分区或U盘即可安装和启动窗口
- 与传统的MBR分区表相比,新型的GPT(GUID划分表,全局唯一标识分区表)对分区)数量没有限制,但在实现gpt的时候,将分区个数限制在128个cpt分区以内,gpt可管
理磁盘大小达到了18EB,因此,只有基于UEFI平台的主板才支持GPT分区引导启动.
R3与R0通信
例如应用程序调用了一个有关I/O的api
这个API实际上被封装在应用层的某个DLL动态库
(kernel.dll,user32.dll),而DLL动态库
中的函数更底层包含在ntdll.dll
文件中
当kernel32.dll
中的API通过ntdll.dll
执行时,会完成参数的检查工作,再调用一个中断(int 2Eh,或者SysEnter指令),从R3进入R0
在内核ntoskrnl
(NT operating system kernel)有一个SSDT,里面存放了与ntdll.dll
中对应的SSDT系统服务处理函数,即内核态的NT*系列函数,
-
从用户模式调用
NT*
和Zw*
API,连接ntdll.lib
都是通过设置系统服务器表中的索引和在栈中设置参数。通过syscall指令进入内核态。然后跳转到
KiSystemService
跳转到对应的系统服务器例程。由于是用户模式进入内核模式,代码会严格检查用户空间传入的参数 -
从内核模式调用
NT*
和Zw*
API,连接ntoskrnl.lib
NT*
系统API将直接调用对应的函数代码Zw*
系统API则通过kiSystemService
最终跳转到对应的函数代码处。这两种调用对内核中
PreviousMode
进行,如果是从内核模式调用NativeAPI
则PM
是用户态;如果从内核模式,则PM
是内核态当
PM
是用户态``NativeAPI`会严格检查参数当
PM
是内核态NativeAPI
不会检查参数在调用用户模式
NT*API
不会改变PM
调用
Zw*API
会将PM改为内核态因此在
KernelModeDriver
尽量使用Zw*API
可以提高效率
内核主要由各种驱动(在磁盘上是.sys文件)组成,这些驱动有的是 Windows系统自带的(例如 ntfs.sys、tepip.sys、wn32k.sys),有的是由第三方软件厂商提供的。驱动加载之后,会生成对应的设
备对象,并可以选择向 R3提供一个可供访问和打开的符号链接。常见的盘符C、D、E等其实都是文件系统驱动创建的设备对象的符号链接,对应的符号链接名
应用层程序可以根据内核驱动的符号链接名调用CreateFile0函数打开。在获得一个句柄(Handle)之后,程序就可以调用应用层函数与内核驱动进行通信了,例如ReadFile0)、WriteFile0)及DeviceloControl()等。
内核驱动一旦执行了 DriverEntry入口函数,就可以接收 R3层的通信请求了。在内核驱动中专门有一组分发派遣函数用来分别响应应用层的调用请求
Windows内核结构
Windows内核中主要可以分成三层:硬件抽象层(HAL),内核层(也称微内核micro-kernel),执行体层。
内核层实现操作系统的基本机制,而所有的策略决定则留给执行体。执行体中的对象绝大多数封装了一个或者多个内核对象,并且通过某种方式(比如对象句柄)暴露给应用程序。
Windows内核为用户模式提供了一组系统服务,供应用程序使用内核中的功能。应用程序通常并不直接调用这些系统服务,而是通过一组系统DLL,最终通过ntdll.dll切换到内核模式下的执行体API函数中,以调用内核中的系统服务。
Ntdll.dll是链接用户模式代码和内核模式系统服务的桥梁。对于内核提供的每一个系统服务,该DLL都提供一个相应的存根函数,这些存根函数的名称以“Nt”作为前缀,例如NtCreateProcess、NtOpenFile等。另外ntdll.dll还提供了许多系统级的支持函数,比如映像加载器函数(以“Ldr”为前缀)、系统时间函数(以“Etw”为前缀),以及一般的运行支持函数(以“Rtl”为前缀)和字符串支持函数等。
执行体API函数接收的参数来自于各种应用程序,因此为了保证系统的安全以及抵抗来自用户模式的恶意攻击,所有的执行体API都必须保证参数的有效性。通常执行体系统服务函数会在其开始处,对所接收的参数逐一探查它们的可访问性。
例如
PreviousMode = KeGetPreviousMode();//获取当前的执行模式,内核模式or用户模式
if (PreviousMode != kernelMode){//如果是用户模式
try{
ProbeForWrite(InputInformation,InputInformationLength,sizeof(ULONG));//检查内存可写性,检查inputinformation指向的内存区域是否可以安全写入数据,第二三个参数分别是数据的长度和数据大小
//接着检查是否传递了ReturnLength参数,如果传递了就进一步检查内存可谢谢
if (ARGUMENT_PRESENT(ReturnLength)){
ProbeForWriteUlong(ReturnLength);
} except(EXCEPTION_EXECUTE_HANDLER){
return GetExceptionCode();//获取错误码
}
}
}
硬件抽象层HAL
硬件抽象层(Hardware Abstraction Layer,HAL),这一层把所有与硬件相关联的代码逻辑隔离到一个专门的模块中,为操作系统的上层提供一个抽象的、一致的硬件资源模型。这使得上层的模块无须考虑硬件的差异,它们通过HAL而不是直接访问硬件。
在Windows中,HAL是一个独立的动态链接库。HAL提供了一些例程供其他内核模块或设备驱动程序调用,这使得一个驱动程序可以支持同样的设备在各种硬件平台上运行。HAL不仅涵盖了处理器的体系结构,也涉及了中断控制器、单处理器或多处理器等硬件条件。
内核层
这是大内核中的小内核,也称微内核。它是内核模块ntoskrnl.exe的下层部分(上层为执行体),最接近HAL层,负责线程调度和中断、异常的处理。对于多处理器系统,它还负责同步处理器之间的行为,以优化系统的性能。
Windows的内核实现了抢占式线程调度机制,按照优先级顺序将线程分配到处理器上,并且允许高优先级的线程中断或抢占低优先级的线程。每个线程有一个基本优先级值(base priority)和一个动态优先级值。根据这俩个值,内核根据调度规则来切换线程,让系统更快响应用户的动作,以及在系统服务和其他低优先级进程之间平衡处理器资源的分配。
Windows内核按照面向对象的思想来设计,它管理俩种类型的对象:分发器对象和控制对象。分发器对象实现了各种同步功能,这些对象的状态会影响线程的调度。Windows内核实现的分发器对象包括事件(event)、突变体(mutant)、信号量(semaphore)、进程(process)、线程(thread)、队列(queue)、门(gate)和定时器(timer)。控制对象被用于控制内核的操作但是不影响线程的调度,它包括异步过程调用(APC)、延迟过程调用(DPC),以及中断对象等。
执行体层
执行体是内核模块ntoskrnl.exe的上层部分,它包含5种类型的函数:
- 系统服务调度函数(System Service Dispatch Functions):这些函数主要负责响应系统服务请求。当用户模式应用程序请求操作系统服务时,这些函数会被调用。例如NtCreateFile、NtReadFile等函数。
- 内核模式支持函数(Kernel Mode Support Functions):这类函数提供给其他内核模式组件使用,以执行各种底层任务,如内存管理、进程和线程管理等。例如ExAlloctePool:分配内核池内存、KeSetEvent:设置一个事件对象的状。
- 执行对象管理函数(Executive Object Management Functions):这些函数用于管理Windows中的各种执行对象,如进程、线程、事件、信号量等。例如 ObOpenObjectByPointer :根据对象指针打开对象、 ExCreateCallback:创建一个回调对象。
- 安全引用监视器函数(Security Reference Monitor Functions):这些函数用于实现操作系统的安全机制,包括访问控制、权限检查等。例如 SeAccessCheck:检查访问权限。
- I/O系统支持函数(I/O System Support Functions):这些函数支持输入/输出系统的操作,包括文件系统的管理、设备驱动程序的交互等。例如 IoWriteErrorLogEntry:写入错误日志条目。
与应用层函数不同,在windows操作系统中调用内核函数必须要关注中断请求级别(IRQL,Interrupt Request Level)。
IRQL 是一个表示中断优先级的数字,用于确保处理器在处理不同的任务时维持正确的操作顺序和安全性。操作系统内核使用不同的 IRQL 来管理对硬件资源的访问,以及处理不同级别的中断和异常。
在调用内核函数时,必须要确保当前的IRQL与被调用函数所要求的级别相符。不遵守这一规则可能导致系统崩溃或数据损坏。IRQL的级别如下:
- 被动级别(PASSIVE_LEVEL):这是最低的IRQL级别。在此级别,线程可以被抢占,可以执行任何类型的内核模式代码,包括页面操作。
- APC级别(APC_LEVEL):此级别用于阻止异步过程调用(APC)的执行。
调度级别(DISPATCH_LEVEL):在此级别,可以阻止线程调度,但仍允 - 处理硬件中断。很多非分页内存操作需要在此级别或更低级别执行
- DIRQL(设备IRQL):这是特定于设备的中断级别。不同的设备驱动程序可能会使用不同的DIRQL。
- 高IRQL(HIGH_LEVEL):这是最高的IRQL级别,用于系统关键操作,此时几乎所有的中断都被禁止。
开发内核模式驱动程序时,合理地管理IRQL至关重要。如果一个函数要求在低IRQL下运行,而当前IRQL较高,就不能直接调用那个函数;反之亦然。不正确的IRQL处理可能导致系统不稳定或蓝屏(BSOD)。
执行体中除了函数组成,还有以下多个重要的组件
- 内存管理器:实现了虚拟内存管理,既负责系统地址空间的内存管理,又为每个进程提供了一个私有的地址空间,支持进程之间的内存共享。内存管理器也为缓存管理器提供了底层支持。
- 缓存管理器:它为文件系统提供了统一的数据缓存支持,允许文件系统驱动程序将磁盘上的数据映射到内存中,并通过内存管理器来协调物理内存的分配。
- 文件系统:管理文件和目录的创建、读写和组织。
- 进程和线程管理器:负责进程线程的创建和终止。在Windows中,对于进程和线程的底层支持是在内核层提供的,执行体只是在其基础上提供了一些语义和功能。
- 即插即用管理器:负责列举设备,加载并初始化设备所需的驱动程序。还负责检测系统中的设备变化。
- 安全引用监视器(SRM):该组件强制在本地计算机上实施安全策略,它守护着操作系统的资源,执行对象的保护和审计。
- 配置管理器:管理系统注册表,提供系统配置和启动信息。
- I/O管理器:实现了与设备无关的输入和输出功能,负责将I/O请求分发给正确的设备驱动程序以便进一步处理。
- 对象管理器:它负责创建、管理、删除Windows执行体对象,以及用于表达操作系统资源的抽象数据类型,比如进程、线程和各种同步对象。
- 局域网管理器(本地过程调用,LPC):负责处理进程之间的通信,管理消息传递和远程过程调用。
设备驱动程序
在内核中除了内核模块ntoskrnl.exe
和HAL
以外,其他模块几乎都以设备驱动程序的形式存在。
Windows中的设备驱动程序,并不一定对应物理设备;它既可以创建虚拟设备,也可以与设备无关,它仅仅是内核的扩展模块。从软件结构角度而言,可以认为设备驱动程序是Windows内核的一种扩展机制,系统通过设备驱动程序来支持新的物理设备或者扩展功能。
设备驱动程序是可以加载到系统中的模块,其文件扩展名为**.sys**,其格式是标准的 PE文件格式
。驱动程序中的代码运行在内核下,尽管它们可以直接操纵硬件,但理想的情况是,调用HAL中的函数与硬件打交道
,因此,驱动程序往往用C/C++语言来编写
,从而可以方便地在Windows所支持的体系结构之间进行源代码层次上的移植。
根据设备驱动程序的功能和行为可以将设备驱动程序分为三类:
- 即插即用驱动程序:支持即插即用技术的驱动程序。它们可以在设备连接到计算机时自动被识别和配置,无需用户手动干预。
- 非即插即用驱动程序:不支持即插即用技术的驱动程序。可能在安装时需要用户手动配置。
- 文件系统驱动程序:专门用于处理文件操作的驱动程序,如管理文件的存取、文件系统的结构等。
即插即用驱动程序,也可以称为WDM(Windows Driver Model)驱动程序。WDM是一种设备驱动模型,它提供了一个统一的框架,使驱动程序可以在不同版本的WIndwos操作系统上运行。
WDM通常分为三个层次:
- 总线驱动程序:负责管理总线上的设备,也为总线上的设备提供了访问总线资源的方法。
- 功能驱动程序:负责管理具体的设备,向操作系统提供该设备的功能。
- 筛选/过滤驱动程序:监视一个设备的I/O请求以及其处理过程,增加或改变一个设备或驱动程序的行为。
在WDM中,每个硬件设备都有一个设备驱动程序栈(简称设备栈),其中包含一个总线驱动程序和一个功能驱动程序,以及零个或多个过滤驱动程序。
文件系统/存储管理
在现代操作系统中,文件系统是外部存储设备的标准接口,它为应用程序使用这些设备中的数据提供了统一的抽象,多个应用程序和系统本身可以共享使用这些设备。
在Windows中,文件系统的接口部分由I/O管理器定义和实现,但文件系统的实现部分位于专门的一类驱动程序中。但文件系统接收到I/O请求时,它会根据文件系统格式规范,将这些请求转变为更底层的对于外部存储设备的I/O请求,通过它们的设备驱动程序来完成原始的I/O请求。
因此,文件系统的驱动程序定义了外部存储设备中数据的逻辑结构,使得这些数据可直接被操作系统和应用程序使用。
这些文件系统驱动程序负责管理磁盘上的文件和目录,处理文件的创建、读取、写入和删除操作,提供了文件存储和访问的基本功能。
常见的文件系统:
NTFS(NT File System):这是Windows的原生文件系统,其驱动程序为ntfs.sys。NTFS是专门为Windows设计的,它提供了许多高级的如元数据支持、数据压缩加密的功能,同时支持大型存储卷和大文件。
FAT(File Allocation Table):这是从DOS时代发展起来的文件系统格式,格式规范相对简单,目前主要用于兼容老版本的操作系统,以及用于移动设备以便跨操作系统传送数据。
文件系统的底层是对存储设备的管理。大容量存储设备以“分区(Partition)”和“卷(volume)”来管理整个存储空间。
分区是指存储设备上连续的存储区域(连续的扇区),而卷是指扇区的逻辑集合。一个卷内部的扇区可能来自一个分区,也可能来自多个分区,甚至来自不同的磁盘。文件系统则是卷内部的逻辑结构。
网络
在Windows操作系统中,网络是由一系列网络驱动程序和网络协议栈组成。
Windows内核层中的网络相关组件:
- 网络驱动程序(Network Drivers):负责管理物理网络接口卡(NIC)或虚拟网络适配器的通信。
- 协议栈(Protocol Stack):是一个多层次的协议栈,用于处理网络通信。这个协议栈包括了各种网络协议,如TCP/IP、UDP、ICMP等。协议栈负责数据包的封装、路由、传输和解包,确保数据在网络中的正确传输。
- 套接字(Sockets):套接字是应用程序与网络协议栈之间的接口,允许应用程序创建网络连接、发送和接收数据。Windows内核还提供了套接字API,应用程序可以使用这些API与网络进行交互。
- 网络服务(Network Services):Windwos操作系统还提供了各种网络服务,如DHCP客户端、DNS客户端、网络发现服务等,它们能够进行获取IP地址,解析域名或发现网络设备等操作。
- 网络筛选器驱动程序(Network Filter Drivers):该驱动程序允许实施网络策略和安全性控制,如防火墙和安全软件可能会使用网络筛选器驱动程序来监视和过滤网络流量。
Windows为应用程序提供了多种网络API:
-
Winsock(Windows Sockets) : 套接字API,允许应用程序使用套接字进行网络通信。可以使用Winsock来创建TCP/IP和UDP网络连接,发送和接收数据。
-
HTTP API:允许应用程序创建HTTP服务器和客户端,发送HTTP请求、接收HTTP响应,并处理Web服务。
-
WebSocket API:这是一种双向通信的说协议,允许实时数据传输,适用于在线游戏、即时聊天等应用。
-
WebRTC API:一种用于实时音视频通信的开放标准,可以用于创建支持视频会议、实时音频通话等应用程序。
-
UPnP API:一种用于自动发现和配置网络设备的协议。
-
WinINet API:WinINet提供了对Internet资源的访问,包括HTTP、FTP和Gopher等协议,它允许应用程序进行Web页面下载、文件上传和下载等操作。
-
Network Management API:网络管理API,允许应用程序管理网络设置、配置网络连接和查看网络状态。
这些网络API都提供了用户模式的动态链接库(DLL)。当应用程序通过这些DLL发出网络I/O请求时,它们将这些请求传递给内核中相应的驱动程序。通常,这些网络API要么通过专门的系统服务切换到内核模式,比如命名管道和邮件槽就有专门的系统服务;要么通过标准的系统服务接口,比如NtReadFile、NtWriteFile和NtDeviceIoControlFile,由I/O管理器和对象管理器将网络请求转送至对应的驱动程序中。
Windows子系统
Windows子系统是Windows操作系统的组成部分,用于支持不同类型的应用程序和环境在Windows平台上运行。每个子系统专门设计用于处理特定类型的应用程序和操作环境。
Windows提供了多种子系统,同时在PE文件格式中的SubSystem域指示了可执行文件的子系统类型,即程序应在何种环境下运行;SubSystem域通常包含一个数字值,代表不同的子系统类型。
以下是一些常见的SubSystem及其对应的子系统:
Native (0):
Native子系统表示该PE文件是一个本地的执行文件,通常是驱动程序或操作系统内核组件。这些文件在操作系统内核模式下运行。
Windows GUI (2):
Windows GUI子系统表示该PE文件是一个图形用户界面(GUI)应用程序。它运行在Windows桌面环境中,通常有用户界面和窗口。
Windows CUI (3):
Windows CUI子系统表示该PE文件是一个字符用户界面(CUI)应用程序。它通常是命令行应用程序,没有图形界面,用户通过控制台来与之交互。
OS/2 CUI (5):
OS/2 CUI子系统表示该PE文件是一个OS/2字符用户界面应用程序。这种类型的应用程序通常用于运行在IBM OS/2环境中。
Posix CUI (7):
Posix CUI子系统表示该PE文件是一个POSIX兼容的字符用户界面应用程序。它适用于在Windows上运行UNIX/Linux应用程序。
Windows子系统中既有用户模式部分,也有内核模式部分。内核模式部分的核心是win32k.sys,虽然它的形式是一个驱动程序,但实际上它并不处理I/O请求,相反,它向代码提供大量的系统服务。从功能上讲,它包含俩部分:窗口管理和图形设备接口。其中窗口管理部分负责收集和分发消息,以及控制窗口显示和管理屏幕输出;图形设备接口部分包含各种形状绘制以及文本输出功能。
窗口管理
Windows子系统的用户界面管理有一个层次结构,通常应用程序只是在一个默认的桌面上运行。
每个子系统会话都有自己的会话空间,属于某一个会话的资源将会从该会话空间中分配。当用户登录到Windwos中时,操作系统为该用户建立一个会话;即使用户通过远程桌面或者终端服务连接到一个系统中。系统也会为该用户建立一个单独的会话。
在一个会话中,有一个交互式窗口站,可能还有非交互式窗口站。在交互式窗口站中通常有三个桌面:登录桌面、默认桌面和屏幕保护桌面。交互式窗口站有独立的剪贴板、键盘、鼠标、显示器等,在它的三个桌面中,任一时刻只有一个是激活的,输入输出设备归激活的桌面所有。
在每个桌面,都有一个顶级窗口列表,这些窗口往往可以相互重叠,有系统菜单、最大化/最小化按钮和滚动条等。通常各个图形界面应用程序的主窗口属于当前桌面的顶级窗口。在Windows中,窗口可以有子窗口,子窗口占据父窗口的客户区域。因此,桌面上的窗口形成了一个层次结构。一个窗口下总是可以创建它自己的子窗口。
Windows为常用的窗口定义了一些窗口类(window class)。窗口类规定了其对象将如何响应各种信息,包括系统发送给它的消息和用户触发的消息。
Windows子系统会话有一个RIT(Raw Input Thread)线程,负责从输入设备读取原始的输入时间,生成消息寄送到正确的线程消息队列。
图形设备接口
Windows的图形引擎有俩方面特点:
- 提供了一套与设备无关的编程接口,即GDI,这使得应用程序可以适应各种底层显示设备的差异
- 应用程序与图形设备驱动之间通信足够高效。在频繁输出和刷新图形元素的情况下,windows也能提供良好的视觉效果。
上图是Windows子系统定义的图形体系结构。win32k.sys通过DDI(显示设备驱动程序接口)与现实驱动程序打交道,而显示驱动程序通过ENG(图形引擎接口)调用Win32k.sys中图形引擎的功能。
视频端口驱动程序实际上是一个动态链接库,用于辅助视频小端口驱动程序实现一些公共的、与图形有关的功能,以及为小端口驱动程序提供一个与系统内核和执行体打交道的环境。视频小端口驱动程序则直接负责的硬件资源管理和控制。
系统线程和系统进程
系统线程则是一些特殊线程,与普通用户线程不同,系统线程不属于任何特定的用户进程,它直接运行在内核模式下。
一些常见的系统线程:
- Idle线程:它的任务是在系统没有其他任务要执行时,占用CPU周期并降低CPU功耗,它通常属于最低优先级,以确保在需要时可以立即释放CPU资源给其他任务。
- Deferred Procedure Call (DPC) 线程:DPC线程是用于处理延迟的硬件中断请求的系统线程。当硬件设备产生中断请求时,DPC线程负责处理这些请求并执行相应的处理程序。
- System线程:System线程执行一些关键的内核操作,如系统调度、中断处理、内存管理等。它是操作系统的核心部分,用于协调和管理其他系统线程和用户进程。
- 系统线程中还有一组系统辅助线程(system worker thred),它们代表操作系统或者其他的应用进程来完成一些特殊的工作。实际上,系统辅助线程是一个线程池,Windows在系统初始化时创建了一定数量的辅助线程,而且随着辅助线程的负载的变化,执行体也会动态地创建一些辅助线程,以满足系统负载的变化需求。
在Windows操作系统中,有一些重要的系统进程,它们负责管理和控制操作系统的不同方面。
一些常见的系统进程:
- 系统空闲进程(Idle):该进程的PID为0,其中每个处理器或核对应有一个线程。
- System 进程:这是操作系统的好恶心进程,PID为4。它负责管理内核模式下线程、设备驱动程序、中断处理和其他核心人物。
- 会话管理器(smss.exe):Windows系统中创建的第一个用户模式进程,在Windows启动过程中创建环境变量,(启动了子系统进程csrss.exe和登录进程winlogon.exe)。另外,它还负责创建新的终端服务器会话,包括建立会话空间的数据结构,为新建的终端服务器加载子系统。
- 登录进程(winlogon.exe):负责处理交互用户的登录和注销。
Windows子系统进程(csrss.exe):负责为用户提供一个子系统环境。 - 本地安全权威子系统进程(lsass.exe):负责本地系统安全策略。
- Shell进程(explorer.exe):Windows的默认Shell,它提供了系统与用户打交道的各种界面,包括开始菜单、任务栏、资源管理窗口等几乎所有Windows用户都熟悉的界面。
- 服务控制管理器(services.exe):负责Windows的系统服务,指一些特殊的进程。
内核基本概念
Windows内核中的各个组件并非单纯的独立模块,相反地,组件之间不可避免地包含了复杂的依赖关系,甚至存在交叉引用。下面是一些Windows内核中的基本概念。
处理器模式
用户模式下,处理器只能访问用户地址空间,而在内核模式下,处理器不仅可以访问用户地址空间,也可以访问系统地址空间。在内核模式下的代码和数据都是共享的,所有的进程一旦其指令流进入到内核模式下,则系统地址空间的代码和数据都是相同的。
一个指令流(即线程)在执行时,在以下情况会发生模式切换:
- 用户模式代码触发了异常,则控制流进入到内核模式,内核中的异常处理函数可以决定该控制流是否继续执行。
- 用户模式代码执行时,被一个中断打断,控制流进入特权模式,等中断处理例程完成后,它若调用iret/iretd指令,则控制流恢复到用户模式下。
- 执行特殊的模式切换指令,如Intel x86的
sysenter
指令,从用户模式切换到内核模式。若想从内核模式切换到用户模式1,通常使用sysexit
、iret/iretd
这样的指令。
由于系统空间是所有进程共享的,所以,任何一个进程在执行内核模式的代码时,实际上是在使用操作系统的服务。在Windows体系结构中,内核模式向上有一个执行体API,对于应用程序而言,这便是系统服务。
Windows将这些系统服务组织成了一张表,称为SDT(Service Descriptor Table,服务描述符表)。
内存管理
任何一个进程都定义了它自己完整的4GB地址空间(虚拟内存空间),在内存空间分布一图中,将其划分成2GB内核空间以及2GB应用空间,换句话说,内核空间是所有进程共享的,也称作系统地址空间,剩下的2GB空间才是它自己私有的,也叫进程地址空间。
为了有效管理2GB的系统地址空间,Windows将2GB划分成了一些固定的区域,主要包括:内核模块映像、PFN数据库、换页内存池、非换页内存池、会话空间、系统缓存区、系统视图以及页表等。
分页机制
Windows使用分页机制管理虚拟内存和物理内存。它将虚拟内存和物理内存划分成固定大小的页面,通过映射这些页面来实现虚拟地址到物理地址的转换。
通常操作系统将内存划分为大小固定的页面,通常为 4KB、8KB 或其他大小。这些页面是虚拟内存和物理内存的基本单位;之后操作系统将进程的虚拟地址空间也划分成页面大小的块。当程序访问进程的虚拟地址时,操作系统将虚拟地址转化成相应的物理地址。
为了实现虚拟地址到物理地址的转换,需要使用到页面表。
- 页面表 是操作系统中的一个数据结构,用于记录虚拟地址空间中每个页面与实际物理内存中的对应关系。页面表的条目存储了虚拟页号到物理页号的映射关系。
- 页面表中的每个条目称为页表项(Page Table Entry,PTE)。**每个 PTE 存储了虚拟页号到物理页号的映射,以及一些额外的控制信息,**例如页面是否在物理内存中、是否被修改等
当程序访问进程的虚拟地址时,MMU负责将这个虚拟地址通过页面表转化成物理地址。
如果虚拟页已经在物理内存中,则直接获取物理地址。如果虚拟页不在物理内存中,就需要进行页面调度。
页面调度
- 如果虚拟页不在物理内存中,会先引发一个 缺页异常。这时,操作系统需要根据页表中的信息确定要将哪一页加载到物理内存中。
- 然后操作系统会将当前没用的物理页写入磁盘中,将需要的虚拟页加载入物理页。
从内存中获取数据的过程:
- 程序访问进程的虚拟地址
- MMU在通过页面表查询虚拟地址对应的虚拟页是否在物理内存中
- 若在,直接获取物理地址,返回数据;
- 若不在,引发缺页异常,MMU在页面表中查找对应的虚拟页,通过页面调度将虚拟页加载到物理内存中
- 获取物理地址,返回数据。
至于数据在物理内存中还是虚拟内存中是没有规律的,取决于数据使用的频繁程度。
进程和线程管理
**进程(process)**定义了一个执行环境,包括它自己的私有空间、一个句柄表、以及一个安全环境;线程(thread)是一个个控制流,有自己的调用栈(call stack),记录它的执行历史。
一个进程包含一个或多个线程。
用户模式下的进程只能访问进程地址空间,若在内核模式下,就可以访问真个地址空间。
windows实现了**抢占式线程调度,每个线程都有一个基本优先级和动态优先级。本质上每个线程都处于俩种状态之一:满足继续执行的条件,正在排队或已经执行;不满足继续执行的条件,处于等待状态,或者它的调用栈升职所处的进程已经被换出内存。**在前一种情况下,线程按照优先级排队执行;对于多处理器系统,排队过程要更为复杂,不仅要处理多个队列,还要考虑每个处理器的就绪线程队列的平衡程度。还需要考虑处理器亲和性。
中断和异常
中断(interrupt)是指处理器外部事件(如硬件设备)触发的信号,它会中断当前的处理器活动。异常(Exceptions)指程序执行过程中出现的非正常或意外情况,由处理器内部事件(如执行了错误指令)触发。
尽管中断和异常的触发来源和方式不同,但Intel x86处理器内部使用同一套陷阱机制来处理中断和异常,它利用IDT(Interrupt Descriptor Table,中断描述符表),将每个中断或异常与一个处理该中断或异常的服务例程联系起来,因而一旦发生异常或中断,该相应的服务例程将被执行。Windows在此基础上,添加了一种更灵活的机制,允许设备驱动程序为特定的中断向量添加它的中断服务例程(ISR,Interrupt Service Routine)。一个中断向量允许连接多个中断对象(interrupt object),这里中断对象是一种封装了中断服务例程的内核对象。当中断发生时,这些中断对象中的服务例程都有机会处理该中断。通过中断对象机制,设备驱动程序可以在不操纵IDT的情况下加入它们的中断服务例程;另一方面,多个硬件设备也可以共享同样的硬件中断向量。
执行体层中介绍过中断请求级别IRQL。
异常是程序指令流执行过程中的同步处理过程,既可以由处理器硬件产生,也可以由指令流软件产生。Windows为所有需要处理的异常都提供了异常处理器(exception handler,即异常处理例程)。
同步
在现代操作系统中,由于多处理器、多核或者中断各种并发性(concurrency)因素的存在,同样的代码可能被并发执行,而数据也可能被并发访问。在这种情况下,对于可能被并发访问的数据进行必要的同步(synchronization)保护是一种常见的编程实践。
一些同步中会遇到的概念:
- 关于锁。什么是锁?锁是一种同步机制,用于确保在同一时刻只有一个线程能够访问共享资源;也就是保护临界区,确保在任何时刻只有一个线程可以进入临界区执行操作。锁包括互斥锁、自旋锁、读写锁等不同类型。
- 临界区。什么临界区?临界区是用于保护共享资源免受多个线程同时访问的一种同步机制:当一个线程进入临界区时,其他线程必须等待,直到第一个线程离开临界区。常用于用户模式下。
Windows根据执行环境中的IRQL大于APC_LEVEL 或者等于PASSIVE_LEVEL,将同步机制分为“不依赖线程调度的同步机制”和“基于线程调度的同步机制”。
不依赖线程调度的同步机制主要是在IRQL的高优先级下执行的,通常用于中断处理程序和内核模式代码中,以确保在处理中断或执行关键内核代码时,不会被其他线程打断。常见的不依赖线程调度的同步机制包括:
- 自旋锁(Spin Lock):自旋锁用于在多个线程之间互斥的访问共享资源,本质上是一种忙等待(busy-wait),意思是**线程会一直自旋(忙等待)直到锁可用为止,而不会被挂起等待。**常使用在高IRQL下,因为此时是不允许切换线程的,使用自旋锁可以确保关键代码不会被其他线程打断。一些自旋锁扩展:执行体自旋锁(支持共享和独占的语义)、排队自旋锁(queued spin lock)和栈内排队自旋锁(in-stack queued spin lock)。
- 中断服务例程(ISR):ISR用于响应硬件中断,当硬件设备触发中断时,操作系统会立即执行ISR来处理中断。ISR运行在IRQL的高优先级下,不允许进行线程调度,以确保快速响应中断。
- 延迟过程调用(Deferred Procedure Call,DPC):DPC用于延迟执行一些代码块或处理程序,常与中断处理有关。当硬件设备触发中断时,操作系统会将**中断服务例程(ISR)**用于快速响应中断,但有时需要执行一些耗时的操作,例如数据传输或资源释放,这时就会使用DPC来延迟执行这些操作。
另一种基于线程调度的同步机制:当一个线程的执行条件不满足时,该线程进入等待状态,系统将控制权交由其他满足执行条件但没有得到处理器资源的线程;以后,当该线程的执行条件满足时,它又有机会继续执行。这里的执行条件正是Windows提供的线程同步机制中的语义。Windows定义了统一的机制来支持各种线程同步原语:分发器对象(dispatcher object),其数据结构头部为DISAPATCH_HEADER。
常见的分发器对象:
- 事件(event):用于线程之间的通信和同步,它有俩种状态:已触发和未触发。事件可以用于线程等待某个事件发生,或者通知其他线程事件的发生。
- 互斥量(Mutex):互斥量用于确保在同一时刻只有一个线程能够访问共享资源。它允许线程请求锁定,当一个线程获得锁定时,其他线程必须等待,直到锁被释放。
- 信号量(Semaphore):用于控制对共享资源的并发访问。它维护一个计数器,允许指定数量的线程同时访问资源。
条件变量(Condition Variable):条件变量用于线程之间传递信号和等待特定条件的发生。通常与互斥量一起使用,用于等待某个条件满足后执行操作。 - 读写锁(Read-Write Locks):读写锁用于控制对共享资源的读和写,它允许多个线程同时读取资源,但只允许一个线程写入资源。
计数器对象(Counting Semaphore):一种特殊的信号量,它可以增加和减少计数器的值,通常用于跟踪资源的可用性或完成的任务数量。
**互斥锁(Mutex)**是一种基于线程调度的同步机制,它用于确保在同一时刻只有一个线程能够访问共享资源。当一个线程获得互斥锁的所有权后,其他线程必须等待,直到该线程释放锁。
关于互斥锁和自旋锁:互斥锁基于线程调度,而自旋锁不依赖线程调度;还有一个区别是线程尝试获取锁时,如果该锁被占用,线程是被挂起还是一直自旋;互斥锁适用于长时间的临界区和资源竞争激烈的情况,而自旋锁适用于非常短暂的临界区。
Windows在上述基础上,实现了同步语义更为丰富的一些同步机制,包括:快速互斥体(fast mutex)、守护互斥体(guarded mutex)、执行体资源(executive resource)和推锁(push lock)。
仅仅学习了概念方面的部分(概念比较多,现在仅仅是了解了一下比较重要的),后期会进阶学习Windows系统相关的开发技术