Windows 进程

来源:互联网 发布:ubuntu firefox 崩溃 编辑:程序博客网 时间:2024/05/16 19:02

window的进程(Process),有两部分组成

  • 进程内核对象。操作系统用来进程的的数据结构。也是报错进程统计信息的地方
  • 一个地址空间。存储着所有可执行的代码。或dll模板和数据。才外,还包括动态分配的内存(如堆,栈)。

一个进程的创建会自动创建一个线程(Thread)。我们称为主线程。它们是和进程关联而又独立的。关联是因为它们在进程的地址空间中工作,而且通过进程中的一些数据结构来进行共享信息,或者是互斥同步操作。独立是应为包括创建线程或者进程的所有代码都是在线程上跑的,但是每一个线程有都有自己的内核对象,寄存器和堆栈。windows 通过某种复杂的逻辑来“同时”运行系统中成千上万的线程。如果进程没有任何与之一关联的线程的话,操作系统也就会销毁进程并归还被占用的内存。

入口函数

CUI程序的入口函数是main,wmain。GUI的入口核函数WinMain,wWinMain。这是因为linker会根据我们在.cpp文件中写的是什么函数。而链接相应的.obj文件。

入口函数 启动函数 main mainCRTStartup wmain wmainCRTStartup WinMain WinMainCRTStartup wWinMain wWinMainCRTStartup

所以所有的C/C++代码都是从这些启动函数调用入口函数开始。源代码在crtexe.c中
这些启动函数大体做一样是事情

  • 获得指向进程完整命令行的指针
  • 获得指向进程完整环境变量的指针
  • 初始化所有c/c++运行库中的全局变量。如果包含了stdlib,我们就可以访问这些变量了
  • 初始化c运行库内存分配函数。和一些底层io例程使用的堆
  • 调用所有c++全局或者静态类对象的构造函数

而从入口函数返回后。启动函数会将其返回值。传递给exit函数。而这个函数做一下事情

  • 调用_onexit调用所注册的任何函数
  • 调用所有c++ 全局或者静态类对象的析构函数
  • 在DEBUG中。如果设置了_CRTDBG_LEAK_CHECK_DF标志。就会调用_CrtDumpMemoryLeaks函数生成内存泄漏报告
  • 调用EexitProcess向其传入exit接受的值。这样会让系统结束进程,并设置退出代码

创建进程

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);

lpApplicationName:
这个参数我们基本上就是传递nullptr,因为系统运行那个可执行文件是看命令行第一个标记。存在的目的是为了支持windows的POSIX子系统

进程的命令行

lpCommandLine:
这个参数是LPTSTR类型的。但是我们通常会写如下程序

    STARTUPINFO si = { sizeof(si) };    PROCESS_INFORMATION pi;    CreateProcess(nullptr,TEXT("KK") ,         nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);

他会出现下面一个异常
这里写图片描述

是的往不该写入的地方写了东西了。上面的代码等价为下面

    STARTUPINFO si = { sizeof(si) };    PROCESS_INFORMATION pi;    PTCHAR ptcstr  = TEXT("KK");    CreateProcess(nullptr, ptcstr,         nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);

这可能就是c++的”遗留问题”了TEXT("KK")是const但是可以指向PTCHAR

所以一般我们怎么做

    STARTUPINFO si = { sizeof(si) };    PROCESS_INFORMATION pi;    TCHAR ptcstr[] = TEXT("KK");    CreateProcess(nullptr, ptcstr,        nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);

这样就没问题了
Tips

CreateProcess的ANSI版本是没有问题的。因为他会在函数内创建Unicode的字符串副本再传递给W版本的函数

回到命令行。进程的命令行几乎总是非空。至少包含一个可执行文件名称的标记(token),也是第一个参数。
但是WinMain函数的pszCmdLine参数。他指向的是GetCommandLine返回完成命令行,然后忽略可执行文件名称的剩余部分。

windows 会根据第一个标记来运行相应的可执行文件,搜索顺序如下
1. 完整路径优先
2. 主调进程exe所在目录
3. 主调进程当前目录(环境变量中的)
4. windows 系统目录,及GetSystemDirectory返回的System32子文件夹
5. windows 目录
6. PATH列出来的目录

Tips:
GetCommandLine函数返回的是一个可修改的指针,但是最好不要去尝试修改。一旦修改就无法知道以前的样子了

shellAPI.h 头文件包含了shell32.dll到处的函数ComandLineToArgvW函数来是命令行分解成单独的标记

环境变量

lpEnvironment
是内存中的一块字符串形如下面这样(一部分)。

[0]=::=::\[1]=C:=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE[2]C:\ProgramData=C:\Prog[3]C:\User=C:\Users\Administra[4]C:\Program Files\C=C:\Program File[5]C:\Program Files (x86)\=C:\Program Files ([6]C:\Program Files\C=C:\Program File[7]1=1[8]WEN=WE[9]C:\WIND=C:\WINDOWS\sys[10]1=1[11]Internet Explorer=Internet

为NULL就会在生成进程时生成默认的环境块,及及子进程包括父进程的环境变量。且包括注册表中的定义的环境变量
当前系统
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
当前用户
HKEY_CURRENT_USER\Environment

在程序中我们可以通过GetEnvironmentStrings获得。每一行都是都有\0来分割。
我们也可以通过GetEnvrionmentVariable来判断是否由摸个变量存在。缓冲区大小传入0返回需要缓冲区的大小
对于包含两个%的“可代替字符”。我们可以是用ExpandEnvrionmentStrings来查询代替字符。同样缓冲区大小传入0返回需要缓冲区的大小
最好我们可以使用GetEnvrionmentVariable来添加和移除环境变量

Tips:
我们可以在如下地方设置
这里写图片描述

修改玩环境变量之后,如果希望程序做出相应一般都要重启,一让程序重新穿件线程并载入环境变量,但是个有的应用如文件资源管理器,任务管理器,控制面板。收到下面消息会重新加载环境变量

SendMessage(HWND_BROADCAST,WM_SETTINGCHANGE ,0,(LPRAM)TEXT("Envrionment"))

进程当前目录

在环境变量中有一个叫做进程当前目录,形如

=C:=C:\Utility\Bin=D:=D:\Program Files

着个环境变量是任何需要路径工作的函数使用的比如。如果你给CreateFile函数一个D:ReadMe的参数。那么就会去打开D:\Program File\ReadMe。前面说过CreateProcess寻找可执行文件的时候也可能用上

我们可以使用c/c++运行库的_chdir或者wchdir。或者使用win32api Get/SetCurrentDirectory

我也可以通过GetFullPathName提取某个驱动器的当前路径

dwCreationFlags
影响进程的创建方式,太多了无法例举

lpStartupInfo
启动时的一些选项,太多了无法例举

lpProcessInformation

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

当一个进程被创建会随之创建一个主线程线程。所以一个进程就会由有一个进程内核对象和一个制主线程内核对象。而它们会在CreateProcess返回之前被系统填充在_PROCESS_INFORMATION的hProcess,hThread参数中

Note:
CreateProcess函数创建进程(包括生成的主线程)时,会打开它们的进程内核对象和主进程内核对象。所以这是他们的被使用计数是2.所以如果希望这两个内核对象被系统回收至少满足2点
1CreateProcess之后调用CloseHandle来让被使用计数减1
2主线程和主进程内部结束,包括运行完毕和被终止

dwProcessId和dwThreadId是进程和线程的PID(process ID)。这个值是系统唯一的。也就是说所有进程和所有线程之间不会出现相同的值。

这里写图片描述
图中黄色的进程叫做PID为0。正常的进程和线程PID是不会被系统分配为0的。这个PID为0的进程叫做System Idle Process。它是一个的进程。在cpu上没有运行进程的地方会被这个占位符占位用来表示未被真实进程使用的cpu使用率。
Tips:
一旦进程或者线程被被销毁或者运行完毕。这个系统为的PID值就会归还给系统。这样另一个要运行的进程和线程就会可能会 分配到这个值。所以我们最好不用通过PID来跟踪线程或者进程

GetCurrentProcessId 获得当前进程的PID
GetCurrentThreadId 获得当前线程的PID
GetProcessId获得指定句柄的进程PID
GetThreadId获得指定句柄的线程PID
GetProcessIDofThread通过线程句柄来得到线程ID

虽然我们的操作系统会记住每一个进程的父进程PID。但是切记这个PID说不定已经指向别人了。ToolHelp函数运行进程通过PROCESSENTRY32结构来查询父进程。此结构的thParentProcessID成员存储着父进程ID。

终止进程

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

  • 主线程入口函数返回,强烈建议
  • 进程中的一个线程调用ExitProcess函数,避免使用
  • 另一个进程调用TerminateProcess函数,避免使用
  • 所有线程都“自然死亡”,几乎不会发生

一旦进程被终止,不管是什么方式,操作系统都会执行彻底的清理工作确保不会泄漏任何系统资源。所有的内存会被释放。所有打开的文件会被关闭。所有内核对象的被使用计数被递减。所有用户对象和GDI对象都会被销毁。所以进程被终止之后是不会泄漏任何东西的。

主线程入口函数返回
入口函数的返回可以保证下面这些情况被执行

  • 通过该线程的创建的所有c++类的对象都会被调用析构函数
  • 操作系统释放线程栈内存
  • 操作系统通过入口的返回值来设置退出代码
  • 系统递减内核对象的使用次数(如果没有CloseHandle的话)

ExitProcess

ExitProcess(    _In_ UINT uExitCode    );

将进程的退出代码设置为uExitCode。该函数不会返回值因为进程已终止了。之后的所有代码,包括别的线程都会终止,所以这个函数是强烈不建使用。

Note:
正常的入口函数结束后,会返回到启动函数。启动函数会正确的释放c运行资源,之后才会调用ExitProcess。这也就解释了为什么主线程结束后就会终止所有线程

还有一个ExitThread函数。他会终止当前线程,再也不返回函数调用。但是如果还有其他线程在运行进程就不会终止。但是如果主线程中使用了这个函数,是不返回函数调用的,所以也就不会让启动函数为我们做清理工作了。进程的退出代码为最后的一个线程的退出代码

TerminateProcess
任何线程都可以通过进程句柄参数调用这个函数来关闭自己或者别的进程。和ExitThread函数的后果是一样的。一个进程会戛然而止。不会有后续的代码执行,操作系统回收资源。

当进程终止时
1. 终止进程中遗留的线程
2. 释放所有用户对象和GDI对象,关闭内核对象
3. 进程的退出代码STILL_ACTIVE变为入口函数的返回值,ExitProcess,TerminateProcess中设置的值
4. 进程的内核对象变为触发状态,waitForSingleObject运用的地方
5. 进程的内核对象递减1

Tips:

GetExitCodeProcess获取内核对象中存储着的退出代码。如果没退出会返回STILL_ACTIVE

GetLogicalProcessorInformation获得cpu的详细信息

原创粉丝点击