Windows线程函数概述 - 《C++编程艺术》

来源:互联网 发布:115 for mac 编辑:程序博客网 时间:2024/05/15 12:23

(转)

进程概述

进程定义:

通常被定义为一个正在运行的程序实例,是一个程序在其自身的地址空间中的一次执行活动

程序相关描述:

  • 定义:  计算机指令集合,它以文件的形式存储在磁盘上
  • 与进程关系:  一个程序可以对应多个进程
  • windows支持两种类型的应用程序:GUI程序(Graphical User Interface 图形用户界面)和CUI程序(Console User Interface 控制台用户界面).

进程组成:

  • 内核对象:  内核对象也是用系统用来存放进程的统计信息的地方.内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员维护该对象的各种信息.由于内核对象的数据结构只能被内核访问使用,因此应用程序在内存中无法找到该数据结构,并直接改变其内容,只能通过 windows 提供的一些函数来实现内核对象的操作,进程内核对象存活时间至少能和进程本身一样长.
  • 地址空间:  它包含所有可执行模块(.exe)或动态链接库模块(.dll)的代码和数据,另外,它也包含动态分配的空间

地址空间:

系统赋予每个进程独立的虚拟的地址空间,每个进程都有自己的私有的地址空间,各自的线程都可以访问各自进程地址空间中的数据,但是一般情况下各个进程的线程是无法直接访问其他进程的地址空间的数据

注意:

  • 进程重来不执行任何东西,它只是线程的容器,若要使进程完成某项操作,它必须拥有一个在它环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码,也就是说,真正完成代码执行的是线程,而进程只是线程的容器,或者说进程的执行环境.当创建一个进程时,操作系统会自动创建这个进程的第一个线程,也就是主线程,也就是执行 main 函数或 WinMain 函数的线程,可以把 main 函数或 WinMain 函数看做是主线程的进入点函数,此后,主线程可以创建其他线程.所以若没有线程要执行进程地址空间包含的代码,进程就失去了继续存在下去的意义.这时候系统就会自动销毁经常及其地址空间.
  • 进程在终止后不会泄露任何东西

VC 的编译链接器

集成开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终可执行文件.

    • 对应CUI程序,这个连接器的开关是 /SUBSYSTEM:CONSOLE 
    • 对应GUI程序来说则是 /SUBSYSTEM:WINDOWS
    • 当然可以完全从项目中移除/SUBSYSTEM链接器开个,一旦这样做,链接器会自动判断应用程序设置为哪一个子系统

对应程序类型和相应的入口函数

应用程序类型                 入口函数          嵌入可执行文件的启动函数
处理ANSI字符和字符串的GUI应用程序     _tWinMain(WinMain)   WinMainCRTStartup
处理Unicode字符和字符串的GUI应用程序     _tWinMain(wWinMain)  wWinMainCRTStartup
处理ANSI字符和字符串的GUI应用程序     _tmain(main)      mainCRTStartup
处理Unicode字符和字符串的GUI应用程序     _tmain(wmain)       wmainCRTStartup

实例句柄(HINSTANCE)

加载到进程地址空间的每一可执行文件(EXE)或动态链接库(DLL)文件都被赋予了一个独一无二的实例句柄(HINSTANCE).(w)WinMain 的 hInstance 参数的实际值是一个内存基址,系统将可执行文件的映像加载到进程空间的这个位置

为了知道一个可执行文件(EXE)或动态链接库(DLL)被加载到进程地址空间的什么位置,可以使用 GetModuleHandle 函数来返回一个句柄/基址

还需注意的是事实上 HMOBULE 和 HINSTANCE 完全是一回事,如果某个文档指出需要一个HMODULE 参数,我们可以传入一个 HINSTANCE

进程标识(PID)

创建一个进程内核对象时,系统会为此对象分配一个独一无二的标识符.系统中没有别的进程内核对象会有相同的 ID 编号,这同样适用于线程内核对象,创建一个线程内核对象时,此对象会被分配一个独一无二的,系统级别的ID编号,进程 ID 和线程 ID 分享同一个号码池,这意味着线程和进程不可能有相同的 ID,此外,一个对象分配到的 ID 绝不可能是0.进程和线程ID会被系统立即重用(假定在创建一个进程之后,系统初始化可一个进程对象,并将 ID 值 124 分配给他,如果在建立一个新的进程对象,系统不会将同一个 ID 编号分配给它,但是,如果一个进程对象以及释放,系统可以将 124 分配给下一个创建的进程对象).

获得进程(或线程)标识的函数

    • GetCurrentProcessId 来获得当前进程ID.
    • GetCurrentThreadId 来获得当前正在运行的线程ID.
    • GetProcessId 来获得与指定句柄对应的一个进程ID.
    • GetThreadId 来获得与指定句柄对应的一个线程ID.
    • GetProcessIdOfThread 来获得其线程所在进程的ID.

系统确实会记住每个进程父进程的 ID,但由于 ID 会立即被重用,所以等我们获得父进程的 ID 的时候,那个 ID 可能已经是系统运行的一个完全不同的进程.要保证一个进程或线程 ID 不被重用,唯一的办法就是保证进程或线程对象不被销毁,对应子进程,除非父进程复制了自己的进程或线程对象句柄,并允许子进程继承这些句柄,否则无法确保父进程的进程ID或线程ID的有效性.

获取当前进程句柄

获取当前进程句柄 GetCurrentProcess (这个函数都返回的是一个伪句柄.它不会在主调进程的句柄表中新建句柄.而且调用这个函数,不会影响进程内核对象的使用计数器.如果调用CloseHandle 函数关闭一个伪句柄,CloseHandle 只是简单地忽略此调用,并返回 FALSE,将伪句柄转换为真正的句柄: DuplicateHandle)


创建进程函数详解(可以参看Windows核心编程第四章有详细介绍)

显示相关函数

附加说明

CreateProcess 时,系统将会创建一个进程内核对象,其初始化使用计数器为1,经常内核对象不是进程本身,而是操作系统用来管理进程的一个小型数据结构(可以把进程内核对象想象成有进程统计信息构成的一个小心的数据结构).然后系统为新进程创建一个虚拟地址空间.并将可执行文件(和所有必有的动态链接库(DLL))的代码以及数据加载到进程的地址空间中.

CreateProcess 在进程完全初始化好之前就返回 TRUE.这意味着操作系统加载程序尚未尝试定位所有必要的动态链接库(DLL).如果有一个动态链接库(DLL)找不到或者不能正确初始化.进程就会终止.因为 CreateProcess 返回 TRUE.所以父进程不会注意到子进程的任何初始化问题


进程的创建

创建过程:

  • 定义两个结构体变量,用于新进程的主界面出现的样式和存放进程创建后的相关信息
  • 创建进程
  • 在进程创建后进行后续的清理工作

  具体流程图

注意:

在创建一个进程时,系统会为该进程建立一个警察内核对象和一个线程内核对象,而该内核对象有有一个计数器,系统会为这两个对象赋予初始的计数为 1,在CreateProcess 函数返回之前,它将打开创建的进程对象和线程对象,并将每个对象与进程和线程相关的句柄放在其最后一个参数 PROCESS_INFORMATION 结构体变量的对应成员中.当CreateProcess 函数在其内部打开这些对象时,每个对象的使用计数就变为2,如果在父进程中不需要使用子进程的这两个句柄则可以调用 CloseHandle 函数关闭它们(关闭一个进程或线程的句柄.是不会强迫系统"杀死"此进程或线程的.关闭句柄只是告诉系统我们队进程或线程的统计数据不再感兴趣了.进程或线程会继续运行直到自行终止),系统会将子进程的进程内核对象和线程对象的计数器减1,当子进程终止运行时,系统会将这些使用计数器减 1,这时子进程的进程内核对象和线程内核对象都为 0,这两个内核对象就能够被释放了,所以在编程中,当不需要这些内核对象时,总应该调用 CloseHandle 函数关闭它们

代码样例:

定义必要结构体变量:

//指定新进程的主界面出现的样式STARTUPINFO sui;//用于接收创建新进程后新进程的一些信息PROCESS_INFORMATION pi;

初始化 startupinfo:

ZeroMemory(&sui,sizeof(STARTUPINFO));

创建进程:

//打开 vim 编辑器CreateProcess("c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL,        true,0,NULL,NULL,&sui,&pi);

进程创建后的清理操作:

//关闭进程句柄CloseHandle(pi.hProcess);//关闭主线程句柄CloseHandle(pi.hThread);

 程序源码:

View Code

运行结果:


进程的终止

终止进程的 4 中方式

  1. 主线程的入口点函数返回(推荐)
  2. 进程中的一个线程调用 ExitProcess 函数(避免)
  3. 另一个进程中的线程调用 TerminateProcess 函数(避免)
    (被终止的进程得不到自己要被终止的通知,而且应用程序不能正确清理,也不能阻止它自己被强行终止(除非通过正常的安全机制))
  4. 进程中所有的线程都自然死亡

ExitProcess / ExitProcess 说明:

调用 TerminateProcess 或 ExitProcess 会导致进程或线程直接终止运行,C/C++ 应用程序应该避免调用这些函数,因为C/C++ 运行库也许不能正确执行清理工作

调获取进程退出代码

用 GetExitCodeProcess 来获得已经终止的一个进程退出代码

设置进程响应严重错误

每个进程都关联了一组标志,这些标志的作用是让系统知道进程如何响应严重错误(磁盘介质错误,未处理异常,文件查找错误,数据对其错误等),进程可以调用SetErrorMode 函数来告诉系统如何处理这些错误.默认情况下子进程是继承父进程的错误模式的标志

代码样例

用 ExitProcess / ExitProcess 退出进程样例

复制代码
#include <windows.h>#include <iostream>#include <cstdlib>using namespace std;class TEST{public:    ~TEST(){cout<<"this is TEST destructor!"<<endl;}};void main(){    TEST test;    //通过 TerminateProcess 杀死进程    //DWORD nID;    //HANDLE hPro;    //获得进程 ID,后通过进程 ID 获得进程句柄    //nID = GetCurrentProcessId();    //hPro = OpenProcess(PROCESS_ALL_ACCESS,FALSE,nID);    //TerminateProcess(hPro,0);        //通过 ExitProcess 杀死进程    ExitProcess(0);    //下面这句话永远无法执行    system("pasue");}
复制代码

参考资料

  • VC通过进程ID获得句柄的方法

进程执行流程

执行流程(下图是看Windows核心编程的个人理解,若发现者错误若发现恳请提出)

执行流程相关解释

启动函数用途简单总结:(即上图的C/C++运行库启动代码)

  1. 获取执行新进程的完整命令行的一个指针
  2. 获取指向新进程的环境变量的一个指针
  3. 初始化C/C++运行库的全局变量(如果包含了stdlib.h,代码就可以访问到这些变量<Windows 核心编程的 P69 页有详细介绍>)
  4. 初始化C运行库内存分配函数
  5. 调用所有全局变量和静态C++类的构造函数

主函数返回以后,启动函数将调用 C 运行库函数 exit,向其传递返回值(nMainRetVal).exit 函数执行以下任务

  1. 调用 _onexit 函数调用所注册的任何一个函数
  2. 调用所有全局和静态 C++ 类对象的析构函数
  3. 清理进程使用的全部 C 运行时资源
    (C/C++ 运行库为应用程序采取了一个不同的策略:不管进程中是有其他线程在运行,只要应用程序主线程从它的入口函数返回,C/C++ 运行库就会调用ExitProcess 来终止进程.不管如果在入口函数中调用的是 ExitThread,而不是 ExitProcess 或者入口点函数直接返回,应用程序的主线程将停止执行,但是进程中还有其他线程正在运行,进程就不会终止.)
  4. 在 DEBUG 生成中.如果设置了_CRTDBG_LEAK_CHECK_DF 标志,就通过调用 _CtrDumpMemoryLeaks 函数来生成内存泄露报告
  5. 调用操作系统的 ExitProcess 函数,向其传入 nMainRetVal.这会导致操作系统"杀死"我们的进程.并设置它的退出代码

一个进程终止时,系统会依次执行以下操作:(即为上图的进程清理工作)

  1. 终止进程中遗留的任何线程
  2. 释放经常分配的所有用户对象和 GDI 对象,关闭所有内核对象(如果没有其他进程打开这些内核对象的句柄,那么他们也会被销毁,不过,如果其他进程打开了它们的句柄,那么他们就不会被销毁)
  3. 操作系统正确释放线程所栈使用的内存
  4. 进程的退出代码从 STILL_ACTIVE 变为传给 ExitProcess 或 TerminateProcess 函数的代码
  5. 经常内核对象的状态变为以触发状态
  6. 进程计数器对象递减 1

原创粉丝点击