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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

进程线程003 模拟线程切换

發(fā)布時(shí)間:2025/3/21 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 进程线程003 模拟线程切换 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 示例代碼
    • 關(guān)鍵結(jié)構(gòu)體
    • 調(diào)度鏈表
    • 初始化線程堆棧
    • 線程切換
      • 被動(dòng)切換
      • 主動(dòng)切換
      • 線程調(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; //線程名 相當(dāng)于線程IDint Flags; //線程狀態(tài)int SleepMillsecondDot; //休眠時(shí)間void* initialStack; //線程堆棧起始位置void* StackLimit; //線程堆棧界限void* KernelStack; //線程堆棧當(dāng)前位置,也就是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//當(dāng)前線程的索引 int CurrentThreadIndex = 0;//線程的列表 GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };//線程狀態(tài)的標(biāo)志 enum FLAGS {GMTHREAD_CREATE = 0x1,GMTHREAD_READY = 0x2,GMTHREAD_SLEEP = 0x4,GMTHREAD_EXIT = 0x8, };//啟動(dòng)線程的函數(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; }//向棧中壓入一個(gè)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;//申請(qǐng)空間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); //通過這個(gè)指針來找到線程函數(shù),線程參數(shù)PushStack(&StackDWordParam, (unsigned int)0); //平衡堆棧的(不用管)PushStack(&StackDWordParam, (unsigned int)GMThreadStartup); //線程入口函數(shù) 這個(gè)函數(shù)負(fù)責(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//當(dāng)前線程的棧頂GMThreadp->KernelStack = StackDWordParam;//當(dāng)前線程狀態(tài)GMThreadp->Flags = GMTHREAD_READY;return; }//將一個(gè)函數(shù)注冊(cè)為單獨(dú)線程執(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:當(dāng)前線程結(jié)構(gòu)體指針 2:要切換的線程結(jié)構(gòu)體指針 __declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp) {__asm{//提升堆棧push ebpmov ebp, esp//保存當(dāng)前線程寄存器push edipush esipush ebxpush ecxpush edxpush eaxmov esi, SrcGMThreadpmov edi, DstGMThreadpmov[esi + GMThread_t.KernelStack], esp//經(jīng)典線程切換,另外一個(gè)線程復(fù)活mov esp, [edi + GMThread_t.KernelStack]pop eaxpop edxpop ecxpop ebxpop esipop edipop ebpret //把startup(線程函數(shù)入口)彈到eip 執(zhí)行的就是線程函數(shù)了} }//這個(gè)函數(shù)會(huì)讓出cpu,從隊(duì)列里重新選擇一個(gè)線程執(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; //線程名 相當(dāng)于線程IDint Flags; //線程狀態(tài)int SleepMillsecondDot; //休眠時(shí)間void* initialStack; //線程堆棧起始位置void* StackLimit; //線程堆棧界限void* KernelStack; //線程堆棧當(dāng)前位置,也就是ESPvoid* lpParameter; //線程函數(shù)的參數(shù)void(*func)(void* lpParameter); //線程函數(shù) }GMThread_t;

在Windows里,每一個(gè)線程都有一個(gè)結(jié)構(gòu)體叫ETHREAD,這個(gè)結(jié)構(gòu)體就是模擬的線程結(jié)構(gòu)體,只不過把和線程無關(guān)的屬性刪掉了,只保留了線程切換必須要用到的成員

調(diào)度鏈表

線程在不同的狀態(tài)下存儲(chǔ)的位置是不一樣的,正在運(yùn)行中的線程在KPCR里,等待狀態(tài)中的線程在等待鏈表里,就緒的線程在32個(gè)就緒鏈表里

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-uk3ROhFd-1581302990410)(assets/1581237190834.png)]

模擬線程切換的代碼沒有寫的那么復(fù)雜,而是用一個(gè)數(shù)組來表示所有線程,然后通過Flags來區(qū)分線程的狀態(tài)。所謂的創(chuàng)建線程,就是創(chuàng)建一個(gè)結(jié)構(gòu)體,并且掛到這個(gè)數(shù)組里,此時(shí)線程處于創(chuàng)建狀態(tài),這個(gè)創(chuàng)建的過程就結(jié)束了。

這里面有一個(gè)小細(xì)節(jié),在這個(gè)數(shù)組里存結(jié)構(gòu)體的時(shí)候,是從下標(biāo)為1的位置開始存的,而不是從最開始的位置開始存的;最開始的這個(gè)位置是給當(dāng)前的線程用的

當(dāng)我們把結(jié)構(gòu)體掛到數(shù)組以后,當(dāng)前線程處于創(chuàng)建狀態(tài),還不能進(jìn)行調(diào)度;因?yàn)槿绻粋€(gè)線程想要執(zhí)行,一定要有自己的初始化的一些環(huán)境,例如寄存器的值要確定,當(dāng)前線程的堆棧在哪里也要確定。現(xiàn)在僅僅有這么一個(gè)結(jié)構(gòu)體,這些初始化的值還沒有。

所以接下來還要做一件非常重要的事情,就是初始化當(dāng)前線程的堆棧

初始化線程堆棧

當(dāng)我們創(chuàng)建一個(gè)線程的時(shí)候,第一件事情就是為線程結(jié)構(gòu)體賦值,把線程名 線程函數(shù)以及線程函數(shù)的參數(shù)填充到這個(gè)結(jié)構(gòu)體。

當(dāng)這些值賦好了以后,當(dāng)前的線程處于創(chuàng)建狀態(tài)

接下里就要為當(dāng)前的線程準(zhǔn)備一份堆棧。首先通過VirtualAlloc函數(shù)申請(qǐng)了一個(gè)堆棧。然后給堆棧的相關(guān)參數(shù)賦值。當(dāng)線程初始化代碼執(zhí)行完成之后,線程狀態(tài)如圖所示:

灰色的部分就是給當(dāng)前線程分配的一塊堆棧,VirtualAlloc分配的地址加上堆棧的大小指向的就是堆棧開始的位置,中間的一部分就是這個(gè)線程所需要的堆棧。

StackLimit指向當(dāng)前堆棧的邊界。KernelStack是當(dāng)前線程的棧頂,相當(dāng)于ESP

接下來往堆棧里push了一堆數(shù)據(jù),這些數(shù)據(jù)就是當(dāng)線程開始執(zhí)行的時(shí)候必須要用到的一些數(shù)據(jù)。

所謂的創(chuàng)建線程就是創(chuàng)建一個(gè)線程結(jié)構(gòu)體,所謂的初始化線程就是為當(dāng)前的線程再準(zhǔn)備一份堆棧

線程切換

被動(dòng)切換

接下來看一下線程是如何進(jìn)行調(diào)度的

當(dāng)代碼執(zhí)行到for循環(huán)的時(shí)候,已經(jīng)創(chuàng)建好了四個(gè)線程。也就是說四個(gè)線程結(jié)構(gòu)體已經(jīng)掛到了數(shù)組里,四個(gè)線程所需要的堆棧也分配完成了。

接著代碼模擬系統(tǒng)時(shí)鐘每隔20毫秒進(jìn)行線程切換,系統(tǒng)時(shí)鐘的存在會(huì)讓線程每隔一段時(shí)間進(jìn)行切換,這種切換是被動(dòng)的。

主動(dòng)切換

還有一種切換是主動(dòng)切換

這里的GMSleep模擬的是W32 API,只要進(jìn)程調(diào)用了API,就會(huì)主動(dòng)產(chǎn)生線程切換。

接下來看看線程是如何進(jìn)行主動(dòng)切換的,跟進(jìn)GMSleep這個(gè)函數(shù),這里要把它當(dāng)成所有的W32 API

這里會(huì)修改線程的狀態(tài)為Sleep,然后調(diào)用Scheduling函數(shù)進(jìn)行線程切換和調(diào)度

線程調(diào)度

那么線程是如何進(jìn)行調(diào)度的呢?

這個(gè)函數(shù)首先會(huì)做一件事情,就是遍歷這個(gè)數(shù)組,找到第一個(gè)處于READY狀態(tài)的線程

真正進(jìn)行線程切換的是SwitchContext函數(shù),這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)當(dāng)前線程的線程結(jié)構(gòu)體指針,第二個(gè)參數(shù)是要切換的線程的線程結(jié)構(gòu)體指針。

這個(gè)函數(shù)首先執(zhí)行了一堆壓棧的操作,將當(dāng)前線程用到的寄存器存儲(chǔ)到堆棧里。

此時(shí)的ESI是當(dāng)前線程的結(jié)構(gòu)體指針,EDI里存儲(chǔ)的是要切換的線程結(jié)構(gòu)體指針。接著把當(dāng)前線程的ESP存儲(chǔ)在當(dāng)前線程的KernelStack里。

然后再把要切換的線程的KernelStack賦值給ESP,此時(shí)堆棧切換,另一個(gè)線程復(fù)活,如圖:

此時(shí)當(dāng)前的ESP指向目標(biāo)線程的EAX

接著執(zhí)行一系列pop指令使線程的堆棧指向Startup,接著ret將Startup(線程的函數(shù)入口)彈到eip,接下來執(zhí)行的就是線程函數(shù)了。

這個(gè)函數(shù)是線程切換的核心,也是兩個(gè)線程的轉(zhuǎn)折點(diǎn),上半部分一個(gè)進(jìn)程進(jìn)來,下半部分另一個(gè)線程出去。

總結(jié)

  • 線程不是被動(dòng)切換的,而是主動(dòng)讓出CPU
  • 線程切換并沒有使用TSS來保存寄存器,而是使用堆棧
  • 線程切換的過程就是堆棧切換的過程
  • 總結(jié)

    以上是生活随笔為你收集整理的进程线程003 模拟线程切换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。