因为Wi n d o w s并不维护进程之间的父/子关系。即使父进程已经终止运行,子进程仍然会继续运行。Microsoft Windoss 2000提供了一个新的作业内核对象,使你能够将进程组合在一起,并且创建一个“沙框”,以便限制进程能够进行的操作。最好将作业对象视为一个进程的容器。但是,创建包含单个进程的作业是有用的,因为这样一来,就可以对该进程加上通常情况下不能加的限制。
创建一个新作业内核对象:
HANDLE CreateJobObject( PSECURITY_ATTRIBUTES psa, PCTSTR pszName);
HANDLE OpenJobObject( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);
应该知道,关闭作业对象并不会迫使作业中的所有进程终止运行。该作业对象实际上做上了删除标记,只有当作业中的所有进程全部终止运行之后,该作业对象才被自动撤消。
注意,关闭作业的句柄后,尽管该作业仍然存在,但是该作业将无法被所有进程访问。
将一个进程放入一个作业,以限制该进程进行某些操作的能力。 Windows 98 Windows 98不支持作业的操作。

























































































对作业进程的限制
进程创建后,通常需要设置一个沙框(设置一些限制),以便限制作业中的进程能够进行的操作。可以给一个作业加上若干不同类型的限制:
• 基本限制和扩展基本限制,用于防止作业中的进程垄断系统的资源。
• 基本的U I限制,用于防止作业中的进程改变用户界面。
• 安全性限制,用于防止作业中的进程访问保密资源(文件、注册表子关键字等)。
通过调用下面的代码,可以给作业加上各种限制:
BOOL SetInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, PVOID pJobObjectInformation, DWORD cbJobObjectInformationLength);
可以做的限制有:(具体参数的意思参看windows核心编程)
JOB_OBJECT_BASIC_LIMIT_INFORMATION结构类似下面的样子: (基本限制)
J O B O B J E C T _ E X T E N D E D _ L I M I T _ I N F O R M AT I O N结构对作业设置: (扩展限制)typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION
![]()
{
LARGE_INTEGER PerProcessUserTimeLimit;
LARGE_INTEGER PerJobUserTimeLimit;
DWORD LimitFlags;
DWORD MinimumWorkingSetSize;
DWORD MaximumWorkingSetSize;
DWORD ActiveProcessLimit;
DWORD_PTR Affinity;
DWORD PriorityClass;
DWORD SchedulingClass;
} JOBOBJECT_BASIC_LIMIT_INFORMATION, * PJOBOBJECT_BASIC_LIMIT_INFORMATION;
![]()












J O B O B J E C T _ B A S I C _ U I _ R E S T R I C T I O NS结构的样子: (其他限制)


















当然,一旦给作业设置了限制条件,就可以查询这些限制。通过调用下面的代码,就可以进行这一操作
BOOL QueryInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, PVOID pvJobObjectInformation, DWORD cbJobObjectInformationLength, PDWORD pdwReturnLength);
将进程放入作业
BOOL AssignProcessToJobObject( HANDLE hJob, HANDLE hProcess);
注意:调用此函数后要调用ResumeThread();这样,进程的线程就可以在作业的限制下执行代码。
终止作业中所有进程的运行
若要撤消作业中的进程,只需要调用下面的代码:
BOOL TerminateJobObject( HANDLE hJob, UINT uExitCode);
查询作业统计信息
Q u e r y I n f o r m a t i o n J o b O b j e c t函数来获取对作业的当前限制信息。也可以使用它来获取关于作业的统计信息。通过传入不同的参数,可以查询到不同的作业统计信息.
例如,若要获取基本的统计信息,可以调用Q u e r y I n f o r m a t i o n J o b O b j e c t,为第二个参数传递J o b O b j e c t B a s i c A c c o u n t i n g I n f o r m a t i o n ,并传递J O B O B J E C T _ B A S I C _ A C C O U N T I N G _ I N F O R M AT I O N结构的地址:
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;
除了查询这些基本统计信息外,可以进行一次函数调用,以同时查询基本统计信息和I/O统计信息。为此,必须为第二个参数传递J o b O b j e c t B a s i c A n d I o A c c o u n t i n g I n f o r m a t i o n ,并传递J O B O B J E C T _ B A S I C _ A N D _ I O _ A C C O U N T I N G _ I N F O R M AT I O N结构的地址:
typedef struct JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION { JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo; IO_COUNTERS IoInfo; } JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION;
如你所见,这个结构只返回一个J O B O B J E C T _ B A S I C _ A C C O U N T I N G _ I N F O R M AT I O N结构和I O _ C O U N T E R S结构:












另外,可以使用下面这个新的G e t P r o c e s s I o C o u n t e r s函数,以便获取不是这些作业中的进程的这些信息:
BOOL GetProcessIoCounters( HANDLE hProcess, PIO_COUNTERS pIoCounters);
也可以随时调用Q u e r y I n f o r m a t i o n J o b O b j e c t函数,以便获取当前在作业中运行的进程的一组进程I D。若要进行这项操作,首先必须确定你想在作业中看到多少进程,然后必须分配足够的内存块,来放置这些进程I D的数组,并指定J O B O B J E C T _ B A S I C _ P R O C E S S _ I D _ L I S T结构的大小:
typedef struct _JOBOBJECT_BASIC_PROCESS_ID_LIST { DWORD NumberOfAssignedProcesses; DWORD NumberOfProcessIdsInList; DWORD ProcessIdList[1]; } JOBOBJECT_BASIC_PROCESS_ID_LIST, *PJOBOBJECT_BASIC_PROCESS_ID_LIST;
因此,若要获得当前作业中的一组进程I D,必须执行类似下面的代码:
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 = _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(int 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. }
作业通知信息
如果关心的是分配的所有C P U时间是否已经到期,那么可以非常容易地得到这个通知信息。当作业中的进程尚未用完分配的C P U时间时,作业对象就得不到通知。一旦分配的所有C P U时间已经用完, Wi n d o w s就强制撤消作业中的所有进程,并将情况通知作业对象。通过调用Wa i t F o r S i n g l e O b j e c t (或类似的函数),可以很容易跟踪这个事件。有时,可以在晚些时候调用S e t I n f o r m a t i o n J o b O b j e c t函数,使作业对象恢复未通知状态,并为作业赋予更多的C P U时间。
当开始对作业进行操作时,我觉得当作业中没有任何进程运行时,应该将这个事件通知作业对象。毕竟当进程和线程停止运行时,进程和线程对象就会得到通知。因此,当作业停止运行时它也应该得到通知。这样,就能够很容易确定作业何时结束运行。但是, M i c r o s o f t选择在分配的C P U时间到期时才向作业发出通知,因为这显示了一个错误条件。由于许多作业启动时有一个父进程始终处于工作状态,直到它的所有子进程运行结束,因此只需要在父进程的句柄上等待,就可以了解整个作业何时运行结束。S t a r t R e s t r i c t e d P r o c e s s函数用于显示分配给作业的C P U时间何时到期,或者作业中的进程何时终止运行。
前面介绍了如何获得某些简单的通知信息,但是尚未说明如何获得更高级的通知信息,如进程创建/终止运行等。如果想要得到这些通知信息,必须将更多的基础结构放入应用程序。特别是,必须创建一个I / O完成端口内核对象,并将作业对象或多个作业对象与完成端口关联起来。然后,必须让一个或多个线程在完成端口上等待作业通知的到来,这样它们才能得到处理。
一旦创建了I / O完成端口,通过调用S e t I n f o r m a t i o n J o b O b j e c t函数,就可以将作业与该端口关联起来,如下面的代码所示:
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp; //Any value to uniquely identify this job joacp.CompletionKey = 1; //Handle of completion port that receives notifications joacp.CompletionPort = hIOCP; SetInformationJobObject(hJob, jobObjectAssociateCompletionPortInformation, &joacp, sizeof(jaocp));
当上面的代码运行时,系统将监视该作业的运行,当事件发生时,它将事件送往I / O完成端口(顺便说一下,可以调用Q u e r y I n f o r m a t i o m J o b O b j e c t函数来检索完成关键字和完成端口句柄。但是,这样做的机会很少)。线程通过调用G e t Q u e u e d C o m p l e t i o n S t a t u s函数来监控I / O完成端口:
BOOL GetQueuedCompletionStatus( HANDLE hIOCP, PDWORD pNumBytesTransferred, PULONG_PTR pCompletionKey, POVERLAPPED *pOverlapped, DWORD dwMilliseconds);
当该函数返回一个作业事件通知时,* p C o m p l e t i o n K e y包含了调用S e t I n f o r m a t i o n J o b O b j e c t时设置的完成关键字值,用于将作业与完成端口关联起来。它使你能够知道哪个作业存在一个事件。* p N u m B y t e s Tr a n s f e r r e d中的值用于指明发生了哪个事件。