Windows via C/C++, 5th Edition - Jobs
Microsoft Windows 提供了作业内核对象(Job Kernel Object),允许将进程组合在一起,并创建一个“沙盒(sandbox)”来限制进程可以执行的操作。最好将作业对象视为进程的容器。创建包含单个进程的作业也是很有用的,因为您可以对该进程施加各种限制。
如下 StartRestrictedProcess
函数将一个进程放置到一个限制进程执行某些操作的能力的作业中:
1 |
|
注:默认情况下,当您通过 Windows 资源管理器(Windows Explorer)启动应用程序时,该进程会自动关联到一个专用作业,其名称以“PCA”字符串为前缀。当作业中的进程退出时,资源管理器可能会收到通知。因此,当由 Windows 资源管理器启动的传统应用程序出现故障时,将触发程序兼容性助手(Program Compatibility Assistant)。从 Shell 中启动应用程序则不会发生这种作业关联。
对作业的进程设置限制
创建作业后,通常需要设置沙盒(设置限制)以控制作业中的进程可以执行的操作。通过调用 SetInformationJobObject
可以对作业设置几种不同类型的限制:
- 基本限制和扩展基本限制,可防止作业中的进程独占系统资源。
- 基本 UI 限制,可防止作业中的进程更改用户界面。
- 安全限制,可防止作业中的进程访问安全资源(文件、注册表子项等)。
一旦对作业设置了限制,您可能希望查询这些限制。可以通过调用 QueryInformationJobObject
函数轻松地完成此操作。
在作业中放置进程
使用 AssignProcessToJobObject
函数可以将进程放置到作业中,一旦进程被放置到作业中,它就不能被转移到其它作业中。可以使用 IsProcessInJob
函数检查进程是否已经在指定的作业中。
在创建进程后,将进程放置到作业之前,进程并不是作业的一部分,因此它并不会受到作业的限制,进程可以在这段时间内执行开发者预期要限制的事情。可以在创建计划要放置到作业中的进程时,在调用 CreateProcess
时使用 CREATE_SUSPENDED
标志。这将会创建新进程,但不允许该进程执行任何代码。在将进程放置到作业后,可以调用 ResumeThread
函数使进程中的线程恢复执行。
终止作业中的所有进程
要想终止作业中的所有进程,只需调用 TerminateJobObject
。这类似于为作业中的每个进程调用 TerminateProcess
,并将其退出代码设置为 uExitCode
。
作业通知
通知允许您获知与作业相关的事件。例如,作业中的所有进程何时终止了,或者所有分配的 CPU 时间是否已到期了?作业中何时产生了新进程,或者作业中的进程何时终止了?
如果您关心的是所有分配的 CPU 时间是否已到期,则可以通过调用 WaitForSingleObject
或类似函数来捕获此事件。这是因为一旦用完所有分配的 CPU 时间,Windows 就会终止作业中的所有进程并将作业对象置于已示意(signaled)。随后,您可以通过调用 SetInformationJobObject
并给予作业更多的 CPU 时间来将作业对象重置回未示意(nonsignaled)状态。
如果需要获得更高级的通知信息(如进程的创建/终止),那么必须创建 I/O 完成端口内核对象(I/O Completion Port Kernel Object),并将作业对象或对象与其关联。然后,必须有一个或多个线程在完成端口上等待作业通知到达,以便可以对其进行处理。
创建 I/O 完成端口后,通过调用 SetInformationJobObject
将作业与其关联,如下所示:
1 |
|
上述代码执行后,系统将监视作业,并在事件发生时将其发布到 I/O 完成端口。线程通常通过调用 GetQueuedCompletionStatus
来监视 I/O 完成端口。
最后需要注意的一点是:默认情况下,作业对象被配置为当作业分配的 CPU 时间到期时,作业的所有进程都将自动终止,并且不会发布 JOB_OBJECT_MSG_END_OF_JOB_TIME
通知。如果要防止作业对象终止进程,而只是通知您已超过时间,则必须执行如下代码:
1 |
|