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

目录

16. 作业

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

17.  线程

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

18. 线程调度

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

16. 作业

(1) 作业

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

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

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

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

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

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

(2) 一个简单例程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#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 创建作业
1
2
3
4
HANDLE WINAPI CreateJobObject( //创建作业内核对象
     __in_opt  LPSECURITY_ATTRIBUTES lpJobAttributes, //安全结构体
     __in_opt  LPCTSTR lpName   //名称,可以为NULl
     );
(4)作业限制 和 SetInformationJobObject

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

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

1
2
3
4
5
6
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] 基本限制

1
2
3
4
5
6
7
8
9
10
11
12
//基本限制: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] 扩展限制

1
2
3
4
5
6
7
8
9
//扩展限制:基本限制+内存限制
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限制

1
2
3
4
//基本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标志,防止加入作业前进程执行任何代码。

1
2
3
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) 终止作业
1
2
3
4
BOOL WINAPI TerminateJobObject(
     __in  HANDLE hJob,    //作业
     __in  UINT uExitCode  //退出码。作业中所有进程的退出码自动设为uExitCode
     );
(7) QueryInformationJobObject 查询作业的统计信息
(8) 作业的通知消息

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#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) 线程回调函数(入口函数)
1
2
3
4
5
6
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 创建线程
1
2
3
4
5
6
7
8
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。

clipboard.png

(5) 线程的终止

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

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

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

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

clipboard1.png

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

(7) _beginthreadex 函数
1
2
3
4
5
6
7
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大致相同,可以通过宏实现转换

1
2
3
4
5
6
7
8
9
10
11
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 函数
1
VOID WINAPI Sleep(  __in  DWORD dwMilliseconds );

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

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

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

(3) 切换线程
1
BOOL WINAPI SwitchToThread( void );

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

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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函数设置优先级。

1
SetPriorityClass(GetCurrentProcess(),IDLE_PRIORITY_CLASS);

通过GetPriorityClass函数获取优先级

1
DWORD GetPriorityClass( HANDLE hProcess);
(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函数设置线程的优先级.

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

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

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

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

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

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

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

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

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

(10) 亲缘性

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

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

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

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

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

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

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

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

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值