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 ;
}

 

 

总结

  1. 线程不是被动切换的,而是主动让出CPU
  2. 线程切换并没有使用TSS来保存寄存器,而是使用堆栈
  3. 线程切换的过程就是堆栈切换的过程
posted @ 2020-05-25 15:52  ReVe1Se  阅读(391)  评论(0编辑  收藏  举报