playerken

博客园 首页 新随笔 联系 订阅 管理

进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:

  • 一个是操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。
  • 另一个是地址空间,它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。

 

Windows支持两种类型的应用程序。一种是基于图形用户界面(GUI)的应用程序,另一种是基于控制台用户界面(CUI)的应用程序。

1

启动函数的功能:

  1. 检索指向新进程的完整命令行的指针。
  2. 检索指向新进程的环境变量的指针。
  3. 对C/C++运行期的全局变量进行初始化。如果包含了stdlib.h文件,代码就能访问这些变量。
  4. 对C运行期内存单元分配函数(malloc和calloc)和其他底层输入/输出例程使用的内存栈进行初始化。
  5. 为所有全局和静态C++类对象调用构造函数。
  6. 调用应用程序的进入点函数。

当进入点函数返回时,启动函数便调用C运行期的exit函数,将返回值传递给它。Exit函数负责下面的操作:

  1. 调用由_onexit函数的调用而注册的任何函数。
  2. 为所有全局的和静态的C++类对象调用析构函数。
  3. 调用操作系统的ExitProcess函数,将返回值传递给它。这使得该操作系统能够撤消进程并设置它的exit代码。

 

进程的实例句柄

加载到进程地址空间的每个可执行文件或DLL文件均被赋予一个独一无二的实例句柄。可执行文件的实例作为WinMain的第一个参数hinstExe来传递。WinMain的hinstExe参数的实际值是系统将可执行文件的映象加载到进程的地址空间时使用的基本地址空间。VC链接程序使用的默认基地址是0X00400000。

HMODULE与HINSTANCE是完全相同的对象,之所以存在两个数据类型,原因是在16位Windows中,HMODULE和HINSTANCE用于标识不同的东西。

GetModuleHandle函数返回可执行文件或DLL文件加载到进程的地址空间时所用的句柄/基地址,它只查看调用进程的地址空间。若参数传NULL值,就会返回进程的地址空间中可执行文件的基地址。

C/C++运行期启动代码总是将NULL传递给WinMain的hinstExePrev参数。该参数用在16位Windows中,并且保留了WinMain的一个参数,目的仅仅是为了能够容易地转用16位Windows应用程序。

当C运行期的启动代码开始运行的时候,它要检索进程的命令行,跳过可执行文件的名字,并将指向命令行其余部分的指针传递给WinMain的pszCmdLine参数。

GetCommandLine函数返回一个指向包含完整命令行的缓存的指针,该命令行包括执行文件的完整路径名。

 

进程的环境变量

每个进程都有一个与它相关的环境块。环境块是进程的地址空间中分配的一个内存块。通常,子进程可以继承一组与父进程相同的环境变量。所谓继承,指的是子进程获得它自己的父进程的环境块拷贝,子进程与父进程并不共享相同的环境块。

GetEnvironmentVariable函数,就能够确定某个环境变量是否存在以及它的值。

 

CreateProcess

当一个线程调用CreateProcess时:

  1. 系统就会创建一个进程内核对象,其初始使用计数是1。
  2. 系统为新进程创建一个虚拟地址空间,并将可执行文件或任何必要的DLL文件的代码和数据加载到该进程的地址空间中。
  3. 系统为新进程的主线程创建一个线程内核对象,其使用计数为1。
  4. 通过执行C/C++运行期启动代码,该主线程便开始运行。
  5. 如果系统成功地创建了新进程和主线程,CreateProcess便返回TRUE。

pszApplicationName和pszCommandLine参数分别用于设定新进程将要使用的可执行文件的名字和传递给新进程的命令行字符串。pszApplicationName可以被设为NULL,在这种情况下,可执行模块的名字必须处于pszCommandLine参数的最前面并由空格符与后面的字符分开。

psaProcess和psaThread参数分别设定进程对象和线程对象需要的安全性。可以为这些参数传递NULL,在这种情况下,系统为这些对象赋予默认安全性描述符。也可以指定两个SECURITY_ATTRIBUTES结构,并对它们进行初始化,以便创建自己的安全性权限,并将它们赋予进程对象和线程对象,父进程将来生成的任何子进程都可以继承这两个对象句柄中的任何一个。

psiStartInfo参数用于指向一个STARTUPINFO结构。如果未能将该结构的内容初始化为零,那么该结构的成员将包含调用线程的堆栈上的任何无用信息。有一点很重要,那就是将该结构的未用成员设置为零,这样,CreateProcess就能连贯一致地运行。

ppiProcInfo参数用于指向你必须指定的PROCESS_INFORMATION结构。当进程内核对象创建后,系统赋予该对象一个独一无二的标识号,系统中的其他任何进程
内核对象都不能使用这个相同的I D号。线程内核对象的情况也一样。进程I D和线程I D共享相同的号码池。在CreateProcess返回之前,它要用这些ID填入PROCESS_INFORMATION结构的dwProcessId和dwThreadId成员中。ID使你能够非常容易地识别系统中的进程和线程。如果应用程序想要与它的“创建者”进行通信,最好不要使用ID。应该定义一个持久性更好的机制,比如内核对象和窗口句柄等。

 

终止线程运行

若要终止进程的运行,可以使用下面四种方法:

  • 主线程的进入点函数返回(最好使用这个方法)。它将返回给C/C++运行期启动代码,它能正确地清除该进程使用的所有的C运行期资源。当C运行期资源被释放之后,C运行期启动代码就显式调用ExitProcess,并将进入点函数返回的值传递给它。请注意,进程中运行的任何其他线程都随着进程而一道终止运行。它可以确保:
    • 该线程创建的任何C++对象将能使用它们的析构函数正确地撤消。
    • 操作系统将能正确地释放该线程的堆栈使用的内存。
    • 系统将进程的退出代码(在进程的内核对象中维护)设置为进入点函数的返回值。
    • 系统将进程内核对象的返回值递减1。
  • 进程中的一个线程调用ExitProcess函数(应该避免使用这种方法)。
    • 当进程中的一个线程调用ExitProcess函数时,进程便终止运行。如果在进入点函数中调用ExitThread,而不是调用ExitProcess或者仅仅是返回,那么应用程序的主线程将停止运行,但是,如果进程中至少有一个线程还在运行,该进程将不会终止运行。
  • 另一个进程中的线程调用TerminateProcess函数(应该避免使用这种方法)。该函数与ExitProcess有一个很大的差别,那就是任何线程都可以调用TerminateProcess来终止另一个进程或它自己的进程的运行。hProcess参数用于标识要终止运行的进程的句柄。TerminateProcess函数是个异步运行的函数,也就是说,它会告诉系统,你想要进程终止运行,但是当函数返回时,你无法保证该进程已经终止运行。
  • 进程中的所有线程自行终止运行(这种情况几乎从未发生)。

当进程终止运行时,下列操作将启动运行:

  1. 进程中剩余的所有线程全部终止运行。
  2. 进程指定的所有用户对象和GDI对象均被释放,所有内核对象均被关闭(如果没有其他进程打开它们的句柄,那么这些内核对象将被撤消。但是,如果其他进程打开了它们的句柄,内核对象将不会撤消)。
  3. 进程的退出代码将从STILL_ACTIVE改为传递给ExitProcess或TerminateProcess的代码。
  4. 进程内核对象的状态变成收到通知的状态。系统中的其他线程可以挂起,直到进程终止运行。
  5. 进程内核对象的使用计数递减1。

进程内核对象的寿命可能大大超过它的进程寿命。如果系统中的另一个进程拥有正在被撤消的进程的内核对象的打开句柄,那么该进程内核对象的使用计数不会降为0。当父进程忘记关闭子进程的句柄时,往往就会发生这样的情况。可以通过调用GetExitCodeProcess来获得目前已经撤消的进程的退出代码。如果调用GetExitCodeProcess函数时进程尚未终止运行,那么该函数就用STILL_ACTIVE标识符填入DWORD。如果进程已经终止运行,便返回数据的退出代码值。

 

ExitProcess终止进程,局部对象未被析构:

#include <stdio.h>
#include <windows.h>


class A
{
public:
	A(int i):data(i){printf("Constructor: %d!\n", data);}
	~A(){printf("Destructor: %d!\n", data);}
private:
	int data;
};

A a(1);

int main()
{
	A b(2);		// Destructor won't be called.
	ExitProcess(0);

    return 0;
}

如上代码输出:

Constructor1!
Constructor2!
Destructor1!
ExitThread终止主线程,子线程还在继续,进程不会结束;若主线程正常返回,进程结束,子线程被终止:
#include <stdio.h>
#include <windows.h>
#include <process.h>

// Print a increated number every 2 seconds.
void ThreadEntry(void*)
{
	int i = 1;
	while(true)
	{
		printf("%d\n", i);
		::Sleep(2000);
		++i;
	}
}

int main()
{
	// Start a working thread.
	_beginthread(ThreadEntry, 0, NULL);

	// Hold the main thread.
	getchar();

	// Exit the main thread.
	// Since the working thread is still working and the main thread did not
	// return, the process won't return.
	// If the main thread returned naturally, the process would also return,
	// and the working thread would be terminated.
	_endthread();

    return 0;
}
如上代码_endthread()终止了主线程,但是工作线程还在继续,进程不会结束。若没有_endthread(),主线程正常返回,则进程结束,工作线程被终止。
 

子进程

大多数情况下,应用程序将另一个进程作为独立的进程来启动。这意味着进程创建和开始运行后,父进程并不需要与新进程进行通信,也不需要在完成它的工作后父进程才能继续运行。若要放弃与子进程的所有联系,必须通过调用CloseHandle来关闭它与新进程及它的主线程之间的句柄。

posted on 2011-08-28 14:20  playerken  阅读(394)  评论(0编辑  收藏  举报