因为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不支持作业的操作。
void StartRestrictedProcess() 

{
//Create a job kernel object.
HANDLE hjob = CreateJobObject(NULL, NULL);
//Place some restrictions on processes in the job.
//First,set some basic restrictions.
JOBOBJECT_BASIC_LIMIT_INFORMATION jobli =
{ 0 };
//The process always runs in the idle priority class.
jobli.PriorityClass = IDLE_PRIORITY_CLASS;
//The job cannot use more than 1 second of CPU time.
jobli.PerJobUserTimeLimit.QuadPart = 10000000;
//1 sec in 100-ns intervals
//These are the only 2 restrictions I want placed on the job (process).
jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS |
JOB_OBJECT_LIMIT_JOB_TIME;
SetInformationJobObject(hjob,
JobObjectBasicLimitInformation,
&jobli, sizeof(jobli));
//Second, set some UI restrictions.
JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
jobuir.UIRestrictionsClass = JOB_OBJECT_UILIMIT_NONE;
//A fancy zero
//The process can't log off the system.
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;
//The process can't access USER objects
//(such as other windows) in the system.
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;
SetInformationJobObject(hjob,JobObjectBasicUIRestrictions,
&jobuir, sizeof(jobuir));
//Spawn the process that is to be in the job.
//Note: You must first spawn the process and then place the process in
//the job. This means that the process's thread must be initially
//suspended so that it can't execute any code outside
//of the job's restrictions.

STARTUPINFO si =
{ sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, "CMD", NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, π);
//Place the process in the job.
//Note:if this process spawns any children,the children are
//automatically part of the same job.
AssignProcessToJobObject(hjob,pi.hProcess);
//Now we can allow the child process's thread to execute code.
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
//Wait for the process to terminate or for all the job's
//allotted CPU time to be used.
HANDLE h[2];
h[0] = pi.hProcess;
h[1] = hjob;
DWORD dw = WaitForMultipleObjects(2,h,FALSE,INFINITE);
switch( dw-WAIT_OBJECT_0 ) 
{
case 0:
//The process has terminated
break;
case 1:
//All of the job's allotted CPU time was used
break;
}
//Clean up properly.
CloseHandle(pi.hProcess);
CloseHandle(hjob);
}
对作业进程的限制
进程创建后,通常需要设置一个沙框(设置一些限制),以便限制作业中的进程能够进行的操作。可以给一个作业加上若干不同类型的限制:
• 基本限制和扩展基本限制,用于防止作业中的进程垄断系统的资源。
• 基本的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;
typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION

{
JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
IO_COUNTERS oInfo;
SIZE_T ProcessMemoryLimit;
SIZE_T JobMemoryLimit;
SIZE_T PeakProcessMemoryUsed;
SIZE_T PeakJobMemoryUsed;
} JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_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结构的样子: (其他限制)
typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS

{
DWORD UIRestrictionsClass;
} JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;
typedef struct _JOBOBJECT_SECURITY_LIMIT_INFORMATION

{
DWORD SecurityLimitFlags;
HANDLE JobToken;
PTOKEN_GROUPS SidsToDisable;
PTOKEN_PRIVILEGES PrivilegesToDelete;
PTOKEN_GROUPS RestrictedSids;
} JOBOBJECT_SECURITY_LIMIT_INFORMATION, *PJOBOBJECT_SECURITY_LIMIT_INFORMATION;
当然,一旦给作业设置了限制条件,就可以查询这些限制。通过调用下面的代码,就可以进行这一操作
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结构:
typedef struct _IO_COUNTERS

{
ULONGLONG ReadOperationCount;
ULONGLONG WriteOperationCount;
ULONGLONG OtherOperationCount;
ULONGLONG ReadTransferCount;
ULONGLONG WriteTransferCount;
ULONGLONG OtherTransferCount;
} IO_COUNTERS;
另外,可以使用下面这个新的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中的值用于指明发生了哪个事件。
本文深入讲解Windows作业对象的创建及使用方法,包括进程限制、进程分配、作业通知等内容,帮助读者掌握作业对象的核心功能。
302

被折叠的 条评论
为什么被折叠?



