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. Fescar(Seata)-Springcloud流程分析-1阶段

    Fescar是阿里18年开源的分布式事务的框架.Fescar的开源对分布式事务框架领域影响很大.作为开源大户,Fescar来自阿里的GTS,经历了好几次双十一的考验,一经开源便颇受关注.今天就来看了F ...

  2. chrome主页被篡改为hao123 win10系统

    应该是开了个从流氓网站下的蓝灯,然后发现主页被篡改 尝试chrome设置修改无效,应该是快捷方式被改了 系统 win10 1.打开对应的下面两个地址,找到chrome的快捷方式,右键属性 C:\Use ...

  3. Java中1.0 / 0.0 会输出什么?

    蓝桥杯失利后发现算法与数据结构的重要性,开始学习算法,刚刚在看<算法4>,看到了这么个东西,让我对java中的size运算有了新的感悟. 在java中输出1/0会发生什么,毫无疑问会报异常 ...

  4. 关于时间的那些事--PHP、JavaScript、MySQL操作时间

    PHP篇 PHP中时间操作单位是秒 一.将时间戳转为普通日期格式 //当前时间戳 time(); //当前时间格式 date("Y-m-d H:i:s",time()); //昨天 ...

  5. Microsoft SQL Server 2016 RC3 安装

    首先下载SQL Server 2016 RC3 安装iso 下载链接 ed2k://|file|cn_sql_server_2016_rc_3_x64_dvd_8566578.iso|24648232 ...

  6. vs code配置flutter开发android

    下载flutter_sdk压缩包,解压到指定目录,把sdk的bin目录添加到系统环境变量Path 设置中国临时镜像:添加两个系统变量 FLUTTER_STORAGE_BASE_URL=https:// ...

  7. jmeter接口测试实战-创建用户

    jmeter接口测试实战-创建用户 相信大多数看到标题的同学都会有疑问, 创建用户不是很简单吗, 调用一下创建用户接口, 传入指定入参, 用户即可创建成功, 今天我们的实战来讲讲创建场景.通过接口创建 ...

  8. 结对编程项目总结 by:陈宏伟&刘益

    结对编程项目在欢快的国庆假期中也顺利结束了.从最初拿到结对编程项目的思考,再到一步一步实现,中间经历了一个漫长的过程.在我和队友的多次协商下,最终我们还是选择使用基于python来实现这一次结对编程项 ...

  9. dbgrideh 哪些行被选中了

    在dbgrideh中允许选择多行,如何知道哪些行被选中是个BOOKMARK类型的属性.SelectedRows: TBookmarkListprocedure TForm1.Button1Click( ...

  10. gradle 编译war包出现乱码,设置为utf-8格式

    1.找gradle 安装目录下的 gradle 2.修改 DEFAULT_JVM_OPTS="-Dfile.encoding=UTF-8"