揭开Windows线程切换的神秘面纱

声明:本文来自滴水公司于海东老师的课程。 

ThreadSwitch.h#pragma once //最大支持的线程数#define MAXGMTHREAD 100 //线程信息的结构typedef struct {    char* name; //线程名    int Flags; //线程状态    int SleepMillsecondDot; //休眠时间     void* initialStack; //线程堆栈起始位置    void* StackLimit; //线程堆栈界限    void* KernelStack; //线程堆栈当前位置,也就是ESP     void* lpParameter; //线程函数的参数    void(*func)(void* lpParameter); //线程函数}GMThread_t; void GMSleep(int MilliSeconds);int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter);void Scheduling(void);ThreadSwitch.cpp#include "stdafx.h"#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; **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 ebp PushStack(&StackDWordParam, (unsigned int)7);//push edi PushStack(&StackDWordParam, (unsigned int)6);//push esi PushStack(&StackDWordParam, (unsigned int)3);//push ebx PushStack(&StackDWordParam, (unsigned int)2);//push ecx PushStack(&StackDWordParam, (unsigned int)1);//push edx PushStack(&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);//返回线程句柄}//切换线程__declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp){ __asm {  push ebp  mov ebp, esp  push edi  push esi  push ebx  push ecx  push edx  push eax     mov esi, SrcGMThreadp  mov edi, DstGMThreadp  mov [esi+GMThread_t.KernelStack], esp //将当前线程的栈顶存入线程结构体  //经典线程切换,另外一个线程复活  mov esp, [edi+GMThread_t.KernelStack] //从目的线程结构体中取出栈顶放入esp  pop eax  pop edx  pop ecx  pop ebx  pop esi  pop edi  pop ebp  ret //相当于pop eip,执行这以后就会进入线程入口函数GMThreadStartup  //因为ebp弹出后,该入口函数就是栈顶元素了 }}//这个函数会让出cpu,从队列里重新选择一个线程执行void Scheduling(void){ int i; int TickCount; GMThread_t* SrcGMThreadp; GMThread_t* DstGMThreadp; TickCount = GetTickCount(); SrcGMThreadp = &GMThreadList[CurrentThreadIndex]; DstGMThreadp = &GMThreadList[0]; for (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;}//这是有该函数存在,线程才能让出CPUvoid 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 "stdafx.h"#include "ThreadSwitch.h" extern int CurrentThreadIndex; extern GMThread_t GMThreadList[MAXGMTHREAD];void Thread1(void*) {    while(1){        printf("Thread1");        GMSleep(500);    }}void Thread2(void*) {    while (1) {        printf("Thread2");        GMSleep(200);    }} void Thread3(void*) {    while (1) {        printf("Thread3");        GMSleep(10);    }} void Thread4(void*) {    while (1) {        printf("Thread4");        GMSleep(1000);    }}  int main(){    RegisterGMThread("Thread1", Thread1, NULL);    RegisterGMThread("Thread2", Thread2, NULL);    RegisterGMThread("Thread3", Thread3, NULL);    RegisterGMThread("Thread4", Thread4, NULL);     while(TRUE) {        Sleep(20);        Scheduling();    }     return 0;}

总结:

1)线程切换不是线程被动让出CPU,而是主动让出

2)线程切换用堆栈来保存寄存器

3)线程切换 本质是堆栈的切换

 

一个开卷有益的公众号:IT平头哥

分享入门级编程者需要的数据结构和算法、网络协议栈、操作系统等知识;

分享嵌入式开发相关知识,诸如u-boot、内核、文件系统知识等;

分享他人的精彩华章及IT界闲闻趣事;

不定期推荐资料,提供下载链接;

总之本号是个期待与你一起交流进步的平台,加入进来一起成长吧!

IT平头哥
​​​​

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云镛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值