您的位置:首页 > 编程语言

【windows核心编程】 第五章 作业

2013-06-11 00:38 309 查看
Windows核心编程第五章作业

1、作业可以看成是进程的容器,可以有多个进程运行在作业中,便于把这组进程看作一个整体来处理,可以对作业(中的进程)进行限制,即限额。

作业中的进程不能脱离作业,也就不能从一个作业转移到另一个作业。

把进程加入作业的过程是:创建进程,创建标志一定要有CREATE_SUSPEND,即创建后挂起新进程的主线程,把新进程放入作业后调用ResumeThread(新进程.主线程)启动该新进程。对于新进程中又生成的子进程来说,默认也是在作业中的,但是可以修改一些标志让新进程的子进程不运行在作业中。

作业相当于一个【沙箱】,可以把一组进程组合在一起来限制进程能做什么,一个作业可以只包含一个进程。

2、一些函数

BOOLIsProcessInJob(//hProcess标识的进程是否在某个作业中

HANDLEhProcess,//进程句柄

HANDLEhJob,//作业句柄,如果为NULL则是验证是否在任意一个作业中

PBOOLpbInJob//_out,TRUE为在某个作业中,FALSE为不在某个作业中

);

//创建一个作业内核对象

HANDLEWINAPICreateJobObject(

__in_optLPSECURITY_ATTRIBUTESlpJobAttributes,//安全属性

__in_optLPCTSTRlpName//名字,最大限制为MAX_PATH个字符,名字是大小写敏感的,如果为NULL则该内核对象是无名的,如果名字为一个已经存在的比如事件对象、信号量、互斥对象、可等待定时器或者文件映射对象等,函数会失败,GetLastError返回ERROR_INVALID_HANDLE。这个名字可以在一个私有的命名空间中,也可以在”Global\”或”Local\”为前缀的的命名空间中。

);

//把一个hProcess标识的进程放入hJob标志的作业中,需要注意的是CreateProcess创建进程时应该传入CREATE_SUSPEND标志。

BOOLWINAPIAssignProcessToJobObject(

__inHANDLEhJob,//作业内核对象句柄

__inHANDLEhProcess//进程内核对象句柄

);


hJob为用CreateJobObject创建的或者用OpenJobObject打开的作业内核对象句柄。

这个句柄必须有JOB_OBJECT_ASSIGN_PROCESS访问权限。

hProcess标识的进程不能在别的作业中,否则会以ERROR_ACCESS_DENIED错误失败。这个句柄必须有PROCESS_SET_QUOTA和PROCESS_TERMINATE访问权限。

函数成功返回非零,函数失败返回零。

默认情况下,在windowsvista中通过windows资源管理器来启动一个应用程序时,进程会自动同一个专用的作业关联,此作业的名称使用了”PCA”字符串前缀。如果已经为应用程序定义了一个清单(manifest),windows资源管理器就不会将我们的进程同带”PCA”前缀的作业关联,它会假定我们已经解决了任何可能出现的问题,但是,在需要调试调试应用程序时,如果调试器是从windows资源管理器启动的,即使我们的应用程序有一个清单(mainifest),它也会从调试器继承带”PCA”前缀的作业,一个简单的解决方案是从命令行而不是从windows资源管理器中启动调试器,在这种情况下,我们的进程就不会与作业关联。

3、对作业中的进程施加限制

BOOLWINAPISetInformationJobObject(

__inHANDLEhJob,//作业句柄

__inJOBOBJECTINFOCLASSJobObjectInfoClass,//限制种类

__inLPVOIDlpJobObjectInfo,//限制种类对应的数据结构

__inDWORDcbJobObjectInfoLength//第三个数据结构参数的大小

);


限制类型

1基本限额

JobObjectBasicLimitInformation—JOBOBJECT_BASIC_LIMIT_INFORMATION

2扩展后的基本限额

JobObjectExtendedLimitInformation—JOBOBJECT_EXTENDED_LIMIT_INFORMATION

3基本的UI限额

JobObjectBasicUIRestrictions—JOBOBJECT_BASIC_UI_RESTRICTIONS

4安全限额

JobObjectSecurityLimitInformation—JOBOBJECT_SECURITY_LIMIT_INFORMATION

①基本限额

typedefstruct_JOBOBJECT_BASIC_LIMIT_INFORMATION{

LARGE_INTEGERPerProcessUserTimeLimit;//分配给每个进程的最大用户模式时间,时间间隔为100ns,LimitFlags成员要指定JOB_OBJECT_LIMIT_PROCESS_TIME标志


LARGE_INTEGERPerJobUserTimeLimit;//分配给作业对象的最大用户模式时间,时间间隔为100ns,默认情况下,在达到该时间限额时系统将自动终止所有进程的运行。可以在作业运行时定期改变这个值,LimitFlags指定JOB_OBJECT_LIMIT_JOB_TIME

DWORDLimitFlags;//指定哪些标志让哪些成员起效

SIZE_TMinimumWorkingSetSize;//

SIZE_TMaximumWorkingSetSize;//每个进程的最小工作集和最大工作集,LimitFlags指定JOB_OBJECT_LIMIT_WORKINGSET

DWORDActiveProcessLimit;//指定作业中能并发运行的进程的最大数量,超过此限额的任何尝试都会导致新进程终止并报告配额不足的错误,LimitFlags指定JOB_OBJECT_LIMIT_ACTIVE_PROCESS

ULONG_PTRAffinity;//指定能够运行进程的CPU子集,单独的进程可以进一步对此设置,LimitFlags指定JOB_OBJECT_LIMIT_AFFINITY

DWORDPriorityClass;//指定关联的所有进程的优先级类,LimitFlags指定JOB_OBJECT_LIMIT_PRIORITY_CLASS

DWORDSchedulingClass;//做作业中的线程指定一个相对时间量差,值在0~9之间,LimitFlags指定JOB_OBJECT_LIMIT_SCHEDULING_CLASS

}JOBOBJECT_BASIC_LIMIT_INFORMATION,*PJOBOBJECT_BASIC_LIMIT_INFORMATION;


对于LimitFlags来说,需要注意一个标志如下:

JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION,这个限额会导致系统关闭与作业关联的每一个进程的【未处理的异常】对话框,为此系统会为作业中的每个进程调换用SetErrorMode函数,并想它传递SEM_NOGPFAULTERRORBOX标志,作业中的一个进程在引发一个未处理的异常后,会立即终止运行,不会显示任何用户界面。

②扩展基本限额

typedefstruct_JOBOBJECT_EXTENDED_LIMIT_INFORMATION{

JOBOBJECT_BASIC_LIMIT_INFORMATIONBasicLimitInformation;

IO_COUNTERSIoInfo;//保留

SIZE_TProcessMemoryLimit;//如果JOBOBJECT_BASIC_LIMIT_INFORMATION结构的LimitFlags成员指定了JOB_OBJECT_LIMIT_PROCESS_MEMORY,那么这个值为进程虚拟内存的大小

SIZE_TJobMemoryLimit;//如果JOBOBJECT_BASIC_LIMIT_INFORMATION结构的LimitFlags成员指定了JOB_OBJECT_LIMIT_JOB_MEMORY,那么这个值为作业虚拟内存的大小

SIZE_TPeakProcessMemoryUsed;//只读

SIZE_TPeakJobMemoryUsed;//只读

}JOBOBJECT_EXTENDED_LIMIT_INFORMATION,*PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;



UI限额


typedef
struct_JOBOBJECT_BASIC_UI_RESTRICTIONS{

DWORDUIRestrictionsClass;

}
JOBOBJECT_BASIC_UI_RESTRICTIONS,*PJOBOBJECT_BASIC_UI_RESTRICTIONS;

这个结构只有一个成员,



含义

JOB_OBJECT_UILIMIT_DESKTOP

0x00000040

阻止使用CreateDesktopandSwitchDesktop函数来创建或切换桌面

JOB_OBJECT_UILIMIT_DISPLAYSETTINGS

0x00000010

阻止进程通过ChangeDisplaySettings
函数来更改显示设置

JOB_OBJECT_UILIMIT_EXITWINDOWS

0x00000080

阻止进程通过ExitWindowsorExitWindowsEx函数来注销、关机、重启或切断电源等.

JOB_OBJECT_UILIMIT_GLOBALATOMS

0x00000020

为作业指定其专有的全局原子表,并限定作业中的进程只能访问此作业的表

JOB_OBJECT_UILIMIT_HANDLES

0x00000001

组织作业中的进程使用该作业外的进程创建的用户对象(比如在一个作业中创建进程spy++,该进程就不能使用作业外的进程创建的用户对象,比如窗口句柄等。这个限制是单向的,该作业外部的进程还是可以使用该作业内部的进程创建的用户对象的)

JOB_OBJECT_UILIMIT_READCLIPBOARD

0x00000002

组织进程读取剪贴板内容

JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS

0x00000008

阻止进程通过SystemParametersInfo
函数更改系统参数

JOB_OBJECT_UILIMIT_WRITECLIPBOARD

0x00000004

阻止进程写剪贴板

有时候我们需要作业内的进程和作业外的进程进行通信,而又有对作业的UI限额,因此可以使用下面的函数来为作业的进程授权/拒绝其访问某个作业外部的进程创建的用户对象。

BOOLUserHandleGrantAccess(

HANDLEhUserObj,
//某个需要授权/拒绝访问的用户对象

HANDLEhJob,//作业句柄

BOOL
bGrant//TRUE为授权,FALSE为拒绝

);

NOTE:该函数不允许作业内部的进程调用,这样可防止自己授权自己访问某作业外部进程创建的用户对象。


安全限额


typedefstruct_JOBOBJECT_SECURITY_LIMIT_INFORMATION{

DWORDSecurityLimitFlags;//安全限制标志(windows核心编程第五版130页)

HANDLEJobToken;//作业中所有进程使用的访问令牌

PTOKEN_GROUPSSidsToDisable;//禁止对哪些SID进行访问检查

PTOKEN_PRIVILEGES
PrivilegesToDelete;//从访问令牌中删除哪些特权

PTOKEN_GROUPSRestrictedSids;//一组只能拒绝的SID

}JOBOBJECT_SECURITY_LIMIT_INFORMATION,*PJOBOBJECT_SECURITY_LIMIT_INFORMATION;

///

void
CwindowsCoreDlg::OnBnClickedButton2()

{

//TODO:在此添加控件通知处理程序代码;

SECURITY_ATTRIBUTESar;

ar.bInheritHandle
=FALSE;

ar.lpSecurityDescriptor
=NULL;

ar.nLength=sizeof(SECURITY_ATTRIBUTES);

HANDLEhJob=CreateJobObject(&ar,
_T("firstJob"));

if(NULL!=hJob)

{

DWORDdwErr=GetLastError();

if(ERROR_ALREADY_EXISTS
==dwErr)

{

AfxMessageBox(_T("已经存在一个同名作业"));

}

else

{

AfxMessageBox(_T("作业创建成功"));

}

TCHARchAppName[]
=_T("D:\\Program
Files\\Tencent\\QQ\\QQProtect\\Bin\\QQProtect.exe");

TCHARchAppName2[]
=_T("D:\\Program
Files\\EditPlus3\\EditPlus.exe");

STARTUPINFOstartInfo1
={sizeof(STARTUPINFO)};

PROCESS_INFORMATIONprocesinfo1;

BOOLbRet1=CreateProcess(

NULL,

chAppName,

NULL,

NULL,

FALSE,

CREATE_SUSPENDED|CREATE_BREAKAWAY_FROM_JOB,

NULL,

NULL,

&startInfo1,

&procesinfo1

);

STARTUPINFOstartInfo2
={sizeof(STARTUPINFO)};

PROCESS_INFORMATIONprocesinfo2
;

BOOLbRet2=CreateProcess(

NULL,

chAppName2,

NULL,

NULL,

FALSE,

CREATE_SUSPENDED|CREATE_BREAKAWAY_FROM_JOB,

NULL,

NULL,

&startInfo2,

&procesinfo2

);

if(bRet1!=FALSE)

{

AssignProcessToJobObject(hJob,
procesinfo1.hProcess);

ResumeThread(procesinfo1.hThread);

CloseHandle(procesinfo1.hProcess);

CloseHandle(procesinfo1.hThread);

}

if(bRet2!=FALSE)

{

AssignProcessToJobObject(hJob,
procesinfo2.hProcess);

ResumeThread(procesinfo2.hThread);

CloseHandle(procesinfo2.hProcess);

CloseHandle(procesinfo2.hThread);

}

CloseHandle(hJob);

}

else

{

AfxMessageBox(_T("创建作业失败"));

}

}

///

void
CwindowsCoreDlg::OnBnClickedButton3()

{

//TODO:在此添加控件通知处理程序代码;

constintMAX_PROCESS_IDS=10;

DWORDcb=sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)
+

(MAX_PROCESS_IDS-1)*sizeof(DWORD);

PJOBOBJECT_BASIC_PROCESS_ID_LISTpjobpil=(PJOBOBJECT_BASIC_PROCESS_ID_LIST)(_alloca(cb));//_alloca在栈上动态份分配

pjobpil->NumberOfAssignedProcesses
=MAX_PROCESS_IDS;

pjobpil->NumberOfProcessIdsInList
=MAX_PROCESS_IDS;

QueryInformationJobObject(m_hJob,
JobObjectBasicProcessIdList,pjobpil,cb,
&cb);

CStringstrIDs;

for(inti=0;i<pjobpil->NumberOfProcessIdsInList;
++i)

{

CStringstrTemp;

strTemp.Format(_T("%d"),
pjobpil->ProcessIdList[i]);

strTemp+=CString(_T(","));

strIDs+=strTemp;

}

AfxMessageBox(strIDs);

}



4、
其他

查询作业中的限额信息

BOOLWINAPIQueryInformationJobObject(

__in_optHANDLEhJob,//作业内核对象句柄

__inJOBOBJECTINFOCLASSJobObjectInfoClass,//限额种类

__outLPVOIDlpJobObjectInfo,//限额种类结构体

__inDWORDcbJobObjectInfoLength,//结构体大小

__out_optLPDWORDlpReturnLength//指出缓冲区中填充了多少字节,如果不关心传NULL即可

);

作业内的进程调用此函数时为hJob参数传入NULL即可或得本进程所在的作业被施加了哪些限额。

NOTE:

1当一个进程成为作业中的进程时,就不能脱离作业或者成为其他作业中的进程。

2一个作业中的进程的子进程默认也会成为该作业中的进程,但是有两种方法可以去除这种默认的情况:


基本限额JOBOBJECT_BASIC_LIMIT_INFORMATIO的LimitFlags成员打开JOB_OBJECT_LIMIT_BREAKAWAY_OK标志,并且在调用CreateProcess时为fdwCreate参数打开CREATE_BREAKAWAY_FROM_JOB,两者必须同时打开或同时不打开。


打开JOBOBJECT_BASIC_LIMIT_INFORMATION的LimitFlags成员的JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK标志即可,对于CreateProcess则不用传入其他标志。这个标志会让作业中进程的子进程强制脱离作业。

5、
终止作业中的所有进程


BOOLTerminateJobObject(

HANDLE
hJob,//作业内核对象句柄

UINT
uExitCode//退出代码

);

查询作业统计信息

QueryInformationJobObject函数传入种类obObjectBasicAccountingInformation和一个JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构即可

typedefstruct_JOBOBJECT_BASIC_ACCOUNTING_INFORMATION{

LARGE_INTEGER
TotalUserTime;

LARGE_INTEGER
TotalKernelTime;

LARGE_INTEGER
ThisPeriodTotalUserTime;

LARGE_INTEGER
ThisPeriodTotalKernelTime;

DWORDTotalPageFaultCount;

DWORDTotalProcesses;

DWORDActiveProcesses;

DWORDTotalTerminatedProcesses;

}JOBOBJECT_BASIC_ACCOUNTING_INFORMATION,
*PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION;

查询基本统计信息和I/O统计信息

QueryInformationJobObject传入一个种类JobObjectBasicAndIoAccountingInformation和一个JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATON结构即可

typedefstructJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION{

JOBOBJECT_BASIC_ACCOUNTING_INFORMATIONBasicInfo;

IO_COUNTERSIoInfo;

}JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION,
*PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION;

typedefstruct_IO_COUNTERS{

ULONGLONG
ReadOperationCount;

ULONGLONG
WriteOperationCount;

ULONGLONG
OtherOperationCount;

ULONGLONG
ReadTransferCount;

ULONGLONG
WriteTransferCount;

ULONGLONG
OtherTransferCount;

}IO_COUNTERS,*PIO_COUNTERS;

这个结构指出已由作业中的进程执行过的读操作、写操作以及读/写操作的次数(以及这些操作期间传输的字节总数),对于不属于任何作业的进程,可以用如下函数:

BOOLGetProcessIoCounters(

HANDLEhProcess,//进程标识

PIO_COUNTGERSpIoCounters//IO_COUNTETRS结构指针

)

6、
作业通知


Microsoft选择在已分配的CPU时间到期时才讲作业的状态变成已触发,因为那意味着一个错误条件(error
condition),在许多作业中,都会有一个父进程一直在运行,直至其所有子进程全部结束。所以我们可以等待父进程的句柄,借此得知整个作业何时结束,可以如下:

HANDLE
h[2];

H[0]
=processInfo.hProcess;

H[1]
=hJob;

DWORD
dw=WaitForMulitObjects(2,h,FALSE,INFINITE);

Switch(dw
–WAIT_OBJECT_0)

{

Case0:

//进程结束

Break;

Case1:

//作业中所有的分配的CPU时间都用完

Break;

}

如果想要得到一些更具体的通知,比如进程创建/终止运行,要获得这些通知,必须在自己当程序中创建一个I/O完成端口(completionport)对象,并将我们的作业对象与完成端口关联。然后必须有一个或者多个线程等待作业对象通知到达完成端口,以便进行处理。

创建了IO完成端口后,调用SetInformationJobObject将它与一个作业关联起来:

JOBOBJECT_ASSOCIATE_COMPLETION_PORTjoacp;

joacp.CompletionKey
=1;//可以唯一标识这个作业的任意值

joacp.CompletionPort
=hICOP;//完成端口内核对象句柄

SetInformationJobObject(hJob,
JobObjecgtAssociateCompletionPortInformation,&joacp,sizeof(joacp));

执行上述代码后,系统将监视作业,只要有事件发生,就会把他们投递到对应的IO完成端口,同时可以调用QueryInformationJobObject来获取完成键(completionkey)和完成端口句柄(completionport)。

子线程通过调用GetQueuedCompletionStatus来监视完成端口。

BOOLGetQueuedCompletionStatus(

HANDLEhIOCP,//IO完成端口句柄

PDWORDpNumBytesTransferred,//表示事件种类,见后面

PULONG_PTRpCompletionKey,//完成键(completionkey)

POVERLAPPED*pOverlapped,//根据事件不同表示不同的值

DWORDdwMilliseconds
//用于指定调用者线程等待完成端口的时间

);

作业事件通知:

消息标识

描述(什么事件,什么时候向完成端口投递此通知)

JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS

一个进程由于未处理的异常而终止时,lpOverlapped表示退出进程的ID

JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT

试图超过作业中的活动进程数时,

lpOverlapped为NULL

JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO

作业中没有进程在运行时,lpOverlapped为NULL.

JOB_OBJECT_MSG_END_OF_JOB_TIME

关于其说明见表格下方

作业分配的CPU时间到期时,但其中的进程不会自动终止(默认情况下会自动终止),我么可以允许进程继续执行,也可以设置一个新的时间限额

ThevalueoflpOverlapped
为NULL.

JOB_OBJECT_MSG_END_OF_PROCESS_TIME

进程分配的CPU时间到期时,并且进程将终止运行。

lpOverlapped是这个进程ID

JOB_OBJECT_MSG_EXIT_PROCESS

某个进程终止时,

lpOverlapped是退出的进程ID

JOB_OBJECT_MSG_JOB_MEMORY_LIMIT

进程调拨的存储超过作业的限额时,

lpOverlapped是这个进程ID,当进程没有报告他的ID时系统不会投递这个消息通知

JOB_OBJECT_MSG_NEW_PROCESS

一个作业添加了一个新的进程时,

lpOverlapped是新的进程ID

JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT

当进程试图调拨的存储超过进程的限额时,

lpOverlapped是此进程ID,当进程没有报告他的ID时系统不会投递这个通知

NOTE:

在作业的基本限额中我们知道当为作业分配的用户模式时间超时时作业中的进程会自动终止,此时不会投递JOB_OBJECT_MSG_END_OF_JOB_TIME这个通知(上面表格中),如果我们期望的是当为作业分配的用户模式时间超时时投递JOB_OBJECT_MSG_END_OF_JOB_TIME这个通知并且作业中的进程继续运行,那么我们必须执行以下类似代码:

JOBOBJECT_END_OF_JOB_TIME_INFORMATIONjoeojti;

Joeojti.EndOfJobTimeAction=JOB_OBJECT_POST_AT_END_OF_JOB;//对于EndOfJobTimeAction,其默认值为JOB_OBJECT_TERMINATE_AT_END_OF_JOB,即当作业分配时间用完时作业中的进程都终止运行。

//告诉作业对象,当作业的分配时间用完时投递JOB_OBJECT_MSG_END_OF_JOB_TIME这个通知给完成端口

SetInformationJobObject(hJob,JobObjectEndOfJobTimeInformation,

&joeojti,sizeof(joeojti));
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: