文章目录
第5章 作业
- 作业能够将进程组合在一起,并且创建一个“沙框”,以便限制进程能够进行的操作
- 可以把作业对象视为一个进程容器
- 创建单个进程的作业,可以对该进程添加平时不能加的限制
一些基本知识
- 关闭作业对象不会迫使作业中的所有进程终止运行,作业对象做了删除标记,只有当作业中的所有进程全部终止运行之后,该作业对象才被自动撤消。
- 关闭作业的句柄后,尽管作业仍在,但是作业无法被所有进程访问。
5.1 对作业进程的限制
限制类型
- 基本限制和扩展基本限制,用于 防止 作业中的 进程垄断系统的资源
- 基本的 UI 限制,用于 防止 作业中的 进程改变用户界面
- 安全性限制,用于 防止 作业中的 进程访问保密资源(文件、注册表子关键字等)
添加限制
- 可以通过调用函数指明用于作业的限制条件
限制条件
- CPU的亲缘关系、工作区大小、每个进程占用的 CPU 时间等等
书中列出一些类型:
-
JOB_OBJECT_LIMIT_JOB_TIME 标志:限制当前活动进程使用了多少 CPU 时间
-
JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME 标志:与上面的标志互斥,不减去已终止运行的进程的 CPU 时间
-
SchedulingClass 成员 :改变拥有相同优先级的作业的相对调度关系,范围:0~9,默认为5,值越大、系统给该作业的运行时间量越长,值越小、系统给该作业的运行时间量越短。尽可能避免使用大数字,大的时间量会降低系统中其他作业、进程和线程的总体响应能力。
(Microsoft 计划在将来的 Windows 版本中对线程调度程序进行更重要的修改,因为它认
为操作系统应该为作业、进程和线程提供更宽松的线程调度环境。) -
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION 限制标志:可使系统为与作业相关的每个进程关闭“未处理的异常情况”对话框。
系统通过调用 SetErrorMode 函数,将作业中的每个进程的 SEM_NOGPFAULTERRORBOX 标志传递给它。作业中产生未处理的异常情况的进程会立即终止运行,不显示任何用户界面。对于服务程序和其他面向批处理的作业来说,这是个非常有用的限制标志。如果没有这个标志,作业中的进程就会产生一个异常情况,并且永远不会终止运行,从而浪费了系统资源
-
JOBOBJECT_EXTENDED_LIMIT_INFORMATION 结构:对作业设置扩展限制
-
JOB_OBJECT_UITIMIT_HANDLES 标志:作业中没有一个进程能访问该作业外部的进程创建的 USER 对象
如果试图在作业内部运行Microsoft Spy++,那么除了Spy++ 自己创建的窗口外,你看不到任何别的窗口。
图 5-1 显示的 Spy++ 中打开了两个 MDI 子窗口。注意,Threads 1的窗口包含一个系统中的线程列表。这些线程中只有一个线程,即 000006AC SPYXX 似乎创建了一些窗口。这是因为我是在它自己的作业中运行 Spy++ 的,并且限制了它对 UI 句柄的使用。
在同一个窗口中,可以看到 MSDEV 和 EXPLORER 两个线程,可以保证,这些线程肯定创建了窗口,但是 Spy++ 无法访问它们。
在对话框的右边,可以看到 Windows 3窗口,在这个窗口中,Spy++ 显示了桌面上存在的所有窗口的层次结构。注意,它只有一个项目,即 00000000。Spy++ 必须将它作为占位符放在这里。UI限制是单向的。作业外部的进程能看到作业内部的进程创建 USDR 对象。
如果我在一个作业中运行 Notepad,并在作业的外部运行 Spy++ ,若在 Notepad 上设置了 JOB_OBJECT_UILIMIT_HANDLES 标志。 在Spy++的作业中,能看到 NotePad 窗口,但是 Notepad 因为设置了限制,所以看不到 Spy++ 窗口。
作业组成部分的进程,要与作业外部的进程通信,可以限制 UI 句柄
-
UserHandGrantAccess 函数:解决 UI 句柄限制的问题,作业中的进程将窗口消息,发送或显示在作业外部的进程创建的窗口中
-
JOBOBJECT_SECURITY_LIMIT_INFORMATION:安全性相关的限制类型,一旦使用,就无法取消安全性限制
5.2 将进程放入作业
- 子进程不属于作业的组成部分
- 当创建子进程后,子进程开始运行前,必须现实地将进程放入新创建的作业
可以调用下面的代码:
5.3 终止作业中所有进程的运行
常用的操作是撤销作业中的所有进程,只需要调用下面的代码:
5.4 查询作业统级信息
-
可以使用 QueryInformationJobObject 函数来获取 对作业当前的限制信息,也可以用它来获取作业的统计信息
-
可以调用 QueryInformationJobObject 为第二个参数传递 JobObjectBasicAccountingInformation 并传递 JOBOBJECT_BASIC_ACCOUNTING_INFORMATION 结构的地址,来获取基本的统计信息 :
-
此外,还可以进行 一次函数调用,同时查询 基本统计信息和 I/O 统级信息,必须为第二个参数传递 JobObjectBasicAndIoAccountingInformation 并传递 JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION 结构的地址:
该结构告诉你 作业中的进程 已经执行的读、写和非读 / 写操作的数量(以及在这些操作期间传送的字节数) -
此外,可以用下面这个 GetProcessIoCounter 函数,以便获取 不是这些作业中的进程的这些信息:
-
获取当前作业中的 一组进程ID,可以执行以下代码
-
操作系统实际上保存了更多的关于作业的信息,使用性能计数器 来进行操作。可以使用 Performance Data Helper 函数库
(P D H . d l l)中的函数来检索这些信息。也可以使用 Microsoft Management Console(MMC)的Performance Monitor Snap-In来查看作业信息。如图所示,显示了系统中的作业对象可以使用一些计数器
注意,当调用 CreateJobObject 函数时,只能为已经赋予名字的作业获取性能计数器信息。由于这个原因,即使不打算按名字来 共享跨越进程 的作业对象,也应该创建带有名字的这些对象。
5.5 作业通知信息
通知信息包括
- 作业中的所有进程何时终止运行
- 分配的全部 CPU 时间是否已经到期
- 作业中何时生成新进程或作业中的进程何时终止运行
如果关心这些时间,还需要做一些工作
得到通知信息
分配的所有 CPU 时间是否已经到期
- 一旦分配的所有 CPU 时间已经用完, Windows 就强制撤消作业中的所有进程,并将情况通知作业对象。
- 通过调用 WaitForSingleObject(或类似的函数)可以很容易跟踪这个事件
- 可以在晚些时候调用 SetInformationJobObject 函数,使作业对象恢复未通知状态,并为作业赋予更多的 CPU 时间。
整个作业何时运行结束
- 由于许多作业启动时有一个父进程始终处于工作状态,直到它的所有子进程运行结束,因此只需要在父进程的句柄上等待,就可以知道
- StartRestrictedProcess 函数用于显示分配给作业的 CPU 时间何时到期,或者作业中的进程何时终止运行
获得更高级的通知信息:进程创建/终止运行等
- 必须将更多的基础结构放入应用程序,特别是,必须创建一个 I/O 完成端口内核对象,并将作业对象或多个作业对象与完成端口关联起来
- 然后,必须让一个或多个线程在完成端口上等待作业通知的到来,这样它们才能得到处理
- 一旦创建了 I/O 完成端口,通过调用 SetInformationJobObject 函数,就可以将作业与该端口关联起来,如下面的代码所示
5.6 JobLab 实例应用程序
- JobLab 应用程序 “05JobLab.exe” 使你很容易对作业进行实验性操作
- 该应用程序创建了一个 I/O 完成端口,并将作业对象与它相关联。这样就可以对来自作业的通知进行监控,并可
显示在窗口底部的列表框中。 - 应用程序中除了使用基本限制和扩展限制条件外,还可以打开和关闭各种 UI 限制
- 源代码定义了一个 Cjob C++ 类,用于封装操作系统的作业对象,操作起来更容易,不必到处传递作业句柄,还减少了平常调用 QueryInformationJobObject 和 SetInformationJobObject 函数时需要进行的转换工作量。