Windows线程调度学习(一)

前言

Windows 线程调度器的实现分散在内核各处,并且与许多组件都有关联,很难进行系统地学习,所以我打算写几篇文章来记录下自己学习过程中的思考和分析,同时也方便日后查阅,此文可以看作是《Windows内核原理与实现》中线程调度部分的读书笔记和简单总结。

正文

一. 线程当前状态


在对调度器函数进行分析学习之前,首先要明确一个概念:调度器只由内核层进行负责实现,不涉及执行体层。因此线程相关的数据结构只有 KTHREAD,其中调度相关的最重要的成员是 State,它标识了线程的当前状态,取值由名为 KTHREAD_STATE 的枚举类型定义:

typedef enum _KTHREAD_STATE {Initialized,Ready,Running,Standby,Terminated,Waiting,Transition,DeferredReady,GateWait
} KTHREAD_STATE;

已初始化 (Initialized):线程创建过程中的内部状态,此时线程不参与调度。
就绪 (Ready):线程已经准备好运行,等待被调度。
运行中 (Running):线程正在某一处理器上运行。
待命 (Standby):线程被选为某一处理器上下一个将要被执行的线程。
已终止 (Terminated):线程已终止,正在进行资源回收。
等待中 (Waiting):线程正在等待某个条件满足,比如事件对象被触发。
转移 (Transition):线程已经准备好运行但内核栈不在内存中。
延迟就绪 (DeferredReady):线程尚未被确定在哪个处理器上运行,此状态对于单处理器系统没有意义。
门等待 (GateWait):线程正在等待一个门对象。

其中就绪延迟就绪状态的主要区别是:延迟就绪线程尚未确定被分配到哪个处理器上运行,而就绪线程已经被分配到了某个处理器上。


对于线程各个状态间的转移规则,可以参考线程状态转移图(引自潘爱民老师的《Windows内核原理与实现》):

图片描述


二. 进程的当前状态


进程在内核层所对应的 KPROCESS 结构中,也有一个用来标识当前状态的 State 成员,它的取值由名为 KPROCESS_STATE 的枚举类型定义:

typedef enum _KPROCESS_STATE {ProcessInMemory,ProcessOutOfMemory,ProcessInTransition,ProcessOutTransition,ProcessInSwap,ProcessOutSwap
} KPROCESS_STATE;

ProcessInMemory:表示进程的虚拟地址空间内容在物理内存中。
ProcessOutOfMemory:表示进程的虚拟地址空间内容已被换出物理内存。
ProcessInTransition:表示进程的虚拟地址空间内容不在物理内存中,但已请求换入。
ProcessOutTransition:表示进程的虚拟地址空间内容存在于物理内存中,但已请求换出。
ProcessInSwap:表示正在将进程的虚拟地址空间内容换入物理内存,换入完成后,状态将变更为 ProcessInMemory
ProcessOutSwap:表示正在将进程的虚拟地址空间内容换出物理内存,换出完成后,状态将变更为 ProcessOutOfMemory


换入或换出进程的虚拟地址空间会导致进程状态的切换,此工作是由名为 平衡集管理器 (Balance Set Manager) 的内核组件负责的,在内核第一阶段初始化接近结束时,MmInitSystem 函数创建了两个平衡集管理器线程,其对应例程分别是 KeBalanceSetManagerKeSwapProcessOrStack 函数。

KeBalanceSetManager 线程循环等待一个每秒触发一次的定时器对象和一个工作集管理器事件对象,当等待成功后,它触发名为 KiSwapEvent 的事件对象来通知交换线程,以尝试对满足条件的线程的内核栈执行换出操作。KeSwapProcessOrStack 即为交换线程,它循环等待上述的 KiSwapEvent 对象,一旦等待成功,会根据情况执行进程线程内核栈的换入换出工作。

一个进程的换出操作发生在进程的 StackCount 为 0 时,StackCount 记录了该进程中有多少个线程的内核栈位于内存中,当该进程的所有线程的内核栈都被换出内存时,KiOutSwapKernelStacks 会将进程插入到待换出链表中,并触发 KiSwapEvent 对象,交换线程会在下次循环中调用 KiOutSwapProcesses 函数将该进程换出内存。

平衡集管理器实质上是内存管理器组件,有关它更多更详细的内容将在之后的文章中更新。


三. 调度器主要函数实现


1. KiReadyThread


KiReadyThread 从名字上来看是将一个线程转为就绪状态,而实际上这个函数根据三种不同情况来进行处理:

void __fastcall KiReadyThread(IN PKTHREAD Thread) {PKPROCESS Process;Process = Thread->ApcState.Process;if (Process->State != ProcessInMemory) {Thread->State = Ready;Thread->ProcessReadyQueue = TRUE;InsertTailList(&Process->ReadyListHead, &Thread->WaitListEntry);if (Process->State == ProcessOutOfMemory) {Process->State = ProcessInTransition;InterlockedPushEntrySingleList(&KiProcessInSwapListHead, &Process->SwapListEntry);KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);}return;} else if (Thread->KernelStackResident == FALSE) {ASSERT(Process->StackCount != MAXULONG_PTR);Process->StackCount += 1;ASSERT(Thread->State != Transition);Thread->State = Transition;InterlockedPushEntrySingleList(&KiStackInSwapListHead, &Thread->SwapListEntry);KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);return;} else {KiInsertDeferredReadyList(Thread);return;}
}

分支一:

首先,此函数根据上文提到的 KPROCESSState 成员,来判断目标线程所属进程当前是否处于 ProcessInMemory 状态,即进程虚拟地址空间是否在物理内存中,若不是则将目标线程设置为就绪状态,并将线程的 ProcessReadyQueue 标志设置为 TRUE,然后将线程插入到所属进程的就绪链表 (ReadyListHead) 中,ProcessReadyQueue 用来标识线程是否在其所属进程的就绪链表中。而后进一步判断进程是否处于 ProcessOutOfMemory 状态,若是则将该进程设置为 ProcessInTransition 状态,并插入到待换入进程链表中,最后触发 KiSwapEvent 对象通知交换线程执行进程换入操作。由此可以看出,ProcessInTransition 是一种中间状态,他标识了进程将要但还没有被执行换入操作,此状态介于 ProcessInMemoryProcessInSwap 之间。

当进程当前处于 ProcessOutOfMemory 状态时,其后续操作是:平衡集管理器的交换线程成功等待到 KiSwapEvent,进而调用 KiInSwapProcesses 函数将之前插入到待换入进程链表中的进程换入内存(通过 MmInSwapProcess 函数),之后将进程状态修改为 ProcessInMemory。此时进程虚拟地址空间已在物理内存中,可以对进程中所有的就绪线程进行调度,所以 KiInSwapProcesses 函数遍历该进程的就绪链表,对其中的所有线程再次调用 KiReadyThread,而后将线程从链表中移除。由于这一次进程已存在于内存中,所以此次 KiReadyThread 函数不会再执行到此分支。

而对于 ProcessInTransitionProcessInSwap 这两种状态,则不需要通知交换线程将进程换入内存,因为此时交换线程已经或将要执行 KiInSwapProcesses 函数,如上所述,此函数会在将进程换入内存后,对该进程就绪链表中的所有线程再次调用 KiReadyThread。

最后,若进程处于 ProcessOutTransitionProcessOutSwap 状态(进程因其所有线程的内核栈都被换出内存而导致自身也被换出内存,在换出的过程中,如果有属于该进程的新线程被创建,或某一现有线程挂靠到该进程上,则 KiReadyThread 被调用,此时进程可能处于这两种状态),那么剩下的工作将由交换线程通过调用 KiOutSwapProcesses 函数来完成,此函数负责将待换出进程链表中的进程换出内存,它在两个阶段分别检查待换出进程的就绪链表:若进程尚未换出内存,则取消换出操作并将进程状态修改为 ProcessInMemory,然后对该进程就绪链表中的所有线程再次调用 KiReadyThread;若进程已换出内存,则修改进程状态为 ProcessInTransition 并触发 KiSwapEvent 对象,交换线程会在下次循环中调用 KiInSwapProcesses 执行后续操作。

综上所述,只有当进程处于 ProcessOutOfMemory 状态时,此函数才通知交换线程将进程换入内存,其余情况平衡集管理器会进行判断和处理,而无论哪种一情况,进程最后都会变为 ProcessInMemory 状态,进而交由其他分支处理,所谓异途同归。


分支二:

如果进程当前处于 ProcessInMemory 状态(经分支一处理后,进程必然处于此状态),则继续判断目标线程的内核栈是否在物理内存中(由 KernelStackResident 标志指示)。上文提到,线程栈的换入和换出操作也是由平衡集管理器负责的,当一个线程处于等待状态超过一定时间之后,交换线程调用 KiOutSwapKernelStacks 函数将其内核栈换出物理内存。因此若线程的内核栈已被换出物理内存,则要先通知交换线程将内核栈其换入内存,交换线程通过调用 KiInSwapKernelStacks 函数将线程内核栈换入物理内存,而后直接调用 KiInsertDeferredReadyList 函数将线程插入到延迟就绪链表中,关于 KiInsertDeferredReadyList 函数,见分支三

另外上文还提到,进程 KPROCESS 对象中的 StackCount 成员记录了该进程中有多少个线程的内核栈位于内存中,对于一个将要被换入内存的线程,自然要将其所属进程的 StackCount 加一(由于线程终止或挂靠到其他进程时也会引起 StackCount 的变动,所以此成员不由平衡集管理器维护)。


分支三:

进入到分支三就表示线程已满足执行条件(内核栈和所属进程都已在物理内存中),因此调用 KiInsertDeferredReadyList 函数执行下一步操作:

PKPRCB Prcb;
Prcb = KeGetCurrentPrcb();
Thread->State = DeferredReady;
Thread->DeferredProcessor = Prcb->Number;
PushEntryList(&Prcb->DeferredReadyListHead, &Thread->SwapListEntry);

此函数逻辑十分简单,所做的仅仅是将线程设置为延迟就绪状态,并将其插入到当前处理器 PRCB 结构中的延迟就绪链表中,以后当调度器获得控制权时,KiProcessDeferredReadyList 函数将遍历此链表,并对每个线程调用 KiDeferredReadyThread 函数,使其有机会变为就绪待命状态。注:此处所说的就绪状态是真正的就绪,区别于上文所说进程就绪链表中的线程,后者不满足执行条件(需要等待其所属进程被换入内存)。


至此 KiReadyThread 函数已分析完毕,可以看出,经过此函数处理后的任何线程都会变为延迟就绪状态,这对线程来说是一个重要转折点,意味着它将有机会获得执行权,而在此之前,该线程不会被考虑执行。


TO BE CONTINUED ...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/255885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

scapy 安装及简单测试

关于scapy Scapy的是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting&…

JavaScript 基础知识 - BOM篇

前言 本篇文章是JavaScript基础知识的BOM篇,如果前面的《JavaScript基础知识-DOM篇》看完了,现在就可以学习BOM了。 注意: 所有的案例都在这里链接: 提取密码密码: yvxo,文章中的每个案例后面都有对应的序号。 1. BOM 基本概念 B…

全球首例机器人自杀事件 因受够无聊家务

据凤凰网,一个奥地利家庭购买一小机器人,每天工作就是倒垃圾、倒垃圾。一天完工后,它竟自己启动,爬到炉边,推开上面的锅,把自己活活烧死…专家称这个机器人实在受够了无聊的家务琐事,才毅然选择自杀机器人也是有尊严的!为这有骨气的robot点根…

【python基础】——数据类型(列表、字典、集合)

骏马金龙——python语法基础 python基础 变量与运算 符号//%**意义整除整除取余幂次方数据种类 #mermaid-svg-7nSRRijcYFCYwTDr .label{font-family:trebuchet ms, verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-7nSRRijcYFCYw…

机器人实现屠宰自动化

当 WESTFLEISCH 注册合作社考虑在 Coesfeld 肉类加工中心内自动化原有的人工屠宰设备过程时,首先在“剔除直肠”及“切开盆腔骨及腹部”两个流程中测试使用了两台库卡机器人。在此过程中,机器人主要以它工作的质量及经济效益说服了使用者。 实施措施/解…

python数据结构《排序专题复习》

目录 常见的三种排序方法 冒泡排序 插入排序 选择排序 其他经典的排序方法 快速排序 堆排序 归并排序 希尔排序 不同排序方法的各维度对比 排序方式的稳定性:若两个相同的元素在排序前后的相对位置不发生改变的排序为稳定排序,否则不稳定排序 常…

BZOJ2844 albus就是要第一个出场

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id2844 这题貌似HDU上有一道差不多的题,不过我没做过,也就没管了。 首先讲一个线性基的东西,大概就是这样: 然后就是一个什么性质:S异或起来会出现重…

HTG Explains: Why Linux Doesn’t Need Defragmenting

If you’re a Linux user, you’ve probably heard that you don’t need to defragment your Linux file systems. You’ll also notice that Linux distributions don’t come with disk-defragmenting utilities. But why is that? To understand why Linux file systems d…

Spring AOP 实战运用

Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有…

大话设计模式之策略模式

第二章:商场促销——策略模式 策略模式的定义:策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,知识实现不同,他可以以相同的方式调用所有的算法,减少了各类算法类与使…

【Python学习】——语言风格(变量赋值、深浅拷贝、for循环陷阱)

目录 1、赋值 2、赋值的分类——引用赋值、值赋值 1) 不可变对象引用赋值——字符串、数值、元组等 2)可变对象引用赋值——列表、集合、字典 3)可变与不可变对象的引用赋值内部分析 4)在py文件中,和作用域有关,如…

判断庄家是否出货

1. 大盘处于强势的时候 日平均线在横盘的时候,缓慢拉升然后急剧下跌 高位盘整的时候 2. 有利好消息发布的时候 因为庄家会利用这个对于散户来说这个买入时机来进行出货操作,可见庄家真是阴险狡诈转载于:https://www.cnblogs.com/dcz1001/p/6115893.html

【深度学习】——常见深度学习模型总结、anchor-free和anchor-based

目录 1、faster rcnn: 2、SSD: 3、YOLOv1: 小结: 拓展:anchor-based和anchor-free anchor 1、faster rcnn: FasterRcnn 算法原理讲解笔记(非常详细)https://blog.csdn.net/xjtdw/article…

真静态和伪静态的区别

首先肯定的是纯静态和伪静态都是SEO的产物,但纯静态和伪静态还是有很大区别的。 纯静态是生成真实的HTML页面保存到服务器端,用户访问时直接访问这 个HTML页面即可,从而大大的减轻了服务器压力(如dedecms就是采用的纯静态&#xf…

非常有趣的Console

console觉醒之路,打印个动画如何? 原文地址: http://www.helloweba.com/view-blog-383.html 批量去掉或替换文本中的换行符(notepad、sublime text2) 原文地址:http://m.blog.csdn.net/article/details?id43228729 有…

假期实践

第一天 地点:杭州颐高数码城 第一天,我来到了自己家附近的颐高数码城。文三路这边有一个卖数码产品的一条街,这里也是最贴近我专业实践的地方,所以第一天的实践我选择了这里。 2001年开业的颐高数码广场座落于“电子一条街”文三路、学院路口…

3.AngularJS-过滤器

转自:https://www.cnblogs.com/best/p/6225621.html 二、过滤器 使用过滤器格式化数据,变换数据格式,在模板中使用一个插值变量。语法格式如下: {{ express | filter:parameter1:p2:p3… | … | …}} 过滤器分了内置过滤器与自定义…

【深度学习】——训练过程

包含哪些层 训练过程 其实就是yf(x)的求参过程,先给参数一个初始值,然后根据初始函数计算得到预测值,根据预测值和真值计算损失,然后又根据损失函数进行反向传播更新参数,更新参数后,再次计算预测值&#…

thinkphp自定义模板标签(一)

thinkphp内置的foreach和include等模板标签使用是非常方便的;但是内置的那些标签只能满足常用功能,个性化的功能就需要我们自己编写自定义模板标签了;下面就是要讲解如何实现; 示例环境:thinkphp3.2.3 thinkphp的模板标…

【深度学习】——激活函数(sigmoid、tanh、relu、softmax)

目录 激活函数 1、作用 2、常用激活函数 3、衡量激活函数好坏的标准: 4、不同的激活函数 1)sigmoid 2)tanh函数 3)RULE函数和leak-relu函数 4)softmax函数 激活函数 1、作用 如果只是线性卷积的话&#xff0c…