Windows内核 模拟线程切换
在之前的概念中,线程切换仿佛一直都是由CPU来控制的,CPU为每个线程分配一个时间片,当线程的时间片用完之后,CPU将会切换线程,让其他线程进行执行,但事实并不是这样。
接下来,逐步分析一下ThreadSwitch代码
// ThreadSwitch.cpp : Defines the entry point for the console application. // #include "stdio.h" #include "ThreadCore.h" #include "windows.h" int main(int argc, char* argv[]) { //初始化线程环境 RegisterGMThread("Thread1",Thread1,NULL); RegisterGMThread("Thread2",Thread2,NULL); RegisterGMThread("Thread3",Thread3,NULL); RegisterGMThread("Thread4",Thread4,NULL); //仿Windows线程切换 for (;;) { Sleep(20); ThreadPolling(); } return 0; }
// ThreadCore.h: interface for the ThreadCore class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_THREADCORE_H__3C5DBE32_012F_4176_884F_8D9EA510122D__INCLUDED_) #define AFX_THREADCORE_H__3C5DBE32_012F_4176_884F_8D9EA510122D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define MAXGMTHREAD 0x100 #define GMTHREAD_CREATE 0x01 #define GMTHREAD_READAY 0x02 #define GMTHREAD_RUNING 0x04 #define GMTHREAD_SLEEP 0x08 #define GMTHREAD_SUSPEND 0x10 #define GMTHREAD_EXIT 0x100 typedef struct { char *name; // 线程名 相当于线程TID int Flags; // 线程状态 int SleepMillisecondDot; // 休眠时间 void *InitialStack; // 线程堆栈起始位置,也就是EBP void *StackLimit; // 线程堆栈界限 void *KernelStack; // 线程堆栈当前位置,也就是ESP void *lpParameter; // 线程函数的参数 void (*func)(void *lpParameter); // 线程函数 } GMThread_t; //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- /* 线程结构体数组 * 线程在不同状态的存储位置不同 * 正在运行的线程位于KPCR * 等待中的线程位于等待链表 * 就绪的线程位于调度链表中 * 这里对于以上三种情况使用一个数组进行包含 * main函数也是一个线程,信息存在第一个数组成员里,也就是下标为0的位置 * 创建线程时,是从下标为1的位置开始分配的 */ extern GMThread_t GMThreadList[MAXGMTHREAD]; //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- void IdleGMThread(void *lpParameter); void GMThreadStartup(GMThread_t *GMThreadp); 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); void GMSleep(int Milliseconds); void ThreadPolling(); void Thread1(void *lpParameter); void Thread2(void *lpParameter); void Thread3(void *lpParameter); void Thread4(void *lpParameter); #endif // !defined(AFX_THREADCORE_H__3C5DBE32_012F_4176_884F_8D9EA510122D__INCLUDED_)
首先分析一下头文件:
GMThread_t 其实就代表了一个线程中的各种属性组成的结构体,该结构体就代表一个线程。
将GMThread_t 导出成为一个结构体数组 ,其实就是一个线程数组,数组中每一个成员都代表其中的一个线程,而且主线程(main)占用了数组中的0号位置,即其他线程是从1号位置进行创建的。
void IdleGMThread(void *lpParameter); void GMThreadStartup(GMThread_t *GMThreadp); //线程启动时运行的函数 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); void GMSleep(int Milliseconds); //睡眠 void ThreadPolling(); //线程轮换
#include "stdafx.h" #include "stdio.h" #include "windows.h" #include "ThreadCore.h" #define _SELF "Windows线程切换" //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- int CurrentThreadindex = 0; GMThread_t GMThreadList[MAXGMTHREAD] = { NULL,0 }; #define GMTHREADSTACKSIZE 0x80000 void *WindowsStackLimit = NULL; //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- __declspec(naked) void SwitchContext(GMThread_t *SrcGMThreadp,GMThread_t *DstGMThreadp) { __asm { //保存现场 push ebp mov ebp,esp //sub esp,__LOCAL_SIZE push edi push esi push ebx push ecx push edx push eax mov esi,SrcGMThreadp //当前线程结构体指针 mov edi,DstGMThreadp //目标线程结构体指针 // esi + GMThread_t.KernelStack == SrcGMThreadp.KernelStack mov [esi+GMThread_t.KernelStack], esp //---------------经典堆栈切换 另一个线程复活---------------------------------- // edi + GMThread_t.KernelStack == DstGMThreadp.KernelStack mov esp, [edi+GMThread_t.KernelStack] //此时,ESP为目标线程堆栈栈顶 pop eax pop edx pop ecx pop ebx pop esi pop edi //add esp,__LOCAL_SIZE pop ebp ret //ebp之后是GMThreadStartup函数地址 } } //用来执行线程函数 void GMThreadStartup(GMThread_t *GMThreadp) { //执行线程函数 GMThreadp->func(GMThreadp->lpParameter); //线程函数执行结束,设置线程状态为EXIT GMThreadp->Flags = GMTHREAD_EXIT; //线程切换 Scheduling(); return ; } void IdleGMThread(void *lpParameter) { Scheduling(); return ; } void PushStack(unsigned int **Stackpp,unsigned int v) { // ESP 减去一个单位(4个字节) *Stackpp -= 1; //[ESP] = 参数v **Stackpp = v; return ; } void initGMThread(GMThread_t *GMThreadp,char *name,void (*func)(void *lpParameter),void *lpParameter) { unsigned char *StackPages; unsigned int *StackDWORDParam; //结构初始化赋值 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); //堆栈栈底(EBP) GMThreadp->InitialStack = (StackPages+GMTHREADSTACKSIZE-0x10); //堆栈边界地址 GMThreadp->StackLimit = StackPages; StackDWORDParam = (unsigned int*)GMThreadp->InitialStack; //入栈 PushStack(&StackDWORDParam,(unsigned int)GMThreadp); //线程结构体自身指针,用来寻找 线程函数|函数参数 PushStack(&StackDWORDParam,(unsigned int)9); //平衡堆栈 PushStack(&StackDWORDParam,(unsigned int)GMThreadStartup); //函数地址,执行线程函数的入口函数 //下面的值可以随便写 PushStack(&StackDWORDParam,5); //push ebp PushStack(&StackDWORDParam,7); //push edi PushStack(&StackDWORDParam,6); //push esi PushStack(&StackDWORDParam,3); //push ebx PushStack(&StackDWORDParam,2); //push ecx PushStack(&StackDWORDParam,1); //push edx PushStack(&StackDWORDParam,0); //push eax //执行后,堆栈变化如下 GMThreadp->KernelStack = StackDWORDParam; //指向当前线程的栈顶(ESP) GMThreadp->Flags = GMTHREAD_READAY; //线程状态设置为就绪 return ; } int RegisterGMThread(char *name,void (*func)(void *lpParameter),void *lpParameter) { int i; //为数组下标为0的成员赋值,GM Thread,相当于main函数线程 if (GMThreadList[0].name==NULL) { //申请堆栈初始化操作 线程数组 ,线程名字 ,函数地址 ,参数 initGMThread(&GMThreadList[0], "IDLE GM Thread", IdleGMThread, NULL); } //新增的线程从下标为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(void) { int i; int TickCount; GMThread_t *SrcGMThreadp; GMThread_t *DstGMThreadp; TickCount = GetTickCount(); SrcGMThreadp = &GMThreadList[CurrentThreadindex]; //当前线程结构体指针 DstGMThreadp = &GMThreadList[0]; //目标线程结构体指针 //遍历线程数组,找到状态为就绪的线程 for (i=1;GMThreadList[i].name;i++) { if (GMThreadList[i].Flags&GMTHREAD_SLEEP) { if (TickCount>GMThreadList[i].SleepMillisecondDot) { GMThreadList[i].Flags = GMTHREAD_READAY; } } if ((GMThreadList[i].Flags&GMTHREAD_READAY)) { //检测到有线程的状态为就绪,将其作为目标线程 DstGMThreadp = &GMThreadList[i]; break; } } CurrentThreadindex = DstGMThreadp-GMThreadList; //得到即将执行的线程下标 SwitchContext(SrcGMThreadp,DstGMThreadp); //线程切换 return ; } //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- void GMSleep(int Milliseconds) { GMThread_t *GMThreadp; GMThreadp = &GMThreadList[CurrentThreadindex]; if ((GMThreadp->Flags&GMTHREAD_SUSPEND)==0) { GMThreadp->SleepMillisecondDot = GetTickCount()+Milliseconds; GMThreadp->Flags = GMTHREAD_SLEEP; } //线程切换 Scheduling(); return ; } void ThreadPolling() { unsigned char StackPage[GMTHREADSTACKSIZE]; memset(StackPage,0,GMTHREADSTACKSIZE); //模拟线程切换 IdleGMThread(StackPage); return ; } //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- void vmmprint(char *f,int l,char *fmt, ...) { int ret; char buffer[0x100]; va_list args; //---------------------------------- va_start(args, fmt); _snprintf(buffer,0x80,"[%s]:",f,l); ret = _vsnprintf(buffer+strlen(buffer), 0x100-strlen(buffer), fmt, args); if (ret == -1) { strcpy(buffer, "vmmprint: error."); } //---------------------------------- printf("%s",buffer); //OutputDebugString(buffer); return ; } void Thread1(void *lpParameter) { int i; for (i=0;i<3;i++) { vmmprint(_SELF,__LINE__,"Thread1 \n"); GMSleep(1000); } return ; } void Thread2(void *lpParameter) { for (;;) { vmmprint(_SELF,__LINE__,"Thread2 \n"); GMSleep(500); } return ; } void Thread3(void *lpParameter) { for (;;) { vmmprint(_SELF,__LINE__,"Thread3 \n"); GMSleep(800); } return ; } void Thread4(void *lpParameter) { for (;;) { vmmprint(_SELF,__LINE__,"Thread4 \n"); GMSleep(200); } return ; }
从main函数开始进行分析:
先初始化线程环境后,执行ThreadPolling()。
那么初始化线程环境是怎样进行操作的,就要分析RegisterGMThread()
int RegisterGMThread(char *name,void (*func)(void *lpParameter),void *lpParameter) { int i; //为数组下标为0的成员赋值,GM Thread,相当于main函数线程 if (GMThreadList[0].name==NULL) { //申请堆栈初始化操作 线程数组 ,线程名字 ,函数地址 ,参数 initGMThread(&GMThreadList[0], "IDLE GM Thread", IdleGMThread, NULL); } //新增的线程从下标为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); }
到此线程环境初始化的工作完成,接下来执行ThreadPolling()
void ThreadPolling() { unsigned char StackPage[GMTHREADSTACKSIZE]; memset(StackPage,0,GMTHREADSTACKSIZE);//初始化堆栈 //模拟线程切换 IdleGMThread(StackPage); return ; } void IdleGMThread(void *lpParameter) { Scheduling(); return ; } void Scheduling(void) { int i; int TickCount; GMThread_t *SrcGMThreadp; GMThread_t *DstGMThreadp; TickCount = GetTickCount(); SrcGMThreadp = &GMThreadList[CurrentThreadindex]; //当前线程结构体指针 DstGMThreadp = &GMThreadList[0]; //目标线程结构体指针 //遍历线程数组,找到状态为就绪的线程 for (i=1;GMThreadList[i].name;i++) { if (GMThreadList[i].Flags&GMTHREAD_SLEEP) { if (TickCount>GMThreadList[i].SleepMillisecondDot) { GMThreadList[i].Flags = GMTHREAD_READAY; } } if ((GMThreadList[i].Flags&GMTHREAD_READAY)) { //检测到有线程的状态为就绪,将其作为目标线程 DstGMThreadp = &GMThreadList[i]; break; } } CurrentThreadindex = DstGMThreadp-GMThreadList; //得到即将执行的线程下标 SwitchContext(SrcGMThreadp,DstGMThreadp); //线程切换 return ; } __declspec(naked) void SwitchContext(GMThread_t *SrcGMThreadp,GMThread_t *DstGMThreadp) { __asm { //保存现场 push ebp mov ebp,esp //sub esp,__LOCAL_SIZE push edi push esi push ebx push ecx push edx push eax mov esi,SrcGMThreadp //当前线程结构体指针 mov edi,DstGMThreadp //目标线程结构体指针 // esi + GMThread_t.KernelStack == SrcGMThreadp.KernelStack mov [esi+GMThread_t.KernelStack], esp //---------------经典堆栈切换 另一个线程复活---------------------------------- // edi + GMThread_t.KernelStack == DstGMThreadp.KernelStack mov esp, [edi+GMThread_t.KernelStack] //此时,ESP为目标线程堆栈栈顶 pop eax pop edx pop ecx pop ebx pop esi pop edi //add esp,__LOCAL_SIZE pop ebp ret //ebp之后是GMThreadStartup函数地址 } } //用来执行线程函数 void GMThreadStartup(GMThread_t *GMThreadp) { //执行线程函数 GMThreadp->func(GMThreadp->lpParameter); //线程函数执行结束,设置线程状态为EXIT GMThreadp->Flags = GMTHREAD_EXIT; //线程切换 Scheduling(); return ; }
总结
- 线程不是被动切换的,而是主动让出CPU
- 线程切换并没有使用TSS来保存寄存器,而是使用堆栈
- 线程切换的过程就是堆栈切换的过程