The process in windows


1.进程属性

1.1.进程概述

在Win中,进程是正在运行程序的一个实例,包括一个内核对象和一个地址空间。

操作系统(Operating System,OS)用内核对象来管理进程及保存进程的统计信息。而在地址空间内,包含所有可执行文件或DLL模块的代码和数据及线程堆栈和分配的堆。

在Win中,进程本身没有执行代码的能力,需要依靠进程内的线程来完成。当OS创建一个进程时,会自动为进程创建一个线程,成为主线程(Primary-thread)。主线程可以创建更多的线程。每个线程都有自己的寄存器和堆栈。所有的线程都在进程的地址空间中“同时”执行代码。

如果进程中没有线程执行代码,OS将自动销毁进程并进行相关清理工作。

系统会轮流为每个要运行的线程调度一些CPU时间,使得看上去所有线程都在并发运行。

1.2.进程实例句柄

每个被加载到进程地址空间的EXE或DLL文件都被分配了一个唯一的实例句柄(HINSTANCE)。EXE文件的实例句柄以入口函数(w)WinMain的参数hInstanceExe传入。

PS:在非16Bit的Win中,HINSTANCE和HMODULE完全相同,可以互相代替使用。个人猜测和长短指针有关。

hInstanceExe参数表示OS将可执行文件映像加载到进程地址空间的某个内存基地址。基地址的选择(默认)由连接器决定。

为了得到一个EXE或者DLL文件的内存基地址,可以通过调用GetModuleHandle函数。

如果在DLL中想要得到调用DLL进程的基地址,可以调用GetModuleHandleEx,并传入GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS

1.3.进程命令行

OS在创建一个进程时,会传递一个命令行,包含附加启动信息(程序启动参数)

在一个WinGUI程序中,可以调用GetCommandLine来获取进程完整的命令行。CUI程序的命令行大多保存在argv[]中。

对于WinGUI程序,调用CommandLineToArgvW函数可以将完整的命令行分解为单独的token,但是此API仅支持UNICODE编码,且调用API后,需要用HeapFree来释放相关内存空间。

1.4.进程环境变量

每个进程都有一个关联的环境块(environment block)分配在进程地址空间内。

其包含的字符串大致如下:

:::\…
VarName1 = VarValue1\0
VarName2 = VarValue2\0
\0

对环境变量的操作可以通过GetEnvironmentStringsFreeEnvironmentStringsGetEnvironmentVariableExpandEnvrionmentStringsSetEnvrionmentVariable完成

1.5.进程关联性

强迫线程在可用CPU的一个子集上运行的能力。

1.6.错误模式

每个进程都关联了一组错误标志,用于反馈进程如何响应严重错误。

错误标志可以通过SetErrorMode完成

需要注意的是,默认情况下,子进程会继承父进程的错误模式标志。即如果父进程更改某个标志位,创建子进程时也会反映到子进程上。

可以在创建子进程时传递CREATE_DEFAULT_ERROR_MODE来组织继承

1.7.进程的当前驱动器和目录

如果在操作文件时(打开,修改,删除.etc),不提供完整的路径名,API会自动在当前驱动器的当前目录中查找该文件。

OS在每个进程中跟踪记录本进程的当前驱动器和目录,返回当前驱动器和目录可以调用GetCurrentDirectory

要修改当前目录可以调用SetCurrentDirectory

PS:在Windows,文件路径的最大长度是MAX_PATH,定义为260

一个进程可以通过修改环境变量来设置多个当前目录,利用CRT函数_chdir而不是直接调用SetCurrentDirectory

 

2.创建进程

通过调用CreateProcess来创建一个进程

BOOL WINAPI CreateProcess(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation
);

OS会创建一个进程内核对象和线程内核对象,并将引用计数初始化为1。

另外,如果调用成功,CreateProcess会在进程完全初始化完毕前就返回TRUE。判断进程是否真正创建完毕,需要调用WaitForSingleObject。

对于参数,有几点说明:

虽然可以将应用程序的路径传入第一个参数,但是通常对一个参数传入NULL,而将程序的完整命令行(包括路径和附加的命令)传入第二个参数。

如果第一个参数为NULL,则第二个参数的第一个token部分作为程序路径。如果没有指定扩展名,假设为.exe。

如果只传入程序名,则CreateProcess会自动按以下顺序搜索:1)主调进程.EXE文件所在目录 2)主调进程的当前目录 3)Windows系统目录 4)Windows目录 5)系统环境变量指定的目录

根据MSDN的描述和该API的泄露的SRC来看,这么做的原因应该是出于兼容性考虑

还有一点需要注意,第二个参数lpCommandLine的类型是LPTSTR,表明API(可能)会在内部对传入的字符串进行修改,所以不应将常量型字符串传入,避免造成系统读写错误

接下来的三个参数都和内核对象句柄的继承性有关,详细请自行翻阅MSDN

lpStartupInfo需要一个指向STARTUPINFO的指针。该结构包含进程的启动信息。

如果希望使用默认设置,至少应该做两件事:1)将结构的cb字段设置为结构的大小 2)将其他所有没有用到的字段置为0

最后一个参数指向PROCESS_INFORMATION结构,结构体如下

typedef struct _PROCESS_INFORMATION {
	HANDLE hProcess;
	HANDLE hThread;
	DWORD dwProcessId;
	DWORD dwThreadId;
} PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;

这个结构保存了进程内核对象和线程内核对象。

在CreateProcess返回前,函数会使用完全访问权限打开进程内核对象和线程内核对象,设置PROCESS_INFORMATION的hProcess和hThread成员,并使每个引用计数都自增1.

这意味着销毁一个进程对象需要 1)进程终止,计数减少1 2)父进程调用CloseHandle,进程减至0

这种设计并不是BUG,而是Windows的一个Feature……

 

3.终止进程

Windows的进程终止可以通过以下4中方式

1)主线程入口点函数返回(几乎所有程序正常退出的方式)
2)调用ExitProcess函数
3)调用TerminateProcess函数
4)进程中所有线程被杀死

在99.99%的情况下,都推荐使用第一种方法终止进程,因为它能保证所有清理资源的代码(比如C++对象的destructor)能够正常运行。

这也是不推荐使用2和3的原因

第四种方法通常情况下不会发生,而且手动调用ExitThread和TerminateThread也(可能)会导致无法正常执行资源清理部分代码的问题

进程终止后,OS会清理所有用户对象和GDI对象,并且关闭所有内核对象的句柄。

如果有内核对象的技术引用为0,则执行回收工作

进程退出代码从STILL_ACTIVE改变为退出时设置的退出代码。

PS:当且仅当进程内核对象未被销毁时(一般发生在父进程尚未调用CloseHandle关闭句柄),可以调用GetExitCodeProcess来获得一个已经终止进程的退出代码

 

4.子进程

Windows的子进程有一个Feature:子进程对于父进程来说,可以作为一个独立的进程存在。

这意味着父进程创建子进程完毕后,便不再关心子进程的一切,因为子进程已经是独立的进程,两个进程间的父子关系也已经结束。

要创建一个独立的子进程只需要在CreateProcess创建进程之后,调用CloseHandle关闭PROCESS_INFORMATION结构体中的hProcess和hThread即可。

进程和线程内核对象的引用计数会自减1,而进程不会被销毁。

How come?这就是前面所说的feature

当然,最好利用WaitForSingleObject保证进程已经正确无误的初始化完毕后在调用CloseHandle断绝父子关系(可以在某种程度上避免函数返回TRUE而进程初始化错误挂掉)

PROCESS_INFORMATION pi;
BOOL bSuccess = CreateProcess(..., &pi);

if(bSuccess)
{
   //Allow the system to destroy the process & thread kernel
   //objects will be destroyed as process terminates
   CloseHandle(pi.hThread);
   CloseHandle(pi.hProcess);
}

, , , , ,

  1. #1 by run on 2011 年 02 月 12 日 - 上午 10:01

    终于回家了,终于有网上了!

    [回复]

(will not be published)