• 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 
    ");
            GMSleep(1000);
        }
    
        return ;
    }
    
    void Thread2(void *lpParameter)
    {
        for (;;)
        {
            vmmprint(_SELF,__LINE__,"Thread2 
    ");
            GMSleep(500);
        }
        
        return ;
    }
    
    void Thread3(void *lpParameter)
    {
        for (;;)
        {
            vmmprint(_SELF,__LINE__,"Thread3 
    ");
            GMSleep(800);
        }
        
        return ;
    }
    
    void Thread4(void *lpParameter)
    {
        for (;;)
        {
            vmmprint(_SELF,__LINE__,"Thread4 
    ");
            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. 线程切换的过程就是堆栈切换的过程
  • 相关阅读:
    su和sudo命令详解
    JS线程Web worker
    Navicat 批处理 自动备份数据库
    MySql【Error笔记】
    vue入门
    动态库
    环境变量
    cmake_learn
    自动编译
    网络编程
  • 原文地址:https://www.cnblogs.com/Virus-Faker/p/12957330.html
Copyright © 2020-2023  润新知