windows核心编程4

 
第4章进程
本章介绍系统如何管理所有正在运行的应用程序。首先讲述什么是进程,以及系统如何创建进程内核对象,以便管理每个进程。然后将说明如何使用相关的内核对象来对进程进行操作。接着,要介绍进程的各种不同的属性,以及查询和修改这些属性所用的若干个函数。还要讲述创建或生成系统中的辅助进程所用的函数。当然,如果不深入说明如何来结束进程的运行,那么这样的介绍肯定是不完整的。
 
进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:
一个是操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计
信息的地方。
另一个是地址空间,它包含所有可执行模块或 D L L 模块的代码和数据。它还包含动态内
存分配的空间。如线程堆栈和堆分配空间。
进程是不活泼的。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,该线程负责执行包含在进程的地址空间中的代码。
 
4.1 编写第一个Wi n d o w s应用程序
Wi n d o w s支持两种类型的应用程序。一种是 基于图形用户界面( G U I)的应用程序,另一种是 基于控制台用户界面(C U I)的应用程序。
当使用Microsoft Visual C++来创建应用程序时,这种集成式环境安装了许多不同的链接程
序开关,这样,链接程序就可以将相应的子系统嵌入产生的可执行程序。用于C U I应用程序的链接程序开关是 / S U B S Y S T E M : C O N D O L E,而用于G U I 应用程序的链接程序开关是 S U B S Y S T E M : W I N D O W S
 
Wi n d o w s应用程序必须拥有一个在应用程序启动运行时调用的进入点函数。 操作系统实际上并不调用你编写的进入点函数。它调用的是 C / C + + 运行期启动函数。该函数负责对C / C + +运行期库进行初始化,这样,就可以调用m a l l o c和f r e e之类的函数。它还能够确保已经声明的任何全局对象和静态C + +对象能够在代码执行以前正确地创建。
 
表4-1 应用程序的进入点
应用程序类型                                                    进入点               嵌入可执行文件的启动函数
需要 A N S I 字符和字符串的 G U I 应用程序             Wi n M a i n    Wi n M a i n C RT S t a r t u p
需要 U n i c o d e 字符和字符串的 G U I 应用程序        w Wi n M a i n   w Wi n M a i n C RT S t a r t u p
需要 A N S I 字符和字符串的 C U I 应用程序              m a i n          m a i n C RT S t a r t u p
需要 U n i c o d e 字符和字符串的 C U I 应用程序           w m a i n       w m a i n C RT S t a r t u p
 
链接程序负责在它连接可执行文件时选择相应的C / C + +运行期启动函数。如果设定了
/ S U B S Y S T E M : W I N D O W S链接程序开关,那么该链接程序期望找到一个Wi n M a i n或w Wi n m a i n函数。如果这两个函数都不存在,链接程序便返回一个“未转换的外部符号”的错误消息。否则,它可以分别选择Wi n M a i n C RT S t a r t u p函数或w Wi n M a i n C RT S t a r t u p函数。
同样,如果设定了/ S U B S Y S T E M : C O N S O L E链接程序开关,那么该链接程序便期望找到m a i n或w m a i n函数,并且可以分别选择m a i n C RT S t a r t u p函数或w m a i n C RT S t a r t u p函数。同样,如果m a i n或w m a i n都不存在,那么链接程序返回一条“未转换外部符号”的消息。
但是,人们很少知道这样一个情况,即可以从应用程序中全部删除/ S U B S Y S T E M链接程序开关。当这样做的时候,链接程序能够自动确定应用程序应该连接到哪个子系统。当进行链接时,链接程序要查看代码中存在4个函数(Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)中的哪一个。然后确定可执行程序应该是哪一个子系统,并且确定可执行程序中应该嵌入哪个C / C + +启动函数。
 
所有的 C / C + + 运行期启动函数的作用基本上都是相同的, 将启动函数的功能归纳如下:
检索指向新进程的完整命令行的指针。
检索指向新进程的环境变量的指针。
C / C + + 运行期的全局变量进行初始化。如果包含了 S t d L i b . h 文件,代码就能访问这些变量。
C 运行期内存单元分配函数( m a l l o c c a l l o c )和其他低层输入 / 输出例程使用的内存栈进行初始化。
为所有全局和静态 C + + 类对象调用构造函数。
当所有这些初始化操作完成后, C / C + + 启动函数就调用应用程序的进入点函数。
 
当进入点函数返回时,启动函数便调用C运行期的e x i t函数,将返回值 n M a i n R e t Va l 传递给它。E x i t函数负责下面的操作:
• 调用由_ o n e x i t函数的调用而注册的任何函数。
• 为所有全局的和静态的C + +类对象调用析构函数。
• 调用操作系统的E x i t P r o c e s s函数,将n M a i n R e t Va l传递给它。这使得该操作系统能够撤消进程并设置它的e x i t代码。
 
4.1.1 进程的实例句柄
加载到进程地址空间的每个可执行文件或 D L L 文件均被赋予一个独一无二的实例句柄。可
执行文件的实例作为 ( w ) Wi n M a i n 的第一个参数 h i n s t E x e 来传递。对于加载资源的函数调用来说,通常都需要该句柄的值。
( w ) Wi n M a i n的h i n s t E x e参数的实际值是系统将可执行文件的映象加载到进程的地址空间时使用的基本地址空间。例如,如果系统打开了可执行文件并且将它的内容加载到地址0 x 0 0 4 0 0 0 0 0中,那么(w) Wi n M a i n的h i n s t E x e参数的值就是0 x 0 0 4 0 0 0 0 0。
可执行文件的映像加载到的基地址是由链接程序决定的。不同的链接程序可以使用不同的
默认基地址。Visual C++链接程序使用的默认基地址是0 x 0 0 4 0 0 0 0 0,因为这是在运行Wi n d o w s9 8时可执行文件的映象可以加载到的最低地址。可以改变应用程序加载到的基地址,方法是使用M i c r o s o f t的链接程序中的/ B A S E : a d d r e s s链接程序开关。
 
4.1.2 进程的前一个实例句柄
如前所述,C / C + +运行期启动代码总是将N U L L传递给( w ) Wi n M a i n的h i n s t E x e P r e v参数。该参数用在1 6位Wi n d o w s中,并且保留了( w ) Wi n M a i n的一个参数,目的仅仅是为了能够容易地转用1 6位Wi n d o w s应用程序。决不应该在代码中引用该参数。
 
4.1.3 进程的命令行
当一个新进程创建时,它要传递一个命令行。该命令行几乎永远不会是空的,至少用于创
建新进程的可执行文件的名字是命令行上的第一个标记。但是在后面介绍C r e a t e P r o c e s s函数时我们将会看到,进程能够接收由单个字符组成的命令行,即字符串结尾处的零。
4.1.4 进程的环境变量
每个进程都有一个与它相关的环境块。环境块是进程的地址空间中分配的一个内存块。
应用程序通常使用环境变量来使用户能够调整它的行为特性。环境变量存在的问题是,用户难以设置或理解这些变量。用户必须正确地拼写变量的名字,而且必须知道变量值期望的准确句法。另一方面,大多数图形应用程序允许用户使用对话框来调整应用程序的行为特性。这种方法对用户来说更加友好。
 
4.1.5 进程的亲缘性
一般来说,进程中的线程可以在主计算机中的任何一个C P U上执行。但是一个进程的线程
可能被强制在可用C P U的子集上运行。这称为进程的亲缘性。
 
4.1.6 进程的错误模式
与每个进程相关联的是一组标志,用于告诉系统,进程对严重的错误应该如何作出反映,
这包括磁盘介质故障、未处理的异常情况、文件查找失败和数据没有对齐等。进程可以告诉系统如何处理每一种错误。方法是调用S e t E r r o r M o d e函数。
 
4.1.7 进程的当前驱动器和目录
当不提供全路径名时,Wi n d o w s的各个函数就会在当前驱动器的当前目录中查找文件和目录。
 
4.1.8 进程的当前目录
系统将对进程的当前驱动器和目录保持跟踪,但是它不跟踪每个驱动器的当前目录。
 
4.1.9 系统版本
 
4.2 CreateProcess函数
当一个线程调用C r e a t e P r o c e s s时,系统就会创建一个进程内核对象,其初始使用计数是1。该进程内核对象不是进程本身,而是操作系统管理进程时使用的一个较小的数据结构。可以将进程内核对象视为由进程的统计信息组成的一个较小的数据结构。然后,系统为新进程创建一个虚拟地址空间,并将可执行文件或任何必要的D L L文件的代码和数据加载到该进程的地址空间中。
然后,系统为新进程的主线程创建一个线程内核对象(其使用计数为1)。与进程内核对象
一样,线程内核对象也是操作系统用来管理线程的小型数据结构。通过执行C / C + +运行期启动代码,该主线程便开始运行,它最终调用Wi n M a i n、w Wi n M a i n、m a i n或w m a i n函数。如果系统成功地创建了新进程和主线程,C r e a t e P r o c e s s便返回T R U E。
 
……(函数参数的解析略)…..
 
4.3 终止进程的运行
若要终止进程的运行,可以使用下面四种方法:
主线程的进入点函数返回(最好使用这个方法)。
进程中的一个线程调用 E x i t P r o c e s s 函数(应该避免使用这种方法)。
另一个进程中的线程调用 Te r m i n a t e P r o c e s s 函数(应该避免使用这种方法)。
进程中的所有线程自行终止运行(这种情况几乎从未发生)。
这一节将介绍所有这四种方法,并且说明进程结束时将会发生什么情况。
 
4.3.1 主线程的进入点函数返回
始终都应该这样来设计应用程序,即只有当主线程的进入点函数返回时,它的进程才终止
运行。这是保证所有线程资源能够得到正确清除的唯一办法。
让主线程的进入点函数返回,可以确保下列操作的实现:
• 该线程创建的任何C + +对象将能使用它们的析构函数正确地撤消。
• 操作系统将能正确地释放该线程的堆栈使用的内存。
• 系统将进程的退出代码(在进程的内核对象中维护)设置为进入点函数的返回值。
• 系统将进程内核对象的返回值递减1。
4.3.2 ExitProcess函数
当主线程的进入点函数( Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)返回时,它将返回给C / C + +运行期启动代码,它能正确地清除该进程使用的所有的C运行期资源。当C运行期资源被释放之后,C运行期启动代码就显式调用E x i t P r o c e s s,并将进入点函数返回的值传递给它。这解释了为什么只需要主线程的进入点函数返回,就能够终止整个进程的运行。请注意,进程中运行的任何其他线程都随着进程而一道终止运行。
注意显式调用E x i t P r o c e s s和E x i t T h r e a d是导致应用程序不能正确地将自己清除的常见原因。在调用E x i t T h r e a d时,进程将继续运行,但是可能会泄漏内存或其他资源。
4.3.3 Te r m i n a t e P r o c e s s函数
该函数与E x i t P r o c e s s有一个很大的差别,那就是任何线程都可以调用Te r m i n a t e P r o c e s s来终止另一个进程或它自己的进程的运行。只有当无法用另一种方法来迫使进程退出时,才应该使用Te r m i n a t e P r o c e s s。
4.3.4 进程终止运行时出现的情况
当进程终止运行时,下列操作将启动运行:
1) 进程中剩余的所有线程全部终止运行。
2) 进程指定的所有用户对象和G D I对象均被释放,所有内核对象均被关闭(如果没有其他进程打开它们的句柄,那么这些内核对象将被撤消。但是,如果其他进程打开了它们的句柄,内核对象将不会撤消)。
3) 进程的退出代码将从S T I L L _ A C T I V E改为传递给E x i t P r o c e s s或Te r m i n a t e P r o c e s s的代码。
4) 进程内核对象的状态变成收到通知的状态(关于传送通知的详细说明,参见第9章)。系
统中的其他线程可以挂起,直到进程终止运行。
5) 进程内核对象的使用计数递减1。
注意,进程的内核对象的寿命至少可以达到进程本身那么长,但是进程内核对象的寿命可
能大大超过它的进程寿命。当进程终止运行时,系统能够自动确定它的内核对象的使用计数。
如果使用计数降为0,那么没有其他进程拥有该对象打开的句柄,当进程被撤消时,对象也被撤消。
再一次提醒你,应该通过调用C l o s e H a n d l e函数,告诉系统你对进程的统计数据已经不再感兴趣。如果进程已经终止运行,C l o s e H a n d l e将递减内核对象的使用计数,并将它释放。
 
4.4 子进程
 
4.5 枚举系统中运行的进程
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值