当前位置: 首页 > news >正文

windows内核研究(进程与线程-等待链表和调度链表和线程切换)

进程和线程


等待链表和调度链表

之前的章节有讲到线程的链表,当我们手动去断链时,在调试器中确实看不到相关线程了,但是并不影响操作系统的正常执行,那就说明操作系统并不是通过这个链表来访问各个线程的

等待链表

当线程调用了Sleep()或者WaitForSingleObject()等函数时,就挂到这个链表

在WinDbg中查看

dd KiWaitListHead

在我win10x86上并不是这个变量名

33个链表

线程有3种状态:就绪、等待、运行

正在运行中的线程存储在KPCR中,就绪和等待的线程全在另外的33个链表中(一个等待链表,32个就绪链表)

查看调度链表:

dd KiDispatcherReadyListHead L70

同样在我win10x86上并不是这个变量名

总结:

  1. 正在运行的线程在KPCR中
  2. 准备运行的线程在32个调度链表中(0-31级)
  3. 等待状态的线程存储在等待链表中
  4. 这些链表挂在_KTHREAD一个相同的相同的位置(我当前操作系统是9c)

线程切换-主动切换

在windows中有一个函数用来做线程切换KiSwapContext

时钟中断切换

如果要获取当前的时钟间隔,可以使用Win32API:GetSystemTimeAdjustment

当发生时钟中断时,系统的执行流程:

在这里插入图片描述

_IDT表中对应的函数

在这里插入图片描述

总结:

  1. 主动调用API函数
  2. 时针中断
  3. 异常处理

如果一个线程不调用API,在代码中屏蔽中断(CLI指令),并且不会出现异常,那么当前线程将永久占有CPU,单核就100%,双核就是50%


时间片管理

时钟中断的前置条件:

  1. 当前的线程CPU时间片到期
  2. 有备用线程(KPCR.PrcbData.NextThread)

当一个新的线程开始执行时,初始化程序会在_KTHREAD.QuantumReset赋初始值,该值的大小由_KPROCESS.ThreadQuantum决定

我们查看一下这个WmiApSrv.exe进程的信息

在这里插入图片描述

dt _KPROCESS afbf2040 // 查看afbf2040这个地址的_KPROCESS结构

在这里插入图片描述

注:内核中不同的操作系统结构体中的属性名称和值可能会不一样

每次时钟中断会调用KeUpdateRunTime函数,该函数每次将当前线程QuantumReset减少3个单位,如果减到0,则将KPCR.PrcbData.QuantumEnd的值设置为非0

这个函数目前我在win10_x86环境下已经搜索不到了

调用KiDispatchInterrupt函数来判断时间片到期,调用KiQuantumEnd(重新设置时间片,找到要运行的线程)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程切换的三种情况总结:

  1. 当前线程主动调用API
    • .API函数->KiSwapThread->KiSwapContext->SwapContext
  2. 当前线程时间片到期
    • KiDispatchInterrupt->KiQuantumEnd->SwapContext
  3. 有备用线程(KPCR.PrcbData.NextThread)
    • KiDispatchInterrupt->SwapContext

线程切换与TSS的关系

线程的内核堆栈

在这里插入图片描述

调用API进0环

  • 普通调用:通过TSS.ESP0得到0环堆栈
  • 快速调用:从MSR得到一个临时0环堆栈,代码执行后仍然通过TSS.ESP0得到当前线程0环堆栈

线程切换与FS的关系

FS:[0]寄存器在3环时指向TEB,进入0环后FS:[0]指向KPCR

在系统中同时存在多个线程,这意味着在3环FS:[0]指向多个TEB,但在实际的使用中发现,在3环查看不同线程的FS寄存器时,FS的段选择子都是相同的,那是如何实现通过一个FS寄存器指向多个TEB呢?

这其中的细节任然在SwapContext代码中实现

直接说结论(懒得翻代码了):虽然在3环中FS:[0]的段选择子没有发生变化 ,但是在其中的基址发生了变化


线程的优先级

前端有讲过,切换线程有三种情况:1、主动切换,2、当前线程时间片到期,3、有备用线程,在线程切换时,都是通过KiFindReadyThread函数来找下一个要切换的线程

KiFindReadyThread是根据什么来选择下一个要执行的线程呢?

按照优先级进行查找:在本次查找中,如果当前高级别的链表里面有线程,那么就不会查找下一级的链表了

但是这样每次查找都要从最优先级的链表开始查效率会非常低,所以微软通过一个DWORD变量来记录,当向调度链表(32个)中挂入或者移除某个线程时, 会判断当前级别的链表是否为空,如为空就将DWORD对应的位置为0,否则置为1

这个变量是:_KiReadySummary

没有就绪线程的情况

有一种情况,就是当我_KiReadySummary变量中的所有位都为0时,没有就绪等待执行的线程了,CPU会怎么办呢?

PrcbData结构体:

  • CurrentThread // 当前准备执行的线程
  • NextThread // 下一个要执行的线程
  • IdleThread // 空闲线程

答案是如果_KiReadySummary对应的位都为0时,那么CPU就会去把每个进程中的空闲进程拿过去来执行


进程挂靠

进程与线程的关系:

  • 一个进程可以包含多个线程
  • 一个进程至少要有一个线程

进程为线程提供资源,也就是提供CR3的值,CR3中存储的是页目录表的基址,CR3确定了,线程能访问的内存也就确定了

当一个线程执行如下代码时CPU要如何解析呢?

mov eax,dword ptr ds:[0x12345678]

  1. 当CPU解析一个线性地址时,要通过页目录表来查找到对应的物理地址,页目录表基址存储在CR3寄存器当中
  2. 当前CR3的值来源于当前进程(_KPROCESS.DirectoryTableBae
  3. _ETHREAD结构体当中,就有一个成员ThreadsProcess指向当前的进程(在_KTHREAD结构体中还有一个成员中的子成员也指向当前进程:ApcState->Process)【那在线程切换时,由谁提供指向的进程值呢?】
  4. 在线程切换的时候,会比较_KTHREAD结构体ApcState->Process处指定的EPROCESS是否为同一个,如果不是同一个,会将ApcState->Process处指定的EPROCESSDirectoryTableBase的值取出,赋值给CR3

参考SwapContext函数

修改CR3的值

在得知正常情况下CR3的值是由_KTHREAD中ApcState->Process成员来提供的,那么CR3中的值可以随便改吗?

mov cr3,A.DirecctoryTableBase
mov eax,dword ptr ds:[0x12345678] // 获取到的是A进程的0x12345678内存
mov cr3,B.DirecctoryTableBase
mov eax,dword ptr ds:[0x12345678] // 获取到的是B进程的0x12345678内存
mov cr3,C.DirecctoryTableBase
mov eax,dword ptr ds:[0x12345678] // 获取到的是C进程的0x12345678内存

将当前CR3的值改为其它进程称之为进程挂靠

这样就可以在当前进程中访问其它进程的内存空间(ReadProcessMemory函数)


分析NtReadVirtualMemory函数执行流程

  1. NtReadVirtualMemory
  2. KiAttachProcess
  3. 修改_KTHREAD结构体ApcState->Process中的值为要读取的进程的值
  4. 修改CR3

如果我们自己来实现代码,在切换CR3后关闭中断,并且不会调用导致线程切换的API,就可以不用去修改_KTHREAD结构体ApcState->Process中的值


跨进程读写内存

跨进程操作

进程A中的线程代码:

mov cr3,B.DirctoryTableBase // 将CR3切换为B进程
mov eax,dword ptr ds:[0x12345678] // 将B进程中的0x12345678中的值保存到eax
mov dword ptr ds:[0x00412345],eax // 再将eax中的值保存到进程B中0x00412345的地址
mov cr3,A.DirctoryTableBase // 将CR3切回A进程

思考:如何将数据传递给A进程呢?

在这里插入图片描述

http://www.dtcms.com/a/283020.html

相关文章:

  • Excel 导入导出工具类文档
  • ubuntu中拷贝docker容器中的文件到宿主机
  • 万字长文解析 OneCode3.0 AI创新设计
  • 微服务的编程测评系统2
  • 2025年量化交易平台对比:付费与开源方案深度分析
  • nerdctl - 兼容 Docker 语法 的 containerd 命令行界面
  • 17.图像金字塔采样(放大,缩小处理)拉普拉斯金字塔
  • 闰年的历史由来与C语言实现详解
  • 7.16 拓扑排序 | 欧拉回路 |链表排序 前缀和
  • Vue在线预览Excel和Docx格式文件
  • Redis学习其一
  • Python学习之路(十三)-常用函数的使用,及优化
  • Redis读写策略深度解析:高并发场景下的缓存兵法
  • python基础语法9,用os库实现系统操作并用sys库实现文件操作(简单易上手的python语法教学)
  • 猫眼娱乐IOS开发一面手撕算法
  • 嵌入式学习笔记--MCU阶段--DAY06DHT11练习
  • AR智能巡检:电力行业数字化转型的“加速器”
  • 基于Llama的RAG 3种模型配置方法
  • 51c自动驾驶~合集7
  • 基于C#开发solidworks图库中文件(SLDPRT,SLDASM,SLDDRW等)转换为HTML和PDF,提供批量和实时转换
  • AI产品经理面试宝典第28天:自动驾驶与智慧交通融合面试题与答法
  • 自动驾驶激光3D点云处理系统性阐述及Open3D库函数应用
  • MR 处于 WIP 状态的WIP是什么
  • 小模型的价值重估:从“缩水版DeepSeek”到AI系统的基础执行单元20250716
  • Linux 挂载新磁盘导致原文件被隐藏解决方案
  • 【代码】Matlab鸟瞰图函数
  • sqli-labs靶场通关笔记:第23关 注释符过滤
  • 叉车机器人如何实现托盘精准定位?这项核心技术的原理和应用是什么?
  • 静默的田野守护者:Deepoc具身智能如何让除草机器人读懂大地密语
  • Mybatis08-使用pageHelper