进程

来源:互联网 发布:linux目录扫描工具 编辑:程序博客网 时间:2024/05/13 04:27

        进程常被定义为一个正在运行的程序的实例。其由两部分组成:1)用于管理进程的进程内核对象;2)一个地址空间,其中包括代码和数据,以及动态分配的空间。

        进程是死的,它至少应该有一个主线程,该主线程可以创建其它线程。进程中的线程可以“同时”运行,因此每个进程都有自己的CPU寄存器和退栈。实际上,在某个时刻只有一个线程运行在处理上,操作系统给每个运行的线程一个时间片,这样就造成了多个线程同时运行的假象。但线程结束时,都需要对自己的上下文环境进行存储,以保多个下次线程能够接着正确运行。

        当进程创建时,系统会自动创建一个主线程。在主线程中,可以创建其他的线程。

        windows支持两种类型的应用程序:1)基于图形用户界面的应用程序;2)基于控制台用户界面的应用程序。

       windows有4个进入点函数,用于在应用程序启动运行时调用。其如下:

int WINAPI WinMain(   HINSTANCE hinstExe,   HINSTANCE ,   PSTR pszCmdLine,   int nCmdShow);int WINAPI wWinMain(   HINSTANCE hinstExe,   HINSTANCE ,   PWSTR pszCmdLine,   int nCmdShow);int _cdecl  main(   int argc,   char *argv[],   char *envp[]);int _cdecl  wmain(   int argc,   wchar_t *argv[],   wchar_t *envp[]);

类型

进入点函数

嵌入可执行文件中的启动函数

需要ANSI字符和字符串的GUI应用程序

WinMain

WinMainCRTStartup

需要Unicode字符和字符串的GUI应用程序

wWinMain

wWinMainCRTStartup

需要ANSI字符和字符串的CUI应用程序

main

mainCRTStartup

需要Unicode字符和字符串的CUI应用程序

wmain

wmainCRTStartup

         操作系统调用的并不是我们的进入点函数,而是调用C/C++运行期启动函数,也就是上满的WinMainCRTStartup、wWinMainCRTStartup、mainCRTStartup和wmainCRTStartup。具体调用哪个启动函数,需要根据我们的应用程序中使用了哪个进入点函数。

         在启动函数中,主要做以下工作:

         1)检索指向新进程的完整命令行的指针;

         2)检索指向新进程的环境变量的指针;

         3)对C/C++运行期的全局变量进行初始化。如果我们的程序包含了Stdlib.h文件,就可以访问这些全局变量,后面会列出这些全局变量;

         4)如果是C++程序,那么会对全局和静态的C++类对象调用构造函数。

         5)在启动函数中,上面工作完成后,就会调用我们的进入点函数。运行期启动函数就会用下面的方式调用:

GetStartupInfo(&StartupInfo);int nMainRetVal=wWinMain(GetModuleHandle(NULL),NULL,pszCommandLineUnicode,(StartupInfo.dwFlags & STARTF_USESHOWWINDOW)?        StartupInfo.wShowWindow:SW_SHOWDEFAULT);编写的程序进入点函数为WinMain的调用方式GetStartupInfo(&StartupInfo);int nMainRetVal=WinMain(GetModuleHandle(NULL),NULL,pszCommandLineAnsi,(StartupInfo.dwFlags & STARTF_USESHOWWINDOW)?StartupInfo.wShowWindow:SW_SHOWDEFAULT);编写的程序进入点函数为wmain的调用方式int nMainRetVal=wmain(__argc,__wargv,_wenviron);编写的程序进入点函数为main饿调用方式int nMainRetVal=main(__argc,__argv,_environ);

         6)当进入点函数返回时,启动函数就会调用C运行期的exit函数,并将返回值nMainRetVal传递给exit函数。其中exit函数做以下操作:

  •           调用由_onexit函数注册的任何函数(先注册后调用);
  •           为所有全局和静态的C++对象调用构造函数;
  •           调用操作系统的ExitProcess函数,并将返回值nMainRetVal传递给它。这样操作系统就能够撤销进程并且设置它的exit代码。

下面是应用程序能够使用的C/C++运行期全局变量:

sd

        每个加载到进程地址空间的可执行文件或DLL文件均被赋予一个独一无二的实例句柄。实例句柄实际上是可执行文件映像加载到进程地址空间的基地址,例如,系统打开一个可执行文件并将它加载到地址0x00400000中,那么WinMain的hinstExe参数的值就为0x00400000。

        可执行文件加载到的基地址是由链接程序决定的。不同的链接程序的默认基地址不同,但是可以改变的。可以通过调用GetModuleHandle函数来获取可执行文件或DLL文件加载到进程的地址空间时的基地址,也就是实例句柄。

 一、参数介绍

int WINAPI WinMain( HINSTANCE hinstExe, HINSTANCE , PSTR pszCmdLine, int nCmdShow);

 进程的命令行

        当进程创建时,会传递一个命令行,该命令行至少包括一个可执行文件的名字。当C运行期的启动代码开始运行时,会检索命令行,并将出去可执行文件名字后的其余部分的命令行的指针传递给WinMain的pszCmdLine参数。

        获取完整命令行的函数,包括命令行开头的可执行文件的完整路径名:PTSTR GetCommandLine();

        分开获取命令行的参数,:PWSTR  CommandLineToArgvW(PWSTR pszCmdLine,int *pNumArgs); 其中pszCmdLine为命令行,pNumArgs为命令行中的参数的个数,该函数返回参数数组。

        在CommandLineToArgvW函数在内部会分配内存,因此我们使用完后,应该调用函数HeapFree释放内存。

进程的环境变量

       每个进程都有一个环境块。环境块是进程的地址空间的一个内存块。每个环境块都包括一组字符串,每个字符串都是一个环境变量名,后面跟着一个等号,等号后面是赋给环境变量的值。这些环境变量按照环境变量名的字母先后顺序进行排序。环境变量名不能为等号,并且环境变量名的空格是有意义的。最后环境块后面必须跟一个\0。其形式如下:

       ValueName1=varValue1\0

       ValueName2=varValue2\0

       ......

       ValueNameX=varValueX\0

       \0

       子进程可以继承父进程的环境块,但父进程可以控制子进程继承自己的哪些环境变量。子进程继承后的环境变量,不再与父进程有任何关系。

       应用程序通过环境变量来调整它的行为特征。当应用程序运行,通过查询环境块,找出环境变量,分析调整自己的行为特征。

       可以通过函数GetEnvironmentVariable来判断环境变量是否存在和环境变量值:   

         DWORD GetEnvironmentVariable(PCTSTR pszName,PTSTR pszValue,DWORD cchValue);
  • pszName:其为要查询的环境变量名。
  • pszValue:存放变量的缓存。
  • cchValue:缓存的大小。
  • 返回值:返回0,表示环境变量不存在;不为0,表示返回到缓存的大小。 

      在许多字符串包含了里面可取代的字符串,可以将其中的环境变量替换为具体的环境变量值。进行字符串替换的函数:

         DWORD ExpendEnvironmentVariable(PCSTR pszSrc,PSTR pszDst,DWORD nSize);
  • pszSrc:要替换的字符串的地址。
  • pszDst:用于替换的字符串的缓冲区。
  • nSize:缓冲区的大小。

       使用函数SetEnvitonmentVariable函数来添加、删除或修改变量:

         BOOL SetEnvitonmentVariable(PCTSTR pszName,PCTSTR pszValue);
  • pszName:为环境变量名。
  • pszValue:表示环境变量的值。
  • 注意:存在,修改;不存在,修改;存在,,并且pszValue设置NULL,表示删除该环境变量。

进程的错误模式       

        进程可以通过使用SetErrorMode函数来告诉系统如何处理一种错误:

        UINT SetErrorMode(UINT fuErrorMode);

       其中fuErrorMode参数可以为以下表中的值,并且可以通过OR连接多个标志。

sd

        子进程可以继承父进程的错误模式标志。但父进程可以通过CreateProcess函数设置CREATE_DEFAULT_ERROR_MODE来禁止子进程继承自己的错误模式标志。

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

        当没有提供全路径名时,windows函数会在当前驱动器和当前目录查找文件和目录。系统在内部对每个进程的当前驱动器和当前目录进行跟踪,该信息是是对每个进程来维护的。我们可以通过下面函数来获取和设置当前驱动器和当前目录:

       DWORD GetCurrentDirectory(DWORD cchCurDir,PTSTR pszCurDir);       BOOL SetCurrentDirectory(PCTSTR pszCurDir);


       系统会跟踪每个进程的当前驱动器和当前目录,但不会跟踪每个驱动器的当前目录。但,有些操作系统可以通过进程的环境块来支持对驱动器的当前目录。其形式如下:

      =C:=C:\Usdfd\sdfds

     =D:=D:\program files

      上面表示C盘的进程当前目录为\Usdfd\sdfds,而D盘的进程当前目录为\program files。

       那么,如果我们用CreateFile函数来打开D:Read.TXT,就会首先查看进程的环境块,是否有环境变量=D,如果存在,就会在当前目录D:\program files中打开Read.TXT.如果不存在,那么就会在D盘的根目录打开Read.TX。

      可以通过GetFullPathName函数来获取驱动器的当前目录:

      DWORD GetFullPathName(PCTSTR pszFile,DWORD cchPath,PTSTR pszPath, PTSTR *ppszFilePart);

系统版本

        可以通过GetVersion返回windows的版本,该函数高字表示MS-DOS的版本,低字表示windows的版本。对于每个字来说高字节表示主版本,低字节表示次版本。其形式如下:

      DWORD GetVersion();

后来,又增加额一个GetVersionEx函数,该函数会用到一个OSVERSIONINFOEX结构,其形式如下:

      BOOL GetVersionEx(OSVERSIONINFOEX pVersionInformation);

后来又设置了一个函数用于将主机的操作系统版本和你的应用程序需要的版本进行比较:

      BOOL VerifyVersionInfo(POSVERSIONINFOEX pVersionInformation,DWORD dwTypeMask,DWORDLONG dwlConditionMask);

二、CreateProcess函数

BOOL CreateProcess(    PCTSTR pszApplicationName,    PTSTR pszCommandLine,    PSECURITY_ATTRIBUTES psaProcess,    PSECURITY_ATTRIBUTES psaThread,    BOOL bInheritHandles,    DWORD fdwCreate,    PVOID pvEnvironment,    PCTSTR pszCruDir,    PSTARTUPINFO psiStartInfo,    PROCESS_INFORMATION ppiProcInfo);

       当一个线程调用了CreateProcess函数,系统就会创建一个进程内核对象,并将引用计数初始化为1。然后系统就会为进程分配一个虚拟地址空间,并将可执行文件或必要的DLL文件加载到进程的虚拟地址空间中。然后,系统会为新进程的主线程创建一个线程内核对象,并将引用计数初始化为1。然后,系统通过调用C/C++启动代码,进行一些列初始化后,并调用main、wmain、wWinMain或WinMain进入点函数。如果系统成功创建了新进程和主线程后,CreateProcess就返回TRUE。
pszApplicationName和pszCommandLine

        pszApplicationName和pszCommandLine用于设定新进程将要使用的可执行文件的文件名和传递给新进程的命令行参数。

        如果pszApplicationName为NULL,那么CreateProcess就会分析pszCommandLine,并假设字符串的第一个标记为可执行文件的名字,如果没有指定扩展名,那么假设扩展名为.exe。如果没有指定可执行文件的全路径,那么CreateProcess函数就会按下面的顺序搜索该可执行文件:

       1)包括调用进程的.exe的目录。

       2)调用进程的当前目录。

       3)windows的系统目录。

       4)windows目录。

       5)PATH环境变量中列出的目录。

        如果pszApplicationName参数不为NULL时,可以将可执行文件的地址通过pszApplicationName参数。传递给参数的可执行文件必须包括扩展名,并且如果没有指定全路径,那么会假设为当前目录,如果在当前目录没有找到,那么就会运行失败。

       如果pszAplicationName为NULL,那么通过函数GetCommandLine函数获取的值为pszCommandLine的值。如果pszApplicationName不为NULL,那么通过GetCommandLine函数获取的值为可执行文件名空格连接上参数pszCommandLine的值。

psaProcess、psaThread和bInheritHandles

        paProess用于设置进程对象的安全属性。psaThread参数用于设定主线程的安全属性。bInheritHandles参数用于对子进程是否是可继承的,如果设为TRUE,子进程可以继承父进程的可继承的句柄表中的句柄,即将父进程的句柄中的可继承句柄复制到子进程的句柄表中的相同位置。

 fdwCreate

fdwCreate参数为标识标志,用于设定如何创建子线程。可以支持OR操作符。该参数还可以设置进程的优先级类别。其值可为:

标志说明EBUG_PROCESS用于告诉系统,父进程要调试子进程和子进程生成的任何进程。并且指定子进程发生的某些事件,要通知父进程EBUG_ONLY_THIS_PROCESS其与EBUG_PROCESS类似,但只是调试程序只告知父进程紧靠的子进程发生了特定事件CREATE_SUSPENDED新进程创建时,挂起主线程。可以通过ResumeThream函数来恢复DETACHED_PROCESS阻止给予CUI的进程运用父进程的控制台窗口,并将输出发送到另一个新的控制台窗口CREATE_NEW_CONSOLE告诉系统,为新进程创建一个新的控制台窗口CREATE_NO_WINDOWS告诉系统,不要为进程创建任何控制台窗口CREATE_NEW_PROCESS_GROUP用于修改用户在按下ctrl+c或ctrl+break键时得到通知的进程列表CREATE_DEFAULT_ERROR_MODE用于告诉系统,新进程不应该继承父进程的错误处理模式CREATE_SEPARATE_WOW_VDM用于在windows2000中运行16位windows程序使用CREATE_SHARED_WOW_VDM用于在windows2000中运行16位windows程序使用CREATE_UNICODE_ENVIROMENT用于告诉系统,子进程的环境块应该包括UNICODE字符CREATE_FORCEDOS用于强制系统运行嵌入16位OS/2应用程序的MS_DOS应用程序CREATE_BREAKAWAY_FROM_JOB使作业中的进程生成一个与作业相关联的新进程进程的优先级类别共6个系统默认的优先级类别为正常。

 pvEnvironment

        pvEnvironment参数用于指向新进程将要使用的环境字符串的内存块。设为NULL,那么子进程将继承父进程正在使用的一组环境字符串。也可以使用函数GetEnviromentStrings来获取调用进程正在使用的环境字符串的数据块的地址。

      PVOID GetEnvironmentStrings();      BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);     

 pszCruDir

pszCurDir参数用于设置新进程的当前驱动器和工作目录。如果设为NULL,那么新进程与父进程的工作目录相同。如果不为NULL,那么是以0结束的字符串。

 psiStartInfo

ppiProcInfo 

三、终止进程运行

 终止线程有4种方法:

1)主线程的进入点函数返回。

2)进程中一个线程调用函数ExitProcess函数。

3)另一个进程中的线程调用函数TerminateProcess。

4)进程中的所有线程执行终止运行。

1.主线程中的进入函数返回这是唯一一种方法能够保证所有线程中资源能够得到正确释放。其可以确保以下操作:

  • 该线程创建的C++对象将调用析构函数正确地撤销。
  • 操作系统能够正确的释放线程堆栈使用的内存。
  • 系统将进程的推出代码设置为进入点函数的返回值。
  • 系统将进程内核对象的引用计数减1。

2.ExitProcess函数 

       VOID ExitProcess(UINT fuExitCode);

      该函数用于终止进程运行,并将进程的退出代码设置为fuExitCode。

        在1情况下,主线程的进入点函数返回,它会返回到C/C++运行期启动代码,启动代码会清楚所有C运行期使用的资源。然后C/C++运行期启动代码会调用ExitProcess函数,并将进入点函数的返回值函数ExitProcess。这样改进程就终止运行了。

 3.TerminateProcess函数 

        BOOL TerminateProcess(HANDLE hProcess,UINT fuExitCode);

        该函数会将退出代码设置为fuExitCode参数的值。被终止的进程在被终止前事先不能够得到通知,也就是说其无法正确的清理资源。但是,操作系统在进程结束后会将所有的内存释放,所有打开的文件全部关闭,所有内核对象的引用计数减1,所有的用户对象和GDI对象均被撤销。

        但是TerminateProcess函数为异步函数,返回时无法保证已经终止了进程。

4.所有线程运行结束

        这种情况的进程退出代码被设置为与终止运行的最后一个线程相同的退出代码。
5.不管用4中方法的那种方法结束进程,下面的操作都会被启动执行:

1)进程中剩余线程都将终止运行。

2)进程中指定的用户对象和GDI对象均被释放,所有内核对象均被关闭(关闭只是说进程不能再访问该内核对象了,但不是指引用计数减1,需要调用CloseHandle函数)。

3)进程中的退出代码将从STILL_ACTIVE改为传递给ExitProcess或TerminateProcess的代码。

4)进程内核对象的状态变成收到通知状态。

5)进程的内核使用计数减1。

可以调用GetExitCodeProcess函数来获取进程的退出代码,如果进程还在运行,返回STILLACTIVE;如果进程已经运行,返回进程的退出代码:

         BOOL GetExitCodeProcess(HANDLE hProcess,PDWORD pdwExitCode);