5作业.docx
《5作业.docx》由会员分享,可在线阅读,更多相关《5作业.docx(50页珍藏版)》请在冰豆网上搜索。
5作业
第5章作业
通常,必须将一组进程当作单个实体来处理。
例如,当让MicrosoftDeveloperStudio为你创建一个应用程序项目时,它会生成Cl.exe,Cl.exe则必须生成其他的进程(比如编译器的各个函数传递)。
如果用户想要永远停止该应用程序的创建,那么DeveloperStudio必须能够终止Cl.exe和它的所有子进程的运行。
在Windows中解决这个简单(和常见的)的问题是极其困难的,因为Windows并不维护进程之间的父/子关系。
即使父进程已经终止运行,子进程仍然会继续运行。
当设计一个服务器时,也必须将一组进程作为单个进程组来处理。
例如,客户机可能要求服务器执行一个应用程序(这可以生成它自己的子应用程序),并给客户机返回其结果。
由于可能有许多客户机与该服务器相连接,如果服务器能够限制客户机的要求,即用什么手段来防止任何一个客户机垄断它的所有资源,那么这是非常有用的。
这些限制包括:
可以分配给客户机请求的最大CPU时间,最小和最大的工作区的大小,防止客户机的应用程序关闭计算机,以及安全性限制等。
MicrosoftWindoss2000提供了一个新的作业内核对象,使你能够将进程组合在一起,并且创建一个“沙框”,以便限制进程能够进行的操作。
最好将作业对象视为一个进程的容器。
但是,创建包含单个进程的作业是有用的,因为这样一来,就可以对该进程加上通常情况下不能加的限制。
我的StartRestrictedProcess函数(见清单5-1)将一个进程放入一个作业,以限制该进程进行某些操作的能力。
Windows98Windows98不支持作业的操作。
清单5-1StartRestrictedProcess函数
voidStartRestrictedProcess()
{
//Createajobkernelobject.
HANDLEhjob=CreateJobObject(NULL,NULL);
//Placesomerestrictionsonprocessesinthejob.
//First,setsomebasicrestrictions.
JOBOBJECT_BASIC_LIMIT_INFORMATIONjobli={0};
//Theprocessalwaysrunsintheidlepriorityclass.
jobli.PriorityClass=IDLE_PRIORITY_CLASS;
//Thejobcannotusemorethan1secondofCPUtime.
jobli.PerJobUserTimeLimit.QuadPart=10000000;
//1secin100-nsintervals
//Thesearetheonly2restrictionsIwantplacedonthejob(process).
jobli.LimitFlags=JOB_OBJECT_LIMIT_PRIORITY_CLASS|
JOB_OBJECT_LIMIT_JOB_TIME;
SetInformationJobObject(hjob,
JobObjectBasicLimitInformation,
&jobli,sizeof(jobli));
//Second,setsomeUIrestrictions.
JOBOBJECT_BASIC_UI_RESTRICTIONSjobuir;
jobuir.UIRestrictionsClass=JOB_OBJECT_UILIMIT_NONE;
//Afancyzero
//Theprocesscan'tlogoffthesystem.
jobuir.UIRestrictionsClass|=JOB_OBJECT_UILIMIT_EXITWINDOWS;
//Theprocesscan'taccessUSERobjects
//(suchasotherwindows)inthesystem.
jobuir.UIRestrictionsClass|=JOB_OBJECT_UILIMIT_HANDLES;
SetInformationJobObject(hjob,JobObjectBasicUIRestrictions,
&jobuir,sizeof(jobuir));
//Spawntheprocessthatistobeinthejob.
//Note:
Youmustfirstspawntheprocessandthenplacetheprocessin
//thejob.Thismeansthattheprocess'sthreadmustbeinitially
//suspendedsothatitcan'texecuteanycodeoutside
//ofthejob'srestrictions.
STARTUPINFOsi={sizeof(si)};
PROCESS_INFORMATIONpi;
CreateProcess(NULL,"CMD",NULL,NULL,FALSE,
CREATE_SUSPENDED,NULL,NULL,&si,π);
//Placetheprocessinthejob.
//Note:
ifthisprocessspawnsanychildren,thechildrenare
//automaticallypartofthesamejob.
AssignProcessToJobObject(hjob,pi.hProcess);
//Nowwecanallowthechildprocess'sthreadtoexecutecode.
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
//Waitfortheprocesstoterminateorforallthejob's
//allottedCPUtimetobeused.
HANDLEh[2];
h[0]=pi.hProcess;
h[1]=hjob;
DWORDdw=WaitForMultipleObjects(2,h,FALSE,INFINITE);
switch(dw-WAIT_OBJECT_0)
{
case0:
//Theprocesshasterminated...
break;
case1:
//Allofthejob'sallottedCPUtimewasused...
break;
}
//Cleanupproperly.
CloseHandle(pi.hProcess);
CloseHandle(hjob);
}
现在,解释一下StartRestrictedProcess函数是如何工作的。
首先,调用下面的代码,创建一个新作业内核对象:
HANDLECreateJobObject(PSECURITY_ATTRIBUTESpsa,PCTSTRpszName);
与所有的内核对象一样,它的第一个参数将安全信息与新作业对象关联起来,并且告诉系统,是否想要使返回的句柄成为可继承的句柄。
最后一个参数用于给作业对象命名,使它可以供另一个进程通过下面所示的OpenJobObject函数进行访问。
HANDLEOpenJobObject(DWORDdwDesiredAccess,
BOOLbInheritHandle,PCTSTRpszName);
与平常一样,如果知道你将不再需要访问代码中的作业对象,那么就必须通过调用CloseHandle来关闭它的句柄。
可以在我的StartRestrictedProcess函数的结尾处看到这个代码的情况。
应该知道,关闭作业对象并不会迫使作业中的所有进程终止运行。
该作业对象实际上做上了删除标记,只有当作业中的所有进程全部终止运行之后,该作业对象才被自动撤消。
注意,关闭作业的句柄后,尽管该作业仍然存在,但是该作业将无法被所有进程访问。
请看下面的代码:
//Createanamedjobobject.
HANDLEhjob=CreateJobObject(NULL,TEXT("Jeff"));
//Putourownprocessinthejob.
AssignProcessToJobObject(hjob,GetCurrentProcess());
//Closingthejobdoesnotkillourprocessorthejob.
//Butthename("Jeff")isimmediatelydisassociatedwiththejob.
CloseHandle(hjob);
//Trytoopentheexistingjob.
hjob=OpenJobObject(JOB_OBJECT_ALL_ACCESS,FALSE,TEXT("Jeff"));
//OpenJobObjectfailsandreturnsNULLherebecausethename("Jeff")
//wasdisassociatedfromthejobwhenCloseHandlewascalled.
//Thereisnowaytogetahandletothisjobnow.
5.1对作业进程的限制
进程创建后,通常需要设置一个沙框(设置一些限制),以便限制作业中的进程能够进行的操作。
可以给一个作业加上若干不同类型的限制:
•基本限制和扩展基本限制,用于防止作业中的进程垄断系统的资源。
•基本的UI限制,用于防止作业中的进程改变用户界面。
•安全性限制,用于防止作业中的进程访问保密资源(文件、注册表子关键字等)。
通过调用下面的代码,可以给作业加上各种限制:
BOOLSetInformationJobObject(
HANDLEhJob,
JOBOBJECTINFOCLASSJobObjectInformationClass,
PVOIDpJobObjectInformation,
DWORDcbJobObjectInformationLength);
第一个参数用于标识要限制的作业。
第二个参数是个枚举类型,用于指明要使用的限制类型。
第三个参数是包含限制设置值的数据结构的地址,第四个参数用于指明该结构的大小(用于确定版本)。
表5-1列出了如何来设置各种限制条件。
表5-1设置限制条件
限制类型
第二个参数的值
第三个参数的结构
基本限制
JobObjectBasicLimitInformation
JOBOBJECT_BASIC_LIMIT_INFORMATION
扩展基本限制
JobObjectExtendedLimitInformation
JOBOBJECT_EXTENDED_LIMIT_INFORMATION
基本UI限制
JobObjectBasicUIRestrictions
JOBOBJECT_BASIC_UI_RESTRICTIONS
安全性限制
JobObjectSecurityLimitInformation
JOBOBJECT_SECURITY_LIMIT_INFORMATION
在StartRestrictedProcess函数中,我只对作业设置了一些最基本的限制。
指定了一个JOB_OBJECT_BASIC_LIMIT_INFORMATION结构,对它进行了初始化,然后调用SetInformationJobObject函数。
JOB_OBJECT_BASIC_LIMIT_INFORMATION结构类似下面的样子:
typedefstruct_JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LARGE_INTEGERPerProcessUserTimeLimit;
LARGE_INTEGERPerJobUserTimeLimit;
DWORDLimitFlags;
DWORDMinimumWorkingSetSize;
DWORDMaximumWorkingSetSize;
DWORDActiveProcessLimit;
DWORD_PTRAffinity;
DWORDPriorityClass;
DWORDSchedulingClass;
}JOBOBJECT_BASIC_LIMIT_INFORMATION,*PJOBOBJECT_BASIC_LIMIT_INFORMATION;
表5-2简单地描述了它的各个成员的情况。
表5-2JOB_OBJECT_BASIC_LIMIT_INFORMATION结构的成员
成员
描述
说明
PerProcessUser-TimeLimit
设定分配给每个进程的用户方式的最大时间(以100ns为间隔时间)
任何进程占用的时间如果超过了分配给它的时间,系统将自动终止它的运行。
若要设置这个限制条件,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_PROCESS_TIME
PerJobUser-TimeLimit
设定该作业中可以使用多少用户方式的时间(以100ns为间隔时间)
按照默认设置,当达到该时间限制时,系统将自动终止所有进程的运行。
可以在作业运行时定期改变这个值。
若要设置该限制条件,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_JOB_TIME
LimitFlags
指明哪些限制适用于该作业
详细说明参见本表下面的一段
MinimumWorkingSetSize/MaximumWorkingSetSize
设定每个进程(不是作业中的所有进程)的最小和最大工作区的大小
通常,进程的工作区可能扩大而超过它的最小值。
设置MaximumWorkingSetSize后,就可以实施硬限制。
一旦进程的工作区达到该限制值,进程就会对此作出页标记。
各个进程对SetProcessWorking-SetSize的调用将被忽略,除非该进程只是试图清空它的工作区。
若要设置该限制,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_WORKINGSET标志
ActiveProcessLimit
设定作业中可以同时运行的进程的最大数量
超过这个限制的任何尝试都会导致新进程被迫终止运行,并产生一个“定额不足”的错误。
若要设置这个限制,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_ACTIVE_PROCESS
Affinity
设定能够运行的进程的CPU子集
单个进程甚至能够进一步对此作出限制。
若要设置这个限制,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_AFFINITY
PriorityClass
设定所有进程使用的优先级
如果进程调用SetPriorityClass函数,即使该函数调用失败,它也能成功地返回。
如果进程调用GetPriorityClass函数,该函数将返回进程已经设置的优先级类,尽管这可能不是进程的实际优先级类。
此外,SetThreadPriority无法将线程的优先级提高到正常的优先级之上,不过它可以用于降低线程的优先级。
若要设置这个限制,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_PRIORITY_CLASS
SchedulingClass
设定分配给作业中的线程的相对时段差
它的值可以在0到9之间(包括0和9),默认值是5。
详细说明参见本表后面的文字。
若要设置这个限制,请在LimitFlags成员中设定JOB_OBJECT_LIMIT_SCHEDULING_CLASS
关于这个结构的某些问题在PlatformSDK文档中并没有说清楚,因此在这里作一些说明。
你在LimitFlags成员中设置了一些信息,来指明想用于作业的限制条件。
我设置了JOB_OBJECT_LIMIT_PRIORITY_CLASS和JOB_OBJECT_LIMIT_JOB_TIME这两个标志。
这意味着它们是我用于该作业的唯一的两个限制条件。
我没有对CPU的亲缘关系、工作区的大小、每个进程占用的CPU时间等作出限制。
当作业运行时,它会维护一些统计信息,比如作业中的进程已经使用了多少CPU时间。
每次使用JOB_OBJECT_LIMIT_JOB_TIME标志来设置基本限制时,作业就会减去已经终止运行的进程的CPU时间的统计信息。
这显示当前活动的进程使用了多少CPU时间。
如果想改变作业运行所在的CPU的亲缘关系,但是没有重置CPU时间的统计信息,那将要如何处理呢?
为了处理这种情况,必须使用JOB_OBJECT_LIMIT_AFFINITY标志来设置新的基本限制条件,并且必须退出JOB_OBJECT_LIMIT_JOB_TIME标志的设置。
这样一来,就告诉作业,不再想要使用CPU的时间限制。
这不是你想要的。
你想要的是改变CPU亲缘关系的限制,保留现有的CPU时间限制。
你只是不想减去已终止运行的进程的CPU时间的统计信息。
为了解决这个问题,可以使用一个特殊标志,即JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME。
这个标志与JOB_OBJECT_LIMIT_JOB_TIME标志是互斥的。
JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME标志表示你想改变限制条件,而不减去已经终止运行的进程的CPU时间的统计信息。
现在介绍一下JOBOBJECT_BASIC_LIMIT_INFORMATION结构的SchedulingClass成员。
假如你有两个正在运行的作业,你将两个作业的优先级类都设置为NORMAL_PRIORITY_CLASS。
但是你还想让一个作业中的进程获得比另一个进程多的CPU时间。
可以使用SchedulingClass成员来改变拥有相同优先级的作业的相对调度关系。
可以设置一个0至9之间的值(包括0和9),5是默认值。
在Windows2000上,如果这个设置值比较大,那么系统就会给某个作业的进程中的线程提供较长的CPU时间量。
如果设置的值比较小,就减少该线程的CPU时间量。
例如,我有两个拥有正常优先级类的作业。
每个作业包含一个进程,每个进程只有一个(拥有正常优先级的)线程。
在正常环境下,这两个线程将按循环方式进行调度,每个线程获得相同的CPU时间量。
但是,如果将第一个作业的SchedulingClass成员设置为3,那么,当该作业中的线程被安排CPU时间时,它得到的时间量将比第二个作业中的线程少。
如果使用SchedulingClass成员,应该避免使用大数字即较大的时间量,因为较大的时间量会降低系统中的其他作业、进程和线程的总体响应能力。
另外,我只是介绍了Windows2000中的情况。
Microsoft计划在将来的Windows版本中对线程调度程序进行更重要的修改,因为它认为操作系统应该为作业、进程和线程提供更宽松的线程调度环境。
需要特别注意的最后一个限制是JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION限制标志。
这个限制可使系统为与作业相关的每个进程关闭“未处理的异常情况”对话框。
系统通过调用SetErrorMode函数,将作业中的每个进程的SEM_NOGPFAULTERRORBOX标志传递给它。
作业中产生未处理的异常情况的进程会立即终止运行,不显示任何用户界面。
对于服务程序和其他面向批处理的作业来说,这是个非常有用的限制标志。
如果没有这个标志,作业中的进程就会产生一个异常情况,并且永远不会终止运行,从而浪费了系统资源。
除了基本限制外,还可以使用JOBOBJECT_EXTENDED_LIMIT_INFORMATION结构对作业设置扩展限制:
typedefstruct_JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
JOBOBJECT_BASIC_LIMIT_INFORMATIONBasicLimitInformation;
IO_COUNTERSoInfo;
SIZE_TProcessMemoryLimit;
SIZE_TJobMemoryLimit;
SIZE_TPeakProcessMemoryUsed;
SIZE_TPeakJobMemoryUsed;
}JOBOBJECT_EXTENDED_LIMIT_INFORMATION,*PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;
如你所见,该结构包含一个JOBOBJECT_BASIC_LIMIT_INFORMATION结构,它构成了基本限制的一个超集。
这个结构有一点儿特殊,因为它包含的成员与设置作业的限制毫无关系。
首先,IoInfo成员保留不用,无论如何不能访问它。
本章后面将要介绍如何查询I/O计数器信息。
此外,PackProcessMemoryUsed和PackJobMemoryUsed成员是只读成员,分别告诉你作业中的任何一个进程和所有进程需要使用的已确认的内存最大值。
另外两个成员ProcessM