进程简介

所谓进程,就是一个包含了一定的资源的集合体。进程拥有独立的地址空间,可执行模块、DLL、代码段和数据段等。进程不执行任何任务,所有的事情都由线程来完成。当启动一个进程的时候,一个主线程也随之启动。

创建进程

创建进程的方法有2中,一种是直接运行一个程序,另外一种是调用CreateProcess函数。不管是哪种方法,他们做的事情都是一样的。首先,系统内核创建一个进程对象,并为该进程分配虚拟的内存空间,初始化堆栈,创建一个执行线程,将指定的程序加载到内存空间,然后初始化全局变量,通过C运行时启动函数调用我们的入口函数。在Windows下,不同的程序类型入口函数不一样。当创建一个win32程序的时候,它可能调用的是如下函数:

static int __cdecl invoke_main()
{
return WinMain(
reinterpret_cast(&__ImageBase),
nullptr,
_get_narrow_winmain_command_line(),
__scrt_get_show_window_mode());
}

如果是控制台程序则调用:

static int __cdecl invoke_main()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}

这也就是为什么当你创建win32程序的时候将函数入口写为main比无法编译通过的原因。
在程序启动的时候,启动函数将做以下操作:

  • 检索指向新进程的完整命令行指针
  • 检索指向新进程的环境变量的指针
  • 对C/C++运行期的全局变量进行初始化
  • 对C运行时内存单元分配函数(malloc、free)和其他底层输入输出例程使用的内存栈进行初始化
  • 为所有全局的和静态C++类对象调用构造函数
  • 调用invoke_main函数切入到我们编写的程序入口

main函数参数说明

int WinMain (
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
);
  • hInstance:指向应用程序的可执行实例,比如a.exe启动的时候会创建一个实例句柄,这个hinstance就是指向这个a.exe的实例句柄。winmain的hinstance实际上是系统将可执行文件的映射加载到进程的地址空间时使用的基本地址空间。例如如果a.exe被加载到0x00400000,那么hinstance的值就是0x00400000。
  • hpreinstance:由于启动函数,一般都是NULL
  • lpcmdline:命令行,一般希望被修改,也可以使用GetCommandLine获取当前进程的命令行
// 获取命令行
int nNumArgs;
PLSTR *ppArgs = CommandLineToArgv(GetCommandLine(),&nNumArgs);
if(*ppArgs[1] == L"x")
{
...
}

程序的退出

当main函数返回时,返回码将被传递给exit函数,然后调用我们在main函数中可能注册过的on_exit函数进行最后的清理。过程如下:

  • main函数返回exitCode
  • 调用由_onexit函数注册的任何函数,顺序为后注册先调用
  • 为所有全局的和静态的C++类对象调用析构函数
  • 调用操作系统的ExitProcess函数,将exitCode传递给它,使得操作系统能够撤销进程并设置它的exit代码

CreateProcess函数

BOOL CreateProcessW(
_In_opt_ LPCWSTR lpApplicationName,// 应用程序名称
_Inout_opt_ LPWSTR lpCommandLine,// 命令行
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,// 进程安全属性
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程安全属性
_In_ BOOL bInheritHandles,// 是否允许该进程的内核句柄被继承
_In_ DWORD dwCreationFlags,// 创建flag?
_In_opt_ LPVOID lpEnvironment,// 环境变量
_In_opt_ LPCWSTR lpCurrentDirectory,// 当前目录
_In_ LPSTARTUPINFOW lpStartupInfo,// 启动信息
_Out_ LPPROCESS_INFORMATION lpProcessInformation// 进程信息
);

当一个线程调用createprocess的时候,系统会创建一个进程内核对象,该进程对象不是进程,而是内核管理进程时使用的一个较小的数据结构。然后系统为新进程创建一个虚拟地址空间,并将可执行文件或DLL文件的代码和数据加载到该进程的地址空间中。然后,系统为新进程的主线程创建一个线程内核对象。通过执行C/C++运行期启动代码,该主线程便开始运行,它最终调用winmain或main函数,如果系统成功创建了新进程和主现成,createprocess便返回TRUE。

lpApplicationName

用于设定新进程将要使用的可执行文件的名称。

lpCommandLine

新进程的命令行字符串。注意lpCommandLine的类型,是一个指向char *,但不可以是常量,下面的使用方法是错误的:

STARTUPINFO sinfo = {sizeof(STARTUPINFO)};
PROCESS_INFORMATION pInfo;
CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&sinfo,&pInfo);

因为NOTEPAD是一个常量字符串,create函数在调用的时候会尝试去修改它,然后在返回之前会将它回复为原来的值,但是常量是不能被修改的,因此会发生错误。

lpProcessAttributes、lpThreadAttributes和bInheritHandles

如果要创建一个新进程,则必须先创建一个内核对象和一个线程内和对象。lpprocessattributes和lpthreadattributes就是用来设定新进程和新线程的安全属性的。