1.简介
一般将进程定义为一个正在运行的程序的一个实例,由以下两部分构成。
- 一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方
- 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。
进程是惰性的,进程要做任何事,都必须让一个线程在它的上下文中运行,该线程负责执行进程地址空间包含的代码,一个进程可以有多个线程,所有的线程都在进程的地址空间中“同时”执行代码。
- 每个线程都有它自己的一组CPU寄存器和它自己的堆栈。
- 每个进程至少需要有一个线程来执行进程地址空间包含的代码。
- 当系统创建一个进程的时候,会自动为进程创建第一个线程,称为主线程,主线程再创建子线程。
2.获取当前进程所在的驱动器和目录
DWORD GetCurrentDirectory(
[in] DWORD nBufferLength,
[out] LPTSTR lpBuffer
);
参数1:当前目录字符串的缓冲区长度,缓冲区长度必须包含终止空字符的空间。
参数2:指向接收当前目录字符串的缓冲区的指针。这个以空结尾的字符串指定到当前目录的绝对路径。
返回值:如果函数成功,返回值指定写入缓冲区的字符数,不包括终止的'\0'。
3.创建进程
一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始化计数为1,进程内核对象对象不是进程本身,而是操作系统用来管理这个进程的一个小型数据结构。然后,系统为新进程创建一个虚拟地址空间,并将可执行文件和必要的DLL的代码及数据加载到进程的地址空间。
接着操作系统为新进程的主线程创建一个线程内核对象(其使用计数为1)。和进程内核对象一样,线程内核对象也是一个小型的数据结构,操作系统用它来管理这个线程。这个主线程一开始就会执行C/C++运行时的启动例程,它是由链接器设为应用程序入口的,最终会调用应用程序WinMain,wWinMain函数。如果系统成功创建了新进程和主线程,返回TRUE。
BOOL CreateProcessW(
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
lpApplicationName:可执行文件的名称。
- lpApplicationName参数可以为NULL。在这种情况下,模块名必须是lpCommandLine字符串中以空格分隔的第一个标记;当CreateProcess解析lpCommandLine 字符串时,它会检查字符串中的第一个标记,并假记此标记为我们想运行的可执行文件的名称,如果可执行文件的名称没有扩展名,默认为.exe。并且如果文件名不包含一个完整的路径,CreateProcess还会按以下顺序来搜索可执行文件:
(1)主调进程.exe文件所在的目录
(2)主调进程的当前目录
(3)windows系统目录,即GetSystemDirectory返回的System32子文件夹
(4)windows目录
(5)PATH环境变量中列出的目录
- 如果不是NULL,该参数必须包含文件扩展名,不假定默认扩展名。
lpCommandLine:传给新进程的命令行字符串,这个参数类型是LPWSTR,是一个非常量的字符串,CreateProcess实际上会修改我们传给它的命令行字符串。当然在它返回前,它会把这个字符串还原,所以这样的代码是错误的:因为C/C++编译器把“NOTEPAD”字符串放在只读内存中。
STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi) ;
- 解决问题的最佳方式是把常量字符串放在一个临时缓冲区:
-
STARTUPINFO si = {sizeof(si)} ; PROCESS_INFORMATION pi ; TCHAR szCommandLine[] = TEXT("NOTEPAD") ; CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi) ;
lpProcessAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定新进程对象的返回句柄是否可以被子进程继承。如果lpProcessAttributes为NULL,句柄不能被继承。
-
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定新线程对象的返回句柄是否可以被子进程继承。如果lpThreadAttributes为NULL,句柄不能被继承。
-
bInheritHandles:如果此参数为TRUE,则调用进程中的每个可继承句柄将被新进程继承。如果参数为FALSE,则不继承句柄。
-
dwCreationFlags:控制优先级类和进程创建的标志。
-
lpEnvironment:指向新进程的环境块的指针。如果该参数为NULL,则新进程使用调用进程的环境。
-
lpCurrentDirectory:进程当前目录的完整路径。如果此参数为NULL,则新进程将具有与调用进程相同的当前驱动器和目录。
-
lpStartupInfo:指向STARTUPINFO或STARTUPINFOEX结构体的指针。
-
lpProcessInformation:指向PROCESS_INFORMATION结构的指针,该结构接收关于新进程的标识信息。
示例:创建一个子进程。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
void _tmain( int argc, TCHAR *argv[] )
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
if( argc != 2 )
{
printf("Usage: %s [cmdline]\n", argv[0]);
return;
}
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line)
argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
{
printf( "CreateProcess failed (%d).\n", GetLastError() );
return;
}
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
4.终止进程
有以下4中方式终止:
- 主线程的入口函数返回(强烈推荐使用)
- 进程中的一个线程调用ExitProcess函数(不推荐使用)
- 另一个进程中的线程调用TerminateProcess函数(不推荐使用)
- 进程中的所有线程都“自然死亡”(几乎不会发生)
4.1使用ExitProcess函数
会导致进程或线程直接终止运行,不能正常执行清理工作,示例如下:
class Test {
public:
Test() { printf("Construct \r\n"); }
~Test() { printf("Destruct \r\n"); }
};
Test g_test;
int main()
{
Test test;
ExitProcess(0);
}
结果如下图所示:只构造,不析构。
4.2使用TerminateProcess函数
此函数与ExitProcess有一个明显的区别:任何线程都可以调用TerminalProcess来终止另一个进程或者它自己的进程。
只有在无法通过其他方法来强制进程退出时,才使用TerminateProcess。
虽然进程没有机会执行自己的清理工作,但操作系统会在进程终止之后彻底进行清理,确保不会泄露任何操作系统资源。这意味着使用的所有内存都会被释放,所有打开的文件都会被关闭,所有的内核对象的使用计数都将递减等等。
TerminateProcess是异步的;它启动终止并立即返回。如果需要确定进程已经终止,可以调用带有进程句柄的WaitForSingleObject函数。