C++ 內核對象

轉自http://www.cnblogs.com/Adon/archive/2009/10/11/1580784.html
1. 內核對象概述內核對象是操作系統的基礎,系統內部的內核對象有:令牌(token)、事件(event)、文件(file)、文件映射(file-mapping), I/O完成端口(I/O completion port), 作業(job), 郵件mailslot, mutex, pipe, process, semaphore, thread, waitable timer, thread pool worker factory等。內核對象有如下的特點:
(1) 它只能通過內核訪問。用戶程序只能通過句柄進行操作,而且句柄是對進程特定的(不能在進程間共享),但可以通過某種方式進行傳遞。
(2) 它使用引用計數。
(3) 它的創建函數都帶有一安全參數,這是與GDI等對象的最重要區別。可以使用WinObj工具查看所有的內核對象類型。下面是創建文件映射對象的代碼示例:

SECURITY_ATTRIBUTES sa;sa.nLength = sizeof(sa);          
//用於版本化sa.lpSecurityDescriptor = pSD;   
// 已初始化的SD的地址sa.bInheritHandle = FALSE;       
// 不能在進程間共享HANDLE 
hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa,   PAGE_READWRITE, 0, 
       1024, TEXT("MyFileMapping"));

如果你是操作一個存在的內核對象,一般情況下你都要在第一個參數值上傳遞你想要的操作。如:

HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE,   TEXT("MyFileMapping"));
  1. 進程的內核對象表當一個進程創建時,系統爲它分配一個句柄表,這個表只用於內核對象而不用於UI或GDI對象。這個表的結構等是未文檔化的。它像下面這個樣子:
    Index 內在塊指針 訪問標識 標識
    1 0x???????? 0x???????? 0x????????
    2 0x???????? 0x???????? 0x????????…
    開始時這張表是空的。當進程創建內核對象時就會在這張表中分配相應的內容。所有的創建內核對象都返回句柄(如CreateThread, CreateFile, CreateFileMapping, CreateSematics等)。這個句柄值除4後(右移2位,這兩位是Microsoft內部使用)就是句柄表中真正的索引(當然這是未文檔化的,有可能會改變)。所以第一個有效的句柄值是4,而創建失敗一般返回的句柄值是0或-1(很少,如CreateFile,你應該與INVALID_HANDLER_VALUE比較)。進程調用CloseHandler關閉內核對象,當調用這個函數後,它檢查傳遞的句柄是否有效(在句柄表中檢查),如果有效就取得該對象的內存地址並減少其引用計數(如果計數爲零就從內存中清除該對象)。否則該函數返回FALSE,如果進程處於調試中,該函數會引發0xC0000008異常)注意當調用CloseHandler後,該句柄不能再使用(無效的或是已經是其它類型的對象了)。

  2. 跨進程共享內核對象一些跨進程共享內核對象的情況:
    (1) 文件映射對象允許你在同機器上不同進程間共享數據塊
    (2) Mailslot和命名管道允許你在不同機器上的進程間通過網絡傳遞數據塊
    (3) Mutex, Semaphore和事件等對象允許你在不同進程間進行同步共享方式有: 繼承(父子進程間),命名對象以及複製對象句柄

(1). 繼承方式: 通過安全描述符結構體的bInheritHandler值設置爲true就可以讓它在父子進程間繼承。

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;   
// 使得返回的句柄可繼承HANDLE 
hMutex = CreateMutex(&sa, FALSE, NULL);

如果句柄是可繼承的,它在進程的句柄表中的Flags值是0x00000001。當然要讓子進程真正使用這個句柄,父進程還需要把它傳遞給子進程,方法是在調用CreateProcess時把bInheritHandles參數值設置爲TRUE。這時系統會遍歷父進程句柄表,把可繼承的句柄複製到子進程的句柄表中。之後父進程的句柄與子進程的句柄就沒什麼關係了,也就是說父子進程都需要關閉句柄。這個過程也意味着如果父進程在創建子進程之後新建了其它的內核對象,這些對象不會被原子進程繼承。
偶而時候你可能想改變內核對象的標識,如父進程只能讓直接進程繼承而不讓孫子進程繼承,這時可以調用SetHandleInformation函數,例如要打開和關閉對象的繼承標識,分別調用如下代碼:

SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);

要查看一個內核對象句柄是否是可繼承的,可用如下方式:

DWORD dwFlags;
GetHandleInformation(hObj, &dwFlags);
BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));

(2) 命名對象
在創建內核對象的所有函數中,其最後一個參數是一PCTSTR類型的字符串,它表示對象的名字。如果創建時這個參數是NULL,就創建了匿名對象,這就是上面的情況。如果是非NULL的字符串,就創建命名對象。這個名字是全局空間(整個機器內共享,而且所有對象都共享)。例如下面的代碼:

HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("JeffObj"));
HANDLE hSem = CreateSemaphore(NULL, 1, 1, TEXT("JeffObj"));
DWORD dwErrorCode = GetLastError();

它對hSem返回NULL,而GetLastError()返回6。
如果創建內核對象時傳遞的名字已經在系統中存在同類型對象的名字,它就會返回那個名字的對象。這樣就可以進行進程間共享了。另一種方式是調用OpenXXX方法使用命名的內核對象。如果不存在該名字的對象或該名字的對象的類型不一樣,它返回NULL(這兩種情況,錯誤代碼是不一樣的)。
在創建命名對象時,名字的選取是很關鍵的,但Microsoft並沒有這方面的指導。
命名對象有很多用途,其中之一是用於防止一個程序的多人實例運行,代碼如下:

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine,
   int nCmdShow) {
   HANDLE h = CreateMutex(NULL, FALSE,
      TEXT("{FA531CC1-0497-11d3-A180-00105A276C3E}"));  //注意命名
   if (GetLastError() == ERROR_ALREADY_EXISTS) {
      // 已經有一個實例在運行了
      // 關閉對象並退出
      CloseHandle(h);
      return(0);
   }

   //這個程序的第一個實例在運行
   ...
   //退出前關閉句柄
   CloseHandle(h);
   return(0);
}

注意,這種情況在有終端服務的情況下有所改變,運行終端服務的機器對於內核對象有多個命名空間:對所有客戶會話的一個全局命名空間,這個命名空間一般用於服務。以及對於每個客戶會話一個自己的命名空間。這樣確保多個會話運行同樣的多個程序而不會相互影響。遠程桌面和快速用戶切換等功能都使用終端服務的。
在Vista以前的系統中,在任何用戶登錄前,services開始在第一個會話,它是非交互的。而在Vista系統中,只要一個用戶登錄,程序都啓動在一個新會話中(與Session0不同,它是服務進程的,這些核心組件一般都有高的優先級。所以編寫服務程序如果要與用戶程序進行內核對象共享,要注意這些問題,具體見文檔)。
如果要知道你的進程運行在哪個終端服務會話中,可用ProcessIdToSessionId查看,如下代碼:
DWORD processID = GetCurrentProcessId();
DWORD sessionID;
if (ProcessIdToSessionId(processID, &sessionID)) {
tprintf(
TEXT(“進程 ‘%u’ 運行在終端服務會話 ‘%u’”),
processID, sessionID);
} else {
// 如果沒有足夠的權限它會失敗,但這種情況在這裏不會出現,因爲我們在自己的進程ID
tprintf(
TEXT(“Unable to get Terminal Services session ID for process ‘%u’”),
processID);
}
當然,在終端服務中,程序也可通過在名字前加Global\強制命名內核對象進入全局命名空間。要顯式地表示對象在本地命名空間的,可在名字前加Local\,如下代碼:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT(“Global\MyName”));
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT(“Local\MyName”));

上面的命名內核對象有一種問題:任何程序都可以創建一個命名對象,這樣如果某個程序要實現單例運行而創建了一個內核對象,這種情況下另一程序也創建了同名的內核對象時,該單例程序就無法正常運行了。這是DoS攻擊的一種在Vista中有一種機制使得人創建的命名內核對象永遠不會和其它程序創建的對象衝突,要使用定製的前綴並把它作爲人的私有命名空間,如Global和Local,服務進程會確保爲內核對象定義一邊界描述符來保護命名空間。下面是檢查實例的代碼:

void CheckInstances() {   
// Create the boundary descriptor
g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);   
// Create a SID corresponding to the Local Administrator group   
BYTE localAdminSID[SECURITY_MAX_SID_SIZE];   
PSID pLocalAdminSID = &localAdminSID;   
DWORD cbSID = sizeof(localAdminSID);   
if (!CreateWellKnownSid(      WinBuiltinAdministratorsSid, NULL, pLocalAdminSID, &cbSID)) { 
     AddText(TEXT("AddSIDToBoundaryDescriptor failed: %u\r\n"),         GetLastError()); 
     return;   
}   
// Associate the Local Admin SID to the boundary descriptor   
// --> only applications running under an administrator user   
//     will be able to access the kernel objects in the same namespace   
if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID)) { 
     AddText(TEXT("AddSIDToBoundaryDescriptor failed: %u\r\n"),  
       GetLastError()); 
     return;   
}   
// Create the namespace for Local Administrators only   
SECURITY_ATTRIBUTES sa;   
sa.nLength = sizeof(sa);   
sa.bInheritHandle = FALSE;   
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(TEXT("D:(A;;GA;;;BA)"),
      SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL)) {
      AddText(TEXT("Security Descriptor creation failed: %u\r\n"), GetLastError());
      return;   
}   
g_hNamespace =      CreatePrivateNamespace(&sa, g_hBoundary, g_szNamespace);   
// Don't forget to release memory for the security descriptor   
LocalFree(sa.lpSecurityDescriptor);
// Check the private namespace creation resultDWORD 
dwLastError = GetLastError();
if (g_hNamespace == NULL) {
   // Nothing to do if access is denied
   // --> this code must run under a Local Administrator account
   if (dwLastError == ERROR_ACCESS_DENIED) {
      AddText(TEXT("Access denied when creating the namespace.\r\n"));
      AddText(TEXT("   You must be running as Administrator.\r\n\r\n"));
      return;
   } else {
          if (dwLastError == ERROR_ALREADY_EXISTS) {
          // If another instance has already created the namespace,
          // we need to open it instead.
             AddText(TEXT("CreatePrivateNamespace failed: %u\r\n"), dwLastError);
             g_hNamespace = OpenPrivateNamespace(g_hBoundary, g_szNamespace);
             if (g_hNamespace == NULL) {
                AddText(TEXT(" and OpenPrivateNamespace failed: %u\r\n"),
                   dwLastError);
                return;
             } else {
                g_bNamespaceOpened = TRUE;
                AddText(TEXT(" but OpenPrivateNamespace succeeded\r\n\r\n"));
             }
          } else {
             AddText(TEXT("Unexpected error occurred: %u\r\n\r\n"), 
               dwLastError);
             return;
          }
       }
    }
    // Try to create the mutex object with a name
    // based on the private namespace
    TCHAR szMutexName[64];
    StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\%s"),
       g_szNamespace, TEXT("Singleton"));
    g_hSingleton = CreateMutex(NULL, FALSE, szMutexName);
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
       // There is already an instance of this Singleton object
       AddText(TEXT("Another instance of Singleton is running:\r\n"));
       AddText(TEXT("--> Impossible to access application features.\r\n"));
    } else {
      // First time the Singleton object is created
      AddText(TEXT("First instance of Singleton:\r\n"));
      AddText(TEXT("--> Access application features now.\r\n"));
   }
}

(3) 複製內核對象最後一種跨進程邊界共享內核對象的方法是用DuplicateHandle函數進行對象複製,簡單地說,它就是一個進程的內核句柄表中找出一項,複製到另一個進程的內核句柄表。該複製有三個進程內核對象參與,下面的代碼顯示了這個過程

//下面所有的操作都由進程S執行
//創建一由進程S訪問的mutex對象
HANDLE hObjInProcessS = CreateMutex(NULL, FALSE, NULL);
//取得進程T的內核對象HANDLE 
hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE,  dwProcessIdT);
HANDLE hObjInProcessT;   
//進程T中未初始化的句柄
//讓進程T訪問那個mutex對象
DuplicateHandle(GetCurrentProcess(), hObjInProcessS, hProcessT,   &hObjInProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS);
//使用一些IPC機制把hObjInProcessS句柄值傳給進程T
//不再與進程T通訊CloseHandle(hProcessT);
//當進程S不需要mutex時,關閉它CloseHandle(hObjInProcessS);

還有另一種使用複製方法的情況:設想你有一個可讀寫的文件映射對象,但你需要調用一個函數,而該函數只允許對該文件映射對象進行寫的訪問,這時你也可以使用這種機制。具體代碼如下:

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE,   LPTSTR szCmdLine, int nCmdShow) {
   // 創建一個文件映射對象,句柄是讀寫訪問
   HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE,
      NULL, PAGE_READWRITE, 0, 10240, NULL);
   // 創建到該文件映射對象的另一種句柄,該句柄是隻讀的
   HANDLE hFileMapRO;
   DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),
      &hFileMapRO, FILE_MAP_READ, FALSE, 0);
   // 調用函數   ReadFromTheFileMapping(hFileMapRO);
   //關閉只讀的文件映射對象   CloseHandle(hFileMapRO);
   // We can still read/write the file-mapping object using hFileMapRW.   
   // When the main code doesn't access the file mapping anymore,
   // close it.
   CloseHandle(hFileMapRW);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章