第2章:Windows系统总述(一)
大内核 MacroKernel(类 Unix、Win9x)
使用该结构的操作系统本身也是一个程序,所有基本的 OS 服务都运行在同一内存空间(包含设备驱动程序提供的服务)。
优点:内核组件之间直接通过函数调用实现,不需要进行 R3 与 R0 的转换。
缺点:一块内存空间一旦发生错误,会导致多个系统组件发生错误致使 Crash。
微内核 MicroKernel (Minix、Mach、QNX)
部分关键系统服务直接建立在硬件基础上(内存管理、多任务支持、进程间通信),其他常见的服务(网络服务、文件系统)都在 R3 实现,它们通过进程间通信(IPC,Inter-Process Communication)实现。
优点:小、系统服务可以高级语言编写并动态替换。
缺点:R3 与 R0 通信之间的处理器模式切换以及 Process Contex 切换耗费时间。
混合内核 HybridKernel (Windows NT、Linux)
大内核和微内核的折中版
外核结构 ExoKernel (理论模型,Insecure)
内核小于微内核,硬件抽象(Hardware Abstract Layer)尽量少,此系统上的开发人员可操作硬件的使用。该系统本身只提供对硬件资源的保护与共享使用。应用程序通过库操作系统来使用系统的硬件资源。
虚拟化技术 Virtualization
硬件虚拟化也叫虚拟机:VMware、Microsoft Virtual PC
仿虚拟化,ParaVirtualization:Xen 虚拟机,不是完全模拟硬件而是提供一个 API 供客体使用,但需要修改客体 OS.
SoftWare Isolated Process(SIP):即 Docker, 典型系统 Singularity ,由微软研发。初听 SIP 这个概念有点像 Windows 中:每个进程都有自己的相对独立的内存空间。
XP 之前 Windows 还支持 POSIX 和 OS/2 环境子系统,为其提供仿真执行环境。XP 以后只有 Windows 子系统随系统发行。
Windows 内核结构
- 硬件抽象层(HAL,Hardware Abstract Level)
- 内核层(微内核,micro-kernel)
- 执行体(executive)
HAL 将硬件层封装,使得上层的内核层尽可能独立于硬件平台,减少对不同平台的代码开发量。
内核层包含基本的操作系统原语(primitive)和功能,如线程、进程、线程调度、终端、异常处理、同步对象和各种同步机制。
执行体层提供上层应用程序和内核驱动程序直接调用的功能和语义,并且包含一个对象管理器,用于一致的管理执行体中的对象。
执行体层和内核层位于内核基本模块中,名称为 ntoskernel.exe。执行体中绝大多数的对象都封装了一个或多个内核对象,通过对象句柄等方式暴露给应用程序。
由图可知,应用程序调用系统服务时会经过 ntdll.dll 再调用。对于系统中的每一个函数 ntdll.dll 中都对应有一个存根(stub)函数并以 Nt 为前缀,比如 NtCreatePoress 等。ntdll 中还包含:
- Ldr 系列,映像加载器函数等
- Csr 系列,Windows 子系统进程通信函数等
- Dbg 系列,调试函数等
- Rtl 系列(一般运行支持函数)、Etw 系列(系统事件支持函数)和字符串支持系列
//通常,执行体系统服务函数还会在其开始处对所接收的参数进行检查
//InputInformation(指针) 和 ReturnLength 是该系统服务的直接参数
PreMode = ExGetPreviousMode(); if(PreMode != KernelMode) //如果非内核模式,就要开始检查IN的这些参数都否可读 { __try{ //这里的 ProbeForWrite 系列,首先都会检查地址是否越界至内核空间,若越界则将该地址赋值为 MM_USER_PROBE_ADDRESS
//或 0 ( if (_AMD64_) ),然后取目标地址处的值并写回,检查该地址是否能读写 ProbeForWrite(Inputinformation, InputInformationLength, sizeof(ULONG));
if(ARGUMENT_PRESENT(ReturnLength)){
ProbeForWriteUlong(ReturnLength); } __except(EXCEPTION_EXECUTE_HANDLER){ retrun GetExceptionCode(); } }
Win 10 通常会在判断完模式后通过 sysenter 和 int 2e 分开不同模式的调用。Int 2e 会检查参数,并准备内核态的栈。而 sysenter 是快速系统调用,不需要检查参数。
硬件抽象层
即 hal.dll ,发行时会包含多个 hal ,但安装时只会选中一个将其拷贝并更名为 hal.dll 。自旋锁和中断的功能是在 HAL 中实现的。
内核层(微内核)
负责线程调度和中断、异常的处理,同步多个处理器之间的行为。该层以 C 语言为主,辅以一部分的汇编代码。内核层管理两忠=种类型的对象:分发器对象和控制器对象。分发器影响线程的调度(包含线程、进程、事件、信号量、定时器等等),而控制器对象包含 APC 、DPC 和中断对象,不会影响线程的调度。
执行体
执行体是 ntoskrn.exe 的上层部分,包含 5 种类型的函数:
- 被导出的可在用户模式下调用的函数。接口位于 ntdll.dll 中
- 被导出并可以在用户模式下调用,但无法通过 Windows API 。例如 NtQueryInformation,需要直接链接 ntdll.dll
- 只能在内核模式下调用的导出函数,设备驱动程序可以调用
- 执行体之前互相调用的函数
- 属于一个组件的内部函数
从功能的角度,执行体包含:内存 mgr、进程/线程 mgr、I/O mgr、Cache mgr、配置 mgr、即插即用 mgr、电源 mgr 以及安全引用监视器(SRM,Security Reference Monitor)
设备驱动程序
除 ntoskrn.exe 和 HAL 外,内核中的其它模块几乎都已设备驱动程序的形式存在(.sys)。
设备驱动有三种基本类型:
- 即插即用驱动程序,也称为 WDM (Windows Driver Mode)驱动程序。
- 总共三种类型:①总线驱动程序,负责管理各个设备和提供访问总线资源的方法。②功能驱动程序,管理具体的设备,向操作系统提供该设备的功能(硬件厂商提供,Windows 发行时会自行携带很多这种程序)。③过滤驱动程序,监视一个设备的 I/O 请求和处理过程,干预其执行流程或行为
- WDM 中每个硬件设备都有一个设备栈,包含一个总线驱动和功能驱动以及 0 个或多个过滤驱动程序
- 总线通过构建设备树发现程序,通过设备栈访问程序
- 内核扩展驱动程序。系统工具使用内核扩展驱动程序获取 Windows 内核中的系统信息。
- 文件系统驱动程序。将对文件的请求转变为对存储设备或网络 I/O 设备的请求。
I/O 请求:上层程序与驱动程序之间通信时,上层会发出 I/O请求,大部分请求都打包在 I/O 请求数据包 (IRP) 中。
Windows 支持两种方式的过滤驱动程序:①直接插入设备栈中 ②通过 Windows 提供的管理器驱动程序(FltMgr),通过回调方式来响应 FltMgr 事件。
文件系统/存储管理
文件系统的接口部分由 I/O 管理器实现和定义,但实现部分位于一类专门的驱动程序中。
分区(partition):存储设备上连续的扇区
卷:是一个逻辑上的概念。包含一个或多个分区,可以来自于不同的磁盘。文件系统是卷内部的逻辑结构。
网络
- Winsock 即 Windows 套接字,实现并拓展了 BSD 套接字标准。
- Winnet ,高层网络协议,包含 FTP、HTTP ,IE 使用 Winnet 进行数据传输
- 命名管道和邮件槽。命名管道使用连接的方式通信,而邮件槽使用广播的方式发送消息
- NetBIOS,Windows 兼容的早期的网络编程 API
- RPC,网络编程标准,建立在 Winsock 或命名管道等其它网络 API 上。
afd.sys 等网络 API 驱动程序 通过传输驱动程序接口(Transport Driver Interface,TDI)与 协议驱动程序通信。
其中,网络 API 驱动程序和 TDI 是松耦合关系,即通过特定的框架进行交流但内容和形式(tcp.sys/netbt.sys)较灵活。
协议驱动程序 通过 NDIS(Network Driver Interface Specification)接口与网络适配器的驱动程序进行通信。符合 NDIS 的网络适配器驱动程序称为 NDIS 驱动程序。NDIS 并非标准的设备驱动程序,I/O 管理器不介入 NDIS 与 协议驱动程序的通信过程。
Windows 子系统
PE 文件可选头中 SubSystem 域可指定三中类型:Driver、GUI、CUI(Console)分别对应值 1、2、3
Windows 子系统分有用户模式和内核模式部分。
- 内核模式的核心是 win32k.sys.不处理 I/O 请求,而是提供两部分的系统服务。
- 窗口管理,即窗口消息的收集、分发,控制窗口显示和屏幕输出
- 图形设备接口(GDI),即形状绘制、文本输出
- 用户模式部分包括 csrss.exe 以及一组 DLL。
- Csrss.exe 负责控制台窗口,进程、线程的创建和删除
- DLL 则直接链接到进程中 kernel.dll、user32.dll、gdi32.dll、advapi.dll 等,这些动态库中的 API 函数有些可以直接在 R3 完成,有些则需要在 R0 完成
Win32k.sys 向内核注册一组出调(callout)函数介入内核的线程和进程管理中,也可以接收电源事件。每个线程,一旦调用了 win32k.sys 中的系统服务,那么就属于 GUI 线程,归属于 Windwos 子系统管理。
Win32k 的图形管理:
DirectX:允许图形应用软件绕过GDI图形引擎,直接操纵显示器硬件,获得更快的显示速度。
系统线程和系统进程
Windows 内核中存在一些不同用途的线程,运行在 System 进程中。其中有一组系统辅助线程(System worker thread)。驱动程序既可以在调用者进程中创建线程,也可以在 System 进程中创建线程,或者通过 API 函数放到系统辅助线程的处理队列中,由系统辅助线程处理,但也是在 System 进程中执行,不能访问其它进程空间中的数据。
系统辅助线程会动态增长,满足系统负载的变化需求。通过 PsCreateSystemThread 函数创建的系统线程可以指定其它进程作为它的宿主进程,但默认的宿主进程是 System 进程。
还有一些系统进程:
- 系统空闲进程(Idle),进程 ID 为 0,每个处理器或核对应有一个线程
- 会话管理器(Session Manager,smss.exe)。Windows 启动时第一个创建的用户模式进程,会启动 csrss.exe 和 winlogon.exe。创建新的会话时,smss 会再次启动这两个进程
- 登录进程(Winlogon.exe)负责处理交互用户的登录和注销
- 本地安全权威子系统进程(Local Security Authority Subsystem,lsass.exe),负责本地安全策略,对登录用户进行限制和权限管理。
- Shell 进程(explorer.exe),提供各种交互式界面。
内存管理
Windows 采用页式内存管理,较为典型的内存页面算法有三种:
- 非换页内存池。内存页被映射到物理页面,按照空闲链表的方法,以不同的粒度将空闲页面链接此来。申请和释放页面的操作即是针对空闲链表来进行的
- 换页内存池。空闲的页面没有被映射好物理页面,以位图的方式管理页面的分配(0 位)
- 系统 PTE 区域。这部分内存区域的地址范围是以 PTE 的形式来管理。当内核需要一段虚拟地址来映射物理页面时,可以使用系统 PTE 区域中的地址。
PTE 即 Page Table Entry,页表。
VAD( 虚拟地址描述符,Virtual Address Descriptor ) 是虚拟内存管理器的节点,其通过一棵平衡二叉搜索树来管理进程地址空间被使用的情况。VAD 节点中,有一种重要的节点类型是内存区对象(section object) ,它是Windows平台上两个或多个进程之间共享内存的一种常用方式。内存区对象可以被映射到任意文件或物理内存中。
PFN (Page Frame Number Database,页帧编号数据库)。每一个物理页面都对应于PFN数据库中的一项,此 PFN 项描述了该页面的状态。Windows支持八种状态。
当系统中的进程需要使用大量内存时,当内存管理器将需要有限的物理页面分配给需要使用内存的进程时,会用到 Windows .工作集管理器。这里工作集( working set )是指一个进程当前正在使用的物理页面的集合。Windows系统中除了进程工作集,还有系统工作集(即系统空间中动态映射的页面集合)和会话工作集(即会话空间中的代码和数据区)。每个进程都有一个工作集链表,其中的每一项不仅记录了物理页面的编号,还记录了其他的属性。
工作集管理器运行在一个称为平衡集管理器( balance set manager )的线程中每隔 1s 被触发一次,当可用内存太低时也会被触发。平衡集管理器除了触发工.作集管理器以外,也定期触发进程/栈交换器( process/stack swapper )。进程/栈交换器是另一个单独的线程,一旦被唤醒,就会将满足特定条件的进程和栈换人内存或换出内存。
进程和线程管理
Windows 中创建子进程,不指明属性则直接继承自父进程。与 Unix 相比,Windows 子进程仅仅记录了父进程的 ID,进程间是独立的,关系相对松散。
KPROCESS 和 KTHREAD 是微内核中进程和线程的数据结构,而 EPROCESS 和 ETHREAD 是执行体中进程和线程的数据结构; KeAttachProcess / KeStackAttachProcess 是微内核中将线程附载到指定进程的函数,而PspCreateThread / PspCreateProcess 是执行体中创建线程和进程的函数。
Windows 进程的创建:PspCreateProcess(EPROCESS 和 ETHREAD)
Windows 子系统进程的创建步骤:载入可执行文件 ——> PspCreateProcess(EPROCESS 和 ETHREAD) ——> csrss.exe
作业:执行体支持的内核对象,将一个或多个进程当作一个整体来管理和控制。
纤程:用户线程,对内核不可见,由 kernel32.dll 实现,一个进程中可以创建多个纤程但需要手动控制执行且不可中断,只能终止或切换到另一个纤程。
中断和异常
中断由外部硬件产生。如敲键盘、点鼠标。并且这是异步事件(可不处理)。
异常是指 cpu 内部问题,是一个同步事件(必须处理)。
cpu 外部中断简称中断,cpu 内部中断就叫异常。 windows异常处理_Starr-CSDN博客
IDT ( Interrupt Descriptor Table,中断描述符表),将每个中断或异常与一个处理该中断或异常的服务例程联系起来,因而一旦中断或异常发生,该服务例程即被执行。多个中断服务例程(Interrupt Service Routine,ISR)可以对应一个中断信号,会有一个链表在中断发生时挨个来处理。
中断请求级别( lRQL,Interrupt Request Level )。在Intel x86 系统中,Windows 使用 0-31 来表示 RQL,数值越大,优先级越高。处理器在运行时总是有一个当前 IRQL,如果发生中断时,中断源的 IRQL 等于或者低于当前级别,则该中断被屏蔽,直到处理器的IRQL降下来为止。
- IRQL=0 表示普通线程,称为 PASSIVE_LEVEL ,它的优先级最低,可被任何其他级别的中断打断。
- IRQL=1 表示异步过程调用(APC,Asynchronous Procedure Call),称为 APC_LEVEL,它仅仅比PASSIVE_LEVEL高,因此,在一个线程中插入一个APC对象可以打断该线程的执行。
- IRQL=2 表示处理器正在做以下两件事情: ①正在进行线程调度,比如选择新的线程 ②正在处理一个硬件中断的后半部分(不那么紧急的部分),在Windows 中,这被称为延迟过程调用( DPC, Deferred Procedure Call )。因此 IRQL 为 2 也被称为 DISPATCH/DPC 级别,或者简单地称为 DISPATCH_LEVEL。
- 3~26 是设备 IRQL,27~31 是一些特殊的硬件中断,包括时钟中断、电源中断、处理器间中断等,它们都是硬件中断。
APC :APC 是线程相关的例程,它们只能在特定的线程环境中被执行,因而也一定在特定的地址空间中被执行。APC 的执行优先于线程本身的指令流。当个线程获得执行权时,它的 APC 例程会立刻被执行。这一特性使得 APC 非常适合于实现各种异步通知事件,例如,IO 的完成通知可以用 APC 来实现。
DPC:DPC 的执行优先于任何一个于线程相关的函数,屏蔽了线程调度但低于硬件中断。Timer ,计时器到期是在 DPC 中交付的。
IDT :0 ~ 1F 由 intel 保留,其中 2 号 中断向量是 NMI(Nonmaskable Inerrupt,不可屏蔽中断),e 号是页面错误,由虚拟内存管理器接管。异常经过 IDT 找到具体的处理函数,经过一系列的调用会执行函数 DispatchException ,之后由 ExceptionHandler 挨个处理。具体可参考:第8章:Windows 下的异常处理-SEH - Rev_omi - 博客园 (cnblogs.com)
Windows 同步机制:
- 进程间的同步
- 提升 IRQL
- 互锁操作。 Lock 指令,最多锁 8 Bytes
- 单链表+Lock。使其访问不到下一个节点
- 自旋锁。本质上是一种忙等待,即不停检测锁的状态直到锁可用。通常用于 IRQL 大于2 的代码
- 线程间的同步(IRQL == 1 时的同步):分发器对象
- 分发器对象的头部时 DISPATCH_HEADER
- 对于每个处于等待状态的线程,它有一个等待块链表,链表中的每个节点代表该线程正在等待的一个分发器对象
- 对于每个分发器对象,它也有一个等待块链表,链表中的每个节点代表了正在等待该对象的一个线程。
- 当分发器对象变成有信号状态,则找到在分发器的等待块链表中找到第一个等待块,从而定位到特定线程。查找该线程进入等待的条件( [“ WaitAny 或 WaitAll ”] 和 [“ 分发器对象的状态(唤醒一个线程或所有线程)”]),在该线程的等待块链表中索引到分发器对象,若满足条件则执行该线程
- 进程对象
- 队列对象
- 突变体
- 事件
- 定时器对象
- 门对象。最快捷的方式,可直接将该线程插入到处理器的就绪队列中
- ••••••••••