PHP如何解決網站大流量與高并發(fā)的問題(二)
1014
2025-03-31
雖然內核對象位于獨立于進程之外的內核區(qū)域,我們在開發(fā)中卻只能通過調用Win32 API傳入HANDLE參數來操作內核對象(如SetEvent等)。然而HANDLE句柄只對當前進程有效,離開了當前進程該句柄就無效了(具體原因參考:Windows內核對象(1) – 內核對象與句柄)。所以說,跨進程訪問內核對象的關鍵在于我們怎么跨進程訪問句柄HANDLE?
下面介紹幾種方法來實現跨進程共享內核對象。
一、使用句柄繼承的方式
只有進程之間有父子關系時,才可以使用句柄繼承的方式。在這種情況下,父進程可以生成一個子進程,并允許子進程訪問父進程的內核對象。為了使這種繼承生效,父進程必須執(zhí)行幾個步驟:
(1). 父進程在創(chuàng)建一個內核對象時,父進程必須向系統指定它希望這個內核對象的句柄是可以繼承的。為了創(chuàng)建一個可繼承的內核對象,必須分配并初始化一個SECURITY_ATTRIBUTES結構,如:
SECURITY_ATTRIBUTES?sa; sa.nLength?=?sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle?=?TRUE;??//?可繼承的 sa.lpSecurityDescriptor?=?NULL; HANDLE?h?=?CreateEvent(&sa,?TRUE,?FALSE,?NULL);
(2). 父進程通過CreateProcess生成子進程,且指定bInheritHandles為TRUE,從而允許子進程來繼承父進程的那些“可繼承的句柄”。
//?啟動子進程TestB.exe,將句柄h作為啟動參數傳給進程TestB // TCHAR?cmd_buf[MAX_PATH]; StringCchPrintf(cmd_buf,?MAX_PATH,?TEXT("TestB.exe?%ld"),?(long)h); STARTUPINFO?si?=?{?sizeof(si)?}; PROCESS_INFORMATION?pi; BOOL?ret?=?CreateProcess( NULL,? cmd_buf,? NULL,? NULL,? TRUE,??//?指定子進程可以繼承父進程的“可繼承句柄” 0,? NULL,? NULL,? &si,? &pi ); CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
由于我們傳給bInheritHandles參數的值是TRUE,所以系統在創(chuàng)建子進程時會多做一件事情:它會遍歷父進程的句柄表,對它的每一項進行檢查,凡是包含一個有效的“可繼承的句柄”的項,都會將該項完整的復制到子進程的句柄表。在子進程的句柄表中,復制項的位置與它在父進程句柄表中的位置完全一樣(包含索引),這個就意味著:在父進程和子進程中,對一個內核對象進行標識的句柄值也是完全一樣的。所以我們只需要通過某種方式(如上面示例中的啟動參數的方式,或者環(huán)境變量的方式等任何進程間通訊的方式)將這個值告訴子進程,子進程就可以將該值轉成HANDLE,然后使用這個HANDLE來調用系統API。
二、使用DuplicateHandle方式
明白DuplicateHandle的工作原理,需要先了解進程句柄表,可以參考Windows內核對象(1) – 內核對象與句柄)
2.1 DuplicateHandle功能
DuplicateHandle函數可以將指定“源進程的句柄表”中的某一項復制到“目的進程句柄表”中(除了索引),并且返回該項在目的進程句柄表中的索引(即HADNLE)。
可以在任何時候調用DuplicateHandle函數,DuplicateHandle對源句柄是否是可繼承的沒有要求。
函數聲明如下:
BOOL?DuplicateHandle( ??HANDLE?hSourceProcessHandle, ??HANDLE?hSourceHandle, ??HANDLE?hTargetProcessHandle, ??LPHANDLE?lpTargetHandle, ??DWORD?dwDesiredAccess, ??BOOL?bInheritHandle, ??DWORD?dwOptions );
DuplicateHandle詳細介紹可以參考MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx
2.2 支持的句柄類型
DuplicateHandle函數不能復制所有類型的句柄,只能復制如下類型的句柄(從MSDN復制而來):
不同的事件類型對應的dwDesiredAccess參數不同,具體參考MSDN。
2.3 使用示例
進程TestA源碼
int?main(int?argc,?char**?argv)?{ HANDLE?h?=?CreateEvent(NULL,?TRUE,?FALSE,?NULL); //?啟動子進程TestB.exe // TCHAR?cmd_buf[MAX_PATH]; StringCchPrintf(cmd_buf,?MAX_PATH,?TEXT("D:\\TestB.exe"),?(long)h); STARTUPINFO?si?=?{?sizeof(si)?}; PROCESS_INFORMATION?pi; BOOL?ret?=?CreateProcess(NULL,?cmd_buf,?NULL,?NULL,?TRUE,?0,?NULL,?NULL,?&si,?&pi); assert(ret); assert(pi.hProcess); HANDLE?duplicated_h?=?NULL; ret?=?DuplicateHandle(GetCurrentProcess(),?h,?pi.hProcess,?&duplicated_h,?0,?FALSE,?DUPLICATE_SAME_ACCESS); WaitForSingleObject(pi.hProcess,?INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); bool?has_signal?=?WaitForSingleObject(h,?0)?==?WAIT_OBJECT_0; assert(has_signal?==?true); return?0; }
子進程TestB源碼
int?main(int?argc,?char**?argv) { long?l?=?0; printf("Input?Handle:"); scanf("%ld",?&l); HANDLE?h?=?(HANDLE)l; bool?has_signal?=?WaitForSingleObject(h,?0)?==?WAIT_OBJECT_0; assert(has_signal?==?false); SetEvent(h); ????return?0; }
在父進程TestA中創(chuàng)建一個不可繼承的事件 -> 然后啟動子進程TestB -> 調用DuplicateHandle復制句柄項到TestB進程句柄表 -> 并向TestB輸入句柄值 -> TestB訪問該事件句柄,將事件置為有信號狀態(tài)。
三、使用命名的內核對象的方式
3.1 實現原理
這種方式嚴格的說已經不是文章開頭說到的跨進程訪問句柄了,有點類似跨進程直接訪問內核對象了。
該方式實現起來比較簡單,就是在調用創(chuàng)建內核對象的Create***函數時,通過pszName參數為內核對象取一個名字。
如創(chuàng)建事件Event的函數CreateEvent:
HANDLE?WINAPI?CreateEvent( ??LPSECURITY_ATTRIBUTES?lpEventAttributes, ??BOOL?bManualReset, ??BOOL?bInitialState, ??LPCTSTR?lpName??//?指定名稱 );
HANDLE?h?=?CreateEvent(NULL,?TRUE,?FALSE,?TEXT("TestA_Obj"));
若在其他進程中要訪問這個內核對象,只需要使用打開函數Open***打開該內核對象,系統就會在進程的句柄表中插入一條記錄,并返回這條記錄的索引,也就是句柄。需要注意的是,在打開內核對象時需要留意返回值和GetLastError函數的返回值。由于內核對象是有訪問權限的,有時候雖然這個名字的內核對象存在,但該進程卻不見得有權限可以打開它,這個時候GetLastError函數會返回失敗的原因。
以打開事件的函數OpenEvent為例:
HANDLE?h?=?OpenEvent(READ_CONTROL,?FALSE,?TEXT("TestA_Obj")); if?(h?==?NULL)?{ if?(GetLastError()?==?ERROR_ACCESS_DENIED)?{?//?沒有READ_CONTROL權限 } }
3.2 全局命令空間
不同的會話(Session)有不同的內核對象命名空間(如windows服務程序位于Session 0,而普通的用戶進程位于Session 1),要通過名稱訪問其他會話中的內核對象,需要在名稱前面加上Session\<當前會話ID>。Windows提供了一個全局的內核對象命名空間,處于任何會話中的進程都可以訪問該命名空間,將內核對象放入全局命令空間的方式很簡單:只需要在內核對象名稱前加入Global\即可。
如:
HANDLE?h?=?CreateEvent(NULL,?TRUE,?FALSE,?TEXT("Global\\TestA_Obj"));
windows
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。