Windows核心编程 第五章 作业(上)
第5章 作 业
通常,必须将一组进程当作单个实体来处理。例如,当让 Microsoft Developer Studio为你创建一个应用程序项目时,它会生成
C l . e x e,C l . e x e则必须生成其他的进程(比如编译器的各个函数传递) 。如果用户想要永远停止该应用程序的创建,那么
Developer Studio必须能够终止C l . e x e和它的所有子进程的运行。在
Wi n d o w s中解决这个简单(和常见的)的问题是极其困难的,因为Wi n d o w s并不维护进程之间的父/子关系。即使父进程已经终止运行,子进程仍然会继续运行。
当设计一个服务器时,也必须将一组进程作为单个进程组来处理。例如,客户机可能要求服务器执行一个应用程序(这可以生成它自己的子应用程序)
,并给客户机返回其结果。由于可能有许多客户机与该服务器相连接,如果服务器能够限制客户机的要求,即用什么手段来防止任何一个客户机垄断它的所有资源,那么这是非常有用的。这些限制包括:可以分配给客户机请求的最大C P U时间,最小和最大的工作区的大小,防止客户机的应用程序关闭计算机,以及安全性限制等。
Microsoft Windoss 2000提供了一个新的作业内核对象,使你能够将进程组合在一起,并且创建一个“沙框” ,以便限制进程能够进行的操作。最好将作业对象视为一个进程的容器。但是,创建包含单个进程的作业是有用的,因为这样一来,就可以对该进程加上通常情况下不能加的限制。
下面的S t a r t R e s t r i c t e d P r o c e s s函数,将一个进程放入一个作业,以限制该进程进行某些操作的能力。
Windows 98 Windows 98不支持作业的操作。
TIP:
先说一下运行上面的代码的时候我遇到的问题,上面的代码是书上的代码(略微改动),我照着写了下,比较简单很好理解,但是遇到了几个问题:
1.CreateProcess这个函数,如果你在vs2012上直接按照书上那么写直接编译不过去了因为vs2012上通常CreateProcess是CreateProcessW(现在几乎都是默认宽字节了),如果是CreateProcessW那么第二个cmdline参数就不能直接写常量,而CreateProcessA可以写,原因是*A最后会转换成*W函数去执行,而这个转换过程会定义一个变量,这样即使我们传递错的参数(常量),在*A上也不会崩溃,而在*W上会,又因为默认是*W所以书上的代码就直接崩溃了,我上面的那个代码是改过的了。
虽然看上去*A的也不能用常量,但是用常量是可以的(上面解释了)
2.CreateProcessA这个函数我调用notepad的时候发现了一个奇怪的现象,就是Getlasterror显示的是(),调用cmd也是,但是返回值是teue并且我们的东西可以正常被启动,但是直接调用QQ就没事了。
3.第三个问题是,按照书上的代码,AssignProcessToJobObject这个函数执行会失败,返回的结果是拒绝访问,期初我第一反应就是可能需要管理员权限,于是就提高了自己程序的权限:
然并卵,然后就是找了好久,最后找到的解决方案,是在CreateProcess函数里面增加一个参数
这个参数的MSDN是这么描述的:
在实际调试的时候我发现,如果不加这个参数,首先在把进程放到作业里的时候会失败并且显示权限不足,同时在放失败之后我们CreateProcess创建的进程还是有的(此时主线程没有起来,因为创建的时候挂起了主线程),然后后续在激活主线程,这个创建的程序就跑起来了,但是如果增加了这个参数的话,如果我们因为某种原因,在把进程加到作业里失败之后,这个进程将会被关闭掉。之后再激活主线程也就没意义了。为了模拟出来这种场景,我是直接在创建作业的时候把作业的最大进程数写成0,这样第一个进程就会加入失败。
然后继续。。。解释一下S t a r t R e s t r i c t e d P r o c e s s函数是如何工作的。首先,调用下面的代码,创建一个新作业内核对象:
与所有的内核对象一样,它的第一个参数将安全信息与新作业对象关联起来,并且告诉系统,是否想要使返回的句柄成为可继承的句柄。最后一个参数用于给作业对象命名,使它可以供另一个进程通过下面所示的O p e n J o b O b j e c t函数进行访问。
与平常一样,如果知道你将不再需要访问代码中的作业对象,那么就必须通过调用C l o s e H a n d l e来关闭它的句柄。可以在
S t a r t R e s t r i c t e d P r o c e s s函数的结尾处看到这个代码的情况。应该知道,关闭作业对象并不会迫使作业中的所有进程终止运行。该作业对象实际上做上了删除标记,只有当作业中的所有进程全部终止运行之后,该作业对象才被自动撤消。
注意,关闭作业的句柄后,尽管该作业仍然存在,但是该作业将无法被所有进程访问。请看下面的代码:
5.1 对作业进程的限制
进程创建后,通常需要设置一个沙框(设置一些限制)
,以便限制作业中的进程能够进行的操作。可以给一个作业加上若干不同类型的限制:
• 基本限制和扩展基本限制,用于防止作业中的进程垄断系统的资源。
• 基本的U I限制,用于防止作业中的进程改变用户界面。
• 安全性限制,用于防止作业中的进程访问保密资源(文件、注册表子关键字等) 。
通过调用下面的代码,可以给作业加上各种限制:
第一个参数用于标识要限制的作业。第二个参数是个枚举类型,用于指明要使用的限制类型。第三个参数是包含限制设置值的数据结构的地址,第四个参数用于指明该结构的大小(用于确定版本)
。表5 - 1列出了如何来设置各种限制条件。
在S t a r t R e s t r i c t e d P r o c e s s函数中,我只对作业设置了一些最基本的限制。指定了一个J O B _ O B J E C T _ B A S I C _ L I M I T _
I N F O R M AT I O N结构,对它进行了初始化,然后调用S e t I n f o r m a t i o n J o b O b j e c t函数。J O B _ O B J E C
T _ B A S I C _ L I M I T _ I N F O R M AT I O N结构类似下面的样子:
表5 - 2简单地描述了它的各个成员的情况。
关于这个结构的某些问题在 Platform SDK文档中并没有说清楚,因此在这里作一些说明。你在
L i m i t F l a g s成员中设置了一些信息,来指明想用于作业的限制条件。我设置了J O B _ O B J E C T _ L I M I T _ P R I O R I T Y _ C L A S S和J
O B _ O B J E C T _ L I M I T _ J O B _ T I M E这两个标志。这意味着它们是我用于该作业的唯一的两个限制条件。我没有对C P U的亲缘关系、工作区的大小、每个进程占用的C
P U时间等作出限制。当作业运行时,它会维护一些统计信息,比如作业中的进程已经使用了多少
C P U时间。每次使用J O B _ O B J E C T _ L I M I T _ J O B _ T I M E标志来设置基本限制时,作业就会减去已经终止运行的进程的C
P U时间的统计信息。这显示当前活动的进程使用了多少 C P U时间。如果想改变作业运行所在的C P U的亲缘关系,但是没有重置C
P U时间的统计信息,那将要如何处理呢?为了处理这种情况,必须使用JOB_OBJECT_LIMIT_AFFINITY
标志来设置新的基本限制条件,并且必须退出J O B _ O B J E C T _ L I M I T _ J O B _ T I M E标志的设置。这样一来,
就告诉作业,
不再想要使用C P U的时间限制。这不是你想要的。
你想要的是改变C P U亲缘关系的限制,保留现有的C P U时间限制。你只是不想减去已终止运行的进程的
C P U时间的统计信息。为了解决这个问题,可以使用一个特殊标志,即J O B _ O B J E C T _ L I M I T _ P R E S E RV E _ J O B _ T I M E。这个标志与J
O B _ O B J E C T _ L I M I T _ J O B _ T I M E标志是互斥的。J O B _ O B J E C T _ L I M I T _ P R E S E RV E _ J O B _ T I M E标志表示你想改变限制条件,而
不减去已经终止运行的进程的C P U时间的统计信息。
现在介绍一下J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N结构的S c h e d u l i n g C l a s s成员。假如你有两个正在运行的作业,你将两个作业的优先级类都设置为
N O R M A L _ P R I O R I T Y _C L A S S。但是你还想让一个作业中的进程获得比另一个进程多的
C P U时间。可以使用S c h e d u l i n g C l a s s成员来改变拥有相同优先级的作业的相对调度关系。可以设置一个
0至9之间的值(包括0和9)
,5是默认值。在Windows 2000上,如果这个设置值比较大,那么系统就会给某个作业的进程中的线程提供较长的C P U时间量。如果设置的值比较小,就减少该线程的
C P U时间量。
例如,我有两个拥有正常优先级类的作业。每个作业包含一个进程,每个进程只有一个(拥有正常优先级的)线程。在正常环境下,这两个线程将按循环方式进行调度,每个线程获得相同的C P U时间量。但是,如果将第一个作业的
S c h e d u l i n g C l a s s成员设置为3,那么,当该作业中的线程被安排C P U时间时,它得到的时间量将比第二个作业中的线程少。
如果使用S c h e d u l i n g C l a s s成员,应该避免使用大数字即较大的时间量,因为较大的时间量会降低系统中的其他作业、进程和线程的总体响应能力。另外,我只是介绍了
Windows 2000中的情况。M i c r o s o f t计划在将来的Wi n d o w s版本中对线程调度程序进行更重要的修改,因为它认为操作系统应该为作业、进程和线程提供更宽松的线程调度环境。
需要特别注意的最后一个限制是 J O B _ O B J E C T _ L I M I T _ D I E _ O N _ U N H A N D L E D _E X C E P T I O N限制标志。这个限制可使系统为与作业相关的每个进程关闭“未处理的异常情况”对话框。系统通过调用S
e t E r r o r M o d e函数,将作业中的每个进程的S E M _ N O G P FA U LT E R R O R B O X标志传递给它。作业中产生未处理的异常情况的进程会立即终止运行,不显示任何用户界面。对
于服务程序和其他面向批处理的作业来说,这是个非常有用的限制标志。如果没有这个标志,作业中的进程就会产生一个异常情况,并且永远不会终止运行,从而浪费了系统资源。
除了基本限制外,还可以使用J O B O B J E C T _ E X T E N D E D _ L I M I T _ I N F O R M AT I O N结构对作业设置扩展限制:
如你所见,该结构包含一个J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N结构,它构成了基本限制的一个超集。这个结构有一点儿特殊,因为它包含的成员与设置作业的限制毫无关系。首先,I
o I n f o成员保留不用,无论如何不能访问它。本章后面将要介绍如何查询I / O计数器信息。此外,P a c k P r o c e s s M e m o r y U s e d和P
a c k J o b M e m o r y U s e d成员是只读成员,分别告诉你作业中的任何一个进程和所有进程需要使用的已确认的内存最大值。
另外两个成员P r o c e s s M e m o r y L i m i t和J o b M e m o r y L i m i t分别用于限制作业中的任何一个进程和所有进程使用的已确认的内存量。若要设置这些限制值,可以在
L i m i t F l a g s成员中分别设定J O B _ O B J E C T _ L I M I T _ J O B _ M E M O RY和J
O B _ O B J E C T _ L I M I T _ P R O C E S S _ M E M O RY两个标志。
现在看一下可以对作业设置的另一些限制。下面是J O B O B J E C T _ B A S I C _ U I _ R E S T R I C T I O NS结构的样子:
这个结构只有一个数据成员,即 U I R e s t r i c t i o n s C l a s s,它用于存放表5 - 3中简单描述的一组位标志。
最后一个标志J O B _ O B J E C T _ U I L I M I T _ H A N D L E S是特别有趣的。这个限制意味着作业中没有一个进程能够访问该作业外部的进程创建的
U S E R对象。因此,如果试图在作业内部运行Microsoft Spy++,那么除了S p y + +自己创建的窗口外,你看不到任何别的窗口。图
5 - 1显示的S p y + +中打开了两个M D I子窗口。注意,Threads
1的窗口包含一个系统中的线程列表。这些线程中只有一个线程,即000006AC SPYXX似乎创建了一些窗口。这是因为我是在它自己的作业中运行S p y + +的,并且限制了它对
U I句柄的使用。在同一个窗口中,可以看到
M S D E V和E X P L O R E R两个线程,但是看来它们尚未创建任何窗口。可以保证,这些线程肯定创建了窗口,但是S
p y + +无法访问它们。在对话框的右边,可以看到 Windows 3窗口,在这个窗口中,S p y + +显示了桌面上存在的所有窗口的层次结构。注意,它只有一个项目,即
0 0 0 0 0 0 0 0。S p y + +必须将它作为占位符放在这里。
注意,这个U I限制是单向的。这就是说,作业外部的进程能够看到作业内部的进程创建的U S D R对象。例如,如果我在一个作业中运行
N o t e p a d,并在作业的外部运行S p y + +,那么,如果N o t e p a d所在的作业设定了
J O B _ O B J E C T _ U I L I M I T _ H A N D L E S标志,S p y + +将能够看到N
o t e p a d的窗口。同样,如果S p y + +在它自己的作业中,那么它也可以看到
N o t e p a d的窗口,只要它设定了J O B _ O B J E C T _ U I L I M I T _ H A N D L E S标志。
如果想为作业进程的操作创建真正的沙框,那么限制 U I句柄是可怕的。但是,如果作为作业组成部分的一个进程要与作业外部的进程进行通信,就可以使用这种限制。
实现这个目的有一个简便的方法,那就是使用窗口消息,但是,如果作业的进程不能访问U I句柄,那么作业中的进程就无法将窗口消息发送或显示在作业外部的进程创建的窗口中。不过,可以使用下面这个新函数来解决这个问题:
h U s e r O b j参数用于指明一个U S E R对象,可以为作业中的进程赋予或者撤消对该对象的访问权。它几乎总是一个窗口句柄,但是它可以是另一个
U S E R对象,比如桌面、挂钩、图标或菜单。最后两个参数h j o b和f G r a n t用于指明你赋予或撤消对哪个作业的访问权。注意,如果从h
j o b标识的作业中的一个进程来调用该函数,该函数的运行就会失败 — 这可以防止作业中的进程总是为它自己赋予访问一个对象的权限。
对作业施加的最后一种限制类型与安全性相关(注意,一旦使用这种限制,就无法取消安全性限制)
。J O B O B J E C T _ S E C U R I T Y _ L I M I T _ I N F O R M AT I O N的结构类似下面的形式:
表5 - 4简单地描述了它的各个成员。
你为该函数传递作业的句柄(就像你对 S e t I n f o r m a t i o n J o b O b j e c t操作时那样) ,这些句柄包括用于指明你想要的限制信息的枚举类型,函数要进行初始化的数据结构的地址,以及包含该结构的数据块的长度。最后一个参数是
p d w R e t u r n L e n g t h,用于指向该函数填写的
D W O R D,它告诉你有多少字节放入了缓存。如果你愿意的话,可以(并且通常)为该参数传递
N U L L。
注意
作业中的进程可以调用Q u e r y I n f o r m a t i o n J o b O b j e c t,以便通过为作业的句柄参数传递N U L L,获取关于该进程所属的作业的信息。由于它使进程能够看到已经对它实施了哪些限制,所以这个函数非常有用。但是,如果为作业句柄参数传递
N U L L,那么SetInformationJobObject函数运行就会失败,因为这将允许进程删除对它实施的限制。
5.2 将进程放入作业
上面介绍的是设置和查询限制方面的信息。现在回到S t a r t R e s t r i c t e d P r o c e s s这个函数的操作上来。当对作业实施一些限制之后,通过调用
C r e a t e P r o c e s s,生成了一个进程,我想将它放入作业。但是,注意,当调用C r e a t e P r o c e s s时,我使用了C
R E AT E _ S U S P E N D E D标志。这样,创建了一个新进程,但是不允许它执行任何代码。由于
S t a r t - R e a t r i c t e d P r o c e s s函数是从不属于作业组成部分的进程来执行的,因此子进程也不属于作业的组成部分。如果准备立即允许子进程开始执行代码,那么它将跑出我的沙框,并且能够成功地执行我想限制它做的工作。因此,当创建子进程之后,在我允许它开始运行之前,我必须显式地将该进程放入我新创建的作业,方法是调用下面的代码:
该函数告诉系统,将该进程(由 h P r o c e s s标识)视为现有作业(由
h J o b标识)的一部分。注意,该函数只允许将尚未被赋予任何作业的进程赋予一个作业。一旦进程成为一个作业的组成部分,它就不能转到另一个作业,并且不能是无作业的进程。另外,当作为作业的一部分的进程生成另一个进程的时候,新进程将自动成为父作业的组成部分。不过可以用下面的方法改变它的行为特性:
• 打开J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N的L i m i t F l a g s成员中的J
O B _ O B J E C T _B R E A K AWAY _ O K标志,告诉系统,新生成的进程可以在作业外部运行。若要做到这一点,必须用新的C R E AT E _ B R E A K AWAY _ F R O M _ J O B标志来调用C
r e a t e P r o c e s s。如果用C R E AT E _ B R E A K AWAY _ F R O M _ J O B标志调用C r e a t e P r o c e s s函数,但是该作业并没有打开C
R E AT E _ B R E A K AWAY _ F R O M _ J O B这个标志,那么C r e a t e P r o c e s s函数运行就会失败。
如果新生成的进程也能控制作业,那么这个机制是有用的。
• 打开J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N的L i m i t F l a g s成员中的J
O B _ O B J E C T _S I L E N T _ B R E A K AWAY _ O K标志。该标志也告诉系统,新生成的进程不应该是作业的组成部分。但是没有必要将任何其他标志传递给
C r e a t e P r o c e s s。实际上,该标志将使新进程不能成为作业的组成部分。该标志可以用于原先对作业对象一无所知的进程。
至于S t a r t R e s t r i c t e d P r o c e s s函数,当调用A s s i g n P r o c e s s To J o b O b j e c t后,新进程就成为受限制的作业的组成部分。然后调用R
e s u m e T h r e a d,这样,进程的线程就可以在作业的限制下执行代码。这时,也可以关闭线程的句柄,因为不再需要它了。
5.3 终止作业中所有进程的运行
当然,想对作业进行的最经常的操作是撤消作业中的所有进程。本章开头讲过, D e v e l o p e rS t u d i o没有配备任何便于使用的方法,来停止进程中的某个操作,因为它不知道哪个进程是由第一个进程生成的(这非常复杂。我在
Microsoft Systems Journal期刊1 9 9 8年9月号上Win32
问与答栏中介绍了Developer Studio是如何做到这一点的) 。我认为,Developer Studio的将来版本
将会改用作业来进行操作,因为代码的编写要容易得多,可以用它做更多的工作。
若要撤消作业中的进程,只需要调用下面的代码:
这类似为作业中的每个进程调用 Te r m i n a t e P r o c e s s函数,将它们的所有退出代码设置为u E x i t C o d e。
Windows核心编程 第五章 作业(上)的更多相关文章
- Windows核心编程 第五章 作业(下)
5.4 查询作业统计信息 前面已经介绍了如何使用 Q u e r y I n f o r m a t i o n J o b O b j e c t函数来获取对作业的当前限制信息.也可以使用它来获取关 ...
- windows核心编程---第五章 线程的基础
与前面介绍的进程一样,线程也有两部分组成.一个是线程内核对象.它是一个数据结构,操作系统用它来管理线程以及用它来存储线程的一些统计信息.另一个是线程栈,用于维护线程执行时所需的所有函数参数和局部变量. ...
- windows核心编程 第5章job lab示例程序 解决小技巧
看到windows核心编程 第5章的最后一节,发现job lab例子程序不能在我的系统(win8下)正常运行,总是提示“进程在一个作业里” 用process explorer程序查看 ...
- windows核心编程 第8章201页旋转锁的代码在新版Visual Studio运行问题
// 全局变量,用于指示共享的资源是否在使用 BOOL g_fResourceInUse = FALSE; void Func1() { //等待访问资源 while(InterlockedExcha ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(上)
第7章 线程的调度.优先级和亲缘性 抢占式操作系统必须使用某种算法来确定哪些线程应该在何时调度和运行多长时间.本章将要介绍Microsoft Windows 98和Windows 2000使用的一些算 ...
- windows核心编程---第六章 线程的调度
每个线程都有一个CONTEXT结构,保存在线程内核对象中.大约每隔20ms windows就会查看所有当前存在的线程内核对象.并在可调度的线程内核对象中选择一个,将其保存在CONTEXT结构的值载入c ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- Windows核心编程 第十七章 -内存映射文件(上)
第1 7章 内存映射文件 对文件进行操作几乎是所有应用程序都必须进行的,并且这常常是人们争论的一个问题.应用程序究竟是应该打开文件,读取文件并关闭文件,还是打开文件,然后使用一种缓冲算法,从文件的各个 ...
- Windows核心编程 第六章 线程基础知识 (上)
第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...
随机推荐
- cve-2019-2725 反序列化远程代码执行
描述:部分版本WebLogic中默认包含的wls9_async_response包,为WebLogic Server提供异步通讯服务.由于该WAR包在反序列化处理输入信息时存在缺陷,攻击者可以发送精心 ...
- Percona XtraDB Cluster之流量控制
什么是流量控制? Percona XtraDB Cluster具有一种称为流控制的自调节机制.该机制有助于避免集群中最弱/最慢的成员明显落后于集群中其他成员的情况. 当集群成员在写数据很慢(同时又继续 ...
- Azure Front Door(三)启用 Web Application Firewall (WAF) 保护Web 应用程序,拒绝恶意攻击
一,引言 上一篇我们利用 Azure Front Door 为后端 VM 部署提供流量的负载均衡.因为是演示实例,也没有实际的后端实例代码,只有一个 "Index.html" 的静 ...
- linux程序开机自动启动
linux如果需要实现开机启动, 可以找到 $HOME/.config/autostart 目录(没有的话新建一个),在该文件夹下创建一个空文件,文件名自拟,后缀必须是desktop,如:dingda ...
- Shell 正则表达式详解
Shell 正则表达式 什么是正则表达式? 正则表达式在每种语言中都会有,功能就是匹配符合你预期要求的字符串. 为什么要学正则表达式? 在企业工作中,我们每天做的linux运维工作中,时刻都会面对大量 ...
- Swagger3注解使用
这里只简单的说一下swagger的传值,返回值时注解的使用演示.相关注解及说明见其他文章 接收参数方式1 我们常规接收参数,可以使用一个类,类里面把属性作为接收的参数,使用注解对属性进行说明.但是这种 ...
- java面试-对象的创建、内存布局、访问定位
一.对象的创建 1.虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应的 ...
- 201871030102_崔红梅 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 1.体验软件项目开发中的两人合作,练习结对编程2. 掌握Github协作开发程序的操作方法.3.阅读<现代软 ...
- 关于MySQL日志,我与阿里P9都聊了些啥?
写在前面 周末,我与阿里P9资深技术专家(这里就不说名字了),聊起了MySQL这个话题,为啥会聊这个呢?因为他看到我出版了一部<MySQL技术大全:开发.优化与运维实战>,对书籍的评价也是 ...
- UnitFourSummary
目录 第四单元架构设计 taskOne taskTwo taskThree 四个单元架构设计与OO方法理解的演进 四个单元架构设计 UnitOne UnitTwo UnitThree UnitFour ...