06 模拟线程切换

代码的注解都写好叻,顺序认真看自然会明白的

在这里插入图片描述

main.cpp

// ThreadSwitch.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#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(500);
    }
}
void Thread3(void*) {
    while (1) {
        printf("Thread3\n");
        GMSleep(500);
    }
}
void Thread4(void*) {
    while (1) {
        printf("Thread4\n");
        GMSleep(500);
    }
}
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;
}

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 <windows.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;//设置线程的状态为exit
	Scheduling();//让出线程

	return;
}

//空闲线程的函数
void IdleGMThread(void* lpParameter)
{
	printf("IdleGMThread---------------\n");
	Scheduling();
	return;
}

//向栈中压入一个uint值
void PushStack(unsigned int** Stackpp, unsigned int v) 
{
	*Stackpp -= 1;//地址减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;//设置flag为create
	GMThreadp->name = name;//设置线程名字
	GMThreadp->func = func;//设置线程需要执行的函数地址
	GMThreadp->lpParameter = lpParameter;//设置线程的参数地址
	StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);//分配内存(地址系统自动分配,大小0x8000,分配类型地址空间和物理页都分,访问类型)
	ZeroMemory(StackPages, GMTHREADSTACKSIZE);//初始化分配的内存都为0 
	GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;//设置线程的堆栈的起始位置,就说分配的内存的最大地址
	StackDWordParam = (unsigned int*)GMThreadp->initialStack;
	//入栈
	PushStack(&StackDWordParam, (unsigned int)GMThreadp);//startup 函数所需要的参数
	PushStack(&StackDWordParam, (unsigned int)0);//你好奇这里为什么放0,简单来说是为了平衡堆栈,其次是因为调用startup是要参数的,
												 //pop startup->eip后 esp也就是这里,进函数后会把mov ebp,esp  然后ebp+8 就是函数默认的参数位置,这也就是这里为什么多push一个四字节。
	PushStack(&StackDWordParam, (unsigned int)GMThreadStartup);
	PushStack(&StackDWordParam, (unsigned int)5);
	PushStack(&StackDWordParam, (unsigned int)7);
	PushStack(&StackDWordParam, (unsigned int)6);
	PushStack(&StackDWordParam, (unsigned int)3);
	PushStack(&StackDWordParam, (unsigned int)2);
	PushStack(&StackDWordParam, (unsigned int)1);
	PushStack(&StackDWordParam, (unsigned int)0);
	//当前线程的栈顶
	GMThreadp->KernelStack = StackDWordParam;//线程当前的ESP的位置
	GMThreadp->Flags = GMTHREAD_READY;//设置flag为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 //esp == ebp
		push edi
		push esi
		push ebx
		push ecx
		push edx
		push eax //上面都是保留当前线程的现场
			
		mov esi, SrcGMThreadp
		mov edi, DstGMThreadp
		mov [esi+GMThread_t.KernelStack], esp //将当前的esp的,赋给当前线程结构体的KernelStack
		//经典线程切换,另外一个线程复活
		mov esp, [edi+GMThread_t.KernelStack] //将目标线程的内核堆栈的地址给到esp

		pop eax  //esp在上面已经切换到新的线程栈中,这个栈再pop eax,拿到的就是保存的esp(初始化的esp/运行时esp)
		pop edx
		pop ecx
		pop ebx
		pop esi
		pop edi
		pop ebp
		ret   //把栈顶的值弹到eip中,在这里弹出的就是startup的地址到eip中
	}
}

//这个函数会让出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) {//如果是SLEEP线程
			if (TickCount > GMThreadList[i].SleepMillsecondDot) {//判断时间睡够没有
				GMThreadList[i].Flags = GMTHREAD_READY;//如果睡够了,就把FLAG置为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) {//容错,不可能是0哈
		GMThreadp->Flags = GMTHREAD_SLEEP;//设置线程状态为sleep
		GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;//唤醒时间 = 设置的睡眠时间  + 从操作系统启动到当前所经过的毫秒数
	}

	Scheduling();//可以让出CPU了
	return;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值