windows編程 進程的創建銷燬和分析

Windows程序設計:進程

進程是一個具有一定獨立功能的程序關於某個數據集合的一次運行活動在Windows編程環境下,主要由兩大元素組成:

• 一個是操作系統用來管理進程的內核對象。操作系統使用內核對象存放關於進程的核心信息。

• 另一個是地址空間,在地址空間囊括了所有可執行模塊和動態鏈接庫的代碼和數據。動態內存分配的空間也在其中,典型代表是線程堆棧和堆內存分配。

1進程與線程

進程是不活潑的。當進程開始工作的時候,它必須啓動一個在當前進程上下文中的線程來執行工作流程。這個線程被稱爲主線程,它負責執行包含在進程的地址空間中的代碼。同時,每一個進程可能包含多個線程,所有線程都在併發執行進程地址空間中的代碼。這就意味着,每個線程都有它自己的一組CPU 寄存器和它自己的堆棧。每個進程至少擁有一個線程,來執行當前進程的地址空間中的代碼。如果沒有線程來執行進程的地址空間中的代碼,這就意味着進程的生命週期已經完結,系統就會將已經分配給該進程和它的地址空間全部撤銷收回。

如果,我們希望當前進程中的所有線程都能併發運行,那麼,操作系統就要爲每一個線程分配相應的CPU時間。它通過輪詢方式爲線程提供時間片(稱爲量程),給用戶造成一種假象——似乎所有線程都是同時運行的一樣。如果計算機擁有多個CPU(例如Core2多核處理器),那麼操作系統就要使用更加複雜的算法來實現CPU 上線程的負載均衡,以保證多線程程序可以有效的響應客戶需求。

綜上所述,當創建一個進程時,系統同時會自動創建當前進程的第一個線程。這個線程稱爲主線程。然後,該線程可以創建其他的線程,而這些線程又能創建更多的線程。

2使用CreateProcess創建進程 

Windows操作系統爲用戶提供了C r e a t e P r o c e s s 函數用於進程創建,該函數的簽名如下

BOOL CreateProcess(

   PCTSTR pszApplicationName,

   PTSTR pszCommandLine,

   PSECURITY_ATTRIBUTES psaProcess,

   PSECURITY_ATTRIBUTES psaThread,

   BOOL bInheritHandles,

   DWORD fdwCreate,

   PVOID pvEnvironment,

   PCTSTR pszCurDir,

   PSTARTUPINFO psiStartInfo,

   PPROCESS_INFORMATION ppiProcInfo);

當程序員調用CreateProcess創建進程時,系統就會創建一個進程內核對象,並將其初始使用計數置爲1。這個內核對象不是進程本身,而是操作系統管理進程時使用的一個的數據結構。我們可以把它看做是進程的全部統計信息的彙總。內核對象創建完成之後,系統會爲新進程創建一個虛擬地址空間,並將可執行文件或當前進程所需要的DLL文件的代碼和數據加載到該進程的地址空間中。

接下來,系統爲新進程的主線程創建一個線程內核對象(其使用計數爲1 )。注意,這是線程的內核對象,它與進程內核對象類似,是操作系統用來管理線程的數據結構。通過執行C/C++運行時啓動代碼,該主線程便開始運行,它最終調用Wi n M a i n  或 m a i n 函數。如果上述的兩個步驟順利完成,C r e a t e P r o c e s s函數將返回T R U E ,宣告新進程創建成功。

值得我們注意的是:在進程初始化完成之前CreateProcess函數就會返回TRUE試想一下:當操作系統加載程序試圖找出所有需要的DLL,如果一個DLL無法找到,或者未能正確地初始化,那麼該進程就終止運行。由於CreateProcess已經向用戶程序返回T R U E ,因此父進程不知道出現的任何初始化問題。這在實際編程中需要十分小心。CreateProcess函數僅僅意味着操作系統對於新建進程的管理對象已經創建成功,它不能保證新創建的進程可以正常運行(正常運行取決於進程本身的代碼是否滿足運行條件,與創建者無關)。

3進程的終止

通常,進程運行的終止有如下四種方式

主線程的入口點函數返回(推薦使用這個方法)。

進程中的一個線程調用E x i t P r o c e s s函數(不推薦使用)。

• 另一個進程中的線程調用Te r m i n a t e P r o c e s s函數(不推薦使用)。

• 進程中的所有線程自行終止運行(操作系統強行關機)。

接下來,我們將逐一詳細說明上述四種方式

4主線程的入口點函數返回

在程序中,我們應當力求只有當主線程的入口點函數返回時,它的進程才終止運行。這是保證操作系統分配給線程的所有資源能夠得到正確清除的唯一辦法。讓主線程的入口點函數返回,可以確保下所有的資源都得到恰當的處理

• 該線程創建的任何C++對象的析構函數將被調用,分配的資源被正確撤消。

• 操作系統將能正確地釋放該線程的堆棧內存。

• 系統將進程的退出代碼(在進程的內核對象中維護)設置爲入口點函數的返回值。

• 系統將進程內核對象的返回值計數遞減1。

5慎用ExitProcess函數C++是運行在C語言之上的

當進程中的某個線程調用E x i t P r o c e s s函數時,進程便終止運行:

VOID ExitProcess(UINT fuExitCode);

 

該函數用於終止進程的運行,並將進程的退出代碼設置爲參數f u E x i t C o d e的值。E x i t P r o c e s s函數並不返回任何值,這是因爲進程已經終止運行。如果在調用E x i t P r o c e s s之後又增加了什麼代碼,那麼添加代碼將不可能獲得執行的機會

當主線程的入口點函數( WinMain、wWinMain、main或wmain)返回時,它將返回給C/C++運行時啓動代碼,它能正確地清除該進程使用的所有的C運行時資源。當C運行時資源被釋放之後,C運行時啓動代碼就顯式調用E x i t P r o c e s s函數,並將入口點函數返回的值傳遞給它。因此,我們只需要在主線程入口點返回,就能終止當前進程。請注意,進程中運行的任何其他線程都隨着進程的終止而終止。

WindowsSDK編程手冊指出,進程要等到所有線程終止運行之後才終止運行。就操作系統而言,這是正確的。但是, C/C++運行時對應用程序採用了不同的規則,通過調用E x i t P r o c e s s,使得C/C++運行時啓動代碼能夠確保主線程從它的入口點函數返回時,進程便終止運行,而不管進程中是否還有其他線程在運行。不過,如果在入口點函數中調用E x i t T h r e a d,而不是調用E x t i P r o c e s s或者僅僅是返回,那麼應用程序的主線程將停止運行。此時,不難發現,ExitProcess僅僅退出了當前線程,而非當前進程。如果當前進程中至少有一個線程還在運行,該進程將不會終止運行。

調用E x i t P r o c e s s或E x i t T h r e a d可使進程或線程在函數中就終止運行。就操作系統而言,這沒有問題。進程或線程的所有操作系統資源都將被全部清除。但是, C/C++應用程序應該避免調用這些函數,因爲C/C++運行時可能無法被全部清除,例如

#include <windows.h>

#include <stdio.h>

class CMyObject

{

public:

   CMyObject()

   {

      printf("Constructor\r\n");

   }

   ~CMyObject()

   {

      printf("Destructor\r\n");

   }

};

CMyObject g_GlobalObj;

 

void main()

{

   CMyObject LocalObj;

   ExitProcess(0);     

}

接下來我們轉到對應的文件目錄下面在項目上右鍵彈出菜單選中在文件資源管理器中打開文件夾

獲得文件的目錄然後打開命令行工具,

使用cd 命令,切換到debug文件夾,運行exe文件

我們將會看到

Constructor

Constructor

這個應用創建了兩個對象一個是全局對象另一個是局部對象。不過我們一定不會看到Destructor這個單詞出現, C++對象沒有被正確地撤消,因爲E x i t P r o c e s s函數強制當前進程終止運行,C/C++運行時沒有機會進行調用析構函數釋放資源

這告訴我們,當調用E x i t P r o c e s s函數必須慎重!如果在上面的代碼中刪除了對E x i t P r o c e s s(0)這行代碼,那麼再次運行程序,我們可以得到如下結果

Constructor

Constructor

Destructor

Destructor

只要讓主線程的入口點函數返回, C/C++運行時就能夠執行清理工作,並且正確地撤消任何或所有的C++對象。

顯式調用E x i t P r o c e s s和E x i t T h r e a d是導致應用程序不能正確清理的常見原因。在調用E x i t T h r e a d時,進程可能將繼續運行,但是可能會泄漏內存或其他資源。

6進程終止後操作系統的工作

當進程終止時,操作系統完成下列工作

1) 進程中剩餘的所有線程全部終止運行。

2) 進程指定的所有用戶對象和GDI對象均被釋放,所有內核對象均被關閉(如果沒有其他進程關聯到這些句柄,那麼這些內核對象將被撤消。但是,如果存在其他進程關聯到這些句柄, 內核對象將不會撤消)。

3) 進程的退出代碼將從S T I L L _ A C T I V E改爲傳遞給E x i t P r o c e s s或Te r m i n a t e P r o c e s s的代碼。

4) 進程內核對象的狀態變成受信的狀態。進程中的其他線程掛起,直到進程終止運行。

5) 進程內核對象的使用計數遞減1。

注意,進程的內核對象的壽命一定不會低於進程本身的壽命,進程內核對象的壽命卻有可能大大超過它的進程壽命。當進程終止運行時,系統能夠自動確定它的內核對象的使用計數。如果使用計數降爲0,那麼沒有其他進程關聯到該對象,當進程被撤消時,內核對象也被撤消。

不過,如果系統中的另一個進程關聯到正在被撤消的進程的內核對象的時候,那麼該進程內核對象的使用計數不會降爲0。當父進程忘記關閉子進程的句柄時,就會造成這種情況。進程內核對象維護進程的統計信息。即使進程已經終止運行,該信息也是有用的。例如,當我們想要知道進程需要多少C P U時間,或者,通過調用G e t E x i t C o d e P r o c e s s來獲得目前已經撤消的進程的退出代碼:

BOOL GetExitCodeProcess(HANDLE hProcess,

   PDWORD pdwExitCode);

該函數就是通過查看進程的內核對象(由h P r o c e s s參數來標識),取出內核對象的數據結構中用於標識進程的退出代碼的成員。該退出代碼的值在p d w E x i t C o d e參數指向的D W O R D中返回。

調用G e t E x i t C o d e P r o c e s s函數時,如果進程運行尚未終止,那麼該函數就用S T I L L _ A C T I V E標識符(定義爲0 x 1 0 3)填入D W O R D。如果進程已經終止運行,便返回數據的退出代碼值。

因此爲了保證內核對象被正確關閉,應該及時調用C l o s e H a n d l e函數,告訴系統你對進程的統計數據已經不再感興趣,以便操作系統及時回收內核對象。如果進程已經終止運行,C l o s e H a n d l e將遞減內核對象的使用計數,並將它釋放。

相關視頻可以觀看

https://edu.csdn.net/course/detail/7453

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章