Windows 核心编程 学习笔记(第三部分)
May 5, 2014
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。
(5) 线程的终止 #
- 线程回调函数的返回 (推荐): 能正确释放所有C++对象,正确释放线程堆栈,设置线程退出码,递减线程内核对象的计数
- ExitThread : 不能释放C++对象。
- TerminateThread:异步函数,函数返回时线程不一定终止了,可使用WaitForSingleObject来等待终止。父进程终止时才会撤销线程的堆栈。
(6) 线程创建和初始化的细节 #
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) 线程的相对优先级 #
线程的相对优先级有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表示CPU0,1表示CPU1。最大为MAXIMUM_PROCESSORS (32)
);