进程基础
一般将进程定义为一个正在运行的程序的一个实例,由两个部分组成:
一个内核对象,操作系统利用内核对象来管理进程。内核对象也是系统保存进程统计信息的地方
一个地址空间,包含所有可执行文件或DLL模块的代码和数据进程要做任何事情,都必须让一个线程在其上下文中运行,该线程负责执行进程地址空间的代码。当系统创建进程的时候,会自动为其创建一个主线程。操作系统会以轮询的方式为每个线程分配时间片供其运行,在多线程环境下,多个线程可以同时运行
入口点函数
Windows支持两种类型的应用程序:GUI程序(图像用户界面 Graphical User Interface)和CUI程序(控制台用户界面Console User Interface)。前者的连接器开关是 /SUBSYSTEM:WINDOWS 后者是 /SUBSYSTEM:CONSOLE。Windows应用程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。GUI程序对应入口点函数默认为 WinMain ,CUI程序对应入口点函数默认为 main。操作系统实际会调用由C/C++运行库实现并在链接时使用-enter:命令行选项来设置一个C/C++运行时启动函数,启动函数将初始化C/C++运行库,使得我们能调用 malloc 等函数,还确保在我们的代码开始执行前,我们声明的全局变量和全局静态C++对象被正确的构造。如下命令也可以用于设置入口点函数:
#pragma comment(linker, "/entry:\"Fun\"")
。在vs2010中使用自定义入口函数并有定义全局对象则会有警告 :warning LNK4210: 存在 .CRT 节;可能有未处理的静态初始值设定项或结束符
启动函数用途:
(A):获取指向新进程的完整命令行的指针
(B):获取指向新进程的环境变量的一个指针
(C):初始化C/C++运行库的全局变量,如果包含了 stdlib.h 则我们的代码就可以访问这些变量了
(D):初始化C运行库内存分配函数和其他底层I/O使用的堆
(E):调用所有全局C++类对象的构造函数
备注:自定义入口函数并不完全具备上述功能如需访问进程的环境变量,可以使用如下形式:
int _tmain(int argc, _TCHAR* argv[], _TCHAR* pStr[])
则 pStr 中将存储环境变量入口点函数返回后,启动函数将调用C运行库函数 exit ,向其传递返回值,此函数执行以下任务:
(A):调用 _onexit 函数调用所注册的任何一个函数
(B):调用所有全局C++类对象的析构函数
(C):在 DEBUG 生成中,如果设置了 _CRTDBG_LEAK_CHECK_DF 标志,就通过调用 _CrtDumpMemoryLeaks函数来生成内存泄漏报告
(D):调用操作系统的 ExitProcess 函数,向其传递返回值,这回导致系统杀死我们的进程,并设置退出代码
备注: 定义在头文件 crtdbg.h 中的 _CrtDumpMemoryLeaks 函数,会检测到运行到此函数为止的所有未释放的内存并打印
进程实例句柄
- 加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予一个独一无二的实例句柄,可执行文件的实例句柄被 WinMain 的第一个参数hInstance传入。hInstance参数实际值是一个内存基地址,系统将可执行文件的映像加载到进程地址空间的这个位置
进程的命令行
- GetCommandLine来获取进程的完整命令行
环境变量
每个进程都有一个与他关联的环境块,这是在进程地址空间分配的一块内存,其中的字符串的第一部分是一个环境变量的名称后接一个等号,等号后是希望赋予给变量的值(空格在环境变量的值中是有意义的。环境块中有以=开头的字符串,这种字符串不作为环境变量使用,比如"=C:=C:\Windows\system32"是表示进程在C盘的当前目录)
获取进程环境变量
auto pEnvironment = GetEnvironmentStrings();
auto pBegin = pEnvironment;
vector<wstring> vecStr;
while(1)
{
vecStr.emplace_back(pBegin);
pBegin += _tcslen(pBegin);
++pBegin;
if (!_tcslen(pBegin))
{
break;
}
}
FreeEnvironmentStrings(pEnvironment);
//GetEnvironmentStrings获取完整环境块
//FreeEnvironmentStrings对应的内存释放函数
GetEnvironmentStrings function:
https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms683187(v=vs.85).aspx
用户可以在环境变量对话框中修改环境变量,为了使得这些改动生效,必须注销后重新登录
通常子进程会继承一组环境变量,这些环境变量与父进程的环境变量相同。父进程可以控制继承的过程。子进程继承环境变量并不会与父进程共享同一个环境块,所以子进程修改了自己环境块中的环境变量并不会影响父进程
SetEnvironmentVariable GetEnvironmentVariable 用户改变与获取与进程绑定的环境变量,且更改不会影响其他进程
https://msdn.microsoft.com/en-us/library/windows/desktop/ms686206(v=vs.85).aspx许多字符串的内部包含了可替换字符串,比如注册表的某个地方发现了下面这个字符:%s%\1234 则两个%之间的这一部分就是一个可替换字符串,这种情况下,对应环境字符串s的值就放在这里,ExpandEnvironmentStrings 可以执行以上转换
const int nLenC = 1024;
TCHAR wBuff[nLenC] = {};
GetEnvironmentVariable(TEXT("szn"), wBuff, nLenC); //wBuff = 0x0015efe4 "big"
memset(wBuff, 0, sizeof wBuff);
GetEnvironmentVariable(TEXT("%szn%\\Hello"), wBuff, nLenC); //wBuff = 0x0038ef98 "HelloWord"
memset(wBuff, 0, sizeof wBuff);
ExpandEnvironmentStrings(TEXT("%szn%\\Hello"), wBuff, nLenC); //wBuff = 0x0035f4f0 "big\Hello"
进程的当前驱动器和目录
获取与设置进程当前驱动器和目录:
GetCurrentDirectory SetCurrentDirectory
获取指定驱动器下的当前目录:
GetFullPathName
系统记录与跟踪着进程的当前驱动器和目录,但他没有记录每个驱动器的当前目录,利用操作系统的支持可以处理多个驱动器的当前目录,这个支持是通过进程的环境变量提供的。如果调用一个函数,并且传入的路径限定的是当前驱动器以外的驱动器,系统会在进程的环境块中查找与指定驱动器管理的变量。如果找到,系统就将变量的值作为当前目录使用
SetEnvironmentVariableA("=D:", "D:\\1");//将D盘的当前目录设置为"D:\1"
FILE* fp = _fsopen("D:1.txt", "rb", _SH_DENYWR);
/*
程序运行在桌面
结果是:打开的是"D:\1\1.txt"
若未定义环境变量"=D:",则打开的文件是"D:\1.txt"
*/
if (fp)
{
char buff[1024] = {};
fread(buff, 1, sizeof buff, fp); //buff内容符合"D:\1\1.txt"的内容
fclose(fp);
}
TCHAR wBuff[_MAX_PATH] = {};
GetFullPathName(TEXT("D:"), MAX_PATH, wBuff, nullptr);
//wBuff = 0x0038f4f8 "D:\1"
获取系统信息:
GetVersionEx
利用
CreateProcess
创建子进程
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
终止进程
终止进程的方式:
(A):主线程入口点函数返回(强烈建议)
(B):进程中的一个线程主动调用 ExitProcess (强烈不推荐)
(C):另一个进程的线程调用 TerminateProcess (强烈不推荐)
(D):进程中的所有线程自然死亡 (几乎不会发生)设计一个应用程序的时候,应该保证只有主线程的入口函数返回之后,这个应用进程才终止。这样才能保证主线程所有资源都被清理。让主线程的入口点函数返回,可以保证如下操作被正确执行:
(A):该线程创建的任何C++对象都将由这些对象的析构函数正确销毁
(B):操作系统正确的释放线程栈使用的内存
(C):系统将进程的退出代码(在内核对象中维护)设为入口点函数的返回值
(D):系统递减进程内核对象的使用计数当主线程的入口函数返回时,会返回到C/C++运行库启动代码,后者将正确清理进程使用的全部C运行时资源。释放了C运行时资源之后,C运行时启动代码将显示调用 ExitProcess ,并将入口函数返回值传给他。所以当主线程入口点函数返回,就会终止整个进程。如果入口点函数中调用 ExitThread ,应用程序的主线程将停止执行,但只要进程中仍有其他线程运行,进程就不会终止。调用 ExitProcess 或 ExitThread 会导致进程或线程直接终止运行,就操作系统而言,这样做不会有什么问题,进程或线程的资源都会被释放,不过对于C/C++应用程序而言应避免调用这些函数,因为C/C++运行库也许也不能执行正确的清理工作,比如C++类的析构函数可能不会被调用
使用 TerminateProcess 也可以终止一个进程,任何一个线程都可以使用此函数来干掉另一个进程或自身进程。使用 TerminateProcess 时,被终止的进程得不到自己要被干掉的通知,应用程序不能正确的清理,也不能防止自己被干掉(除非通过正常的安全机制)。虽然进程没有机会自己执行清理工作,但操作系统会在进程终止之后彻底清理,确保不会泄露任何操作系统资源。一旦进程终止(不管如何终止),系统会保证不留他的任何部分。绝对没有任何办法知道那个进程是否运行过。进程在终止后绝对不会泄露任何东西。TerminateProcess 此函数是异步的
一个进程终止时,系统一次执行以下操作:
(A):终止进程中遗留的线程
(B):释放所有用户对象与GDI对象
(C):进程的退出代码从 STILL_ACTIVE 变为传给 ExitProcess 或 TerminateProcess 函数的代码
(D):进程的内核对象的状态变为已触发
(E):进程对象的使用计数被减一GetExitCodeProcess GetExitCodeThread 可以获取进程或线程退出代码