Windows程序设计:进程

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,在Windows编程环境下,主要由两大元素组成:

• 一个是操作系统用来管理进程的内核对象。操作系统使用内核对象来存放关于进程的核心信息。

• 另一个是地址空间,在地址空间囊括了所有可执行模块和动态链接库的代码和数据。动态内存分配的空间也在其中,典型代表是线程堆栈和堆内存分配。

1进程与线程

进程是不活泼的。当进程开始工作的时候,它必须启动一个在当前进程上下文中的线程来执行工作流程。这个线程被称为主线程,它负责执行包含在进程的地址空间中的代码。同时,每一个进程可能包含多个线程,所有线程都在并发执行进程地址空间中的代码。这就意味着,每个线程都有它自己的一组CPU 寄存器和它自己的堆栈。每个进程至少拥有一个线程,来执行当前进程的地址空间中的代码。如果没有线程来执行进程的地址空间中的代码,这就意味着进程的生命周期已经完结,系统就会将已经分配给该进程和它的地址空间全部撤销收回。

如果,我们希望当前进程中的所有线程都能并发运行,那么,操作系统就要为每一个线程分配相应的CPU时间。它通过轮询方式为线程提供时间片(称为量程),给用户造成一种假象——似乎所有线程都是同时运行的一样。如果计算机拥有多个CPU(例如Core2多核处理器),那么操作系统就要使用更加复杂的算法来实现CPU 上线程的负载均衡,以保证多线程程序可以有效的响应客户需求。

综上所述,当创建一个进程时,系统同时会自动创建当前进程的第一个线程。这个线程称为主线程。然后,该线程可以创建其他的线程,而这些线程又能创建更多的线程。

2使用CreateProcess创建进程 

Windows操作系统为用户提供了C r e a t e P r o c e s s 函数用于进程的创建,该函数的签名如下:

BOOL CreateProcess(

PCTSTR pszApplicationName,

PTSTR pszCommandLine,

PSECURITY_ATTRIBUTES psaProcess,

PSECURITY_ATTRIBUTES psaThread,

BOOL bInheritHandles,

DWORD fdwCreate,

PVOID pvEnvironment,

PCTSTR pszCurDir,

PSTARTUPINFO psiStartInfo,

PPROCESS_INFORMATION ppiProcInfo);

当程序员调用CreateProcess创建进程时,系统就会创建一个进程内核对象,并将其初始使用计数置为1。这个内核对象不是进程本身,而是操作系统管理进程时使用的一个的数据结构。我们可以把它看做是进程的全部统计信息的汇总。内核对象创建完成之后,系统会为新进程创建一个虚拟地址空间,并将可执行文件或当前进程所需要的DLL文件的代码和数据加载到该进程的地址空间中。

接下来,系统为新进程的主线程创建一个线程内核对象(其使用计数为1 )。注意,这是线程的内核对象,它与进程内核对象类似,是操作系统用来管理线程的数据结构。通过执行C/C++运行时启动代码,该主线程便开始运行,它最终调用Wi n M a i n  或 m a i n 函数。如果上述的两个步骤顺利完成,C r e a t e P r o c e s s函数将返回T R U E ,宣告新进程创建成功。

值得我们注意的是:在进程初始化完成之前,CreateProcess函数就会返回TRUE。试想一下:当操作系统加载程序试图找出所有需要的DLL,如果一个DLL无法找到,或者未能正确地初始化,那么该进程就终止运行。由于CreateProcess已经向用户程序返回T R U E ,因此父进程不知道出现的任何初始化问题。这在实际编程中需要十分小心。CreateProcess函数仅仅意味着操作系统对于新建进程的管理对象已经创建成功,它不能保证新创建的进程可以正常运行(正常运行取决于进程本身的代码是否满足运行条件,与创建者无关)。

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主线程的入口点函数返回

在程序中,我们应当力求只有当主线程的入口点函数返回时,它的进程才终止运行。这是保证操作系统分配给线程的所有资源能够得到正确清除的唯一办法。让主线程的入口点函数返回,可以确保下所有的资源都得到恰当的处理:

• 该线程创建的任何C++对象的析构函数将被调用,分配的资源被正确撤消。

• 操作系统将能正确地释放该线程的堆栈内存。

• 系统将进程的退出代码(在进程的内核对象中维护)设置为入口点函数的返回值。

• 系统将进程内核对象的返回值计数递减1。

5慎用ExitProcess函数C++是运行在C语言之上的

当进程中的某个线程调用E x i t P r o c e s s函数时,进程便终止运行:

VOID ExitProcess(UINT fuExitCode);

该函数用于终止进程的运行,并将进程的退出代码设置为参数f u E x i t C o d e的值。E x i t P r o c e s s函数并不返回任何值,这是因为进程已经被终止运行。如果在调用E x i t P r o c e s s之后又增加了什么代码,那么添加代码将不可能获得执行的机会。

当主线程的入口点函数( WinMain、wWinMain、main或wmain)返回时,它将返回给C/C++运行时启动代码,它能正确地清除该进程使用的所有的C运行时资源。当C运行时资源被释放之后,C运行时启动代码就显式调用E x i t P r o c e s s函数,并将入口点函数返回的值传递给它。因此,我们只需要在主线程入口点返回,就能终止当前进程。请注意,进程中运行的任何其他线程都随着进程的终止而终止。

Windows的SDK编程手册指出,进程要等到所有线程终止运行之后才终止运行。就操作系统而言,这是正确的。但是, C/C++运行时对应用程序采用了不同的规则,通过调用E x i t P r o c e s s,使得C/C++运行时启动代码能够确保主线程从它的入口点函数返回时,进程便终止运行,而不管进程中是否还有其他线程在运行。不过,如果在入口点函数中调用E x i t T h r e a d,而不是调用E x t i P r o c e s s或者仅仅是返回,那么应用程序的主线程将停止运行。此时,不难发现,ExitProcess仅仅退出了当前线程,而非当前进程。如果当前进程中至少有一个线程还在运行,该进程将不会终止运行。

在调用E x i t P r o c e s s或E x i t T h r e a d可使进程或线程在函数中就终止运行。就操作系统而言,这没有问题。进程或线程的所有操作系统资源都将被全部清除。但是, C/C++应用程序应该避免调用这些函数,因为C/C++运行时可能无法被全部清除,例如:

#include <windows.h>

#include <stdio.h>

class CMyObject

{

public:

CMyObject()

{

printf("Constructor\r\n");

}

~CMyObject()

{

printf("Destructor\r\n");

}

};

CMyObject g_GlobalObj;

void main()

{

CMyObject LocalObj;

ExitProcess(0);

}

接下来,我们转到对应的文件目录下面,在项目上右键弹出菜单,选中在文件资源管理器中打开文件夹,

获得文件的目录,

然后打开命令行工具,

使用cd 命令,切换到debug文件夹,运行exe文件,

我们将会看到:

Constructor

Constructor

这个应用创建了两个对象,一个是全局对象,另一个是局部对象。不过我们一定不会看到Destructor这个单词出现, C++对象没有被正确地撤消,因为E x i t P r o c e s s函数强制当前进程终止运行,C/C++运行时没有机会进行调用析构函数释放资源。

这告诉我们,当调用E x i t P r o c e s s函数必须慎重!如果在上面的代码中删除了对E x i t P r o c e s s(0)这行代码,那么再次运行程序,我们可以得到如下结果:

Constructor

Constructor

Destructor

Destructor

只要让主线程的入口点函数返回, C/C++运行时就能够执行清理工作,并且正确地撤消任何或所有的C++对象。

显式调用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时,进程可能将继续运行,但是可能会泄漏内存或其他资源。

6进程终止后操作系统的工作

当进程被终止时,操作系统将完成下列工作:

1) 进程中剩余的所有线程将全部被终止运行。

2) 进程指定的所有用户对象和GDI对象均被释放,所有内核对象均被关闭(如果没有其他进程关联到这些句柄,那么这些内核对象将被撤消。但是,如果存在其他进程关联到这些句柄, 内核对象将不会撤消)。

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) 进程内核对象的状态变成受信的状态。进程中的其他线程被挂起,直到进程终止运行。

5) 进程内核对象的使用计数递减1。

注意,进程的内核对象的寿命一定不会低于进程本身的寿命,进程内核对象的寿命却有可能大大超过它的进程寿命。当进程终止运行时,系统能够自动确定它的内核对象的使用计数。如果使用计数降为0,那么没有其他进程关联到该对象,当进程被撤消时,内核对象也被撤消。

不过,如果系统中的另一个进程关联到正在被撤消的进程的内核对象的时候,那么该进程内核对象的使用计数不会降为0。当父进程忘记关闭子进程的句柄时,就会造成这种情况。进程内核对象维护了进程的统计信息。即使进程已经终止运行,该信息也是有用的。例如,当我们想要知道进程需要多少C P U时间,或者,通过调用G e t E x i t C o d e P r o c e s s来获得目前已经撤消的进程的退出代码:

BOOL GetExitCodeProcess(HANDLE hProcess,

PDWORD pdwExitCode);

该函数就是通过查看进程的内核对象(由h P r o c e s s参数来标识),取出内核对象的数据结构中用于标识进程的退出代码的成员。该退出代码的值在p d w E x i t C o d e参数指向的D W O R D中返回。

当调用G e t E x i t C o d e P r o c e s s函数时,如果进程运行尚未终止,那么该函数就用S T I L L _ A C T I V E标识符(定义为0 x 1 0 3)填入D W O R D。如果进程已经终止运行,便返回数据的退出代码值。

因此,为了保证内核对象被正确关闭,应该及时调用C l o s e H a n d l e函数,告诉系统你对进程的统计数据已经不再感兴趣,以便操作系统及时回收内核对象。如果进程已经终止运行,C l o s e H a n d l e将递减内核对象的使用计数,并将它释放。

相关视频可以观看

http://laoxiaketang.com/lesson_info.php?id=14

windows编程 进程的创建销毁和分析的更多相关文章

  1. MFC进程的创建销毁、线程的创建与交互

    进程的创建 STARTUPINFO si; //**成员DWORD dwFlags;表示结构体当中哪些成员有效.**STARTF_USESHOWWINDOW|STARTF_USEPOSITION PR ...

  2. Windows进程创建的流程分析

    .   创建进程的大体流程:   创建进程的过程就是构建一个环境,这个环境包含了很多的机制 (比如自我保护, 与外界通信等等). 构建这个环境需要两种"人"来协调完成(用户态和内核 ...

  3. windows进程/线程创建过程 --- windows操作系统学习

    有了之前的对进程和线程对象的学习的铺垫后,我们现在可以开始学习windows下的进程创建过程了,我将尝试着从源代码的层次来分析在windows下创建一个进程都要涉及到哪些步骤,都要涉及到哪些数据结构. ...

  4. 【Windows编程】系列第六篇:创建Toolbar与Statusbar

    上一篇我们学习了解了如何使用Windows GDI画图,该应用程序都是光光的静态窗口,我们使用Windows应用程序,但凡稍微复杂一点的程序都会有工具栏和状态栏,工具栏主要用于一些快捷功能按钮.比如典 ...

  5. Java并发编程:线程和进程的创建(转)

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  6. Windows编程之进程遍历(C++实现)

    Windows编程之进程遍历 PS: 主要扣代码使用,直接滑动到最下面使用. 遍历进程需要几个API,和一个结构体 1.创建进程快照 2.遍历首次进程 3.继续下次遍历 4.进程信息结构体 API 分 ...

  7. LINUX内核分析第六周学习总结——进程的描述和进程的创建

    LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  8. 《Linux内核分析》 第六节 进程的描述和进程的创建

    <Linux内核分析> 第六节 进程的描述和进程的创建 20135307 张嘉琪 原创作品转载请注明出处 +<Linux内核分析>MOOC课程http://mooc.study ...

  9. 20135239益西拉姆 Linux内核分析 进程的描述和进程的创建

    [益西拉姆 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] 第六周 进程的描述 ...

随机推荐

  1. Python 包构建教程

    目录 setuptools 和 setup.py 你所需要做的事 & 一些概念 基础概念 关于源码分发文件和二进制分发文件 示例和分发选择 pure python module package ...

  2. 基本 SQL 之增删改查(一)

    上篇文章,我们介绍了数据的基本 DDL 语句,你应当具备基本的创建数据库.数据表的 SQL 语句,以及表字段的基本数据类型的熟知. 那么本篇就来总结总结大家日常最频繁接触到的 DDM 语句,也就是基本 ...

  3. Flutter 即学即用系列博客——03 在旧有项目引入 Flutter

    前言 其实如果打算在实际项目中引入 Flutter,完全将旧有项目改造成纯 Flutter 项目的可能性比较小,更多的是在旧有项目引入 Flutter. 因此本篇我们就说一说如何在旧有项目引入 Flu ...

  4. Asp.Net MVC Https设置

    1.   IIS设置 1.1 创建SSL证书 点击左侧菜单栏顶部,点击“功能视图”里的“服务器证书”: 点击“创建自动签名证书”创建自动签名证书: 1.2 设置SSL证书 点开网站,在“功能视图”里点 ...

  5. c#下载文件选择路径控件

    <input id="file1" style="width: 240px; height: 20px; display:none;" type=&quo ...

  6. 分布式系统唯一ID的生成方案讨论

    在分布式系统下唯一id问题,就是id咋生成?比如分表分库,因为要是一个表分成多个表之后,每个表的id都是从1开始累加自增长,那是不对的.举个例子,一个表拆分为了2张表,每个表的id都从1开始累加,这个 ...

  7. 学习day03

    1.结构标记  *****   做布局    1.<header>元素    <header></header>    ==> <div id=&quo ...

  8. 简单的shell命令

    grep echo 重定向与管道 tr 特殊文件:/dev/null,/dev/tty 基本命令查找 访问shell脚本的参数 简单的执行跟踪: set -x set +x

  9. spring javaconfig druidsource

    package dataConfig; import java.sql.SQLException; import org.springframework.context.annotation.Bean ...

  10. springboot模块

    1.web <dependency> <groupId>org.springframework.boot</groupId> <artifactId>s ...