日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

(57)模拟线程切换

發布時間:2025/3/21 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (57)模拟线程切换 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、回顧

在之前的課程中,我們學習了 EPROCESS, ETHREAD, KPCR 等重要的內核結構體,學習了存儲等待線程的等待鏈表和調度線程的調度鏈表,這些知識都是為了后面學習線程切換打的基礎。

這次課,我們將學習老師提供的模擬Windows線程切換的源碼,這份代碼可以在3環模擬線程調度,有助于我們理解真正的Windows線程調度源碼。

二、運行結果

這份代碼是我照著視頻抄的(見文末),在后續的學習中,我們要往里面加東西,所以建議不要做過多的修改。

三、RegisterGMThread, InitGMThread, GMThreadStartup

void RegisterGMThread(char *name, void (*func)(void *lpParameter), void *lpParameter); void InitGMThread (GMThread_t *GMThreadp, char *name, void (*func)(void *lpParameter), void *lpParameter);

RegisterGMThread 函數負責創建線程,它遍歷線程調度隊列,找到一個空位作為新線程結構體,然后調用 InitGMThread 初始化。

InitGMThread 函數負責初始化線程結構體;為線程申請堆棧內存;向堆棧壓入必要的初始數據,包括線程結構體指針,GMThreadStartup 函數指針,以及一堆寄存器的初始值(PS. 壓棧用的 PushStack 函數不解釋);最后設置線程狀態為“就緒”。

這里所做的所有壓棧操作,沒有一步是多余的。

7個寄存器是線程恢復時要pop的值,這里設置成0,表示第一次調度時給寄存器設置初始值0,也可以改成其他值。

所有線程都要通過 GMThreadStartup 函數調用自己的線程入口函數,而調用 GMThreadStartup 函數的地方以及傳參的過程設計非常巧妙,這一步發生在 SwitchContext 函數中,恢復線程后,pop了7個寄存器,esp就指向了 GMThreadStartup,此時 SwitchContext 調用 ret 指令,就跳轉到 GMThreadStartup 函數,完全模擬了 call 調用的堆棧,那個看起來沒用的堆棧平衡值其實模擬的是 call 時壓入堆棧的返回地址,而 GMThreadp 模擬的是 call 之前push的參數。進入 GMThreadStartup 后,函數從 ebp + 8 處取得參數1 GMThreadp 。

為什么說返回地址是模擬的?因為 GMThreadStartup 永遠不會執行它的 return 語句。

// 此函數在 SwitchContext 的 ret 指令執行時調用,功能是調用線程入口函數 void GMThreadStartup(GMThread_t *GMThreadp) {GMThreadp->func(GMThreadp->lpParameter);GMThreadp->Flags = GMTHREAD_EXIT;Scheduling();printf("這句永遠不會執行,因為修改線程狀態為退出,Scheduling 永遠不會返回到這里.\n");return; }

四、Scheduling 線程調度函數

void Scheduling();

這個函數負責遍歷線程調度隊列,如果遍歷到“等待”狀態的線程,判斷它是否已經完成了“等待”,如果是,那么修改其狀態為就緒。通過遍歷,找出第一個“就緒”線程,如果遍歷完都沒有發現新的就緒線程,那么就認為主函數是“就緒”線程。

最后,調用 SwitchContext 函數“切換”到剛才找到的“就緒”線程。

五、SwitchContext 切換線程函數

SwitchContext 負責切換線程,舊線程調用 SwitchContext 時,首先把7個寄存器壓到自己的棧頂,然后保存當前棧頂 esp 到 KernelStack,然后從新線程的線程結構體里取出 KernelStack 填到 esp,就完成了線程切換。

接下來就是從新線程的棧頂 pop 還原7個寄存器。pop 了那7個寄存器后,esp 一定是指向下一條指令的地址的,如果新線程尚未被調度過,那么棧頂一定是 GMThreadStartup;如果新線程曾被調度過,那么棧頂一定是新線程上一次調用 SwitchContext 的返回地址,即 Scheduling 函數的末尾。

最后,附上一張線程A切換到線程B的堆棧圖,幫助理解。

六、總結

其他的函數就很簡單了,限于篇幅,我不打算花筆墨解釋其他函數,因為注釋已經說明了我沒有提到的細節。

最重要的 SwitchContext 函數,要理解它是怎么通過切換堆棧來實現切換線程的,要理解 ret 指令跳轉到什么地方;Scheduling 函數也很關鍵,它用了一種非常簡單的遍歷算法尋找“就緒”線程。

這個程序模擬了時鐘中斷切換線程(主函數的循環),和調用API的主動切換(線程主動調用 GMSleep)。

你可以在主函數循環里加一個打印語句:

for (;;){Sleep(20);Scheduling();// 如果回到主線程,說明沒有找到就緒線程,CurrentThreadIndex 一定是 0//printf("時鐘中斷. %d\n", CurrentThreadIndex);}

你會發現,這和空閑線程的執行非常像,后續的課程里,會添加很多功能,比如模擬空閑線程,此處暫時不表。


七、單文件版本源碼

#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_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 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_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 Thread1(void *lpParameter) {int i;for (i = 0; i < 3; i++){printf("Thread1\n");GMSleep(100); // 主動切換,模擬WIN32 API}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; }

總結

以上是生活随笔為你收集整理的(57)模拟线程切换的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。