本來這篇內容在書中是在“其他線程同步函數”這一節中的。這節中介紹了另外的幾個等待函數,比如WaitForInputIdle、MsgWaitForMultipleObjects、WaitForDebugEvent,感覺用途不大,只有SignalObjectAndWait這個函數用途比較大,該函數筆者已經在前面的“等待函數”這一篇中寫過了。
本篇主要討論這本書新增加的內容,就是Windows Vista中提供的調查死結的方法。
在Windows Vista中,提供了用來“檢測線程死結”的一組API,Jeffrey Richter先生在書中將其稱為“Wait Chain Traversal”(WCT)API。查看了網上的翻譯,叫做“等待鏈遍曆”API函數,我也不知道這麼翻譯是否正確或權威,就姑且這麼叫著吧,下面簡稱為WCT。
WCT函數允許你列出“鎖”,並檢測死結,無論是單個進程的,還是跨進程的。Windows對以下線程同步機制的鎖保持著跟蹤:
|
可能的鎖 |
描述 |
|
關鍵程式碼片段 |
Windows對關鍵程式碼片段屬於哪個線程保持著跟蹤 |
|
互斥核心對象 |
Windows對互斥核心對象屬於哪個線程保持著跟蹤,即使互斥核心對象被丟棄 |
|
進程和線程 |
Windows對一個線程正在等待哪個進程或線程保持著跟蹤,另外跟蹤進程和線程是否終止 |
|
SendMessage |
Windows保持跟蹤正在等待SendMessage函數返回的線程 |
|
COM初始化和調用 |
調用CoCreateInstance函數或COM對象的方法的線程被跟蹤 |
|
進階本地程序呼叫(ALPC) |
在Windows Vista中,ALPC代替了LPC,新的沒有文檔的核心進程內部的通訊(IPC)機制 |
需要注意的是,讀寫鎖(SRWLock)同步機制沒有被WPC所跟蹤,另外某些核心對象,比如事件核心對象、訊號量、等待定時器沒有被跟蹤。
本書舉了一個例子來示範了WPC函數的使用。本人總結了下,主要通過以下幾個步驟來實現死結的檢測:
1、開啟等待鏈:使用OpenThreadWaitChainSession函數
2、如果需要檢測COM的話:使用RegisterWaitChainCOMCallback函數
3、取得等待鏈資訊:使用GetThreadWaitChain函數
4、關閉等待鏈:使用CloseThreadWaitChainSession函數
首先,介紹一下OpenThreadWaitChainSession函數:
HWCT OpenThreadWaitChainSession(
DWORD dwFlags, //0表示同步,WCT_ASYNC_OPEN_FLAG表示非同步
PWAITCHAINCALLBACK callback); //非同步話要設定該回呼函數指標
該函數成功,返回一個HWCT類型的控制代碼,該控制代碼要傳遞給CloseThreadWaitChainSession函數,在最後釋放。如果失敗,該函數返回NULL。
一般地,您總是希望從中調用RegisterWaitChainCOMCallback函數以註冊 CoGetCallState和CoGetActivationState這兩個COM函數,以便WCT能夠報告 COM所有權資訊,由於並未記錄這些函數,您需要從ole32.dll通過GetProcAddress 獲得這些函數。本人認為,在您對WCT API進行初始化以尋找COM死結時,WCT API 應自動處理這種細小問題。
VOID WINAPI RegisterWaitChainCOMCallback(
__in PCOGETCALLSTATE CallStateCallback, //CoGetCallState函數地址
__in PCOGETACTIVATIONSTATE
ActivationStateCallback //CoGetActivationState函數地址
);
你可以使用如下代碼這樣使用RegisterWaitChainCOMCallback函數:
PCOGETCALLSTATE CallStateCallback; //函數指標
PCOGETACTIVATIONSTATE ActivationStateCallback; //函數指標
HMODULE hOLE32DLL = LoadLibrary(TEXT("OLE32.DLL"));//載入OLE32.DLL
//從ole32.dll中取得函數地址
CallStateCallback = (PCOGETCALLSTATE)
GetProcAddress(_hOLE32DLL, "CoGetCallState");
ActivationStateCallback = (PCOGETACTIVATIONSTATE)
GetProcAddress(_hOLE32DLL, "CoGetActivationState");
//在等待鏈中註冊COM函數
RegisterWaitChainCOMCallback(CallStateCallback,ActivationStateCallback);
下面,第3步,使用GetThreadWaitChain函數取得等待鏈資訊:
BOOL WINAPI GetThreadWaitChain(
HWCT hWctSession, //等待鏈控制代碼
DWORD_PTR pContext, //非同步方法呼叫中回呼函數的參數
DWORD dwFlags, //查詢類型(位或組合)
DWORD TID, //要查詢的線程的線程ID
PDWORD pNodeCount, //傳回值,指明等待鏈中的結點個數
PWAITCHAIN_NODE_INFO pNodeInfoArray, //傳回值,等待鏈結點資訊
LPBOOL pbIsCycle //傳回值,TURE表示死結
);
pNodeCount參數如果返回的DWORD資料值為0,表明一個問題產生了,比如拒絕訪問。這個時候可以調用GetLastError取得錯誤資訊。
這個函數中,pNodeInfoArray函數是一個WAITCHAIN_NODE_INFO結構的指標,該結構如下:
typedef struct _WAITCHAIN_NODE_INFO
{
WCT_OBJECT_TYPE ObjectType; //結點物件類型
WCT_OBJECT_STATUS ObjectStatus; //結點對象狀態
union {
struct {
WCHAR ObjectName[WCT_OBJNAME_LENGTH];
LARGE_INTEGER Timeout; // Not implemented in v1
BOOL Alertable; // Not implemented in v1
} LockObject;
struct {
DWORD ProcessId;
DWORD ThreadId;
DWORD WaitTime;
DWORD ContextSwitches;
} ThreadObject;
};
} WAITCHAIN_NODE_INFO, *PWAITCHAIN_NODE_INFO;
該結構中的ObjectTyped成員,指明了當前等待鏈中的結點類型,ObjectStatus指明了當前該結點對象的狀態,兩者具體取值就看MSDN或本書吧。如果這個結點類型是一個“阻塞”狀態的線程,即ObjectTyped為WctThreadType的話,內部的ThreadObject成員即有效。
最後,不要忘記調用CloseThreadWaitChainSession關閉WCT控制代碼。當然,如果用LoadLibrary函數載入了OLE23.dll檔案的話,也要記得FreeLibrary函數來釋放。
可以使用如下代碼處理等待鏈中的結點:
HWCT hWCTSession = OpenThreadWaitChainSession(0, NULL);
if (hWCTSession == NULL)
return 0;
//兩個COM的函數指標
PCOGETCALLSTATE CallStateCallback;
PCOGETACTIVATIONSTATE ActivationStateCallback;
HMODULE hOLE32DLL = LoadLibrary(TEXT("OLE32.DLL")); //載入OLE32.DLL
if (hOLE32DLL == NULL)
return 0;
//從ole32.dll中取得兩個函數的地址
CallStateCallback = (PCOGETCALLSTATE)
GetProcAddress(_hOLE32DLL, "CoGetCallState");
ActivationStateCallback = (PCOGETACTIVATIONSTATE)
GetProcAddress(_hOLE32DLL, "CoGetActivationState");
//註冊COM函數,使得WCT可以報告COM相關事件
RegisterWaitChainCOMCallback(CallStateCallback,ActivationStateCallback);
//在你需要處理的線程中:
DWORD dwNodesInChain = 0; //鏈中的結點個數
BOOL bDeadlock = FALSE; //線程是否存在死結
WAITCHAIN_NODE_INFO chain[WCT_MAX_NODE_COUNT];//WCT節點結構數組
//取得等待鏈資訊,並判斷是否正確,TID為線程ID
if (!GetThreadWaitChain(hWCTSession, NULL, WCTP_GETINFO_ALL_FLAGS,
TID, &dwNodesInChain, chain, &bDeadlock))
{
//函數調用失敗,在此處理
//
return 0;
}
// 開始處理等待鏈
dwNodesInChain = min(dwNodesInChain, WCT_MAX_NODE_COUNT);
if (dwNodesInChain == 0) //結點個數為0,可以調用GetLastError處理
return 0;
//可以在此處理本線程有關的等待鏈資訊
if (bDeadlock)
//表明線程ID為TID的線程存在死結
//處理每個鏈中的節點
for (DWORD current = 0; current < dwNodesInChain; ++current)
{
//在此處理練中每個結點資訊
//
}
FreeLibrary(hOLE32DLL); //釋放COM模組
CloseThreadWaitChainSession(hWCTSession); //關閉WCT