• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2021-07-01 12:28 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

作业

概述

  1. 作业可以让我们将一组进程当作一个实体来处理。
  2. Windows提供的作业(job)内核对象,允许我们将进程组合在一起并创建一个“沙箱”来限制进程能够做什么。
  3. 我们可以创建一个只包含一个进程的作业,来对进程施加平时不能施加的限制。

只含一个进程的作业

  1. 这么做的目的是为了对进程施加一些限制。

解析

  1. 首先获取了当前进程,然后将NULL作为第二个参数传给了IsProcessInJob函数,用于验证当前进程是否在一个现有的作业控制之下运行。

  1. 如上面注释中所言,如果一个进程已经与一个作业相关联了,那么是无法将当前进程或它的任何子进程从这个作业中去除的。
    这个特性确保了进程无法摆脱对它施加的限制。
  2. 然后创建一个新的作业内核对象
    和所有内核对象一样,第一个参数是将安全信息将新的作业内核对象关联,然后告诉系统,是否希望返回的句柄可被继承。
    第二个参数是对此作业对象进行命名,使其能够被另一个进程通过OpenJobObject函数进行访问。

  1. 如果确定在自己的这个进程中不会再访问作业内核对象了,调用CloseHandle来关闭它的句柄。
    需要了解的是:
    关闭一个作业的内核对象句柄,并不会导致作业中的所有进程都终止运行。
    作业对象只是加了一个删除标记,只有再作业中的所有进程都终止运行之后,才会自动销毁作业。
    但是,关闭了作业的句柄,会导致所有进程都不能再访问这个作业,即使这个作业仍然存在也不能访问。

对作业中的进程施加限制

  1. 创建好一个进程之后,一般会根据作业中的进程能够执行哪些操作来建立一个沙箱(限制)。
  2. 可向作业应用以下几种类型的限制:
    1. 基本限制和扩展基本限制
      用于防止作业中的进程独占系统资源。
    2. 基本的UI限制
      用于防止作业内的进程更改用户界面。
    3. 安全限制
      用于防止作业内的进程访问安全资源(如文件、注册表子项等)。
  3. 可以调用以下函数向作业施加限制
    1. 第一个参数是要添加限制的作业的句柄
    2. 第二个参数是一个枚举类型,指定了施加限制的类型
    3. 第三个参数是数据结构的地址,该结构中包含了具体的限制设置
    4. 第四个参数指出了此数据结构的大小

限制类型 第二个参数的值 第三个参数对应的数据结构
基本限制 JobObjectBasicLimitInformation JOBOBJECT_BASIC_LIMIT_INFORMATION
扩展后的基本限制 JobObjectExtendedLimitInformation JOBOBJECT_EXTENDED_LIMIT_INFORMATION
基本的UI限制 JobObjectBasicUIRestrictions JOBOBJECT_BASIC_UI_RESTRICTIONS
安全限制 JobObjectSecurityLimitInformation JOBOBJECT_SECURITY_LIMIT_INFORMATION
  1. 查询限制

将进程放入作业中

  1. 向系统表明将此进程当作现有作业的一部分。
  2. 需要注意的是,这个函数只允许将一个尚未分配给作业的一个进程分配给一个作业。
  3. 一旦一个进程成为了一个作业的一部分,那它就不能再成为其他作业的一部分了,也不能成为“无业的”了。

  1. 还有一个点,就是说作业中的一个进程生成了一个子进程,新进程将会自动成为作业的一部分。
    但是,有方法改变这种行为。

  2. 方法一
    打开JOBOBJECT_BASIC_LIMIT_INFORMATION的LimitFlags成员的JOB_OBJECT_LIMIT_BREAKAWAY_OK标志,告诉系统新生成的进程可以在作业外部执行。
    为此,必须调用CreateProcess的时候,指定JOB_OBJECT_LIMIT_BREAKAWAY_OK限额标志。
    如果调用CreateProcess的时候指定了这个限额标志,但是作业没有打开JOB_OBJECT_LIMIT_BREAKAWAY_OK标志,CreateProcess就会调用失败。

    如果希望新生成的进程控制作业,这个标志就比较有用。

  3. 方法二
    打开JOBOBJECT_BASIC_LIMIT_INFORMATION的LimitFlags成员的JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK标志。
    比标志也告诉系统新生成的子进程不应该是作业的一部分。
    这个就没有必要向CreateProcess传递任何额外的标志。
    事实上,此标志会强制新生成的进程脱离当前作业。

    如果进程对象在设立之初就对作业一无所知,这个标志就比较有用。

终止作业中的所有进程

  1. 杀死作业内部的所有进程
    效果类似为作业内的每一个进程调用TerminateProcess,将所有退出代码设为uExitCode。

查询作业统计信息

  1. 这个函数也可以获取作业的统计信息。向第二个参数传递JobObjectBasicAccountingInformation和一个JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构的地址。

  1. 对于那些未放在作业中的进程,可以使用下面这个函数来获取它的信息。

作业通知

  1. 作业中的进程如何尚未用完已分配的CPU时间,作业对象就处于未触发状态的。
    1. 通过调用WaitForSingleObject,可以捕获到这个事件。
    2. 还可以通过调用SetInformationJobObject把作业对象重置为未触发状态,授予作业更多的CPU时间。

额外的通知

  1. 要获得额外的通知,要必须创建一个I/O完成端口内核对象,并将我们的作业对象与完成端口关联起来。
  2. 然后,必须要有一个或多个线程等待作业通知到达完成端口,以便对它们进行处理。

  1. 执行上面代码后,系统将监视作业,只要有事件发生,就会把它们投递到I/O完成端口。
    QueryInformationJobObject函数也可以获取完成键和完成端口句柄,但很少有必要这么做。
  2. 线程通过调用GetQueuedCompletionStatue来监视完成端口。

  1. 当这个函数返回一个作业事件通知的时候,在pCompletionKey中,将包含完成键的值。这个值是在调用SetInformationJobObject将作业与完成端口关联时设置的。这样一来,我们就可以知道是哪个作业有事件发生。
  2. 而pNumBytesTransferred的值指出具体发生了什么事件。
    根据事件的不同,pOverlapped的值所表示的数据类型也不同。比如在作业事件通知中,它表示的是一个对应的进程ID(而不是地址)。
事件 描述
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO 作业中没有进程在运行时,就投递这个通知
JOB_OBJECT_MSG_END_OF_PROCESS_TIME 进程已分配的CPU时间到期时,就投递这个通知,进程将终止运行,给出进程ID
JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT 试图超过作业中的活动进程数时,就投递这个通知
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT 进程试图调拨的存储超过进程的限额时,就投递这个通知,并给出进程ID
JOB_OBJECT_MSG_NEW_PROCESS 一个进程添加到一个作业时,就投递这个通知,同时给出进程ID
JOB_OBJECT_MSG_EXIT_PROCESS 一个进程终止运行时,就头地址这个通知,并给出进程ID
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS 一个进程由于未处理的异常而终止运行时,就头地址这个通知,并给出进程ID
JOB_OBJECT_MSG_END_OF_JOB_TIME 作业分配的CPU时间到期时,就投递这个通知。但其中的进程不会自动终止。我们可以允许进程继续运行,可以设置一个新的时间限额,还可以自己调用TerminateJobObject。

作业对象的默认配置

  1. 在默认的情况下,作业对象是这样配置的:
    1. 当分配作业的CPU时间到期时,它的所有进程都会自动终止,而且不会投递JOB_OBJECT_MSG_END_OF_JOB_TIME通知。
  2. 如果向阻住作业对象“杀死”进程,只是简单的通知我们CPU时间到期,必须执行下面的代码。

  1. 针对EndOfJobTimeAction这个成员,唯一能指定的另一个值是JOB_OBJECT_TERMINATE_AT_END_OF_JOB,这是创建作业时的默认值。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2021-11-20
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享