進程

        進程常被定義爲一個正在運行的程序的實例。其由兩部分組成: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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章