回爐重造之重讀Windows核心編程-003-內核對象

  內核對象是個比較難理解的概念,問題的根源就在於即便是《核心編程》書中也沒有說清楚它的定義,只是不停地舉例和描述它的性質,還有如何使用。編程

  盲人摸象,難見全貌。只能儘量列舉它的性質,注意使用了安全

  引用計數(書中的說法是使用計數)就是內核對象的一個很關鍵的性質。因爲內核對象的擁有者是內核而不是進程,因此只能由內核來作撤銷內核對象的操做。而一般一個內核對象不必定只被一個進程使用的,建立或者撤銷內核對象,就要看引用計數了。引用計數在內核對象被建立的時候被置爲1,被進程訪問一次引用計數就遞增1。當引用計數降爲0,內核就撤銷這個內核對象。(至於什麼時候引用計數遞減1,書中沒有明確說明,不過有編程經驗的你確定知道是何時了)app

  安全性也是內核對象的一個重要的性質,它用於描述內核對象的訪問者。因爲建立內核對象的時候幾乎都會有一個參數,指向Security_Attributes結構體的指針,例如CreateFileMapping(定義不詳)。若是這個指針是NULL值,那就是默認的安全性,只有管理對象的小組成員和建立者能夠訪問;不過,只要你初始化並操做lpSecurityDescriptor這個成員,就能夠設置它的安全性了。這樣,內核對象被訪問的時候(例如OpenFileMapping),在返回一個有效的句柄以前會執行一次安全檢查,若是不經過檢查返回的就不是一個有效句柄,而是NULL了,它的LastError是5(ERROR_ACCESS_DENIED)。函數

  進程的內核對象句柄表,在進程被建立的時候被分配。當線程共有內核對象產生的時候,內核會在句柄表中找出一個空項,把內核對象的內存塊指針寫進去。spa

  若是一個線程中調用函數返回一個句柄,這個句柄能夠也只能夠被線程中的全部線程使用。這些句柄的值其實是句柄在當前進程句柄表中的索引,但不是固定的,如在Win2k中返回的句柄是用於標識句柄表的該對象的字節數。若是給句柄表中傳入一個無效值,GetLastError則會返回6(ERROR_INVALID_HANDLE)。線程

  若是調用函數建立內核對象失敗了,那麼句柄的值一般是0(NULL),也有些函數返回的是INVALID_HANDLE_VALUE。可是不管用什麼方式建立內核對象,都要經過CloseHandle來結束對對象的操做。這個函數會先檢查句柄表,肯定傳入的索引是否有效,並查看引用計數,若是是0則撤銷這個對象。設計

  一個無效的句柄傳給CloseHandle的話,GetLastError會返回ERROR_INVALID_HANDLE。若是進程正在被調試,則通知調試器,以肯定這個錯誤。指針

  加入忘記調用CloseHandle,有可能會產生資源泄漏。由於進程終止的時候,系統會掃描它的句柄表中的無效項目,而後關閉這些對象句柄。這時句柄的引用計數就有機會降爲0而被撤銷了。調試

  當進程間有父子關係的時候,父進程纔有機會使用一個或多個內核對象句柄,而且父進程還能夠產生一個能夠訪問這個內核對象的子進程。步驟以下:orm

  • 父進程建立內核對象時,必須指明這個句柄能夠被繼承。(不是內核對象自己能夠被繼承)
  • 指定一個SECURITY_ATTRIBUTES結構並對它進行初始化,而後把結構的地址傳給Create函數。
  • sa.bInheritHandle = TRUE;

  每一個句柄表項中都有一個標誌位,用以指明這個句柄是否有繼承性。若是是bInheritHandle屬性是TRUE,標誌位被置1,不然置0。

   此後只要調用CreateProcess中的參數bInheritHandle是TRUE,那麼被建立的進程就在建立空的句柄表的同時,遍歷父進程的句柄表找到有繼承屬性的項目,並拷貝到新的句柄表中徹底相同的位置、遞增引用計數。這樣即便父進程關閉了這個句柄,因爲引用計數還沒到0,也要等子進程終止的時候才能置零。這樣子進程只要知道句柄的值就可使用了。

  改變句柄的標誌,可使用SetHandleInformation,第一個參數是句柄,第二個參數就肯定修改哪些標誌了。和每一個句柄相關的就是HANDLE_FLAG_INHERITHANDLE_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),會告訴系統這個句柄不該該被關閉,若是關閉則會產生一個異常。只有一種特殊的狀況,就是子進程又產生了子進程,而有繼承屬性的句柄也有可能會被傳遞到新的子進程。可是舊的子進程有可能在新的子進程生成以前關閉這個句柄,那麼父進程就不能和新的子進程通訊了,由於沒有繼承這個內核對象。這種狀況下,告訴系統句柄不該該關閉纔有意義

  跨進程共享內核對象的第二種方法是給對象命名。

  1. 進程A建立了一個名字爲「JeffMutex」的互斥內核對象。
  2. 進程B也建立一個名字爲「JeffMutex」的互斥內核對象,系統會有如下操做:
    1. 查看是否已經有同名的內核對象
      1. 若是同名,就檢查同名內核對象的類型。
        1. 若是類型也相同,系統會查看調用者的訪問權限。
          1. 若是有就找個空項目,初始化再指向現有的內核對象
          2. 沒有權限則返回NULL。
        2. 若是類型不匹配,返回NULL。
      2. 不一樣名就建立新的內核對象。
    2. 沒有就建立新的內核對象。

  進程B調用函數成功後,不是返回一個內核對象,而是返回一個和進程相關的句柄值。

  按名字共享的另外一種方法是不使用Create*函數,而是Open*函數。原型相同。最後一個參數必須是0結尾的地址,不能傳遞NULL。若是不存在,GetLastError返回值是2(ERROR_FIEL_NOT_FOUND)。還得檢查訪問權限,若是有就把引用計數遞增1。

  爲了保證對象的惟一性,建議建立GUID用來看成對象的名字。這種方法也多用於檢查你的應用程序有另外一個進程正在運行。

  跨進程共享內核對象的最後一個方法是使用DuplicateHandle函數。簡單地說,這個函數只是取出這個進程的句柄表中的一項,拷貝到另外一個進程的句柄表中。

  DuplicateHandle的參數雖然多,但不復雜。既然是複製句柄,天然少不了源進程和源句柄,以及目標進程和目標句柄了,設計句柄,就得有它的屏蔽值與繼承性,這裏給了三個。前四個參數不難理解,只是後面的參數要注意:

  • dwOption參數能夠是0,也能夠是DUPLICATE_SAME_ACCESSDUPLICATE_CLOSE_SOURCE
  • 若是設定了DUPLICATE_SAME_ACCESS,則目標進程的句柄擁有相同的訪問屏蔽,並讓函數忽略dwDesiredAccess參數。
  • 若是設定DUPLICATE_CLOSE_SOURCE,則能夠關閉源進程中的句柄。使用該標誌時,內核對象的引用計數不會受到影響。

  最後書中的例子提到,使用DuplicateHandle函數的時候,dwDesiredAccess參數應該設置爲只讀(FILE_MAP_READ),這樣就能夠不影響源進程的句柄,提升健壯性。

  

DUPLICATE_CLOSE_SOURCE

相關文章
相關標籤/搜索