进程线程003 模拟线程切换
文章目錄
- 示例代碼
- 關(guān)鍵結(jié)構(gòu)體
- 調(diào)度鏈表
- 初始化線程堆棧
- 線程切換
- 被動切換
- 主動切換
- 線程調(diào)度
- 總結(jié)
之前我們已經(jīng)了解過線程的等待鏈表和調(diào)度鏈表,為了更好的學(xué)習(xí)Windows的線程切換,我們要先讀一份代碼
示例代碼
ThreadSwitch.h
#pragma once #include <windows.h> #include "stdio.h"//最大支持的線程數(shù) #define MAXGMTHREAD 100//線程信息的結(jié)構(gòu) typedef struct {char* name; //線程名 相當于線程IDint Flags; //線程狀態(tài)int SleepMillsecondDot; //休眠時間void* initialStack; //線程堆棧起始位置void* StackLimit; //線程堆棧界限void* KernelStack; //線程堆棧當前位置,也就是ESPvoid* lpParameter; //線程函數(shù)的參數(shù)void(*func)(void* lpParameter); //線程函數(shù) }GMThread_t;void GMSleep(int MilliSeconds); int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter); void Scheduling();ThreadSwitch.cpp
#include "ThreadSwitch.h"//定義線程棧的大小 #define GMTHREADSTACKSIZE 0x80000//當前線程的索引 int CurrentThreadIndex = 0;//線程的列表 GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };//線程狀態(tài)的標志 enum FLAGS {GMTHREAD_CREATE = 0x1,GMTHREAD_READY = 0x2,GMTHREAD_SLEEP = 0x4,GMTHREAD_EXIT = 0x8, };//啟動線程的函數(shù) void GMThreadStartup(GMThread_t* GMThreadp) {GMThreadp->func(GMThreadp->lpParameter);GMThreadp->Flags = GMTHREAD_EXIT;Scheduling();return; }//空閑線程的函數(shù) void IdleGMThread(void* lpParameter) {printf("IdleGMThread---------------\n");Scheduling();return; }//向棧中壓入一個uint值 void PushStack(unsigned int** Stackpp, unsigned int v) {*Stackpp -= 1;//esp - 4**Stackpp = v;//return; }//初始化線程的信息 void initGMThread(GMThread_t* GMThreadp, char* name, void(*func)(void* lpParameter), void* lpParameter) {unsigned char* StackPages;unsigned int* StackDWordParam;//結(jié)構(gòu)初始化賦值GMThreadp->Flags = GMTHREAD_CREATE;GMThreadp->name = name;GMThreadp->func = func;GMThreadp->lpParameter = lpParameter;//申請空間StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);//初始化ZeroMemory(StackPages, GMTHREADSTACKSIZE);//初始化地址地址GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;//堆棧限制GMThreadp->StackLimit = StackPages;//堆棧地址StackDWordParam = (unsigned int*)GMThreadp->initialStack;//入棧PushStack(&StackDWordParam, (unsigned int)GMThreadp); //通過這個指針來找到線程函數(shù),線程參數(shù)PushStack(&StackDWordParam, (unsigned int)0); //平衡堆棧的(不用管)PushStack(&StackDWordParam, (unsigned int)GMThreadStartup); //線程入口函數(shù) 這個函數(shù)負責(zé)調(diào)用線程函數(shù)PushStack(&StackDWordParam, (unsigned int)5); //push ebpPushStack(&StackDWordParam, (unsigned int)7); //push ediPushStack(&StackDWordParam, (unsigned int)6); //push esiPushStack(&StackDWordParam, (unsigned int)3); //push ebxPushStack(&StackDWordParam, (unsigned int)2); //push ecxPushStack(&StackDWordParam, (unsigned int)1); //push edxPushStack(&StackDWordParam, (unsigned int)0); //push eax//當前線程的棧頂GMThreadp->KernelStack = StackDWordParam;//當前線程狀態(tài)GMThreadp->Flags = GMTHREAD_READY;return; }//將一個函數(shù)注冊為單獨線程執(zhí)行 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); }//切換線程 1:當前線程結(jié)構(gòu)體指針 2:要切換的線程結(jié)構(gòu)體指針 __declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp) {__asm{//提升堆棧push ebpmov ebp, esp//保存當前線程寄存器push edipush esipush ebxpush ecxpush edxpush eaxmov esi, SrcGMThreadpmov edi, DstGMThreadpmov[esi + GMThread_t.KernelStack], esp//經(jīng)典線程切換,另外一個線程復(fù)活mov esp, [edi + GMThread_t.KernelStack]pop eaxpop edxpop ecxpop ebxpop esipop edipop ebpret //把startup(線程函數(shù)入口)彈到eip 執(zhí)行的就是線程函數(shù)了} }//這個函數(shù)會讓出cpu,從隊列里重新選擇一個線程執(zhí)行 void Scheduling() {int TickCount = GetTickCount();GMThread_t* SrcGMThreadp = &GMThreadList[CurrentThreadIndex];GMThread_t* DstGMThreadp = &GMThreadList[0];for (int i = 1; GMThreadList[i].name; i++){if (GMThreadList[i].Flags & GMTHREAD_SLEEP){if (TickCount > GMThreadList[i].SleepMillsecondDot){GMThreadList[i].Flags = GMTHREAD_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){GMThreadp->Flags = GMTHREAD_SLEEP;GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;}Scheduling();return; }main.cpp
#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(200);} }void Thread3(void*) {while (1){printf("Thread3\n");GMSleep(10);} }void Thread4(void*) {while (1){printf("Thread4\n");GMSleep(1000);} }int main() {RegisterGMThread((char*)"Thread1", Thread1, NULL);RegisterGMThread((char*)"Thread2", Thread2, NULL);RegisterGMThread((char*)"Thread3", Thread3, NULL);RegisterGMThread((char*)"Thread4", Thread4, NULL);for (;;){Sleep(20);Scheduling();}return 0; }關(guān)鍵結(jié)構(gòu)體
//線程信息的結(jié)構(gòu) typedef struct {char* name; //線程名 相當于線程IDint Flags; //線程狀態(tài)int SleepMillsecondDot; //休眠時間void* initialStack; //線程堆棧起始位置void* StackLimit; //線程堆棧界限void* KernelStack; //線程堆棧當前位置,也就是ESPvoid* lpParameter; //線程函數(shù)的參數(shù)void(*func)(void* lpParameter); //線程函數(shù) }GMThread_t;在Windows里,每一個線程都有一個結(jié)構(gòu)體叫ETHREAD,這個結(jié)構(gòu)體就是模擬的線程結(jié)構(gòu)體,只不過把和線程無關(guān)的屬性刪掉了,只保留了線程切換必須要用到的成員
調(diào)度鏈表
線程在不同的狀態(tài)下存儲的位置是不一樣的,正在運行中的線程在KPCR里,等待狀態(tài)中的線程在等待鏈表里,就緒的線程在32個就緒鏈表里
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uk3ROhFd-1581302990410)(assets/1581237190834.png)]
模擬線程切換的代碼沒有寫的那么復(fù)雜,而是用一個數(shù)組來表示所有線程,然后通過Flags來區(qū)分線程的狀態(tài)。所謂的創(chuàng)建線程,就是創(chuàng)建一個結(jié)構(gòu)體,并且掛到這個數(shù)組里,此時線程處于創(chuàng)建狀態(tài),這個創(chuàng)建的過程就結(jié)束了。
這里面有一個小細節(jié),在這個數(shù)組里存結(jié)構(gòu)體的時候,是從下標為1的位置開始存的,而不是從最開始的位置開始存的;最開始的這個位置是給當前的線程用的
當我們把結(jié)構(gòu)體掛到數(shù)組以后,當前線程處于創(chuàng)建狀態(tài),還不能進行調(diào)度;因為如果一個線程想要執(zhí)行,一定要有自己的初始化的一些環(huán)境,例如寄存器的值要確定,當前線程的堆棧在哪里也要確定。現(xiàn)在僅僅有這么一個結(jié)構(gòu)體,這些初始化的值還沒有。
所以接下來還要做一件非常重要的事情,就是初始化當前線程的堆棧
初始化線程堆棧
當我們創(chuàng)建一個線程的時候,第一件事情就是為線程結(jié)構(gòu)體賦值,把線程名 線程函數(shù)以及線程函數(shù)的參數(shù)填充到這個結(jié)構(gòu)體。
當這些值賦好了以后,當前的線程處于創(chuàng)建狀態(tài)
接下里就要為當前的線程準備一份堆棧。首先通過VirtualAlloc函數(shù)申請了一個堆棧。然后給堆棧的相關(guān)參數(shù)賦值。當線程初始化代碼執(zhí)行完成之后,線程狀態(tài)如圖所示:
灰色的部分就是給當前線程分配的一塊堆棧,VirtualAlloc分配的地址加上堆棧的大小指向的就是堆棧開始的位置,中間的一部分就是這個線程所需要的堆棧。
StackLimit指向當前堆棧的邊界。KernelStack是當前線程的棧頂,相當于ESP
接下來往堆棧里push了一堆數(shù)據(jù),這些數(shù)據(jù)就是當線程開始執(zhí)行的時候必須要用到的一些數(shù)據(jù)。
所謂的創(chuàng)建線程就是創(chuàng)建一個線程結(jié)構(gòu)體,所謂的初始化線程就是為當前的線程再準備一份堆棧
線程切換
被動切換
接下來看一下線程是如何進行調(diào)度的
當代碼執(zhí)行到for循環(huán)的時候,已經(jīng)創(chuàng)建好了四個線程。也就是說四個線程結(jié)構(gòu)體已經(jīng)掛到了數(shù)組里,四個線程所需要的堆棧也分配完成了。
接著代碼模擬系統(tǒng)時鐘每隔20毫秒進行線程切換,系統(tǒng)時鐘的存在會讓線程每隔一段時間進行切換,這種切換是被動的。
主動切換
還有一種切換是主動切換
這里的GMSleep模擬的是W32 API,只要進程調(diào)用了API,就會主動產(chǎn)生線程切換。
接下來看看線程是如何進行主動切換的,跟進GMSleep這個函數(shù),這里要把它當成所有的W32 API
這里會修改線程的狀態(tài)為Sleep,然后調(diào)用Scheduling函數(shù)進行線程切換和調(diào)度
線程調(diào)度
那么線程是如何進行調(diào)度的呢?
這個函數(shù)首先會做一件事情,就是遍歷這個數(shù)組,找到第一個處于READY狀態(tài)的線程
真正進行線程切換的是SwitchContext函數(shù),這個函數(shù)有兩個參數(shù),第一個參數(shù)當前線程的線程結(jié)構(gòu)體指針,第二個參數(shù)是要切換的線程的線程結(jié)構(gòu)體指針。
這個函數(shù)首先執(zhí)行了一堆壓棧的操作,將當前線程用到的寄存器存儲到堆棧里。
此時的ESI是當前線程的結(jié)構(gòu)體指針,EDI里存儲的是要切換的線程結(jié)構(gòu)體指針。接著把當前線程的ESP存儲在當前線程的KernelStack里。
然后再把要切換的線程的KernelStack賦值給ESP,此時堆棧切換,另一個線程復(fù)活,如圖:
此時當前的ESP指向目標線程的EAX
接著執(zhí)行一系列pop指令使線程的堆棧指向Startup,接著ret將Startup(線程的函數(shù)入口)彈到eip,接下來執(zhí)行的就是線程函數(shù)了。
這個函數(shù)是線程切換的核心,也是兩個線程的轉(zhuǎn)折點,上半部分一個進程進來,下半部分另一個線程出去。
總結(jié)
總結(jié)
以上是生活随笔為你收集整理的进程线程003 模拟线程切换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程线程002 等待链表 调度链表
- 下一篇: 进程线程004 Windows线程切换的