Windows 核心编程 学习笔记(第三部分)

Windows 核心编程 学习笔记(第三部分)

May 5, 2014
Coding
Windows, C++

16. 作业 #

(1) 作业 #

MSDN: 作业对象允许一组进程被当做一个单元进行管理。作业对象是可命名的、安全的、共享的对象,它能够控制它包含的所有进程的属性。执行在作业上的操作会影响作业包含的所有进程。

作业可视为进程的容器,可以对其中的所有进程加上限制条件。

使用CrateJobObject函数,创建一个作业

使用SetInformationJobObject函数,为作业添加限制条件

使用AssignProcessToJobObject函数,将进程添加到作业

使用IsProcessInJob函数,判断一个进程是否属于一个作业。

(2) 一个简单例程 #

#include <windows.h>
#include <tchar.h>

int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){

    //创建一个作业内核对象
    HANDLE hJob = CreateJobObject(NULL,NULL); //

    ////////////////////////////////////////////////////////////
    //为作业添加一些基本限制

    //基本限制结构体
    JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = {0};

    //作业的优先级
    jobli.PriorityClass = IDLE_PRIORITY_CLASS; //

    //作业的CPU时间限制
    jobli.PerJobUserTimeLimit.QuadPart = 10000000; //1秒,单位是100纳秒

    //指明限制条件
    jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS|JOB_OBJECT_LIMIT_JOB_TIME;

    //设定作业限制
    SetInformationJobObject(hJob,JobObjectBasicLimitInformation,&jobli,sizeof(jobli));

    ////////////////////////////////////////////////////////////
    //为作业添加一些基本UI限制

    //基本UI限制结构体
    JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;

    //初始无限制
    jobuir.UIRestrictionsClass = JOB_OBJECT_UILIMIT_NONE; //

    //增加限制:作业(进程)不能注销操作系统
    jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;

    //增加限制:作业(进程)不能访问 系统的用户对象(如其他窗口)
    jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;

    //设定作业限制
    SetInformationJobObject(hJob,JobObjectBasicUIRestrictions,&jobuir,sizeof(jobuir));

    ////////////////////////////////////////////////////////////
    //创建进程,并添加到作业中。进程初始化时必须是挂起状态,保证在添加到作业前不会执行任何代码

    //创建进程
    STARTUPINFO si={sizeof(si)};
    PROCESS_INFORMATION pi;
    CreateProcess(_T("C:\\Windows\\System32\\cmd.exe"),NULL,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi); //CREATE_SUSPENDED

    //将进程添加到作业
    AssignProcessToJobObject(hJob,pi.hProcess);

    //唤醒进程(的主线程)
    ResumeThread(pi.hThread);

    //关闭句柄
    CloseHandle(pi.hThread);

    ////////////////////////////////////////////////////////////
    //等待进程结束或作业CPU时间耗完
    HANDLE h[2];
    h[0] = pi.hProcess;
    h[1] = hJob;

    DWORD ret = WaitForMultipleObjects(2,h,FALSE,INFINITE);
    switch(ret - WAIT_OBJECT_0){
        case 0:
            //进程结束
            MessageBox(NULL,_T("进程结束"),_T("提示"),MB_OK);
            break;
        case 1:
            //作业分配的CPU时间耗完
            MessageBox(NULL,_T("时间耗尽"),_T("提示"),MB_OK);
            break;
    }

    //关闭句柄
    CloseHandle(pi.hProcess);
    CloseHandle(hJob);

    return 0;
}

(3) CreateJobObject 创建作业 #

HANDLE WINAPI CreateJobObject( //创建作业内核对象
    __in_opt  LPSECURITY_ATTRIBUTES lpJobAttributes, //安全结构体
    __in_opt  LPCTSTR lpName   //名称,可以为NULl
);

(4)作业限制 和 SetInformationJobObject #

作业限制类型有:基本限制、扩展限制、UI限制、安全性限制

使用SetInformationJobObject可以为作业指定限制。

BOOL WINAPI SetInformationJobObject(  //设置作业限制
    __in  HANDLE hJob,                            //要添加限制的作业
    __in  JOBOBJECTINFOCLASS JobObjectInfoClass,  //限制的类型
    __in  LPVOID lpJobObjectInfo,                 //限制的值
    __in  DWORD cbJobObjectInfoLength             //限制的值的长度
);
限制类型 说明 第二个参数的值 第三个参数的结构
基本限制 CPU分配限制 JobObjectBasicLimitInformation JOBOBJECT_BASIC_LIMIT_INFORMATION
扩展限制 基本限制+内存分配限制 JobObjectExtendedLimitInformation JOBOBJECT_EXTENDED_LIMIT_INFORMATION
基本UI限制 防止作业中进程改变UI JobObjectBasicUIRestictions JOBOBJECT_BASIC_UI_RESTRICTIONS
安全性限制 防止作业中进程访问保密资源 JobObjectSecurityLimitInformation JOBOBJECT_SECURITY_LIMIT_INFORMATION

[1] 基本限制

//基本限制:CPU限制
typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
LARGE_INTEGER PerProcessUserTimeLimit; //如果LimitFlags含有JOB_OBJECT_LIMIT_PROCESS_TIME,则此参数表示分配给每个进程的用户模式执行时间,单位100ns.超时进程会被终止
LARGE_INTEGER PerJobUserTimeLimit;     //如果LimitFlags含有JOB_OBJECT_LIMIT_JOB_TIME,则此参数表示分配给作业的用户模式执行时间,超时作业会被终止
DWORD         LimitFlags;              //指明哪些限制对作业有效
SIZE_T        MinimumWorkingSetSize;   //如果LimitFlags含有JOB_OBJECT_LIMIT_WORKINGSET,则此参数表示作业中每个进程的最小工作集大小
SIZE_T        MaximumWorkingSetSize;   //同上,最大工作集大小
DWORD         ActiveProcessLimit;      //如果LimitFlags含有JOB_OBJECT_LIMIT_ACTIVE_PROCESS,则此参数表示作业中可以同时运行的最大进程数量
ULONG_PTR     Affinity;                //如果LimitFlags含有JOB_OBJECT_LIMIT_AFFINITY,则此参数表示能够运行的进程的CPU子集
DWORD         PriorityClass;           //如果LimitFlags含有JOB_OBJECT_LIMIT_PRIORITY_CLASS,则此参数表示作业中所有进程的优先级
DWORD         SchedulingClass;         //如果LimitFlags含有JOB_OBJECT_LIMIT_SCHEDULING_CLASS,则此参数表示相同优先级的作业的调度优先级(0-9,默认5),值越大,CPU时间越长
} JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;

[2] 扩展限制

//扩展限制:基本限制+内存限制
typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; //基本限制
IO_COUNTERS                       IoInfo;                //保留不用。IO计数器
SIZE_T                            ProcessMemoryLimit;    //每个进程能使用的内存量(“基本限制”参数的LimitFlags需含有JOB_OBJECT_LIMIT_PROCESS_MEMORY)
SIZE_T                            JobMemoryLimit;        //作业(所有进程)能使用的内存量(“基本限制”参数的LimitFlags需含有JOB_OBJECT_LIMIT_JOB_MEMORY )
SIZE_T                            PeakProcessMemoryUsed; //只读。单个进程需要使用的内存最大值
SIZE_T                            PeakJobMemoryUsed;     //只读。作业需要使用的内存最大值
} JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;

[3] 基本UI限制

//基本UI限制
typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS {
DWORD UIRestrictionsClass; //下表标志中一个或是组合
} JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;
说明
JOB_OBJECT_UILIMIT_EXITWINDOWS 防止进程通过ExitWindowsEx函数退出、关闭、重启或关闭系统电源
JOB_OBJECT_UILIMIT_READCLIPBOARD 防止进程读取剪切板的内容
JOB_OBJECT_UILIMIT_WRITECLIPBOARD 防止进程写剪切板内容
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS 防止进程通过SystemParametersInfor函数来改变系统参数
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS 防止进程通过ChangeDisplaySettings函数来改变显示设置
JOB_OBJECT_UILIMIT_GLOBALATOMS 防止进程访问全局的基本结构表,为作业分配自己的基本结构表,作业中进程只能访问该表。
JOB_OBJECT_UILIMIT_DESKTOP 防止进程使用CreateDesktop或SwitchDesktop函数创建或转换桌面
JOB_OBJECT_UILIMIT_HANDLES 防止进程使用作业外部的进程创建的用户对象的句柄(如HWND)

[4] 安全性限制

Windows XP(不包括XP)之后的系统不再支持该限制,需要为每个进程单独指定安全设置。

(5) AssignProcessToJobObject 将进程添加到作业 #

要添加到作业的进程在创建时,需使用CREATE_SUSPEND标志,防止加入作业前进程执行任何代码。

BOOL WINAPI AssignProcessToJobObject(  
    __in  HANDLE hJob,    //作业句柄
    __in  HANDLE hProcess //进程句柄
);

一个进程加入到一个作业后,不能再转到另一个作业。

作业中的进程生成的新进程会自动成为作业的一部分。可以通过下面两种方法改变这种特性:

[1] 打开JOBOBJECT_BASIC_LIMIT_INFROMATION 的LimitFlags成员的JOB_OBJECT_BREAKAWAY_OK标志,告诉系统,新生成的进程可以在作业外部运行。同时使用CREATE_BREAKAWAY_FROM_JOB 标志调用CreateProcess创建新进程

[2] 打开JOBOBJECT_BASIC_LIMIT_INFROMATION 的LimitFlags成员的JOB_OBJECT_SILENT_BREAKAWAY_OK标志,告诉系统,新生成的进程可以在作业外部运行。

(6) 终止作业 #

BOOL WINAPI TerminateJobObject(
    __in  HANDLE hJob,    //作业
    __in  UINT uExitCode  //退出码。作业中所有进程的退出码自动设为uExitCode
);

(7) QueryInformationJobObject 查询作业的统计信息 #

(8) 作业的通知消息 #

创建一个IO完成端口(IO Completion Port)内核对象,然后将作业对象或多个作业对象与完成端口关联起来(使用SetInformationJobObject函数),然后让一个或多个线程在完成端口上等待作业通知的到来。

#include <windows.h>
#include <process.h>  //_beginthreadex
#include <tchar.h>

#define CMPKEY_JOBOBJECT 1
#define CMPKEY_TERMINATE 2

typedef unsigned (__stdcall *PTHREAD_START) (void *);

//IO完成端口监听线程回调函数
DWORD WINAPI JobNotify(LPVOID lpParam)
{
    HANDLE hIOCP = (HANDLE)lpParam;

    while (TRUE)
    {
        DWORD dwBytesTransferred;
        ULONG_PTR CompKey;
        LPOVERLAPPED po;

        //从IO完成端口中获取一个消息
        GetQueuedCompletionStatus(hIOCP,&dwBytesTransferred,&CompKey,&po,INFINITE);

        //退出消息
        if (CompKey == CMPKEY_TERMINATE)
        {
            MessageBox(NULL,_T("监听线程退出"),_T("提示"),MB_OK);
            break;
        }

        //来自作业对象hJob的消息
        if(CompKey == CMPKEY_JOBOBJECT)
        {
            MessageBox(NULL,_T("收到来自作业的消息"),_T("提示"),MB_OK);

            switch(dwBytesTransferred){
            case JOB_OBJECT_MSG_END_OF_JOB_TIME:
                MessageBox(NULL,_T("作业限制时间耗尽"),_T("提示"),MB_OK);
                break;
            case JOB_OBJECT_MSG_END_OF_PROCESS_TIME:
            {
                TCHAR szProcessName[MAX_PATH];
                HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,(DWORD)po);

                if(hProcess == NULL){
                    _stprintf(szProcessName,_T("%s"),_T("未知进程名"));
                }
                else{
                    DWORD dwSize = (DWORD)MAX_PATH;
                    QueryFullProcessImageName(hProcess,0,szProcessName,&dwSize);
                    CloseHandle(hProcess);
                }

                TCHAR info[MAX_PATH];
                _stprintf(info,_T("进程%s(ID=%d)限制时间耗尽 "),szProcessName,po);

                MessageBox(NULL,info,_T("提示"),MB_OK);
            }
            break;
            case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
                MessageBox(NULL,_T("运行的进程超过限制"),_T("提示"),MB_OK);
                break;
            case JOB_OBJECT_MSG_NEW_PROCESS:
                MessageBox(NULL,_T("作业中产生新进程"),_T("提示"),MB_OK);
                break;
            case JOB_OBJECT_MSG_EXIT_PROCESS:  {
                TCHAR szProcessName[MAX_PATH];
                HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,(DWORD)po);

                if(hProcess == NULL){
                    _stprintf(szProcessName,_T("%s"),_T("未知进程名"));
                }
                else{
                    DWORD dwSize = (DWORD)MAX_PATH;
                    QueryFullProcessImageName(hProcess,0,szProcessName,&dwSize);
                    CloseHandle(hProcess);
                }

                TCHAR info[MAX_PATH];
                _stprintf(info,_T("进程%s(ID=%d)终止 "),szProcessName,po);

                MessageBox(NULL,info,_T("提示"),MB_OK);
            }
            break;
        }
        }
    }
    return 0;
}

int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){

    //创建一个作业内核对象
    HANDLE hJob = CreateJobObject(NULL,NULL); //

    //创建一个IO完成端口
    HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

    //创建一个线程监听IO完成端口通知消息
    HANDLE hThreadIOCP = (HANDLE)_beginthreadex(NULL,0,(PTHREAD_START)JobNotify,(LPVOID)hIOCP,0,NULL);

    //将IO完成端口与作业关联
    JOBOBJECT_ASSOCIATE_COMPLETION_PORT jobacp;
    jobacp.CompletionKey = (PVOID)CMPKEY_JOBOBJECT;  //任意一个全局唯一的值
    jobacp.CompletionPort = hIOCP;                   //IO完成端口句柄
    SetInformationJobObject(hJob,JobObjectAssociateCompletionPortInformation,&jobacp,sizeof(jobacp)); //关联

    ////////////////////////////////////////////////////////////
    //创建进程,并添加到作业中。进程初始化时必须是挂起状态,保证在添加到作业前不会执行任何代码

    STARTUPINFO si={sizeof(si)};
    PROCESS_INFORMATION pi;
    CreateProcess(_T("C:\\Windows\\System32\\cmd.exe"),NULL,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi); //CREATE_SUSPENDED
    AssignProcessToJobObject(hJob,pi.hProcess);//将进程添加到作业
    MessageBox(NULL,_T("111"),_T("Tips"),MB_OK);
    ResumeThread(pi.hThread);//唤醒进程(的主线程)
    CloseHandle(pi.hThread); //关闭句柄
    CloseHandle(pi.hProcess);
    MessageBox(NULL,_T("MESSAGE"),_T("Tips"),MB_OK);

    //发送一条消息给IO完成端口,结束IO完成端口线程
    PostQueuedCompletionStatus(hIOCP,0,CMPKEY_TERMINATE,NULL);

    //等待IO完成端口线程终止
    WaitForSingleObject(hThreadIOCP,INFINITE);

    //关闭句柄
    CloseHandle(hIOCP);
    CloseHandle(hThreadIOCP);
    CloseHandle(hJob);
    return 0;
}

17. 线程 #

(1) 线程 #

线程由两部分组成

  • 线程的内核对象。操作系统用来管理线程的数据结构,也是用来存放线程统计信息的结构
  • 线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。

进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。

(2) 线程回调函数(入口函数) #

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    DWORD dwResult = 0;
    //…
    return dwResult;
}

(3) CreateThread 和_beginthreadex 区别 #

  • CreateThread 是 Windows API函数,_beginthreadex是C\C++ Runtime Libarary 函数。
  • _beginthreadex 内部调用了 CreateThread. [源码在thread.c中]

理解

C\C++运行库中一些函数使用了全局变量(如errno),直接使用CreateThread会出现多线程同步时的不安全,_beginthreadex则为这些全局变量做了处理,放到了一个_tiddata结构体中,并传递给了线程,使得每个线程了都有一份独立的“全局变量”(TLS,Thread Local Storage)。

如果使用C\C++ Runtime Libaray函数(更准确的说是使用了_tiddata结构体的函数),则应该使用_beginthreadex,防止内存泄露和多线程不安全问题

具体的情况有:

  • 使用了malloc和free,或者new和delete
  • 使用了stdio.h或io.h中的函数
  • 使用了浮点变量或浮点运算函数
  • 调用了任何一个使用了静态缓冲区的Runtime函数,如asctime(),strtok()或rand()

(4) CreateThread 创建线程 #

HANDLE WINAPI CreateThread(
    __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes, //NULL,或者将其的bInheritHandle成员置为TRUE后传入,使该线程内核对象句柄可被子进程继承。
    __in       SIZE_T dwStackSize,                       //线程的堆栈大小(单位字节)。0表示使用默认的堆栈大小
    __in       LPTHREAD_START_ROUTINE lpStartAddress,    //线程回调函数
    __in_opt   LPVOID lpParameter,                       //传递给回调函数的参数
    __in       DWORD dwCreationFlags,                    //标志。0,创建后立即运行;CREATE_SUSPENDED,挂起,调用ResumeThread时再运行;
    __out_opt  LPDWORD lpThreadId                        //线程ID
);
  • dwStackSize 参数,设置线程的堆栈大小,0表示使用系统默认的大小

在Visual Studio 2010中,默认的线程堆栈大小是 1MB,由链接器的/Stack:reserve 开关控制。可以在Liker ->System ->Stack Reserve Size中修改默认的堆栈大小,单位是字节B。

img.png

(5) 线程的终止 #

  • 线程回调函数的返回 (推荐): 能正确释放所有C++对象,正确释放线程堆栈,设置线程退出码,递减线程内核对象的计数
  • ExitThread : 不能释放C++对象。
  • TerminateThread:异步函数,函数返回时线程不一定终止了,可使用WaitForSingleObject来等待终止。父进程终止时才会撤销线程的堆栈。

(6) 线程创建和初始化的细节 #

img.png

BaseThreadStart是一个为文档化的函数,它首先创建一个结构化异常处理帧(SHE,使线程产生的异常能得到系统默认处理),然后调用线程的回调函数,线程返回时,调用ExitThread。

(7) _beginthreadex 函数 #

uintptr_t _beginthreadex( // NATIVE CODE
    void *security,
    unsigned stack_size,
    unsigned ( __stdcall *start_address )( void * ),
    void *arglist,
    unsigned initflag,
    unsigned *thrdaddr  
);

_beginthreadex 只存在于C\C++ Runtime Libarary 的多线程版本中。_beginthreadex的参数类型不依赖Windows API,参数功能与CreateThread大致相同,可以通过宏实现转换

typedef unsigned (__stdcall *PTHREAD_START) (void *);

#define chBEGINTHREADEX(psa, cbStackSize, pfnStartAddr, \
   pvParam, dwCreateFlags, pdwThreadId)                 \
      ((HANDLE)_beginthreadex(                          \
         (void *)        (psa),                         \
         (unsigned)      (cbStackSize),                 \
         (PTHREAD_START) (pfnStartAddr),                \
         (void *)        (pvParam),                     \
         (unsigned)      (dwCreateFlags),               \
         (unsigned *)    (pdwThreadId)))

_beginthreadex 内部首先创建一个tiddata结构体,然后将回调函数的地址(函数名)和参数保存到tiddata结构体,然后调用CreateThead函数,CreateThread函数中回调函数为_threadstartex,传给_threadstartex的参数为tiddata结构体。

18. 线程调度 #

每个线程都拥有一个上下文结构体(CONTEXT),该结构体保存在线程的内核对象中,该结构体保存了线程上次运行时该线程的CPU寄存器的状态。每隔20ms左右,Windows查看当前存在的所有线程内核对象,CPU选择一个可调度的内核对象(不需要调度的线程如:暂停计数器 >= 1,即处于暂停状态的线程;等待事件发生的线程),将它加载到CPU寄存器中,这个操作称为上下文切换(Context Swiche).

(1) 线程挂起和恢复 #

一个线程可以挂起若干次(最大为MAXIMUM_SUSPEND_COUNT),如线程被挂起3次,则必须恢复3次,它才可以被分配CPU。使用CREATE_SUSPEND标志创建一个挂起状态的线程或者DWORD SuspendThread(HANDLE hThread) 可以挂起一个线程。ResumeThread可以恢复一次。

(2) Sleep 函数 #

VOID WINAPI Sleep( __in  DWORD dwMilliseconds );
  • 系统保证在 dwMilliseconds 时间内不调度线程,但过后不保证马上唤醒
  • dwMilliseconds = INFINITE,永不调度线程。最好不要这样做
  • dwMilliseconds = 0, 线程放弃剩余的时间片,迫使系统调度另一个线程

(3) 切换线程 #

BOOL WINAPI SwitchToThread(void);

让操作系统调度另一个线程,如果发生切换,返回非0值,没有切换,返回0。

(4) 线程执行时间 #

BOOL WINAPI GetThreadTimes(
    __in   HANDLE hThread,             //线程句柄
    __out  LPFILETIME lpCreationTime,  //创建时间
    __out  LPFILETIME lpExitTime,      //退出时间。如果线程仍在运行,则为未定义
    __out  LPFILETIME lpKernelTime,    //内核时间(单位100ns)
    __out  LPFILETIME lpUserTime       //用户时间 (单位100ns)
);
#include <windows.h>
#include <process.h>  //_beginthreadex
#include <tchar.h>

DWORD WINAPI ThreadFun(LPVOID lpParam)
{
    DWORD dwResult = 0;
    return dwResult;
}

//FILETIME 抓换为 __int64
__int64 FileTimeToQuadWord(PFILETIME pft) {
    return (Int64ShllMod32(pft->dwHighDateTime, 32) | pft->dwLowDateTime);
}

int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE,PWSTR lpCmdLine,int nShowCmd){

    HANDLE hThread = CreateThread(NULL,0,ThreadFun,NULL,0,NULL);

    FILETIME ftCreate;
    FILETIME ftExit;
    FILETIME ftKernel;
    FILETIME ftUser;

    //获取线程的FILETIME时间
    BOOL ret = GetThreadTimes(hThread,&ftCreate,&ftExit,&ftKernel,&ftUser);
    if(!ret)
        MessageBox(NULL,_T("获取时间失败"),_T("提示"),MB_OK);

    //创建时间 – 转化为本地时间
    SYSTEMTIME stUTCCreate,stLocalCreate;
    FileTimeToSystemTime(&ftCreate,&stUTCCreate);                     //FILETIME转换为UTC时间
    SystemTimeToTzSpecificLocalTime(NULL,&stUTCCreate,&stLocalCreate);//UTC时间转换为本地时间

    TCHAR msg_create[256];
    _stprintf(msg_create,_T("创建时间:%d-%d-%d %d:%d:%d %d"),
              stLocalCreate.wYear,stLocalCreate.wMonth,stLocalCreate.wDay, 
              stLocalCreate.wHour,stLocalCreate.wMinute,stLocalCreate.wSecond,stLocalCreate.wMilliseconds);
    MessageBox(NULL,msg_create,_T("时间"),MB_OK);

    WaitForSingleObject(hThread,INFINITE);  //等待线程结束,再统计内核时间

    //内核时间 – 转化为具体数值
    __int64 kernelTime = FileTimeToQuadWord(&ftKernel);

    TCHAR msg_kernel[256];
    _stprintf(msg_kernel,_T("内核时间:%I64d"),kernelTime);
    MessageBox(NULL,msg_kernel,_T("时间"),MB_OK);

    CloseHandle(hThread);
    return 0;
}

(5) CONTEXT 上下文 #

CONTEXT结构体包含了特定处理器的寄存器数据。系统使用CONTEXT结构执行各种内部操作。目前已经存在为Intel、MIPS、Alpha、PowerPC处理器定义的CONTEXT结构。具体定义在WinNT.h 中。

(6) Windows 线程调度 #

每个线程都会被赋予一个从0(最低)到31(最高)的优先级。

系统引导时,会创建一个特殊的线程,称为0页线程,优先级为0,它是整个系统中唯一一个优先级为0的线程。当系统没有任何线程需要执行时,0页线程负责将系统中的所有空闲RAM页面置0

  • Microsoft 没有将调度程序的行为特性完全固定下来
  • Microsoft 没有让应用程序充分利用调度程序的特性
  • Microsoft 声称调度程序的算法是变化的,在编写代码时应有所准备。

(7) 进程优先级 #

Windows 中定义了如下6个优先级类

优先级 标识符
实时 REALTIME_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
高于正常 ABOVE_NORMAL_PRIORITY_CLASS
正常 NORMAL_PRIORITY_CLASS
低于正常 BELOW_NORMAL_PRIORITY_CLASS
空闲 IDLE_PRIORITY_CLASS
  • CreateProcess时,通过标识位参数设置,标识位参数取值为上表中一个。
  • 通过SetPriorityClass函数设置优先级。
SetPriorityClass(GetCurrentProcess(),IDLE_PRIORITY_CLASS);
  • 通过GetPriorityClass函数获取优先级
DWORD GetPriorityClass(HANDLE hProcess);

(8) 线程的相对优先级 #

img.png

线程的相对优先级有7个等级:关键时间、最高、高于正常、正常、低于正常、最低、空闲。

进程的优先级类和线程相对优先级的映射

线程相对优先级 空闲 低于正常 正常 高于正常 实时 线程相对优先级标识符
关键时间 15 15 15 15 15 31 THREAD_PRIORITY_TIME_CRITICAL
最高 6 8 10 12 15 26 THREAD_PRIORITY_HIGHEST
高于正常 5 7 9 11 14 25 THREAD_PRIORITY_ABOVE_NORMAL
正常 4 6 8 10 13 24 THREAD_PRIORITY_NORMAL
低于正常 3 5 7 9 12 23 THREAD_PRIORITY_BELOW_NORMAL
最低 2 4 6 8 11 22 THREAD_PRIORITY_LOWEST
空闲 1 1 1 1 1 16 THREAD_PRIORITY_IDLE

通过SetThreadPriority函数设置线程的优先级.

BOOL WINAPI SetThreadPriority(
    __in  HANDLE hThread,
    __in  int nPriority   //线程相对优先级标识符
);

GetThreadPriority 函数返回线程的相对优先级。

(9) 动态提高线程的优先级 #

综合考虑线程的相对优先级和线程的进程优先级,系统就可以确定线程的优先级等级,称为线程的基本优先级等级。

系统常常要提高线程的优先级等级,以便对窗口消息或读取磁盘等IO事件作出响应。

系统只能为基本优先级等级在1~15之间的线程提高其优先级等级(称为动态优先级范围),但等级绝不会提高到实时范围(等级高于15)。实时范围的线程能够执行大多数操作系统的函数,因此给等级的提高规定一个范围,就可以防止应用程序干扰操作系统的运行。

通过SetProcessPriorityBoost可以启用或停用系统自动临时提升线程优先级的功能。

BOOL WINAPI SetProcessPriorityBoost(  //停用或启用系统自动对进程中线程优先级的临时提升
    __in  HANDLE hProcess,            
    __in  BOOL DisablePriorityBoost  //TRUE 停用,FALSE 启用
);

通过GetProcessPriorityBoost可以获取当前的启用或停用状态。

(10) 亲缘性 #

  • 软亲缘性: 按照默认设置,系统将线程分配给CPU时,如果其他因素相同,它会优先在上次运行的CPU上运行线程。[让线程留在单个CPU上,有助于重复使用仍在CPU cache中的数据]
  • 硬亲缘性: 直接控制线程在某个CPU上运行。

[1] SetProcessAffinityMask 设置进程的CPU亲缘性

BOOL WINAPI SetProcessAffinityMask( //设置进程所有线程的CPU亲缘性
__in  HANDLE hProcess,
__in  DWORD_PTR dwProcessAffinityMask //亲缘性掩码。位屏蔽,指明可以在哪些CPU上运行。
);

如 dwProcessAffinityMask = 0x00000005,则表明只可以在CPU0 和 CPU1上运行(5->101).

通过GetProcessAffinityMask函数可以获取进程的亲缘性掩码。

[2] SetThreadAffinityMask 设置线程的亲缘性

DWORD_PTR WINAPI SetThreadAffinityMask( //设置线程的CPU亲缘性
    __in  HANDLE hThread,
    __in  DWORD_PTR dwThreadAffinityMask//亲缘性掩码
);

强制给线程分配一个CPU的做法,有时不妥当。可以通过SetThreadIdealProcessor函数为线程选择一个理想的CPU

DWORD WINAPI SetThreadIdealProcessor(  //设置线程的首选CPU
    __in  HANDLE hThread,
    __in  DWORD dwIdealProcessor       //CPU的编号。如0表示CPU01表示CPU1。最大为MAXIMUM_PROCESSORS (32)
);