文章目录

  • 示例代码
  • 关键结构体
  • 调度链表
  • 初始化线程堆栈
  • 线程切换
    • 被动切换
    • 主动切换
    • 线程调度
  • 总结

之前我们已经了解过线程的等待链表和调度链表,为了更好的学习Windows的线程切换,我们要先读一份代码

示例代码

ThreadSwitch.h

#pragma once
#include <windows.h>
#include "stdio.h"//最大支持的线程数
#define MAXGMTHREAD 100//线程信息的结构
typedef struct
{char* name;                            //线程名 相当于线程IDint Flags;                         //线程状态int SleepMillsecondDot;               //休眠时间void* initialStack;                   //线程堆栈起始位置void* StackLimit;                 //线程堆栈界限void* KernelStack;                  //线程堆栈当前位置,也就是ESPvoid* lpParameter;                  //线程函数的参数void(*func)(void* lpParameter);        //线程函数
}GMThread_t;void GMSleep(int MilliSeconds);
int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter);
void Scheduling();

ThreadSwitch.cpp

#include "ThreadSwitch.h"//定义线程栈的大小
#define GMTHREADSTACKSIZE 0x80000//当前线程的索引
int CurrentThreadIndex = 0;//线程的列表
GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };//线程状态的标志
enum FLAGS
{GMTHREAD_CREATE = 0x1,GMTHREAD_READY = 0x2,GMTHREAD_SLEEP = 0x4,GMTHREAD_EXIT = 0x8,
};//启动线程的函数
void GMThreadStartup(GMThread_t* GMThreadp)
{GMThreadp->func(GMThreadp->lpParameter);GMThreadp->Flags = GMTHREAD_EXIT;Scheduling();return;
}//空闲线程的函数
void IdleGMThread(void* lpParameter)
{printf("IdleGMThread---------------\n");Scheduling();return;
}//向栈中压入一个uint值
void PushStack(unsigned int** Stackpp, unsigned int v)
{*Stackpp -= 1;//esp - 4**Stackpp = v;//return;
}//初始化线程的信息
void initGMThread(GMThread_t* GMThreadp, char* name, void(*func)(void* lpParameter), void* lpParameter)
{unsigned char* StackPages;unsigned int* StackDWordParam;//结构初始化赋值GMThreadp->Flags = GMTHREAD_CREATE;GMThreadp->name = name;GMThreadp->func = func;GMThreadp->lpParameter = lpParameter;//申请空间StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);//初始化ZeroMemory(StackPages, GMTHREADSTACKSIZE);//初始化地址地址GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;//堆栈限制GMThreadp->StackLimit = StackPages;//堆栈地址StackDWordParam = (unsigned int*)GMThreadp->initialStack;//入栈PushStack(&StackDWordParam, (unsigned int)GMThreadp);       //通过这个指针来找到线程函数,线程参数PushStack(&StackDWordParam, (unsigned int)0);                //平衡堆栈的(不用管)PushStack(&StackDWordParam, (unsigned int)GMThreadStartup); //线程入口函数 这个函数负责调用线程函数PushStack(&StackDWordParam, (unsigned int)5);              //push ebpPushStack(&StackDWordParam, (unsigned int)7);             //push ediPushStack(&StackDWordParam, (unsigned int)6);             //push esiPushStack(&StackDWordParam, (unsigned int)3);             //push ebxPushStack(&StackDWordParam, (unsigned int)2);             //push ecxPushStack(&StackDWordParam, (unsigned int)1);             //push edxPushStack(&StackDWordParam, (unsigned int)0);             //push eax//当前线程的栈顶GMThreadp->KernelStack = StackDWordParam;//当前线程状态GMThreadp->Flags = GMTHREAD_READY;return;
}//将一个函数注册为单独线程执行
int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter)
{int i;for (i = 1; GMThreadList[i].name; i++){if (0 == _stricmp(GMThreadList[i].name, name)){break;}}initGMThread(&GMThreadList[i], name, func, lpParameter);return (i & 0x55AA0000);
}//切换线程 1:当前线程结构体指针 2:要切换的线程结构体指针
__declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp)
{__asm{//提升堆栈push ebpmov ebp, esp//保存当前线程寄存器push edipush esipush ebxpush ecxpush edxpush eaxmov esi, SrcGMThreadpmov edi, DstGMThreadpmov[esi + GMThread_t.KernelStack], esp//经典线程切换,另外一个线程复活mov esp, [edi + GMThread_t.KernelStack]pop eaxpop edxpop ecxpop ebxpop esipop edipop ebpret   //把startup(线程函数入口)弹到eip 执行的就是线程函数了}
}//这个函数会让出cpu,从队列里重新选择一个线程执行
void Scheduling()
{int TickCount = GetTickCount();GMThread_t* SrcGMThreadp = &GMThreadList[CurrentThreadIndex];GMThread_t* DstGMThreadp = &GMThreadList[0];for (int i = 1; GMThreadList[i].name; i++){if (GMThreadList[i].Flags & GMTHREAD_SLEEP){if (TickCount > GMThreadList[i].SleepMillsecondDot){GMThreadList[i].Flags = GMTHREAD_READY;}}if (GMThreadList[i].Flags & GMTHREAD_READY){DstGMThreadp = &GMThreadList[i];break;}}CurrentThreadIndex = DstGMThreadp - GMThreadList;SwitchContext(SrcGMThreadp, DstGMThreadp);return;
}void GMSleep(int MilliSeconds)
{GMThread_t* GMThreadp;GMThreadp = &GMThreadList[CurrentThreadIndex];if (GMThreadp->Flags != 0){GMThreadp->Flags = GMTHREAD_SLEEP;GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;}Scheduling();return;
}

main.cpp

#include "ThreadSwitch.h"extern int CurrentThreadIndex;extern GMThread_t GMThreadList[MAXGMTHREAD];void Thread1(void*)
{while (1){printf("Thread1\n");GMSleep(500);}
}void Thread2(void*)
{while (1){printf("Thread2\n");GMSleep(200);}
}void Thread3(void*)
{while (1){printf("Thread3\n");GMSleep(10);}
}void Thread4(void*)
{while (1){printf("Thread4\n");GMSleep(1000);}
}int main()
{RegisterGMThread((char*)"Thread1", Thread1, NULL);RegisterGMThread((char*)"Thread2", Thread2, NULL);RegisterGMThread((char*)"Thread3", Thread3, NULL);RegisterGMThread((char*)"Thread4", Thread4, NULL);for (;;){Sleep(20);Scheduling();}return 0;
}

关键结构体

//线程信息的结构
typedef struct
{char* name;                            //线程名 相当于线程IDint Flags;                         //线程状态int SleepMillsecondDot;               //休眠时间void* initialStack;                   //线程堆栈起始位置void* StackLimit;                 //线程堆栈界限void* KernelStack;                  //线程堆栈当前位置,也就是ESPvoid* lpParameter;                  //线程函数的参数void(*func)(void* lpParameter);        //线程函数
}GMThread_t;

在Windows里,每一个线程都有一个结构体叫ETHREAD,这个结构体就是模拟的线程结构体,只不过把和线程无关的属性删掉了,只保留了线程切换必须要用到的成员

调度链表

线程在不同的状态下存储的位置是不一样的,正在运行中的线程在KPCR里,等待状态中的线程在等待链表里,就绪的线程在32个就绪链表里

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uk3ROhFd-1581302990410)(assets/1581237190834.png)]

模拟线程切换的代码没有写的那么复杂,而是用一个数组来表示所有线程,然后通过Flags来区分线程的状态。所谓的创建线程,就是创建一个结构体,并且挂到这个数组里,此时线程处于创建状态,这个创建的过程就结束了。

这里面有一个小细节,在这个数组里存结构体的时候,是从下标为1的位置开始存的,而不是从最开始的位置开始存的;最开始的这个位置是给当前的线程用的

当我们把结构体挂到数组以后,当前线程处于创建状态,还不能进行调度;因为如果一个线程想要执行,一定要有自己的初始化的一些环境,例如寄存器的值要确定,当前线程的堆栈在哪里也要确定。现在仅仅有这么一个结构体,这些初始化的值还没有。

所以接下来还要做一件非常重要的事情,就是初始化当前线程的堆栈

初始化线程堆栈

当我们创建一个线程的时候,第一件事情就是为线程结构体赋值,把线程名 线程函数以及线程函数的参数填充到这个结构体。

当这些值赋好了以后,当前的线程处于创建状态

接下里就要为当前的线程准备一份堆栈。首先通过VirtualAlloc函数申请了一个堆栈。然后给堆栈的相关参数赋值。当线程初始化代码执行完成之后,线程状态如图所示:

灰色的部分就是给当前线程分配的一块堆栈,VirtualAlloc分配的地址加上堆栈的大小指向的就是堆栈开始的位置,中间的一部分就是这个线程所需要的堆栈。

StackLimit指向当前堆栈的边界。KernelStack是当前线程的栈顶,相当于ESP

接下来往堆栈里push了一堆数据,这些数据就是当线程开始执行的时候必须要用到的一些数据。

所谓的创建线程就是创建一个线程结构体,所谓的初始化线程就是为当前的线程再准备一份堆栈

线程切换

被动切换

接下来看一下线程是如何进行调度的

当代码执行到for循环的时候,已经创建好了四个线程。也就是说四个线程结构体已经挂到了数组里,四个线程所需要的堆栈也分配完成了。

接着代码模拟系统时钟每隔20毫秒进行线程切换,系统时钟的存在会让线程每隔一段时间进行切换,这种切换是被动的。

主动切换

还有一种切换是主动切换

这里的GMSleep模拟的是W32 API,只要进程调用了API,就会主动产生线程切换。

接下来看看线程是如何进行主动切换的,跟进GMSleep这个函数,这里要把它当成所有的W32 API

这里会修改线程的状态为Sleep,然后调用Scheduling函数进行线程切换和调度

线程调度

那么线程是如何进行调度的呢?

这个函数首先会做一件事情,就是遍历这个数组,找到第一个处于READY状态的线程

真正进行线程切换的是SwitchContext函数,这个函数有两个参数,第一个参数当前线程的线程结构体指针,第二个参数是要切换的线程的线程结构体指针。

这个函数首先执行了一堆压栈的操作,将当前线程用到的寄存器存储到堆栈里。

此时的ESI是当前线程的结构体指针,EDI里存储的是要切换的线程结构体指针。接着把当前线程的ESP存储在当前线程的KernelStack里。

然后再把要切换的线程的KernelStack赋值给ESP,此时堆栈切换,另一个线程复活,如图:

此时当前的ESP指向目标线程的EAX

接着执行一系列pop指令使线程的堆栈指向Startup,接着ret将Startup(线程的函数入口)弹到eip,接下来执行的就是线程函数了。

这个函数是线程切换的核心,也是两个线程的转折点,上半部分一个进程进来,下半部分另一个线程出去。

总结

  1. 线程不是被动切换的,而是主动让出CPU
  2. 线程切换并没有使用TSS来保存寄存器,而是使用堆栈
  3. 线程切换的过程就是堆栈切换的过程

进程线程003 模拟线程切换相关推荐

  1. (60)逆向分析 KiSwapThread —— 找就绪线程和空闲线程

    一.回顾 上一篇博客,我带着以下的问题逆向分析了 KiSwapContext 和 SwapContext 这两个函数: SwapContext 有几个参数,分别是什么? SwapContext 在哪里 ...

  2. Windows进程与线程学习笔记(五)—— 模拟线程切换

    Windows进程与线程学习笔记(五)-- 模拟线程切换 ThreadSwitch代码分析 ThreadSwitch.cpp ThreadCore.h ThreadCore.cpp 总结 Thread ...

  3. 进程线程004 Windows线程切换的三种方式

    文章目录 主动切换(调用API) KiSwapContext函数分析 哪些API调用了SwapContext函数 总结 时钟中断切换 如何中断一个正在执行的程序 系统时钟 时钟中断的执行流程 总结 时 ...

  4. 模拟线程切换 C++

    为什么80%的码农都做不了架构师?>>>    前言: 本文主要是剖析NachOs的线程切换原理,并通过一个简化的例子(就是将线程部分代码抽取出来再加以修改) 来说明.本文 gith ...

  5. (58)模拟线程切换——添加挂起、恢复线程功能

    一.回顾 我们在上一篇博客分析了模拟线程切换的源码. <模拟线程切换> 我们着重分析了 Scheduling 和 SwitchContext 这两个函数,对线程切换的过程有了新的认识: 线 ...

  6. (57)模拟线程切换

    一.回顾 在之前的课程中,我们学习了 EPROCESS, ETHREAD, KPCR 等重要的内核结构体,学习了存储等待线程的等待链表和调度线程的调度链表,这些知识都是为了后面学习线程切换打的基础. ...

  7. Linux 多线程(一)线程概念:线程概念、线程与进程、线程间的独有与共享、多线程与多进程、线程控制

    线程概念 线程与进程 线程间的独有与共享 多线程与多进程 线程控制 线程概念 什么是线程 线程是进程中的一条执行流,执行程序中的某部分代码.linux下没有具体实现的线程,只有库函数用pcb来实现的线 ...

  8. Linux下的LWP(轻量级进程)、进程 、 线程、用户级线程、内核线程

    一.定义 再看正文之前我要先强调一下几点: 1. Linux中没有真正的线程,但windows中确实有线程 2. Linux中没有的线程是由进程来模拟实现的(又称作:轻量级进程) 3. 所以在Linu ...

  9. Linux_多线程(进程与线程的联系_pthread库_线程创建_线程等待_线程正常终止_线程取消_线程分离_pthread_t与LWP)

    文章目录 1.线程的定义,进程和线程的关系 2.Linux下的线程 Linux原生线程库(pthread库pthread.h) 线程的优点 线程的私有数据 3.线程控制 ①创建线程(pthread_c ...

最新文章

  1. poj2723详解(二分 + 2-SAT)( 两种方法求解 )
  2. c++ 全局变量初始化的一点总结
  3. webpack 教程 那些事儿05-多页应用
  4. GDCM:gdcm::Dict的测试程序
  5. 模型存储在哪里_最强的模型工作收纳站「Artty Station」登场!
  6. 使网页成黑白色调的滤镜(转)
  7. wxPython 笔记(8)设定窗体的样式
  8. PKI/CA与数字证书
  9. IO流(八)之InputStreamReader类与OutputStreamWriter类
  10. java中substring的使用方法
  11. UGUI Scrollbar控件
  12. 用Neo4j图形数据库打造专属于你的高bigger关系图
  13. BGLL算法 C++实现
  14. IOT是什么?有哪些用途和技术?
  15. Qt疑难杂症之编译QPA插件
  16. 解决电脑双网卡不能同时连接内网和外网的问题
  17. 这3款在线PS工具,得试试
  18. 计算机英语四六级成绩查询,全国四六级英语成绩查询入口网址-http://cet.neea.edu.cn/cet...
  19. pod中mysql配置文件修改_Pod中的secret,configmap,downwardapi的使用记录
  20. NS3网络仿真项目(三)—— Events Simulator

热门文章

  1. ML之k-NN:k-NN实现对150朵共三种花的实例的萼片长度、宽,花瓣长、宽数据统计,根据一朵新花的四个特征来预测其种类
  2. 已解决:Ubuntu16.4和Windows10创建共享文件夹
  3. 可以免费下载论文的网站
  4. 【jQuery源码】select方法
  5. Python的数据处理学习(三)
  6. 相关的验证的正则表达式
  7. Java解析HTML
  8. iBeacon的数据包格式
  9. stm32 IAP在线升级的个人经验以及实现方法
  10. RV1108开发环境搭建