06 Simulate thread switching

The annotations of the code are well written, and you will understand the order carefully

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\\
");
        GMSleep(500);
    }
}
void Thread2(void*) {<!-- -->
    while (1) {<!-- -->
        printf("Thread2\\
");
        GMSleep(500);
    }
}
void Thread3(void*) {<!-- -->
    while (1) {<!-- -->
        printf("Thread3\\
");
        GMSleep(500);
    }
}
void Thread4(void*) {<!-- -->
    while (1) {<!-- -->
        printf("Thread4\\
");
        GMSleep(500);
    }
}
int main()
{<!-- -->
    RegisterGMThread("Thread1", Thread1, NULL);
    RegisterGMThread("Thread2", Thread2, NULL);
    RegisterGMThread("Thread3", Thread3, NULL);
    RegisterGMThread("Thread4", Thread4, NULL);
\t
    while(TRUE) {<!-- -->
        Sleep(20);
        Scheduling();
    }
    return 0;
}

ThreadSwitch.h

#pragma once

//The maximum number of threads supported
#define MAXGMTHREAD 100

//Structure of thread information
typedef struct
{<!-- -->
char* name; //thread name
int Flags; //thread status
int SleepMillsecondDot; //sleep time
\t
void* initialStack; //Thread stack starting position
void* StackLimit; //Thread stack limit
void* KernelStack; //The current position of the thread stack, which is ESP
\t
void* lpParameter; //parameter of thread function
void(*func)(void* lpParameter); //thread function
}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 the size of the thread stack
#define GMTHREADSTACKSIZE 0x80000

// index of the current thread
int CurrentThreadIndex = 0;

// list of threads
GMThread_t GMThreadList[MAXGMTHREAD] = {<!-- -->NULL, 0};

// flag of thread state
enum flags
{<!-- -->
GMTHREAD_CREATE = 0x1,
GMTHREAD_READY = 0x2,
GMTHREAD_SLEEP = 0x4,
GMTHREAD_EXIT = 0x8,
};

//Function to start thread
void GMThreadStartup(GMThread_t* GMThreadp)
{<!-- -->
GMThreadp->func(GMThreadp->lpParameter);//Execute the function of the thread
GMThreadp->Flags = GMTHREAD_EXIT;//Set the status of the thread to exit
Scheduling();//Give up the thread

return;
}

//Function for idle thread
void IdleGMThread(void* lpParameter)
{<!-- -->
printf("IdleGMThread---------------\\
");
Scheduling();
return;
}

//Push a uint value onto the stack
void PushStack(unsigned int** Stackpp, unsigned int v)
{<!-- -->
*Stackpp -= 1;//Address minus 4
**Stackpp = v;//Assign a value to the address
\t
return;
}

//Initialize thread information
void initGMThread(GMThread_t* GMThreadp, char* name, void (*func)(void* lpParameter), void* lpParameter)
{<!-- -->
unsigned char* StackPages;
unsigned int* StackDWordParam;
GMThreadp->Flags = GMTHREAD_CREATE;//Set flag to create
GMThreadp->name = name;//Set thread name
GMThreadp->func = func;//Set the address of the function that the thread needs to execute
GMThreadp->lpParameter = lpParameter;//Set the parameter address of the thread
StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);//Allocate memory (the address is automatically allocated by the system, the size is 0x8000, the allocation type is divided into address space and physical page, and the access type)
ZeroMemory(StackPages, GMTHREADSTACKSIZE);//Initialized allocated memory is 0
GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;//Set the starting position of the thread's stack, that is, the maximum address of the allocated memory
StackDWordParam = (unsigned int*)GMThreadp->initialStack;
//Push into the stack
PushStack( & amp;StackDWordParam, (unsigned int)GMThreadp);//parameters required by the startup function
PushStack( & amp;StackDWordParam, (unsigned int)0);//Are you wondering why you put 0 here? Simply speaking, it is to balance the stack, and secondly, because calling startup requires parameters,
// After pop startup->eip, esp is here. After entering the function, mov ebp, esp and then ebp + 8 is the default parameter position of the function, which is why there is an extra four-byte push here.
PushStack( & amp;StackDWordParam, (unsigned int)GMThreadStartup);
PushStack( &StackDWordParam, (unsigned int)5);
PushStack( &StackDWordParam, (unsigned int)7);
PushStack( & amp;StackDWordParam, (unsigned int)6);
PushStack( & amp;StackDWordParam, (unsigned int)3);
PushStack( & amp;StackDWordParam, (unsigned int)2);
PushStack( & amp;StackDWordParam, (unsigned int)1);
PushStack( & amp;StackDWordParam, (unsigned int)0);
//The top of the stack of the current thread
GMThreadp->KernelStack = StackDWordParam;//The thread's current ESP position
GMThreadp->Flags = GMTHREAD_READY;//Set flag to ready
return;
}

// Register a function as a separate thread execution
int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter)//thread name, function pointer, function parameter pointer
{<!-- -->
int i;
for (i = 1; GMThreadList[i].name; i ++ ) {<!-- -->// Traverse the thread list, if there is no traverse, you will get a new list below the table
if (0 == _stricmp(GMThreadList[i].name, name)) {<!-- -->//Comparing the thread names, if the names are the same, it means that the thread has been registered
break;
}
}
initGMThread( & amp;GMThreadList[i], name, func, lpParameter);//Initialize thread (initialize thread list address, thread name, function pointer, function parameter pointer)
return (i & 0x55AA0000);
}

// switch thread
__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 //The above are the sites that retain the current thread
\t\t\t
mov esi, SrcGMThreadp
mov edi, DstGMThreadp
mov [esi + GMThread_t.KernelStack], esp //Assign the current esp to the KernelStack of the current thread structure
//Classic thread switching, another thread is resurrected
mov esp, [edi + GMThread_t.KernelStack] //Give the address of the kernel stack of the target thread to esp

pop eax //esp has been switched to the new thread stack above, this stack is popped again, and what you get is the saved esp (initialized esp/runtime esp)
pop edx
pop ecx
pop ebx
pop esi
pop edi
pop ebp
ret //Pop the value at the top of the stack into eip, and what pops up here is the address of startup into eip
}
}

//This function will let out the cpu and reselect a thread from the queue to execute
void Scheduling(void)
{<!-- -->
int i;
int TickCount;
GMThread_t* SrcGMThreadp;
GMThread_t* DstGMThreadp;
TickCount = GetTickCount();//It returns the number of milliseconds elapsed from the operating system startup to the current
SrcGMThreadp = & amp;GMThreadList[CurrentThreadIndex];//The address of the thread list of the current thread
DstGMThreadp = & amp;GMThreadList[0];//The first address of the thread list

for (i = 1; GMThreadList[i].name; i ++ ) {<!-- -->//Start traversal, the end condition is the current thread
if (GMThreadList[i].Flags & amp; GMTHREAD_SLEEP) {<!-- -->//If it is a SLEEP thread
if (TickCount > GMThreadList[i].SleepMillsecondDot) {<!-- -->//Judging time to sleep enough
GMThreadList[i].Flags = GMTHREAD_READY;//If you sleep enough, set FLAG to ready
}
}
if (GMThreadList[i].Flags & amp; GMTHREAD_READY) {<!-- -->//If it is a ready thread
DstGMThreadp = & amp;GMThreadList[i];//Get the first address of the target thread
break;//jump out of the loop
}
}

CurrentThreadIndex = DstGMThreadp - GMThreadList;//The current thread index = the first address of the target thread - the first address of the thread linked list (if you don't understand, please fill in the basics)
SwitchContext(SrcGMThreadp, DstGMThreadp);//Thread switching (current thread, target thread)
return;
}

void GMSleep(int MilliSeconds)
{<!-- -->
GMThread_t* GMThreadp;
GMThreadp = & amp;GMThreadList[CurrentThreadIndex];//current thread
if (GMThreadp->Flags != 0) {<!-- -->//Fault tolerance, it cannot be 0 ha
GMThreadp->Flags = GMTHREAD_SLEEP;//Set the thread state to sleep
GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;//Wake-up time = set sleep time + the number of milliseconds elapsed from the operating system startup to the current time
}

Scheduling();//You can give up the CPU
return;
}