代码的注解都写好叻,顺序认真看自然会明白的
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;
}