《電子技術(shù)應用》
您所在的位置:首頁 > 其他 > 設(shè)計應用 > 基于分布式環(huán)境的子進程監(jiān)控軟件設(shè)計與實現(xiàn)
基于分布式環(huán)境的子進程監(jiān)控軟件設(shè)計與實現(xiàn)
來源:微型機與應用2013年第7期
張 虎,黃海于
(西南交通大學 信息科學與技術(shù)學院,四川 成都 610031)
摘要: 針對分布式系統(tǒng)環(huán)境下,計算資源代理對其子進程監(jiān)控的方法單一,且不能準確獲取子進程運行狀態(tài)的問題,提出了一種根據(jù)子進程的窗口句柄定時檢測子進程運行狀態(tài)的方法。該方法首先根據(jù)子進程的不同類型采用不同的方法獲取子進程的窗口句柄,然后根據(jù)子進程的窗口句柄定時獲取子進程的運行狀態(tài),最后將傳統(tǒng)的等待子進程退出的方法引入到本應用中。運行結(jié)果表明,本方法可以及時準確檢測出Windows環(huán)境下子進程的運行狀態(tài),并在子進程異常退出時,可以準確地獲取其異常退出碼。
Abstract:
Key words :

摘  要: 針對分布式系統(tǒng)環(huán)境下,計算資源代理對其子進程監(jiān)控的方法單一,且不能準確獲取子進程運行狀態(tài)的問題,提出了一種根據(jù)子進程的窗口句柄定時檢測子進程運行狀態(tài)的方法。該方法首先根據(jù)子進程的不同類型采用不同的方法獲取子進程的窗口句柄,然后根據(jù)子進程的窗口句柄定時獲取子進程的運行狀態(tài),最后將傳統(tǒng)的等待子進程退出的方法引入到本應用中。運行結(jié)果表明,本方法可以及時準確檢測出Windows環(huán)境下子進程的運行狀態(tài),并在子進程異常退出時,可以準確地獲取其異常退出碼。
關(guān)鍵詞: 分布式系統(tǒng);Windows運行環(huán)境;代理;子進程的管理和監(jiān)控;窗口句柄

 隨著高速列車仿真模擬、物聯(lián)網(wǎng)應用等領(lǐng)域?qū)τ嬎銠C計算速度要求的不斷提高,單個的計算機已無法滿足高計算速度的要求。將一個大的計算任務分解成若干個小的計算任務,并利用分布式系統(tǒng)[1]將各計算任務分散到不同的計算機上,以獨立進程的形式進行并行計算是一種比較好的解決方法。而對各獨立進程的運行狀態(tài)的實時監(jiān)控和管理是實現(xiàn)分布式系統(tǒng)高效運行和管理的基礎(chǔ)。但是傳統(tǒng)的子進程監(jiān)控只能對子進程是否正在運行或者退出做出判斷,無法判斷子進程是否處在掛起狀態(tài),也無法及時地獲取子進程的退出碼。
 針對上述需求,本文提出了一種根據(jù)子進程的窗口句柄來檢測當前子進程運行狀態(tài)的方法,并結(jié)合傳統(tǒng)的子進程管理監(jiān)控方法,設(shè)計并實現(xiàn)了一種實時的子進程管理監(jiān)控軟件。該軟件用于啟動和監(jiān)控分布式系統(tǒng)中任務調(diào)度器分配給本機的任務。目前該軟件可以檢測子進程的三個狀態(tài):正常運行、退出、掛起;可以及時的將子進程的退出碼[2]反饋給分布式系統(tǒng)的任務調(diào)度器,為任務調(diào)度器[3]高效利用計算資源提供了一定的依據(jù);并且為開發(fā)人員根據(jù)進程的異常退出碼對程序進行查錯提供了方便。
1 傳統(tǒng)子進程監(jiān)控方法
 通常Windows系統(tǒng)使用CreateProcess函數(shù)來新建一個子進程。其中CreateProcess函數(shù)的最后一個參數(shù)的類型是LPPROCESS_INFORMATION結(jié)構(gòu)體,成功創(chuàng)建子進程后,子進程的基本信息就存儲在該結(jié)構(gòu)體中。在Windows API的定義中,該結(jié)構(gòu)體包含了子進程的進程句柄、進程ID、主線程句柄和主線程ID。創(chuàng)建子進程成功后可以通過子進程的句柄和Windows提供的wait[4]系列函數(shù)等待子進程或者子進程組變?yōu)閟ignaled狀態(tài),從而立刻獲知該進程或進程組退出。
 傳統(tǒng)的子進程監(jiān)控方法的好處是:如果子進程確實正常退出,則該方法能夠及時地獲知子進程已經(jīng)退出,并且可以通過Windows提供的API函數(shù)GetExitCodeProcess獲取子進程的退出碼。但是在大量的實際應用過程中,該方法暴露出了其不足之處。例如,創(chuàng)建一個有錯的(如除0錯誤)MFC程序,然后通過以上方法啟動這個有錯的進程,再用wait系列函數(shù)等待子進程退出。當子進程運行到錯誤的語句時會彈出一個錯誤窗口,如圖1所示。

 這時wait系列函數(shù)沒有返回,說明子進程還處在nonsignaled狀態(tài)。用GetExitCodeProcess函數(shù)獲取該子進程的退出碼時,得到的退出碼為STILL_ACTIVE,也就是說父進程認為子進程還在正常運行,無法獲取子進程的退出碼。
 如果在分布式系統(tǒng)中出現(xiàn)這種子進程明明已經(jīng)出錯導致無法繼續(xù)運行,但是其監(jiān)控系統(tǒng)認為其還在正常運行的情況,會嚴重影響分布式系統(tǒng)的負載均衡,不利于分布式系統(tǒng)高效的運行和管理。
2 判斷子進程的類型
 Windows支持兩種類型的應用程序。一種是基于圖形用戶界面(GUI)的應用程序,另一種是基于控制臺用戶界面(CUI)的應用程序[5]。
 基于控制臺的應用程序?qū)儆谖谋静僮鞯膽贸绦?。它通常不能用于?chuàng)建窗口和處理消息,并且不需要圖形用戶界面。雖然基于CUI的應用程序包含在屏幕上的窗口中,但是窗口只包含文本。命令外殼程序CMD.exe是典型的基于CUI的應用程序?;贕UI的應用程序有一個圖形前端程序,它能創(chuàng)建窗口,擁有菜單,可以通過對話框與用戶打交道,并且可以使用所有的標準Windows組件。
 這兩種類型的應用程序之間的界限是非常模糊的,所以Windows沒有提供API用來判斷一個程序是基于GUI的還是CUI的。但是可以通過應用程序在運行時加載的動態(tài)庫[6]來判斷應用程序的類型。
 user32.dll和comctl32.dll兩個模塊[7]是Windows用戶界面相關(guān)應用程序接口,包括窗口消息處理,基本用戶界面等特性。結(jié)合大量的測試得出,基于GUI的應用程序在運行時肯定會加載這兩個模塊,而單純的基于CUI的應用程序在運行時是不會加載這兩個模塊的。因此,可以通過檢測代理,新啟動的應用程序是否加載了user32.dll和comctl32.dll這兩個模塊來區(qū)分應用程序類型。
 接下來的工作就是如何檢測新啟動的進程加載了哪些模塊。在Windows API中提供了枚舉一個進程所加載模塊句柄的接口:EnumProcessModules函數(shù)。該函數(shù)的原型為:
BOOL WINAPI EnumProcessModules(
  __in          HANDLE     hProcess,
  __out         HMODULE * lphModule,
  __in          DWORD      cb,
  __out         LPDWORD   lpcbNeeded
);
 該函數(shù)接收一個進程的句柄,輸出該進程所加載的所有模塊的句柄數(shù)組,并且通過lpcbNeeded參數(shù)輸出所有模塊的句柄所占的字節(jié)數(shù)。因為在啟動子進程的時候,肯定能夠得到子進程的進程ID和進程句柄,所以通過EnumProcessModules函數(shù)可以方便地得到某個特定的進程所加載模塊的句柄。但是通過模塊的句柄,還無法直觀的得到進程加載的模塊的名稱。這種情況下,可以通過vc提供的另外一個接口:GetModuleFileNameEx函數(shù)來獲取各模塊的名稱。GetModuleFileNameEx的函數(shù)原型為:
DWORD WINAPI GetModuleFileNameEx(
  __in          HANDLE   hProcess,
  __in          HMODULE hModule,
  __out         LPTSTR    lpFilename,
  __in          DWORD    nSize
);
 該函數(shù)接收一個進程的句柄和模塊的句柄,通過lpFilename參數(shù)以字符串的形式輸出模塊的具體名稱。進程的句柄在啟動進程時就可以獲得,模塊的句柄就是之前通過EnumProcessModules函數(shù)獲得模塊句柄數(shù)組。
 計算資源上運行的代理通過這兩個API的配合使用,可以準確獲得啟動的子進程所加載的模塊具體的名稱,也就能夠確定子進程是否加載了user32.dll和comctl32.dll兩個模塊。這樣代理就可以確定子進程是基于GUI的應用程序還是基于CUI的應用程序。在確定了應用程序的類型之后,根據(jù)各種類型應用程序的不同特點,采用不同的子進程監(jiān)控方法對其進行監(jiān)控。
3 基于窗口句柄對子進程監(jiān)控的方法
 在Windows系統(tǒng)中不論是GUI應用程序還是CUI應用程序,在程序啟動時都會生成一個窗口。不同的是,GUI應用程序是根據(jù)自己的程序需求生成窗口,CUI應用程序是系統(tǒng)為其加載的一個文本控制臺窗口。系統(tǒng)為每一個窗口生成了唯一的標示,即窗口句柄。而且Windows提供了一個通過窗口句柄來檢測應用程序是否處于掛起狀態(tài)的API函數(shù),該函數(shù)的原型為:BOOL IsHungAppWindow(HWND hWnd)。該函數(shù)接收一個窗口句柄作為輸入?yún)?shù),并且判斷該窗口所屬的進程是否處于掛起狀態(tài)。當進程處于掛起狀態(tài)時,函數(shù)返回TRUE;當進程處于非掛起狀態(tài)時,函數(shù)返回FALSE。只要能獲取到進程所對應的窗口句柄,就能夠通過定時調(diào)用IsHungAppWindow函數(shù)判斷GUI應用程序是否處于掛起狀態(tài)。
 但是,在子進程創(chuàng)建的過程中,父進程只能獲取到該子進程的進程句柄和該進程的主線程句柄,無法獲取到子進程所對應的窗口句柄。所以,如何獲取子進程所對應的窗口句柄是基于GUI的應用程序監(jiān)控方法的關(guān)鍵。
 獲取窗口句柄的方法有很多種,本應用中針對GUI應用程序和CUI應用程序的不同特點采用了不同的方法獲取這兩種應用程序的窗口句柄。
3.1 獲取GUI應用程序的窗口句柄
 如果應用程序是一個基于GUI的應用程序,則操作系統(tǒng)在啟動的過程中不會為應用程序創(chuàng)建控制臺窗口,而只是加載應用程序。當基于GUI的應用程序啟動之后,就根據(jù)程序自身的需要生成特定的窗口。這樣窗口的進程ID即為應用程序的進程ID。
 針對GUI應用程序的窗口進程ID即為應用程序進程ID的特點,獲取GUI應用程序窗口句柄采用的方法是在創(chuàng)建子進程之后遍歷系統(tǒng)中所有窗口,在遍歷的過程中根據(jù)窗口的句柄來獲取窗口所對應的進程的ID;將獲取到的窗口進程ID與創(chuàng)建的子進程的ID進行匹配。如果匹配成功則該窗口就是子進程創(chuàng)建的窗口,可以通過該窗口的句柄調(diào)用IsHungAppWindow函數(shù)來判斷該子進程是否處于掛起的狀態(tài)。
 在遍歷窗口句柄時,是通過GetTopWindow和GetNextWindow這兩個API函數(shù)協(xié)同工作完成的;而根據(jù)窗口句柄來獲取窗口所對應的進程ID是通過GetWindowThreadProcessId函數(shù)實現(xiàn)的。具體的實現(xiàn)代碼如下:
HWND GetWindowHandleByPid(DWORD dwProcessID)
{
    HWND h=GetTopWindow(0);
    while(h)
    {
        DWORD pid=0;
        DWORD dwThreadID=GetWindowThreadProcessId(h,&pid);
        if(dwThreadID!=0)
        {
            if(pid==dwProcessID)
            {
                return h;
            }
        }
        h=GetNextWindow(h,GW_HWNDNEXT);
    }
    return NULL;
 }
 該函數(shù)的輸入?yún)?shù)為需要獲取窗口句柄的進程的ID。如果查找成功則返回進程所對應窗口的窗口句柄;如果不成功則返回NULL。
3.2 獲取CUI應用程序的窗口句柄
 通常情況下,基于CUI的應用程序不會創(chuàng)建窗口和處理消息,并且不需要圖形用戶界面。但是Windows系統(tǒng)會為CUI的應用程序自動加載一個文本控制臺窗口的外殼程序。CUI應用程序的標準輸入輸出都是在這個外殼程序中完成的,所以也可以通過判斷該控制臺窗口是否處于掛起狀態(tài)來判斷CUI應用程序的狀態(tài),即通過IsHungAppWindow函數(shù)來判斷應用程序是否掛起。
 但是,Windows對CUI應用程序的這種處理方式使得CUI程序與CUI程序的窗口具有不同的進程ID。這樣就不能通過匹配程序進程ID和窗口進程ID的方法來確定某一個窗口是否屬于某一個應用程序。
針對CUI應用程序的以上特點,本應用獲取CUI應用程序窗口句柄的方法是通過檢測窗口的標題來確定該窗口是否屬于某一個CUI程序。采用這種方法的原因是基于CUI的窗口標題肯定為該CUI程序的絕對路徑,并且在分布式計算的環(huán)境下,各個任務的子進程可能同名但是肯定是存在于不同的目錄下的。所以通過標題來確定一個窗口是否屬于CUI程序在本應用環(huán)境下完全可行。
基于CUI子進程的監(jiān)控方法是在啟動子進程之后,根據(jù)子進程的絕對路徑調(diào)用FindWindow系統(tǒng)函數(shù)來獲取該CUI子進程的窗口句柄,這樣就可以通過定時調(diào)用IsHungAppWindow函數(shù)來檢測應用程序的運行狀態(tài)。
4 與傳統(tǒng)方法相結(jié)合的子進程監(jiān)控方法
 以上介紹了通過窗口句柄對各種子進程運行狀態(tài)監(jiān)控的可行性。但是,基于窗口句柄檢測子進程運行狀態(tài)的方法是定時檢測子進程的運行狀態(tài)。所以如果定時檢測的時間較長時,則缺乏好的實時性;如果定時檢測的時間較短時,則增加了計算資源的負載。本應用在綜合考慮了以上問題之后,采用了定時檢測子進程的運行狀態(tài)和開辟新的線程等待子進程退出兩種手段相結(jié)合的方法來監(jiān)控子進程的運行狀態(tài)。這樣同時確保了檢測子進程運行狀態(tài)的實時性又彌補了傳統(tǒng)子進程檢測方法在子進程出現(xiàn)掛起狀態(tài)時無法檢測的問題。
在分布式計算環(huán)境下,計算資源上的代理啟動子進程的時間不確定,并且可能同時管理多個子進程。所以為了方便管理,在本應用中建立了帶頭結(jié)點的子進程信息鏈表用于同時管理多個子進程。鏈表的結(jié)構(gòu)體定義如下:
struct ProcessInfoList
{
     short int ProjectID; //進程工程號
     short int ConditionID; //進程工況號
     short int ModuleID;   //進程模塊號
     short int flag;    //進程運行狀態(tài)。-1為未響應;
 //0為已退出;1為正在運行
 HWND hProcessWND;     //進程對應的窗口句柄
 int ProcessType; //進程的類型。1為GUI應用程序;
 //0為CUI應用程序
 int SendFlag;     
   //用于標示是否已經(jīng)提示調(diào)度器該進程已掛起
     PROCESS_INFORMATION  ProcessInfo; 
 //進程的信息,包括進程句柄和進程ID等
     DWORD ExitCode;   //進程退出時的退出碼
     TaskHistoryList *next;
};
 其中ProjectID、ConditionID、ModuleID在整個分布式計算系統(tǒng)中確定唯一一個子進程,并且子進程的可執(zhí)行文件根據(jù)這三個值的不同而存放在不同的目錄下。SendFlag變量是用于提示調(diào)度器該進程是否已掛起,當該值為1時,說明已經(jīng)提示調(diào)度器該進程掛起,不用重復提醒;當該值為0時,說明還未提示調(diào)度器該進程已掛起。
4.1 定時檢測子進程的運行狀態(tài)
 代理啟動時會創(chuàng)建子進程信息鏈表頭結(jié)點,并會創(chuàng)建定時器T,定時遍歷子進程信息鏈表。當子進程信息鏈表中除頭結(jié)點外沒有其他的結(jié)點時,則等待下一次定時的到來;當子進程信息鏈表中除頭結(jié)點外還有其他進程的信息結(jié)點,則首先關(guān)閉定時器T;然后遍歷鏈表各個結(jié)點中的窗口句柄信息,根據(jù)進程的窗口句柄判斷進程是否處于掛起的狀態(tài),如果進程處于掛起的狀態(tài),則及時將該進程的信息及進程的當前運行狀態(tài)發(fā)送給調(diào)度器,如果進程沒有處于掛起狀態(tài),則繼續(xù)遍歷下一個結(jié)點,最后遍歷完成之后重新創(chuàng)建定時器T,定時遍歷子進程信息鏈表。經(jīng)過大量的實驗,最終將定時器的定時時間設(shè)為3 s,即每3 s檢查所有子進程是否處于掛起狀態(tài)。
4.2 創(chuàng)建新的線程等待子進程退出
 當有任務提交給代理時,代理首先啟動相應的應用程序,然后創(chuàng)建新的線程等待子進程的退出,最后判斷子進程的類型,獲取進程的窗口句柄,為新啟動的應用程序創(chuàng)建子進程鏈表節(jié)點。其中等待子進程退出線程中所做的工作有:(1)根據(jù)新啟動進程的進程句柄調(diào)用WaitForSingleObject函數(shù),等待子進程的退出;(2)當子進程退出后獲取進程的退出碼,并存放在相應的子進程信息鏈表結(jié)點中;(3)獲取退出碼之后,將該子進程的基本信息、子進程的當前狀態(tài)、子進程的退出碼發(fā)送給調(diào)度器,線程結(jié)束。
 以上介紹了在Windows系統(tǒng)環(huán)境下對基于GUI和基于CUI子進程監(jiān)控的實現(xiàn)方法。該方法主要是通過定時檢測子進程對應的窗口是否掛起以及開辟新的線程等待子進程退出兩種手段相結(jié)合的方式實現(xiàn)對子進程的監(jiān)控。雖然通過定時檢測窗口是否掛起的方法存在缺乏實時性的問題,但是通過縮短定時時間也可以將時間控制在毫秒級,在絕大多數(shù)的分布式計算應用系統(tǒng)中是可以接受的。
該方法解決了傳統(tǒng)的子進程檢測方法無法檢測子進程掛起狀態(tài)的問題。對子進程運行狀態(tài)的檢測更準確,提高了分布式計算環(huán)境下的資源利用率。
參考文獻
[1] 葛澎.分布式計算技術(shù)概述[J].微電子學與計算機,2012(5):201-204.
[2] DAVE T. Understanding exit codes[J]. Linux Journal, 2010(197):24-25.
[3] Xie Tao, Qin Xiao. A Security-qriented task scheduler for heterogeneous distributed systems[J]. Lecture Notes in Computer Science, 2006(4297):35-46.
[4] STRVENS W R, RAGO S A. UNIX環(huán)境高級編程(第2版)[M].尤晉元,張亞英,戚正偉譯.北京:人民郵電出版社, 2005:179-182.
[5] RICHTER J, NASARRE C. Windows核心編程(第5版)[M].葛子昂,周靖,廖敏譯.北京:清華大學出版社,2008:69-72.
[6] 高連生,盛柏林.動態(tài)鏈接庫在組態(tài)軟件中的應用[J].工業(yè)控制計算機,2010(6):21-22.
[7] 周超.Windows和Linux動態(tài)鏈接庫研究及應用[D].上海:華東理工大學,2007.
[8] Microsoft. QIsHungAppWindow function(Windows) [OL]. [2012-11-28]. http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ms633526(v=vs.85).aspx

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權(quán)禁止轉(zhuǎn)載。