內核物件是個比較難理解的概念,問題的根源就在于即使是《核心編程》書中也沒有說清楚它的定義,只是不停地舉例和描述它的性質,還有如何使用,
盲人摸象,難見全貌,只能盡可能列舉它的性質,注意使用了,
參考計數(書中的說法是使用計數)就是內核物件的一個很關鍵的性質,由于內核物件的擁有者是內核而不是行程,所以只能由內核來做撤銷內核物件的操作,而通常一個內核物件不一定只被一個行程使用的,創建或者撤銷內核物件,就要看參考計數了,參考計數在內核物件被創建的時候被置為1,被行程訪問一次參考計數就遞增1,當參考計數降為0,內核就撤銷這個內核物件,(至于何時參考計數遞減1,書中沒有明確說明,不過有編程經驗的你肯定知道是什么時候了)
安全性也是內核物件的一個重要的性質,它用于描述內核物件的訪問者,由于創建內核物件的時候幾乎都會有一個引數,指向Security_Attributes結構體的指標,例如CreateFileMapping(定義不詳),如果這個指標是NULL值,那就是默認的安全性,只有管理物件的小組成員和創建者可以訪問;不過,只要你初始化并操作lpSecurityDescriptor這個成員,就可以設定它的安全性了,這樣,內核物件被訪問的時候(例如OpenFileMapping),在回傳一個有效的句柄之前會執行一次安全檢查,如果不通過檢查回傳的就不是一個有效句柄,而是NULL了,它的LastError是5(ERROR_ACCESS_DENIED),
行程的內核物件句柄表,在行程被創建的時候被分配,當執行緒共有內核物件產生的時候,內核會在句柄表中找出一個空項,把內核物件的記憶體塊指標寫進去,
如果一個執行緒中呼叫函式回傳一個句柄,這個句柄可以也只可以被執行緒中的所有執行緒使用,這些句柄的值實際上是句柄在當前行程句柄表中的索引,但不是固定的,如在Win2k中回傳的句柄是用于標識句柄表的該物件的位元組數,如果給句柄表中傳入一個無效值,GetLastError則會回傳6(ERROR_INVALID_HANDLE),
如果呼叫函式創建內核物件失敗了,那么句柄的值通常是0(NULL),也有些函式回傳的是INVALID_HANDLE_VALUE,但是無論用什么方式創建內核物件,都要通過CloseHandle來結束對物件的操作,這個函式會先檢查句柄表,確定傳入的索引是否有效,并查看參考計數,如果是0則撤銷這個物件,
一個無效的句柄傳給CloseHandle的話,GetLastError會回傳ERROR_INVALID_HANDLE,如果行程正在被除錯,則通知除錯器,以確定這個錯誤,
加入忘記呼叫CloseHandle,有可能會產生資源泄漏,因為行程終止的時候,系統會掃描它的句柄表中的無效專案,然后關閉這些物件句柄,這時句柄的參考計數就有機會降為0而被撤銷了,
當行程間有父子關系的時候,父行程才有機會使用一個或多個內核物件句柄,并且父行程還可以產生一個可以訪問這個內核物件的子行程,步驟如下:
- 父行程創建內核物件時,必須指明這個句柄可以被繼承,(不是內核物件本身可以被繼承)
- 指定一個SECURITY_ATTRIBUTES結構并對它進行初始化,然后把結構的地址傳給Create函式,
- sa.bInheritHandle = TRUE;
每個句柄表項中都有一個標志位,用以指明這個句柄是否有繼承性,如果是bInheritHandle屬性是TRUE,標志位被置1,否則置0,
此后只要呼叫CreateProcess中的引數bInheritHandle是TRUE,那么被創建的行程就在創建空的句柄表的同時,遍歷父行程的句柄表找到有繼承屬性的專案,并拷貝到新的句柄表中完全相同的位置、遞增參考計數,這樣即使父行程關閉了這個句柄,由于參考計數還沒到0,也要等子行程終止的時候才能置零,這樣子行程只要知道句柄的值就可以使用了,
改變句柄的標志,可以使用SetHandleInformation,第一個引數是句柄,第二個引數就確定修改哪些標志了,和每個句柄相關的就是HANDLE_FLAG_INHERIT和HANDLE_FLAG_PROJECT_FROM_CLOSE了,第三個引數dwFlags,可以用于指明內核物件的繼承標志:
- 打開繼承標志:SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
- 關閉繼承標志:SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);
使用SetHandleInformation(hObj, HANDLE_FLAG_PROJECT_FROM_CLOSE, HANDLE_FLAG_PROJECT_FROM_CLOSE),會告訴系統這個句柄不應該被關閉,如果關閉則會產生一個例外,只有一種特殊的情況,就是子行程又產生了子行程,而有繼承屬性的句柄也有可能會被傳遞到新的子行程,但是舊的子行程有可能在新的子行程生成之前關閉這個句柄,那么父行程就不能和新的子行程通信了,因為沒有繼承這個內核物件,這種情況下,告訴系統句柄不應該關閉才有意義,
跨行程共享內核物件的第二種方法是給物件命名,
- 行程A創建了一個名字為“JeffMutex”的互斥內核物件,
- 行程B也創建一個名字為“JeffMutex”的互斥內核物件,系統會有以下操作:
- 查看是否已經有同名的內核物件
- 如果同名,就檢查同名內核物件的型別,
- 如果型別也相同,系統會查看呼叫者的訪問權限,
- 如果有就找個空專案,初始化再指向現有的內核物件
- 沒有權限則回傳NULL,
- 如果型別不匹配,回傳NULL,
- 如果型別也相同,系統會查看呼叫者的訪問權限,
- 不同名就創建新的內核物件,
- 如果同名,就檢查同名內核物件的型別,
- 沒有就創建新的內核物件,
- 查看是否已經有同名的內核物件
行程B呼叫函式成功后,不是回傳一個內核物件,而是回傳一個和行程相關的句柄值,
按名字共享的另一種方法是不使用Create*函式,而是Open*函式,原型相同,最后一個引數必須是0結尾的地址,不能傳遞NULL,如果不存在,GetLastError回傳值是2(ERROR_FIEL_NOT_FOUND),還得檢查訪問權限,如果有就把參考計數遞增1,
為了保證物件的唯一性,建議創建GUID用來當作物件的名字,這種方法也多用于檢查你的應用程式有另一個行程正在運行,
跨行程共享內核物件的最后一個方法是使用DuplicateHandle函式,簡單地說,這個函式只是取出這個行程的句柄表中的一項,拷貝到另一個行程的句柄表中,
DuplicateHandle的引數雖然多,但不復雜,既然是復制句柄,自然少不了源行程和源句柄,以及目標行程和目標句柄了,設計句柄,就得有它的屏蔽值與繼承性,這里給了三個,前四個引數不難理解,只是后面的引數要注意:
- dwOption引數可以是0,也可以是DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE,
- 如果設定了DUPLICATE_SAME_ACCESS,則目標行程的句柄擁有相同的訪問屏蔽,并讓函式忽略dwDesiredAccess引數,
- 如果設定DUPLICATE_CLOSE_SOURCE,則可以關閉源行程中的句柄,使用該標志時,內核物件的參考計數不會受到影響,
最后書中的例子提到,使用DuplicateHandle函式的時候,dwDesiredAccess引數應該設定為只讀(FILE_MAP_READ),這樣就可以不影響源行程的句柄,提高健壯性,
DUPLICATE_CLOSE_SOURCE
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/6511.html
標籤:Windows
上一篇:記第一次重裝系統
