核心對象只是作業系統核心分配的一個記憶體塊,並且只能由作業系統核心訪問。該記憶體塊是一種資料結構,它的成員負責維護該對象的各種資訊。Windows提供一組函數建立和操作核心對象。調用一個建立核心對象的函數,函數會返回一個控制代碼,該控制代碼標識了這個核心對象,這個控制代碼可由當前進程中的所有線程調用。也可以通過跨進程邊界共用核心對象,讓其他的進程調用。
使用計數。核心對象有個使用計數資料成員,標識核心對象被多少個進程所使用。大部分情況是核心對象只被建立它的進程所有使用,當這個進程退出時,核心對象的使用計數就會減一,如果核心對象的使用計數為0時,核心對象就會自動銷毀,如果核心對象被多個進程使用時,它的生命週期就可能比建立它的進程要長。只要核心對象的使用計數不為0,它就不會銷毀,噹噹前進程退出時,只要有其他進程使用這個核心對象,它就不會銷毀。但是我們也不用擔心核心對象導致的記憶體泄露,就算進程沒有手動關閉核心對象,進程在退出的時候,會檢查自己的控制代碼表,如果控制代碼表中有使用的核心對象,作業系統會為我們關閉這些控制代碼,被這些控制代碼引用的核心對象的使用計數就會減一,如果使用計數為0,核心對象就會自動銷毀。從上面我們可以看出,核心對象並沒有和建立它的進程所綁定,建立它的進程退出了,核心對象可能還存活,核心對象的操作所有者是作業系統,而不是進程。
核心對象的安全性。核心對象可以用一個安全性描述元來保護,安全性描述元描述了誰是該對象的擁有者,那些組和使用者可以訪問或使用這些對象,這個對象是否可以被繼承等。SECURITY_ATTRIBUTES這個結構體就是用於安全描述的。其結構如下:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;//表示結構體的長度
LPVOID lpSecurityDescriptor;//指向一個安全性描述元
BOOL bInheritHandle;//表示核心對象被子進程繼承
} SECURITY_ATTRIBUTES;
區分一個對象是否是核心對象就可以看建立對象函數的參數中有沒有SECURITY_ATTRIBUTES這個參數,有的就是核心對象,沒有的就不是核心對象。有了這個結構就將核心對象保護起來了,其他對象就不能隨便訪問它,也就不能破壞它的記憶體結構。
進程核心物件控點表。一個進程在初始化時,系統將為它分配一個控制代碼表。這個控制代碼表僅供核心對象使用,不適用於使用者物件和GDI對象,控制代碼表的結構大致包括:
1:索引
2:指向核心對象記憶體塊的指標
3:存取遮罩
4:標誌
一個進程在首次初始化的時候,其控制代碼表為空白,即進程沒有引用任何核心對象,當進程中的一個線程調用一個建立一個核心對象的函數時,核心將會為這個核心對象分配並初始化一個記憶體塊,然後核心會掃描控制代碼表,找到一個空白的記錄項,指標成員會指向剛建立的核心對象的記憶體位址,存取遮罩會設定成擁有完全訪問的許可權,如果這個核心對象可以被繼承,標誌成員將設為1,如果不能被繼承就是0。當調用關閉核心對象的函數Closehandle,引用該核心對象的進程中的控制代碼表中相應的記錄項就會被清除,當進程退出的使用控制代碼表中的所有記錄都會被清除,所有被使用的核心對象的使用計數都會減一,使用計數為0的核心對象就會自動銷毀。就算沒有手動關閉核心對象,進程退出了也不會出現核心對象泄露,當一個核心對象沒有被任何進程引用時會自動銷毀的。
跨進程邊界共用核心對象。在很多時候需要再不同的進程中共用核心對象,如訊號量,互斥量和事件允許不同的進程中的線程同步執行,這時就需要共用核心對象。共用核心對象的方式有三種:
1:使用核心物件控點繼承
2:為對象命名
3:複製物件控點
1:使用核心物件控點繼承。只有進程之間有父子關係時才能使用核心物件控點繼承。若一個對象進程允許其子進程繼承它的控制代碼,那麼它在建立可被繼承的核心對象時,建立核心對象的函數的參數SECURITY_ATTRIBUTES的成員bInheritHandle要設為true,這樣當父進程建立一個子進程時,子進程就會複製父進程的控制代碼表中可以被繼承的控制代碼到自己的控制代碼表中。每個進程都有自己的控制代碼表,父進程與子進程之間並不是共用控制代碼表,所以當子進程建立好了後,父進程在建立一個可以被繼承的核心對象時,只進程並不能訪問這個核心對象,因為它根本不知道這個核心對象的存在,當然子進程建立自己的核心對象時,父進程也是不知道的。因為微軟沒有寫多於的代碼來複製這些可被繼承的控制代碼到相應的控制代碼表中。所以說核心對象並不是真的繼承,而是繼承了核心對象的控制代碼。也就是說控制代碼只是核心對象的一個指標,而不是核心對象的一個成員。
我們可以通過函數SetHandleInformation來標誌核心對象是否可一個被子進程繼承。如有一個可以被子進程繼承的核心對象,但你不想讓某個進程繼承,在建立這個子進程之前將該核心對象標記為不可繼承,在建立子進程,然後在調用函數SetHandleInformation將核心對象標記為可以繼承,這個其他即將要建立的子進程就可以共用這個核心對象。
SetHandleInformation(
__in HANDLE hObject,//核心對象的控制代碼
__in DWORD dwMask,//告訴函數想更改哪個或哪些標誌
__in DWORD dwFlags//是否可以被繼承
);
2:為對象命名。很多建立核心對象的函數都有一個參數lpName指定核心對象的名字,如以下兩個函數:
CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCSTR lpName
);
CreateEventW(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCWSTR lpName
);
最後一個參數lpName的值就是核心對象的名稱,如果為這個參數傳入NULL,表示不想為該核心對象命名。如下面的代碼建立了一個名為CTH的互斥量核心對象。
HANDLE mutexProcessA=CreateMutex(NULL,FALSE,TEXT("CTH"));
另一個進程B也想建立一個互斥量核心對象,代碼如下:
HANDLE mutexProcessB=CreateMutex(NULL,FALSE,TEXT("CTH"));
核心並不會馬上建立一個互斥量核心對象,而是會先檢查是否存在一個名為CTH的互斥兩核心對象,若有,判斷是否有許可權,若有,會將已經存在的互斥量的控制代碼複製到進程B的控制代碼表中,將互斥量的控制代碼返回給mutexProcessB,若不存在名為CTH的對象當然是建立一個,若是存在一個名為CTH的其他類型的核心對象那就會建立失敗,函數返回NULL。還是那句話核心對象屬於作業系統,跟是哪個進程建立它關係並不大,這樣也就很容易實現核心對象的共用了。
3:複製物件控點。使用函數DuplicateHandle。
DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
LPHANDLE lpTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);
這個函數擷取一個進程控制代碼表中的一個記錄項,然後在另一個進程的控制代碼表中建立該記錄項的一個副本,然後另一個進程也就能訪問這個核心對象了。
作者:陳太漢
部落格:http://www.cnblogs.com/hlxs/