作業(yè): 作業(yè)可以看作是一組進程的容器,把這些進程當作一個整體,對這個整體整個加入更多的限制.
因為Wi n d o w s并不維護進程之間的父/子關系。即使父進程已經(jīng)終止運行,子進程仍然會繼續(xù)運行。Microsoft Windoss 2000提供了一個新的作業(yè)內核對象,使你能夠將進程組合在一起,并且創(chuàng)建一個“沙框”,以便限制進程能夠進行的操作。最好將作業(yè)對象視為一個進程的容器。但是,創(chuàng)建包含單個進程的作業(yè)是有用的,因為這樣一來,就可以對該進程加上通常情況下不能加的限制。
創(chuàng)建一個新作業(yè)內核對象:
HANDLE CreateJobObject( PSECURITY_ATTRIBUTES psa, PCTSTR pszName);
與所有的內核對象一樣,它的第一個參數(shù)將安全信息與新作業(yè)對象關聯(lián)起來,并且告訴系統(tǒng),是否想要使返回的句柄成為可繼承的句柄。最后一個參數(shù)用于給作業(yè)對象命名,使它可以供另一個進程通過下面所示的O p e n J o b O b j e c t函數(shù)進行訪問。
HANDLE OpenJobObject( DWORD dwDesiredAccess,
BOOL bInheritHandle, PCTSTR pszName);
與平常一樣,如果知道你將不再需要訪問代碼中的作業(yè)對象,那么就必須通過調用C l o s e H a n d l e來關閉它的句柄。
應該知道,關閉作業(yè)對象并不會迫使作業(yè)中的所有進程終止運行。該作業(yè)對象實際上做上了刪除標記,只有當作業(yè)中的所有進程全部終止運行之后,該作業(yè)對象才被自動撤消。
注意,關閉作業(yè)的句柄后,盡管該作業(yè)仍然存在,但是該作業(yè)將無法被所有進程訪問。
將一個進程放入一個作業(yè),以限制該進程進行某些操作的能力。 Windows 98 Windows 98不支持作業(yè)的操作。
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);
}
對作業(yè)進程的限制
進程創(chuàng)建后,通常需要設置一個沙框(設置一些限制),以便限制作業(yè)中的進程能夠進行的操作。可以給一個作業(yè)加上若干不同類型的限制:
• 基本限制和擴展基本限制,用于防止作業(yè)中的進程壟斷系統(tǒng)的資源。
• 基本的U I限制,用于防止作業(yè)中的進程改變用戶界面。
• 安全性限制,用于防止作業(yè)中的進程訪問保密資源(文件、注冊表子關鍵字等)。
通過調用下面的代碼,可以給作業(yè)加上各種限制:
BOOL SetInformationJobObject(
HANDLE hJob,
JOBOBJECTINFOCLASS JobObjectInformationClass,
PVOID pJobObjectInformation,
DWORD cbJobObjectInformationLength);
第一個參數(shù)用于標識要限制的作業(yè)。第二個參數(shù)是個枚舉類型,用于指明要使用的限制類型。第三個參數(shù)是包含限制設置值的數(shù)據(jù)結構的地址,第四個參數(shù)用于指明該結構的大小(用于確定版本)。
可以做的限制有:(具體參數(shù)的意思參看windows核心編程)
JOB_OBJECT_BASIC_LIMIT_INFORMATION結構類似下面的樣子: (基本限制)
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 _ E X T E N D E D _ L I M I T _ I N F O R M AT I O N結構對作業(yè)設置: (擴展限制)
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;
J O B O B J E C T _ S E C U R I T Y _ L I M I T _ I N F O R M AT I O N的結構類似下面的形式: (安全限制)
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;
當然,一旦給作業(yè)設置了限制條件,就可以查詢這些限制。通過調用下面的代碼,就可以進行這一操作:
BOOL QueryInformationJobObject(
HANDLE hJob,
JOBOBJECTINFOCLASS JobObjectInformationClass,
PVOID pvJobObjectInformation,
DWORD cbJobObjectInformationLength,
PDWORD pdwReturnLength);
你為該函數(shù)傳遞作業(yè)的句柄(就像你對SetInformationJobObject操作時那樣),這些句柄包括用于指明你想要的限制信息的枚舉類型,函數(shù)要進行初始化的數(shù)據(jù)結構的地址,以及包含該結構的數(shù)據(jù)塊的長度。最后一個參數(shù)是pdwReturnLength,用于指向該函數(shù)填寫的DWORD,它告訴你有多少字節(jié)放入了緩存。如果你愿意的話,可以(并且通常)為該參數(shù)傳遞N U L L。
將進程放入作業(yè):
BOOL AssignProcessToJobObject(
HANDLE hJob,
HANDLE hProcess);
該函數(shù)告訴系統(tǒng),將該進程(由hProcess標識)視為現(xiàn)有作業(yè)(由h J o b標識)的一部分。
注意,該函數(shù)只允許將尚未被賦予任何作業(yè)的進程賦予一個作業(yè)。一旦進程成為一個作業(yè)的組成部分,
它就不能轉到另一個作業(yè),并且不能是無作業(yè)的進程。另外,當作為作業(yè)的一部分的進程生成另一個進程的時候,
新進程將自動成為父作業(yè)的組成部分。注意:調用此函數(shù)后要調用ResumeThread();這樣,
進程的線程就可以在作業(yè)的限制下執(zhí)行代碼。終止作業(yè)中所有進程的運行
若要撤消作業(yè)中的進程,只需要調用下面的代碼:
BOOL TerminateJobObject(
HANDLE hJob,
UINT uExitCode);
這類似為作業(yè)中的每個進程調用TerminateProcess函數(shù),將它們的所有退出代碼設置為uExitCode。
查詢作業(yè)統(tǒng)計信息
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函數(shù)來獲取對作業(yè)的當前限制信息。也可以使用它來獲取關于作業(yè)的統(tǒng)計信息。通過傳入不同的參數(shù),可以查詢到不同的作業(yè)統(tǒng)計信息.
例如,若要獲取基本的統(tǒng)計信息,可以調用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,為第二個參數(shù)傳遞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;
除了查詢這些基本統(tǒng)計信息外,可以進行一次函數(shù)調用,以同時查詢基本統(tǒng)計信息和I/O統(tǒng)計信息。為此,必須為第二個參數(shù)傳遞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函數(shù),以便獲取不是這些作業(yè)中的進程的這些信息:
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函數(shù),以便獲取當前在作業(yè)中運行的進程的一組進程I D。若要進行這項操作,首先必須確定你想在作業(yè)中看到多少進程,然后必須分配足夠的內存塊,來放置這些進程I D的數(shù)組,并指定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;
因此,若要獲得當前作業(yè)中的一組進程I D,必須執(zhí)行類似下面的代碼:
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.
}
作業(yè)通知信息
如果關心的是分配的所有C P U時間是否已經(jīng)到期,那么可以非常容易地得到這個通知信息。當作業(yè)中的進程尚未用完分配的C P U時間時,作業(yè)對象就得不到通知。一旦分配的所有C P U時間已經(jīng)用完, Wi n d o w s就強制撤消作業(yè)中的所有進程,并將情況通知作業(yè)對象。通過調用Wa i t F o r S i n g l e O b j e c t (或類似的函數(shù)),可以很容易跟蹤這個事件。有時,可以在晚些時候調用S e t I n f o r m a t i o n J o b O b j e c t函數(shù),使作業(yè)對象恢復未通知狀態(tài),并為作業(yè)賦予更多的C P U時間。
當開始對作業(yè)進行操作時,我覺得當作業(yè)中沒有任何進程運行時,應該將這個事件通知作業(yè)對象。畢竟當進程和線程停止運行時,進程和線程對象就會得到通知。因此,當作業(yè)停止運行時它也應該得到通知。這樣,就能夠很容易確定作業(yè)何時結束運行。但是, M i c r o s o f t選擇在分配的C P U時間到期時才向作業(yè)發(fā)出通知,因為這顯示了一個錯誤條件。由于許多作業(yè)啟動時有一個父進程始終處于工作狀態(tài),直到它的所有子進程運行結束,因此只需要在父進程的句柄上等待,就可以了解整個作業(yè)何時運行結束。S t a r t R e s t r i c t e d P r o c e s s函數(shù)用于顯示分配給作業(yè)的C P U時間何時到期,或者作業(yè)中的進程何時終止運行。
前面介紹了如何獲得某些簡單的通知信息,但是尚未說明如何獲得更高級的通知信息,如進程創(chuàng)建/終止運行等。如果想要得到這些通知信息,必須將更多的基礎結構放入應用程序。特別是,必須創(chuàng)建一個I / O完成端口內核對象,并將作業(yè)對象或多個作業(yè)對象與完成端口關聯(lián)起來。然后,必須讓一個或多個線程在完成端口上等待作業(yè)通知的到來,這樣它們才能得到處理。
一旦創(chuàng)建了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函數(shù),就可以將作業(yè)與該端口關聯(lián)起來,如下面的代碼所示:
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));
當上面的代碼運行時,系統(tǒng)將監(jiān)視該作業(yè)的運行,當事件發(fā)生時,它將事件送往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函數(shù)來檢索完成關鍵字和完成端口句柄。但是,這樣做的機會很少)。線程通過調用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函數(shù)來監(jiān)控I / O完成端口:
BOOL GetQueuedCompletionStatus(
HANDLE hIOCP,
PDWORD pNumBytesTransferred,
PULONG_PTR pCompletionKey,
POVERLAPPED *pOverlapped,
DWORD dwMilliseconds);
當該函數(shù)返回一個作業(yè)事件通知時,* 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時設置的完成關鍵字值,用于將作業(yè)與完成端口關聯(lián)起來。它使你能夠知道哪個作業(yè)存在一個事件。* p N u m B y t e s Tr a n s f e r r e d中的值用于指明發(fā)生了哪個事件。