5.3 终止作业中的所有线程
对于作业,我们经常想做的一件事情就是“杀死”作业中的所有进程。本章伊始,我提到Visual Studio没有一个简单的办法来停止正在进行中的构建(build),因为它必须知道哪些进程是从它生成的第一个进程生成的。(这非常难。我在Microsoft Systems Journal 1998年6月期的Win 32 Q&A专栏讨论过Developer Studio是如何做到这一点的,可以通过以下网址找到这篇文章:http://www.microsoft.com/msj/0698/win320698.aspx。)也许Visual Studio未来的版本会转而使用作业,因为这样一来,代码的编写会变得更容易,而且可以用作业来做更多的事情。
要“杀死”作业内部的所有进程,只需调用以下代码:
BOOL TerminateJobObject(
HANDLE hJob,
UINT uExitCode);
这类似于为作业内的每一个进程调用TerminateProcess,将所有退出代码设为uExitCode。
查询作业统计信息
前面讨论了如何使用QueryInformationJobObject函数来查询作业当前的限制。此外,我们还可以用它来获得作业的统计信息。例如,要获得基本的统计信息,我们可以调用函数QueryInformationJobObject,向第2个参数传递JobObjectBasicAccountingInformation和一个JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构的地址:
typedef struct _JOBOBJECT_BASIC_ACCOUNTING_INFORMATION {
LARGE_INTEGER TotalUserTime;
LARGE_INTEGER TotalKernelTime;
LARGE_INTEGER ThisPeriodTotalUserTime;
LARGE_INTEGER ThisPeriodTotalKernelTime;
DWORD TotalPageFaultCount;
DWORD TotalProcesses;
DWORD ActiveProcesses;
DWORD TotalTerminatedProcesses;
} JOBOBJECT_BASIC_ACCOUNTING_INFORMATION,
*PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION;
表5-5简要描述了该结构的成员。
表5-5 JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构的成员
成 员 | 描 述 |
TotalUserTime | 指出作业中的进程已使用了多少用户模式的CPU时间 |
TotalKernelTime | 指出作业中的进程已使用了多少内核模式的CPU时间 |
ThisPeriodTotalUserTime | 与TotalUserTime一样,不同的是,如果调用SetInformationJobObject来更改基本限额信息,同时没有使用JOB_OBJECT_LIMIT_ PRESERVE_JOB_TIME限额标志,这个值被重置为0 |
ThisPeriodTotalKernelTime | 与ThisPeriodTotalUserTime一样,不同的是,这个值显示的是内核模式CPU时间 |
TotalPageFaultCount | 指出作业中的进程产生的页面错误总数 |
TotalProcesses | 指出曾经属于作业一部分的所有进程的总数 |
ActiveProcesses | 指定作业的当前进程的总数 |
TotalTerminatedProcesses | 指出因为已超过预定CPU时间限额而被“杀死”的进程数 |
从StartRestrictedProcess函数实现的末尾可以看出,通过调用GetProcessTimes函数我们可以获得任何一个进程的CPU占用时间信息,即使此进程不属于任何一个作业,详情请参见第7章。
除了查询基本的统计信息,还可以执行一个调用来同时查询基本统计信息和I/O(输入/输出)统计信息。为此,要向第2个参数传递JobObjectBasicAndIoAccountingInformation和一个JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION结构的地址:
typedef struct JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION {
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo;
IO_COUNTERS IoInfo;
} JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION,
*PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION;
可以看出,这个结构返回了JOBOBJECT_BASIC_ACCOUNTING_INFORMATION和一个IO_COUNTERS结构:
typedef struct _IO_COUNTERS {
ULONGLONG ReadOperationCount;
ULONGLONG WriteOperationCount;
ULONGLONG OtherOperationCount;
ULONGLONG ReadTransferCount;
ULONGLONG WriteTransferCount;
ULONGLONG OtherTransferCount;
} IO_COUNTERS, *PIO_COUNTERS;
这个结构指出已由作业中的进程执行过的读操作、写以及非读/写操作的次数(以及这些操作期间传输的字节总数)。顺便说一句,对于那些不属于任何作业的进程,我们可以使用GetProcessIoCounters函数来获得未放入作业的那些进程的信息,如下所示:
BOOL GetProcessIoCounters(
HANDLE hProcess,
PIO_COUNTERS pIoCounters);
任何时候都可以调用QueryInformationJobObject,获得作业中当前正在运行的所有进程的进程ID集。为此,必须首先估算一下作业中有多少个进程,然后,分配一个足够大的内存块来容纳由这些进程ID构成的一个数组,另加一个JOBOBJECT_BASIC_PROCESS_ID_LIST结构的大小:
typedef struct _JOBOBJECT_BASIC_PROCESS_ID_LIST {
DWORD NumberOfAssignedProcesses;
DWORD NumberOfProcessIdsInList;
DWORD ProcessIdList[1];
} JOBOBJECT_BASIC_PROCESS_ID_LIST, *PJOBOBJECT_BASIC_PROCESS_ID_LIST;
所以,为了获得作业中当前的进程ID集,必须执行以下类似的代码:
void EnumProcessIdsInJob(HANDLE hjob) {
// I assume that there will never be more
// than 10 processes in this job.
#define MAX_PROCESS_IDS 10
// Calculate the number of bytes needed for structure & process IDs.
DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) +
(MAX_PROCESS_IDS – 1) * sizeof(DWORD);
// Allocate the block of memory.
PJOBOBJECT_BASIC_PROCESS_ID_LIST pjobpil =
(PJOBOBJECT_BASIC_PROCESS_ID_LIST)_alloca(cb);
// Tell the function the maximum number of processes
// that we allocated space for.
pjobpil->NumberOfAssignedProcesses = MAX_PROCESS_IDS;
// Request the current set of process IDs.
QueryInformationJobObject(hjob, JobObjectBasicProcessIdList,
pjobpil, cb, &cb);
// Enumerate the process IDs.
for (DWORD x = 0; x < pjobpil->NumberOfProcessIdsInList; x++) {
// Use pjobpil->ProcessIdList[x]...
}
// Since _alloca was used to allocate the memory,
// we don’t need to free it here.
}
这就是使用这些函数所能获得的所有信息。但是,操作系统实际保存着与作业相关的更多的信息。这是通过性能计数器来实现的,可以使用性能数据助手(Performance Data Helper)函数库(PDH.dll)中的函数来获取这些信息。还可以使用管理工具中的可靠性和性能监视器(Reliability and Performance Monitor)来查看作业信息。但是,这样只能看到全局命名的作业对象。不过,利用Sysinternals的Process Explorer(http://www.microsoft.com/technet/sysinternals/ProcessesAndThreads/ProcessExplorer.mspx),可以很好地观察作业。默认情况下,作业限制下的所有进程都用棕色来突出显示。