(58)模拟线程切换——添加挂起、恢复线程功能
生活随笔
收集整理的這篇文章主要介紹了
(58)模拟线程切换——添加挂起、恢复线程功能
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、回顧
我們在上一篇博客分析了模擬線程切換的源碼。
《模擬線程切換》
我們著重分析了 Scheduling 和 SwitchContext 這兩個函數,對線程切換的過程有了新的認識:
- 線程切換本質是通過切換堆棧達到切換寄存器的目的
- 線程切換既有線程主動切換,也有時鐘中斷切換
這次課,我們將給程序添加線程掛起和恢復的代碼。
二、原理
我的做法是在線程調度函數 Scheduling 里判斷線程是否處于掛起狀態,如果是,那么跳過該線程,否則才有資格做進一步判斷是否調度。
我們不需要擔心線程同步互斥等問題,比如說擔心修改線程 Flags 為“掛起”,會不會被 Scheduling 改回“就緒”之類的。其實完全不用擔心,因為對于線程調度程序來說,根本不用擔心線程同步互斥問題,因為所有線程都是由它調度的,同一時刻要么某個線程在跑,要么 Scheduling 函數在跑,根本不可能同時有兩個程序對 Flags 賦值。
三、源碼和運行結果
我這里是在線程1里把其他線程掛起,當線程1執行完后,恢復其他線程,運行結果如下:
SwitchThread.c
#include <stdio.h> #include <tchar.h> #include <string.h> #include <Windows.h>#pragma warning(disable: 4996)//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------#define MAXGMTHREAD 0x100#define GMTHREAD_CREATE 0x01 #define GMTHREAD_READY 0x02 #define GMTHREAD_RUNNING 0x04 #define GMTHREAD_SLEEP 0x08 #define GMTHREAD_SUSPEND 0x10 #define GMTHREAD_EXIT 0x100#define GMTHREADSTACKSIZE 0x80000//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------// 線程結構體(仿ETHREAD) typedef struct {char *name; // 線程名,相當于線程TIDint Flags; // 線程狀態int SleepMillisecondDot; // 休眠時間void *InitialStack; // 線程堆棧起始位置void *StackLimit; // 線程堆棧界限void *KernelStack; // 線程堆棧當前位置,即ESP0void *lpParameter; // 線程函數參數void (*func)(void *lpParameter); // 線程函數 } GMThread_t;//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------// 當前調度線程下標 int CurrentThreadIndex = 0;// 線程調度隊列 GMThread_t GMThreadList[MAXGMTHREAD] = { 0 };void *WindowsStackLimit = NULL;//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------void SwitchContext(GMThread_t *OldGMThreadp, GMThread_t *NewGMThreadp); void GMThreadStartup(GMThread_t *GMThreadp); void IdleGMThread(void *lpParameter); void PushStack(unsigned int **Stackpp, unsigned int v); void InitGMThread (GMThread_t *GMThreadp, char *name, void (*func)(void *lpParameter), void *lpParameter); int RegisterGMThread(char *name, void (*func)(void *lpParameter), void *lpParameter); void Scheduling(); void GMSleep(int Milliseconds); void GMSuspendThread(GMThread_t *GMThreadp); void GMResumeThread(GMThread_t *GMThreadp);void Thread1(void *lpParameter); void Thread2(void *lpParameter); void Thread3(void *lpParameter); void Thread4(void *lpParameter);//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------int _tmain(int argc, _TCHAR* argv[]) {// 初始化線程環境RegisterGMThread("Thread1", Thread1, NULL);RegisterGMThread("Thread2", Thread2, NULL);RegisterGMThread("Thread3", Thread3, NULL);RegisterGMThread("Thread4", Thread4, NULL);// 仿Windows線程切換,模擬系統時鐘中斷,是被動切換//Scheduling();for (;;){Sleep(20);Scheduling();// 如果回到主線程,說明沒有找到就緒線程,CurrentThreadIndex 一定是 0//printf("時鐘中斷. %d\n", CurrentThreadIndex);}return 0; }// 線程切換函數 __declspec(naked) void SwitchContext(GMThread_t *OldGMThreadp, GMThread_t *NewGMThreadp) {__asm{// 當前線程保存寄存器到自己的棧頂push ebp;mov ebp,esp;push edi;push esi;push ebx;push ecx;push edx;push eax;mov esi,OldGMThreadp; // mov esi, [ebp + 0x08]mov edi,NewGMThreadp; // mov edi, [ebp + 0x0C]mov [esi + GMThread_t.KernelStack], esp; // 保存舊ESPmov esp,[edi + GMThread_t.KernelStack]; // 設置新ESP// 從新線程的棧里恢復寄存器的值pop eax;pop edx;pop ecx;pop ebx;pop esi;pop edi;pop ebp;// 返回到新線程之前調用 SwitchContext 的地方;如果是第一次調度,則跳轉到 GMThreadStartupret;} }// 此函數在 SwitchContext 的 ret 指令執行時調用,功能是調用線程入口函數 void GMThreadStartup(GMThread_t *GMThreadp) {GMThreadp->func(GMThreadp->lpParameter);GMThreadp->Flags = GMTHREAD_EXIT;Scheduling();printf("這句永遠不會執行,因為修改線程狀態為退出,Scheduling 永遠不會返回到這里.\n");return; }// 空閑線程,沒事做就調用它 void IdleGMThread(void *lpParameter) {printf("IdleGMThread-------------------\n");Scheduling();return; }// 模擬壓棧 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 *ESP;// 結構初始化賦值GMThreadp->Flags = GMTHREAD_CREATE;GMThreadp->name = name;GMThreadp->func = func;GMThreadp->lpParameter = lpParameter;// 申請棧空間StackPages = (unsigned char*)VirtualAlloc(NULL,GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);// 清零memset(StackPages,0,GMTHREADSTACKSIZE);// 棧初始化地址GMThreadp->InitialStack = (StackPages + GMTHREADSTACKSIZE);// 棧限制GMThreadp->StackLimit = StackPages;// 棧地址ESP = (unsigned int *)GMThreadp->InitialStack;// 初始化線程棧PushStack(&ESP, (unsigned int)GMThreadp); // 通過這個指針來找到:線程函數、函數參數PushStack(&ESP, (unsigned int)0); // 平衡堆棧,此值無意義,詳見 SwitchContext 函數注釋PushStack(&ESP, (unsigned int)GMThreadStartup); // 線程入口函數,這個函數負責調用線程函數PushStack(&ESP, (unsigned int)0); // push ebp,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push edi,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push esi,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push ebx,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push ecx,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push edx,此值無意義,是寄存器初始值PushStack(&ESP, (unsigned int)0); // push eax,此值無意義,是寄存器初始值GMThreadp->KernelStack = ESP;GMThreadp->Flags = GMTHREAD_READY;return; }// 添加新線程到調度隊列,然后初始化線程 int RegisterGMThread(char *name, void (*func)(void *lpParameter), void *lpParameter) {int i;// 找一個空位置,或者是name已經存在的那個項// 下標0是當前正在運行的線程,所以從1開始遍歷for (i = 1; GMThreadList[i].name; i++){if (0 == stricmp(GMThreadList[i].name, name)){break;}}// 初始化線程結構體InitGMThread(&GMThreadList[i], name, func, lpParameter);return (i | 0x55AA0000); }// 線程調度函數,功能是遍歷調度隊列,找到“就緒”線程,然后切換線程 void Scheduling() {int i;int TickCount;GMThread_t *OldGMThreadp;GMThread_t *NewGMThreadp;TickCount = GetTickCount(); // GetTickCount 返回操作系統啟動到目前為止經過的毫秒// 正在調度的線程,第一次是 GMThreadList[0],這個表示主線程OldGMThreadp = &GMThreadList[CurrentThreadIndex];// 遍歷線程調度隊列,找第一個“就緒”線程// 如果找不到,就回到主函數,模擬時鐘中斷NewGMThreadp = &GMThreadList[0]; for (i = 1; GMThreadList[i].name; i++){// 如果線程處于“掛起”狀態,就直接跳過if (GMThreadList[i].Flags & GMTHREAD_SUSPEND){continue;}// 如果達到“等待時間”,就修改狀態為“就緒”if (GMThreadList[i].Flags & GMTHREAD_SLEEP){if (TickCount > GMThreadList[i].SleepMillisecondDot){GMThreadList[i].Flags = GMTHREAD_READY;}}// 找到“就緒”線程if (GMThreadList[i].Flags & GMTHREAD_READY){NewGMThreadp = &GMThreadList[i];break;}}// 更新當前調度線程下標CurrentThreadIndex = NewGMThreadp - GMThreadList;// 線程切換SwitchContext(OldGMThreadp, NewGMThreadp);return; }// 正在運行的線程主動調用此函數,將自己設置成“等待”狀態,然后讓調度函數調度其他線程 void GMSleep(int Milliseconds) {GMThread_t *GMThreadp;GMThreadp = &GMThreadList[CurrentThreadIndex];if ((GMThreadp->Flags) != 0){GMThreadp->SleepMillisecondDot = GetTickCount() + Milliseconds;GMThreadp->Flags = GMTHREAD_SLEEP;}Scheduling();return; }// 掛起線程 void GMSuspendThread(GMThread_t *GMThreadp) {GMThreadp->Flags = GMTHREAD_SUSPEND; }// 恢復線程 void GMResumeThread(GMThread_t *GMThreadp) {GMThreadp->Flags = GMTHREAD_READY; }void Thread1(void *lpParameter) {int i;// 掛起線程printf("Thread1掛起其他線程.\n");GMSuspendThread(&GMThreadList[2]);GMSuspendThread(&GMThreadList[3]);GMSuspendThread(&GMThreadList[4]);for (i = 1; i <= 3; i++){printf("Thread1(%d)\n", i);GMSuspendThread(&GMThreadList[2]);GMSuspendThread(&GMThreadList[3]);GMSuspendThread(&GMThreadList[4]);GMSleep(1000); // 主動切換,模擬WIN32 API}// 恢復線程printf("Thread1恢復其他線程.\n");GMResumeThread(&GMThreadList[2]);GMResumeThread(&GMThreadList[3]);GMResumeThread(&GMThreadList[4]);return; }void Thread2(void *lpParameter) {int i = 0;while (++i){printf(" Thread2(%d)\n", i);GMSleep(200); // 主動切換,模擬WIN32 API}return; }void Thread3(void *lpParameter) {int i = 0;while (++i){printf(" Thread3(%d)\n", i);GMSleep(200); // 主動切換,模擬WIN32 API}return; }void Thread4(void *lpParameter) {int i = 0;while (++i){printf(" Thread4(%d)\n", i);GMSleep(400); // 主動切換,模擬WIN32 API}return; } 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的(58)模拟线程切换——添加挂起、恢复线程功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 结构体对齐,#pragma pack
- 下一篇: (59)逆向分析 KiSwapConte