|
发表于 2020-9-11 18:28:51
|
查看: 6796 |
回复: 0
3.1 进程基本概念
3.1.1 多进程模型
多个进程是分时执行的,每个进程的指令流按顺序执行。
操作系统需要做的事情是:维护一个全局的进程表,记录下当前有哪些进程正在被执行;把时间分成适当的片段,现代处理器结构可以通过设置时钟中断,每次时钟中断到来时系统就会获得控制权,在进程间实施切换,即保留上一个进程的环境信息,恢复下一个进程的执行环境。
3.1.2 进程和程序
程序的内存布局结构
程序内存区域有三种类型:静态数据区,动态数据区,以及维护控制流信息和局部状态的调用栈区域。
静态数据区存放的时全局变量以及静态变量等信息,编写代码时确定。
动态数据区存放程序执行过程中根据需要而分配或回收的区域。
调用栈存放的时程序执行过程中的函数状态信息,函数参数。
3.2 线程基本概念
3.2.1 线程模型
现代计算机结构中有两种线程模型:用户级线程(user-level threads)和内核级线程(kernel-level threads)。
用户级线程是指,应用程序在操作系统提供的单个控制流的基础上,通过在某些控制点(比如系统调用)上分离出一些虚拟的控制流,从而模拟多个控制流的行为。
内核级线程往往指操作系统提供的线程语义。因此,系统有必要维护一个全局的线程表,在线程表中记录每个线程的寄存器、状态以及其他一些信息。
3.2.2 线程调度算法
线程调度算法可以分为非抢占式算法和抢占式算法。
三种典型的线程调度算法:先到先服务算法、时间片轮转调度算法、优先级调度算法。
3.2.3 线程和进程的关系
3.3 Windows中进程和线程数据结构
3.3.1 内核层的进程和线程对象
进程数据结构(base\ntos\inc\ke.h):
每个KPROCESS对象都代表一个进程,反之也成立,即每个进程都拥有一个KPROCESS对象。
Header :一个分发器对象(dispatcher object),表明进程对象本身是可以被等待的。
ProfileListHead : 用于该进程参与性能分析(profiling)时,作为一个节点加入到全局的性能分析进程列表(KiProfileListHead)中。
DirectoryTableBase : 只有两项的数组,第一项指向该进程的页目录表地址,第二项指向该进程的超空间(hyper space)的页目录表地址。
LdtDescriptor、Int2lDescriptor、IopmOffset、Iopl是专门针对Intel x86处理器的。
LdtDescriptor :该进程的LDT(局部描述符表)的描述符
Int2lDescriptor :为了兼容DOS程序,允许它们通过int 21h指令来调用DOS系统功能
IopmOffset :指定了IOPM(I/O权限表,I/O Privilege Map)的位置,内核通过IOPM可控制进程的用户模式I/O访问权限
Iopl : 定义了进程的I/O优先级(I/O Privilege Level)
ActiveProcessors : 记录了当前进程正在哪些处理器上运行
KernerTime ,UserTime :记录内核模式和用户模式所花的时间
ReadyListHead : 记录该进程中处于就绪状态但尚未被加入全局就绪链表的线程
SwapListEntry : 一个单链表项,当一个进程要被换出内存时,它通过此域加入到以KiProcessOutSwapListHead为链头的单链表中;当一个进程要被换入内存时,它通过此域加入到以KiProcessInSwapListHead为链头的单链表中;
VdmTrapcHandler : 指向处理Ctrl+C 中断的函数,仅用于在VDM(虚拟DOS机,Virtual DOS Machine) 环境下运行16位程序
ThreadListHead : 一个链表头,包含了该进程的所有当前线程
ProcessLock : 一个自旋锁(spin lock)对象,用于保护此进程中的数据成员
Affinity : 指定了该进程的线程可以在哪些处理器上运行,其类型时KAFFINITY
ProcessFlags : 包括了进程中的几个标志:AutoAlignment、DisableBoost、DisableQuantum。AutoAlignment 位用于该进程中的内存访问对齐设置。DisableBoost和DisableQuantum位与线程调度过程中的优先级提升和时限(quantum) 分配有关
BasePriority : 用于指定一个进程中的线程基本优先权,所有的线程在启动时都会继承进程的BasePriority值。
QuantumReset : 用于指定一个进程中线程的基本时限重置值,现代Windows版本中被设置为6
State : 说明了一个进程是否存在内存中,共有六种可能的状态:ProcessInMemory、ProcessOutOfMemory、ProcessInTransition、ProcessOutTransition、ProcessInSwap、ProcessOutSwap。
ThreadSeed :用于为该进程的线程选择适当的理想处理器(Ideal processor)
PowerState : 用于记录电源状态
IdealNode : 用于为一个进程选择优先的处理器节点,是进程初始化时设定的
Visited : wrk 未使用
ExecuteOptions : 用于设置一个进程的内存执行选项
StackCount : 记录了当前进程中有多少个线程的栈位于内存中
ProcessListEntry : 用于将当前系统中所有具有活动线程的进程串成一个链表,链表头为KiProcessListHead
总结:KPROCESS对象中记录的信息主要包括两类:一类跟进程内存环境相关,比如与目录表,交换状态等;
另一类是与其线程相关的一些属性,比如线程列表以及线程所需要的优先级、时限设置等、
系统的总进程列表是在执行体层管理的(EPROCESS结构)
线程的数据结构分三部分列出,定义在base\ntos\inc\ke.h,第一部分
%20 Header : 一个分发器对象,线程也可以被等待
MutantListHead : 指向一个链表头,包含了所有属于该线程的突变体对象(mutant,对应于API中的互斥体[mutex]对象)
InitialStack : 记录了原始的栈位置(高地址)
StackLimit : 记录了栈的低地址
KernelStack : 记录了真正内核调用栈的开始位置
StackBase : 记录了当前栈的基位置(高地址),在线程初始化时,InitialStack 和StackBase 是相等的,都指向原始的内核栈高地址
ThreadLock : 一个自旋锁,用于保护线程数据成员
ApcState : 指定了一个线程的APC(Asynchronous Procedure Call)信息
ContextSwitches : 记录了该线程进行了多少次环境切换
State : 反应了该线程当前的状态。关于线程的状态,请参考base\ntos\inc\ke.h 中的KTHREAD_STATE枚举类型
NpxState : 反应了浮点处理器的状态
Alertable : 说明了一个线程是否可以被唤醒,当一个线程正在等待时,如果Alertbale值为TRUE,则它可以被唤醒。
WaitNext : 在发出一个信号后,接下来该线程会马上调用等待函数
WaitIrql : 与WaitNext 一起使用,当WaitNext 为TRUE时,WaitIrql 记录了原先的IRQL值
WaitReason : 记录了一个线程的等待理由,见base\ntos\inc\ke.h KWAIT_REASON
WaitMode : 记录了当线程等待时的处理器模式,即内核模式或用户模式的等待
WaitStatus : 记录了等待的结果状态
WaitBlockList : 指向一个以KWAIT_BLOCK 为元素的链表,其中的KWAIT_BLOCK对象指明了哪个线程在等待哪个分发器对象
GateObject : 记录了正在等待的门对象。等待门对象和等待其他分发器对象不会同时发生的
KTHREAD结构定义的第二部分:
%20 Priority : 包含了该线程的(动态)优先级值
BasePriority : 线程的静态优先级,其初始值时所属进程的BasePriority值,可通过KeSetBasePriorityThread函数设定
PriorityDecrement : 记录了一个线程在优先级动态调整过程中的递减值。Windows中的优先级被分为两个区域:0-15 是普通线程的优先级,16-31是实时线程的优先级。
Saturation : 说明了线程的基本优先级相对于进程的优先级的调整量是否超过了整个区间的一半,其值为0、1或-1
EnableStackSwap : 说明本线程的内核栈是否允许被换出到外存中
SwapBusy : 指定了本线程当前是否正在进行上下文环境切换(context swap)
Alerted : 一个数组,指定了该线程在每一种警告模式下是否可以被唤醒
WaitListEntry 和SwapListEntry : 分别是一个双链表节点和一个单链表节点,它们是一个union,用于不同的情形。当一个线程正在等待被执行时,WaitListEntry作为一个线程节点加入到某个链表中。SwaitListEntry 则被用于当线程的内核栈需要被换入时,插入到全局变量KiStackInSwapListHead为链表头的单链表中。
Queue : 一个队列分发器对象,如果不为NULL,则表示当前线程正在处理此对了对象中的项
WaitTime : 记录了一个线程进入等待时刻的时间点
KernelApcDisable 和SpecialApcDisable 或者CombinedApcDisable : KernelApcDIsable和SpecialApcDisable ,0 表示不禁止APC,负数表示禁止APC
Teb : 指向进程地址空间中的一个TEB(线程环境块)结构,包含了在用户地址空间中需要访问的各种信息,例如与线程相关的GDI信息、系统支持的异常,Winsock 信息等。 在public\sdk\inc\pebteb.h 参考_TEB 定义
Timer : 附在一个线程上的定时器
AutoAlignment 和DisableBoost : 直接继承自所属进程的同名标记,参考KPROCESS成员
WaitBlock : 一个包含4 个KWAIT_BLOCK成员的数组,其中第4 项专门用于等待的定时器对象
QueueListEntry : 记录了线程在处理一个队列项时加入到队列对象的线程链表中的节点地址
KTHREAD 结构定义的第三部分:
TrapFrame : 一个线程中最为关键的部分。记录控制流状态的数据结构,它是一个指向KTRAP_FRAME 类型的指针。KTRAP_FRAME定义在base\ntos\inc\i386.h 中。
CallbackStack : 包含了线程的回调(callback)栈地址,此栈在该线程从内核模式调用到用户模式时使用
ServiceTable : 指向该线程使用的系统服务表(全局变量KeServiceDescriptorTable),如果这是一个图形用户界面(GUI)线程,此域将指向影子系统服务表(全局变量KeServiceDescriptorTableShadow)
IdealProcessor : 指明了在多处理器的机器上该线程的理想处理器
Preempted : 说明这个线程是否被高优先级的线程抢占了,只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,此值才会置为TRUE
ProcessReadyQueue : 说明一个线程是否在所属进程KPROCESS对象的ReadyListHead 链表中,TRUE表示在此链表中
KernelStackResident : 说明该线程的内核栈是否驻留在内存中,当线程栈被换出内存时,置为FALSE;当换入内存时,置为TRUE
Affinity : 指定了线程的处理器亲和性,初始时继承自进程对象的Affinity 值
Process : 指向线程的进程对象,在线程初始化时指定,见base\ntos\ke\thredobj.c 中KeInitThread 函数
ApcStateIndex : 一个索引值,指明了当前的APC状态在ApcStatePointer 中的索引
Win32Thread : 指向由Windows子系统管理的区域
SuspendApc 和 SuspendSemaphore 两个union 相互之间有联系,SuspendApc被初始化成一个专门的APC。当该APC被插入并交付时,KiSuspendThread函数被执行,其执行结果时在线程的SuspendSemaphore 信号量上等待,直到该信号量对象有信号,然后线程被唤醒并继续执行
ThreadListEntry : 一个双链表上的节点,当一个线程被创建时,它会被加入到进程对象的ThreadListHead 链表中,参考KPROCESS的ThreadListHead
SListFaultAddress : 与用户模式互锁单链表POP操作(KeUserPopEntrySListFault函数)的错误处理有关,记录了上一次用户模式互锁单链表POP操作发生页面错误的地址
SuspendSemaphore : 记录了在地址SListFaultAddress 上发生页面错误的次数
3.3.2 执行体层的进程和线程对象
执行体层进程对象的数据结构EPROCESS,见base\ntos\inc\ps.h,第一部分
Pcb : KPROCESS 内嵌结构体
ProcessLock : 一个推锁(push lock)对象,用于保护EPROCESS中的数据成员
CreateTime : 进程的创建时间
ExitTime : 进程的退出时间
RundownProtect : 进程的停止保护锁。当一个进程到最后被销毁时,它要等到所有其他进程和线程以及释放了此锁,才可继续进行
UniqueProcessId : 进程的唯一编号,在进程创建时设定
ActiveProcessLinks : 一个双链表节点。在Windows系统中,所有的活动进程都连接在一起,构成一个双链表,表头时全局变量PsActiveProcessHead
QuotaUsage、QuotaPeak : 指一个进程的内存使用量和尖峰使用量
CommitCharge : 包含了一个进程的虚拟内存已提交的页面数量
CommitChargePeak : 尖峰时刻的已提交页面数量
CommitChargeLimit : 已提交页面数量的限制值,如果是0则表示没有限制
VirtualSize : 指一个进程的虚拟内存大小
PeakVirtualSize : 指虚拟内存大小的尖峰值
SessionProcessLinks : 一个双链表节点,当进程加入到一个系统会话中时,其中SessionProcessLinks 域将作为一个节点加入到该会话的进程链表中。
DebugPort : 一个句柄,指向调试端口
ExceptionPort : 一个句柄,指向异常端口
ObjectTable : 进程的句柄表
Token : 一个快速引用,指向该进程的访问令牌,用于该进程的安全访问检查
EPROCESS 结构的第二部分:
%20 WorkingSetPage : 指包含进程工作集的页面
AddressCreationLock : 一个守护互斥体锁(guarded mutex),用于保护地址空间的操作
HyperSpaceLock : 一个自旋锁,用于保护进程的超空间
ForkInProcess : 指向正在复制地址空间的那个线程,仅当在地址空间复制过程中,此域才会被赋值,其他情况下为NULL
HardwareTrigger : 用于记录硬件错误性能分析次数
PhysicalBadRoot : 指向进程的物理VAD树的根
CloneRoot : 指向一个平衡树的根,当进程地址空间复制时,此树被创建,一直到进程退出才被销毁
NumberOfPrivatePages : 进程私有页面的数量
NumberOfLockedPages : 进程被锁住页面的数量
Win32Process : 指向由Windows子系统管理的进程区域,此值不为NULL,说明这是一个Windows 子系统进程(GUI 进程)
Job : 指向一个_EJOB 对象
SectionObject : 进程的内存区对象(进程的可执行映像文件的内存区对象)
SectionBaseAddress : 进程内存区对象的基地址
QuotaBlock : 指向进程的配额块
WorkingSetWatch : 用于监视一个进程的页面错误(由全局变量PsWatchEnabled开关来控制)
Win32WindowStation : 一个进程所属的窗口站的句柄
InheritedFromUniqueProcessId : 父进程的标识符
LdtInformation : 用来维护一个进程的LDT(局部描述符表)信息
VadFreeHint : 指向一个提示VAD(虚拟地址描述符)节点,用于加速在VAD树中执行查找操作
VdmObjects : 指向当前进程的VDM数据区
DeviceMap : 指向进程使用的设备表
EPROCESS 结构的第三部分:
%20 Spare0 : 一个备用域,wrk 没有使用
PageDirectoryPte : 顶级目录页面的页表项
Session : 指向进程所在的系统会话,实际指向MM_SESSION_SPACE
ImageFileName : 进程的映像文件名
JobLinks : 一个双链表节点,一个Job的所有进程构成了一个链表
LockedPagesList : 一个指向LOCK_HEADER 结构指针
ThreadListHead : 一个双链表的头节点,包含了一个进程中的所有线程
SecurityPort : 指向该进程与Lsass 进程之间的跨进程通信端口
PaeTop : 用于支持PAE内存访问机制
ActiveThreads : 记录了当前进程有多少个活动线程
GrantedAccess : 包含了进程的访问权限
DefaultHardErrorProcessing : 指定了默认的硬件错误处理,默认为1
LastThreadExitStatus : 记录了刚才最后一个线程的退出状态
Peb : 一个进程的进程环境块(PEB,Process Environment Block)
EPROCESS 结构的最后一部分:
PrefetchTrace : 指向与该进程关联的一个预取痕迹结构,以支持进程的预取
ReadOperationCount 、WriteOperationCount : 记录了当前进程NtReadFile和NtWriteFile系统服务被调用的次数
OtherOperationCount : 记录了除读和写操作以外的其他I/O服务的次数
ReadTransferCount、WriteTransferCount : 记录了I/O 读写操完成的次数
OtherTransferCount : 记录了非读写操作完成的次数
AweInfo : 一个指向AWEINFO结构的指针,目的是支持AWE(Adderss Windowing Extension,地址窗口扩展)
SeAuditProcessCreationInfo : 包含了创建进程映像全路径名,ImageFile 就是从这里提取的
Vm : 是Windows为每个进程管理虚拟内存的重要数据结构成员,类型为MMSUPPORT
MmProcessLinks : 一个双链表节点,所有拥有自己地址空间的进程都将加入到一个双链表中,链表头是全局变量MmProcessList
ModifiedPageCount : 记录了该进程中已修改页面的数量
JobStatus :记录了进程所属Job的状态
Flags : 包含了进程的标志位
ExitStatus : 包含了进程的退出状态
NextPageColor : 用于物理页面分配算法
SubSystemVersion : 记录了一个进程的子系统主版本和次版本号
PriorityClass : 一个进程的优先级程度,参考public\sdk\inc\ntpsapi.h
PROCESS_PRIORITYCLASS<XXX>
VadRoot : 指向一个平衡二叉树的根,用于管理该进程的虚拟地址空间
Cookie : 一个代表该进程的随机值
ETHREAD 结构,第一部分:
CreateTime : 线程的创建时间
ExitTime : 线程的退出时间
LpcReplyChain、KeyedWaitChain : 分别用于跨进程通信(LPC)和带键事件的等待链表
ExitStatus : 线程的退出状态
OfsChain : wrk 没有使用
PostBlockList : 一个类型为PCM_POST_BLOCK的双链表头节点,用于一个线程向配置管理器等级注册表键的变化通知
TerminationPort : 一个链表头,当一个线程退出时,系统会通知所有已经登记过要接收其终止事件的那些端口
ReaperLink : 一个单链表节点,仅在线程退出时使用
KeyedWaitVaule : 带键事件的键值
ActiveTimerListHead : 一个双链表的头,包含了当前线程的所有定时器
ActiveTimeListLock : 操作ActiveTimerListHead链表的自旋锁
Cid : 包含了线程的唯一标识符
LpcReplySemaphore : 用于应答LPC通知
KeyedWaitSemaphore : 用于处理带键的事件
LpcReplyMessage : 指向LPCP_MESSAGE的指针
LpcWaitingOnPort : 说明在哪个端口对象上等待
ImpersonationInfo : 指向线程的模仿信息
IrpList : 一个双链表头,包含了当前线程所有正在处理但尚未完成的I/O请求(IRP对象)
TopLevelIrp : 指向线程的顶级IRP
DeviceToVerify : 指向一个待检验的设备对象
ETHREAD 结构的第二部分:
ThreadsProcess :指向当前线程所属的进程
StartAddress : 包含了线程的启动地址
Win32StartAddress : 是Windows子系统的启动地址
LpcReceivedMessageId : 包含了接收到的LPC消息ID
ThreadList : 一个双链表的节点,每个线程都会加入到它所属进程EPROCESS结构的ThreadListHead 中
RundownProtect : 线程的停止保护锁
ThreadLock : 是一把推锁,用户保护线程的数据属性
LpcReplyMessageId : 指明了当前线程正在等待对一个消息的应答
ReadClusterSize : 指明了在一次I/O 操作中读取多少个页面,用于页面交换文件和内存映射文件的读操作
GrantedAccess : 保护了线程的访问权限
CrossThreadFlags : 一些针对跨线程访问的标志位
SameThreadPassiveFlags : 一些只有在最低中断级别(被动级别)上才可以访问的标志位,并且只能被线程自身访问
SameThreadApcFlags : 一些在APC中断级别上被该线程自身访问的标志位
DisablePageFaultClustering : 用于控制页面的聚集与否
ForwardClusterOnly : 指示是否仅仅前向聚集
ActiveFaultCount : 包含了正在进行之中的页面错误数量
3.4 Windows的进程和线程管理
3.4.1 Windows进程中的句柄表
句柄是一个对象引用,同一个对象在不同的环境下可能有不同的引用(句柄)值。句柄仅在一个进程范围内有效。
HANDLE_TABLE 结构的定义:
TableCode : 指向句柄表的最高层表项页面,低2位的值表示当前句柄表的层数。
如果最低2位为0,说明句柄表只有1层,最多能容纳512个句柄。
如果最低2位为1,说明句柄表有2层,最多能容纳5121024个句柄。
如果最低2位为2,说明句柄表有3层,最多能容纳5121024*1024个句柄。
Windows 进程的句柄表结构
执行体在创建进程时,首先为新进程分配一个单层句柄表。由ExCreateHandleTable 函数来完成,该函数调用ExpAllocateHandleTable 来构造初始的句柄表。由函数
ExpAllocateHandleTableEntryShow 来扩展句柄表。代码见base\ntos\ex\handle.c.
FirstFree : 记录了当前句柄表中的空闲句柄链。通过句柄索引值来链接。
HANDLE_TABLE_ENTRY 结构定义如下:
Object : 指向句柄所代表的内核对象,最低3位含义:第0 位OBJ_PROTECT_CLOSE,表示调用者是否允许关闭该句柄;第1位OBJ_INHERIT,指示该进程所创建的子进程是否可以继承该句柄,即是否将该句柄项拷贝到它们的句柄表中;第2位OBJ_AUDIT_OBJECT_CLOSE,指示关闭该对象时是否产生一个审计事件
第二个union中,如果句柄表项指向一个有效的对象,则GrantedAccess 记录了该句柄的访问掩码;如果这是一个空闲的句柄表项,则NextFreeTableEntry 将加入到句柄表的空闲单链表中。
一个有效的句柄有4中可能:-1,表示当前进程;-2表示当前线程;负值,其绝对值为内核句柄表中的索引;不超过226的正值,当前进程的句柄表中的索引。
内核句柄表即系统的全局句柄表,wrk 中即变量ObpKernelHandleTable,也是System 进程的句柄表。解析句柄函数ObReferenceObjectByHandle,见base\ntos\ob\obref.c 。
将一个对象插入到句柄表中的函数是ObInsertObject,见base\ntos\ob\obinsert.c 。
对象的引用有两种,一种在内核中之间通过对象地址来引用,通过
ObReferenceObjectByPoint记录一次新的引用,第二种,通过句柄来引用对象,通过
ObpIncrementHandleCount检查并记录一次句柄引用。
在一个句柄上调用了ObReferenceObjectByHandle后,若该对象不在使用,必须调用ObDereferenceObject 函数。
进程唯一ID,即UniqueProcessId;线程有一个CLIENT_ID成员Cid,包含了所属进程的唯一ID和线程的唯一ID。
3.4.2 获得当前线程和进程
Wrk 中,KeGetCurrentThread 实现:
Intel X86 处理器中,fs 指向处理器控制区(PCR,Processor Control Region)的内存,类型为KPCR,其成员PrcbData是当前处理器的控制块(Processor Control Block),其中包含当前线程的KTHREAD结构指针。KPCR、KPCRCB 结构见base\ntos\inc\i386.h。
PsGetCurrentThread ,PsGetCurrentProcess 实现:
3.4.3 进程和线程的创建过程
参考看雪资料 :https://bbs.pediy.com/thread-114611.htm
http://bbs.pediy.com/showthread.php?t=114958
3.4.4 进程和线程的结束
在执行体层,线程的终止函数是NtTerminateThread,内部调用PspTerminateThreadByPointer完成终止处理。系统线程的终止函数是PsTerminateSystemThread,内部调用
PspTerminateThreadByPointer完成终止处理。
三个函数原型如下:
NtTerminateThread 先把参数ThreadHandle解出来,然后调用
PspTerminateThreadByPointer来结束指定的线程。如果是当前线程
PspTerminateThreadByPointer 参数DirectTerminate 为TRUE,函数不返回;否则为FALSE,返回后,线程的句柄数减1。
PspTerminateThreadByPointer 函数,如果是当前线程被终止,则设置结束标志
PS_CROSS_THREAD_FLAGS_TERMINATED,调用PspExitThread 退出线程。如果不是当前线程,则在该线程中插入一个内核模式APC,为它指定PsExitSpecialApc 、PspExitApcRundown和
PspExitNormalApc ,他们完成终止过程。
NtTerminateProcess 遍历非当前线程,调用PspTerminateThreadByPointer终止线程。当前进程调用PspTerminateThreadByPointer终止自己。
PsTerminateProcess 循环调用PspTerminateThreadByPointer以删除指定进程中的所有线程。
3.4.5 系统初始进程和线程
3.5 Windows中的线程调度
3.5.1 线程优先级
线程优先级分为3种类别:实时类别:16-31;动态类别:1-15;系统类别:0
线程对象得KTHREAD 的BasePriority 和Priority 分别为线程的静态和动态优先级。
KiComputeNewPriority 是计算Priority 值的函数,代码如下:
线程的优先级重新计算只针对非实时优先级类别的线程,并且新的优先级不会低于基本优先级。
执行体在创建进程时,调用PspComputeQuantumAndPriority 来计算进程的优先级。
PspComputeQuantumAndPriority 根据EPROCESS对象的PrioriytClass 查表获得内核层优先级定义,即全局变量PspPriorityTable,相关定义如下:
执行体层的优先级经过全局表PspPriorityTable变换后,就成了内核层的0~31优先级值。
带有“KPRIORITY Increment”参数,函数包括KeInsertQueueApc、KePulseEvent、KeSetEvent、KeReleaseMutant、KeReleaseSemaphore、KeSetProcess、KeBoostProirityThread、KeTeminateThread。在Windows中优先级提升的典型情形:
- 当一个I/O 操作完成时,正在等待此I/O 的线程有更多的机会被立即执行
- 一个线程在等待到事件和信号量以后,其优先级得到提升
- 前台线程从等待状态中醒来时,优先级提升
- 在平衡集管理器系统线程中,扫描那些已金融就绪状态4s 但尚未被执行的线程,将它们的优先级提升至15
- 窗口线程因窗口的活动(比如窗口消息到来)而醒来时,优先级提升 3.5.2 线程状态转移
KTHREAD 的State 反映了线程的当前调度状态,类型为KTHREAD_STATE,见base\ntos\inc\ke.h
线程状态说明:
- 已初始化(Initialized): 一个线程对象的内部已初始化,是线程创建过程中的一个内部状态,此时线程尚未加入到进程的线程链表中,也没有启动
- 就绪(Ready): 该线程已经准备就绪,等待被调度执行
- 运行(Running) : 线程正在运行
- 备用(Standby) : 处于备用状态的线程已经被选中作为某个处理器上下一个要运行的线程
- 已终止(Terminated): 表示线程已经完成任务,正在进行资源回收
- 等待(Waiting) : 表示一个线程正在等待某个条件。
- 转移(Transition) : 处于转移状态的线程已经准备好运行,但是它的内核栈不在内存中
- 延迟的就绪(DeferredReady) : 处于延迟的就绪状态的线程也已经准备好可以运行了,但是,与就绪状态不同的是,它尚未确定在哪个处理器上运行
- 门等待(GateWait) : 线程正在等待一个门对象 Wrk 中的线程状态转移图:
状态转移关键函数KiReadyThread,见base\ntos\ke\thredsup.c
%20 在wrk 中,KiReadyThread 函数在以下情况下被调用:
- 当一个线程被接触等待时,见KiUnwaitThread 函数
- 当一个内核队列对象(KQUEUE) 中有新的成员插入并且满足必要的条件时,见KiInsertQueue 函数
- 当一个线程被附载(attch)到一个新的进程中时,见KiAttachProcess 函数
- 在KeSetEventBoostPriority 函数,如同第一种情形
- 在处理环绕进程链表时,见KiInSwapProcess
- 在处理换出进程链表时,见KiOutSwapProcess
- 在创建线程的函数(PspCreateThread)时,见KeReadThread
延迟的就绪状态的线程到执行,首先KiReadyThread 调用KiInsertDeferredReadyList 把一个线程插入到当前处理器PRCB的延迟就绪表中。调度器获得控制权,KiProcessDeferredReadyList 遍历当前处理器的延迟就绪链表,对每个线程调用KiDeferredReadyThread ,变成就绪或备用状态
一个状态或者备用状态的线程如何被选出来运行。首先,先了解处理器的全局控制块数据结构KPRCB,见base\ntos\inc\i386.h
KPRCB 中,CurrentThread,NextThread,IdleThread 分别指向该处理器上当前正在运行的线程(运行状态),下一个要运行的线程(备用状态),空闲线程(完成初始化后的)。
DispatcherReadyListHead 数组中的线程如何转移到NextThread 或CurrentThread 中。在wrk 中,KiSelectReadyThread,KiFindReadyThread 实现转移。
在wrk 中,以下一些调度点上,一个线程有机会变成运行状态:
- 在处理器的空闲循环中,若处理器的空闲调度标志(即KPRCB的IdleSchedule)被置为TRUE,则调用KiIdleSchedule。如果处理器上的备用线程(NextThread)不为空,则让备用线程运行,否则调用KiSelectReadyThread 找到要运行的线程。如果当前处理器找不到合适的线程,则cong 其他处理器上寻找适合在当处理器上运行的线程(通过调用KiFindReadyThread)。
- 调度点KiSwapThread
- 当前线程的时限已用完,调度点KiQuantumEnd
- 一个线程主动放弃执行权,调度点NtYieldExecution
- 线程的优先级、亲和性发生变化或时限调整时,同样会导致线程状态发生变化。包括KiSetPriorityThread、KeRevertToUserAffinityThread、KeSetSystemAffinityThread、KiAdjustQuanturnThread。 一个线程在自身运行条件满足的情况下的状态转移图:
在wrk 中等待时通过KeDelayExecutionThread、KeWaitForSingleObject 、KeWaitForMultipleObjects 函数来实现。
线程在等待时的状态转移图:
wrk 之 Windows 进程和线程
3.5.3 时限管理
线程对象KTHREAD :Quantum 记录了该线程的当前时限中还剩下多少时间单位,QuantumReset 记录了这个线程的时限重置值,即一个完整时限的时间单位。
3.5.4 优先级调度和环境切换
串联Windows 的优先级调度算法。首先,系统维护了一个全局的处理器数组KiProcessorBlock,其中每个元素对应于一个处理器的KPRCB对象。其次,全局变量KiIdleSummary 记录了哪些处理器当前是空闲的。
线程调度的数据结构和全局信息:
wrk 之 Windows 进程和线程
一个线程主动放弃执行权,会在内核函数中调用KiSwapThread 函数。KiSwapThread
KiSwapContextSwapContext 来完成线程切换。
KiSwapThread 进行线程调度的可能性:
- 线程自愿进入等待状态,即KeDelayExcutionThread、KeWaitForSingleObject、KeWaitForMultipleObjects
- 线程进入门等待状态,即KeWaitForGate
- 一个线程终止了,即KiSwapThread
- KeRemoveQueue
- 一个线程附载到一个进程的地址空间时,即KiAttachProcess 一个线程被迫放弃执行权:时限用完或者线程被抢占。线程被抢占发生在KiDeferredReadyThread 函数中。时限用完之间发生在时钟中断中,即KiDispatchInterrupt 。
线程抢占和时限用完都发生在KiDispatchInterrupt 函数中。线程抢占情形,KiDispatchInterrupt 调用SwapContext 执行线程切换;时限用完情形;调用
KindQuantumEnd。
线程切换的各种情况,(a)代表了一个线程主动放弃执行权得情形,(b)表示一个线程被抢占或时限用完的情形。
wrk 之 Windows 进程和线程
《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!
最后于 2小时前被曹操abc编辑,原因: 图片没了
|
温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【 投诉建议】板块发帖举报。
|