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

目录

16. 作业

  •     作业
  •     一个简单例程
  •     CreateJobObject 创建作业
  •     作业限制和 SetInformationJobObject
  •     AssignProcessToJobObject 将进程添加到作业
  •     终止作业
  •     QueryInformationJobObject 查询作业的统计信息
  •     作业的通知消息

17.  线程

  •     线程
  •     线程的回调函数(入口函数)
  •     CreateThread和_beginthreadex的区别
  •     CreateThread 创建线程
  •     线程的终止
  •     线程创建和初始化的细节
  •     _beginthreadex函数

18. 线程调度

  •     线程挂起和恢复
  •     Sleep函数
  •     切换线程
  •     线程执行时间
  •     CONTEXT上下文
  •     Windows线程调度
  •     进程优先级
  •     线程相对优先级
  •     动态提高线程的优先级
  •     亲缘性

16. 作业

(1) 作业

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

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

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

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

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

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

(2) 一个简单例程

[cpp]#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;
}[/cpp]

(3) CreateJobObject 创建作业

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

(4)作业限制 和 SetInformationJobObject

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

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

[cpp]BOOL WINAPI SetInformationJobObject(  //设置作业限制
__in  HANDLE hJob,                            //要添加限制的作业
__in  JOBOBJECTINFOCLASS JobObjectInfoClass,  //限制的类型
__in  LPVOID lpJobObjectInfo,                 //限制的值
__in  DWORD cbJobObjectInfoLength             //限制的值的长度
);[/cpp]

限制类型
说明
第二个参数的值
第三个参数的结构
基本限制
CPU分配限制
JobObjectBasicLimitInformation
JOBOBJECT_BASIC_LIMIT_INFORMATION
扩展限制
基本限制+内存分配限制
JobObjectExtendedLimitInformation
JOBOBJECT_EXTENDED_LIMIT_INFORMATION
基本UI限制
防止作业中进程改变UI
JobObjectBasicUIRestictions
JOBOBJECT_BASIC_UI_RESTRICTIONS
安全性限制
防止作业中进程访问保密资源
JobObjectSecurityLimitInformation
JOBOBJECT_SECURITY_LIMIT_INFORMATION

[1] 基本限制

[cpp]//基本限制: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;[/cpp]

[2] 扩展限制

[cpp]//扩展限制:基本限制+内存限制
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;[/cpp]

[3] 基本UI限制

[cpp]//基本UI限制
typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS {
DWORD UIRestrictionsClass; //下表标志中一个或是组合
} JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;[/cpp]

说明
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标志,防止加入作业前进程执行任何代码。

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

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

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

[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) 终止作业

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

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

(8) 作业的通知消息

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

[cpp]#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;
}[/cpp]

17. 线程

(1) 线程

线程由两部分组成

-> 线程的内核对象。操作系统用来管理线程的数据结构,也是用来存放线程统计信息的结构

-> 线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。

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

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

[cpp]DWORD WINAPI ThreadProc(LPVOID lpParam)
{
DWORD dwResult = 0;
//...
return dwResult;
}[/cpp]

(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 创建线程

[cpp]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
);[/cpp]

-> dwStackSize 参数,设置线程的堆栈大小,0表示使用系统默认的大小

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

clipboard.png

(5) 线程的终止

-> 线程回调函数的返回 (推荐): 能正确释放所有C++对象,正确释放线程堆栈,设置线程退出码,递减线程内核对象的计数

-> ExitThread : 不能释放C++对象

-> TerminateThread:异步函数,函数返回时线程不一定终止了,可使用WaitForSingleObject来等待终止。父进程终止时才会撤销线程的堆栈

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

clipboard1.png

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

(7) _beginthreadex 函数

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

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

[cpp]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)))[/cpp]

_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 函数

[cpp]VOID WINAPI Sleep( __in  DWORD dwMilliseconds );[/cpp]

-> 系统保证在dwMilliseconds 时间内不调度线程,但过后不保证马上唤醒

-> dwMilliseconds = INFINITE,永不调度线程。最好不要这样做

-> dwMilliseconds = 0, 线程放弃剩余的时间片,迫使系统调度另一个线程

(3) 切换线程

[cpp]BOOL WINAPI SwitchToThread(void);[/cpp]

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

(4) 线程执行时间

[cpp]BOOL WINAPI GetThreadTimes(
__in   HANDLE hThread,             //线程句柄
__out  LPFILETIME lpCreationTime,  //创建时间
__out  LPFILETIME lpExitTime,      //退出时间。如果线程仍在运行,则为未定义
__out  LPFILETIME lpKernelTime,    //内核时间(单位100ns)
__out  LPFILETIME lpUserTime       //用户时间 (单位100ns)
);[/cpp]

 

[cpp]#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;
}[/cpp]

(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函数设置优先级。

[cpp]SetPriorityClass(GetCurrentProcess(),IDLE_PRIORITY_CLASS);[/cpp]

通过GetPriorityClass函数获取优先级

[cpp]DWORD GetPriorityClass(HANDLE hProcess); [/cpp]

(8) 线程的相对优先级

clipboard2.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函数设置线程的优先级.

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

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

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

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

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

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

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

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

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

(10) 亲缘性

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

-> 硬亲缘性: 直接控制线程在某个CPU上运行。

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

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

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

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

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

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

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

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

作者:JarvisChu
原文链接:Windows 核心编程 学习笔记 (第三部分)
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

发表评论