??
字號:
雙方都打開了一個共享內(nèi)存區(qū)對象以后,就可以各自通過NtMapViewOfSection()將其映射到自己的用戶空間。
[code]NTSTATUS STDCALL
NtMapViewOfSection(IN HANDLE SectionHandle,
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress OPTIONAL,
IN ULONG ZeroBits OPTIONAL,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PULONG ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType OPTIONAL,
IN ULONG Protect)
{
. . . . . .
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
SafeBaseAddress = NULL;
SafeSectionOffset.QuadPart = 0;
SafeViewSize = 0;
_SEH_TRY. . . . . . _SEH_END;
. . . . . .
}
else
{
SafeBaseAddress = (BaseAddress != NULL ? *BaseAddress : NULL);
SafeSectionOffset.QuadPart = (SectionOffset != NULL ? SectionOffset->QuadPart : 0);
SafeViewSize = (ViewSize != NULL ? *ViewSize : 0);
}
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(PVOID)&Process,
NULL);
. . . . . .
AddressSpace = &Process->AddressSpace;
Status = ObReferenceObjectByHandle(SectionHandle,
SECTION_MAP_READ,
MmSectionObjectType,
PreviousMode,
(PVOID*)(PVOID)&Section,
NULL);
. . . . . .
Status = MmMapViewOfSection(Section, Process,
(BaseAddress != NULL ? &SafeBaseAddress : NULL),
ZeroBits, CommitSize,
(SectionOffset != NULL ? &SafeSectionOffset : NULL),
(ViewSize != NULL ? &SafeViewSize : NULL),
InheritDisposition, AllocationType, Protect);
ObDereferenceObject(Section);
ObDereferenceObject(Process);
. . . . . .
return(Status);
}[/code]
以前講過,NtMapViewOfSection()并不是專為當(dāng)前進程的,而是可以用于任何已打開的進程,所以參數(shù)中既有共享內(nèi)存區(qū)對象的Handle,又有進程對象的Handle。從這個意義上說,兩個進程之間通過共享內(nèi)存區(qū)的通信完全可以由第三方撮合、包辦而成,但是一般還是由通信雙方自己加以映射的。顯然,這里實質(zhì)性的操作是MmMapViewOfSection(),那是存儲管理底層的事了,我們就不再追究下去了。
一旦把同一個共享內(nèi)存區(qū)映射到了通信雙方的用戶空間(可能在不同的地址上),出現(xiàn)在這雙方用戶空間的特定地址區(qū)間就落實到了同一組物理頁面。這樣,一方往里面寫的內(nèi)容就可以為另一方所見,于是就可以通過普通的內(nèi)存訪問(例如通過指針)實現(xiàn)進程間通信了。
但是,如前所述,通過共享內(nèi)存區(qū)實現(xiàn)的只是原始的、低效的進程間通信,因為共享內(nèi)存區(qū)只滿足了前述三個條件中的兩個,只是提供了信息的(實時)傳輸,但是卻缺乏進程間的同步。所以實際使用時需要或者結(jié)合別的進程間通信(同步)手段,或者由應(yīng)用程序自己設(shè)法實現(xiàn)同步(例如定時查詢)。
2. 信號量(Semaphore)
學(xué)過操作系統(tǒng)原理的讀者想必知道“臨界區(qū)”和“信號量”、以及二者之間的關(guān)系。如果沒有學(xué)過,那也不要緊,不妨把臨界區(qū)想像成一個憑通行證入內(nèi)的工作場所,作為對象存在的“信號量”則是發(fā)放通行證的“票務(wù)處”。但是,通行證的數(shù)量是有限的,一般只有寥寥幾張。一旦發(fā)完,想要領(lǐng)票的進程(線程)就只好睡眠等待,直到已經(jīng)在里面干活的進程(線程)完成了操作以后退出臨界區(qū)并交還通行證,才會被喚醒并領(lǐng)到通行證進入臨界區(qū)。之所以稱之為(翻譯為)“信號量”,是因為“票務(wù)處”必須維持一個數(shù)值,表明當(dāng)前手頭還有幾張通行證,這就是作為數(shù)值的“信號量”。所以,“信號量”是一個對象,對象中有個數(shù)值,而信號量這個名稱就是因這個數(shù)值而來。那么,為什么要到臨界區(qū)里面去進行某些操作呢?一般是因為這些操作不允許受到干擾,必須排它地進行,或者說有互斥要求。
在操作系統(tǒng)理論中,“領(lǐng)票”操作稱為P操作,具體的操作如下:
l 遞減信號量的數(shù)值。
l 如果遞減后大于或等于0就說明領(lǐng)到了通行證,因而就進入了臨界區(qū),可以接著進行想要做的操作了。
l 反之如果遞減后小于0,則說明通行證已經(jīng)發(fā)完,當(dāng)前線程只好(主動)在臨界區(qū)的大門口睡眠,直至有通行證可發(fā)時才被喚醒。
l 如果信號量的數(shù)值小于0,那么其絕對值表明正在睡眠等待的線程個數(shù)。
領(lǐng)到票進入了臨界區(qū)的線程,在完成了操作以后應(yīng)從臨界區(qū)退出、并交還通行證,這個操作稱為V操作,具體的操作如下:
l 遞增信號量的數(shù)值。
l 如果遞增以后的數(shù)值小于等于0,就說明有進程正在等待,因而需要加以喚醒。
這里,執(zhí)行了P操作的進程稍后就會執(zhí)行V操作,但是也可以把這兩種操作分開來,讓有的進程光執(zhí)行V操作,另一些進程則光執(zhí)行P操作。于是,這兩種進程就成了供應(yīng)者/消費者的關(guān)系,前者是供應(yīng)者,后者是消費者,而信號量的P/V操作正好可以被用作進程間的睡眠/喚醒機制。例如,假定最初時“通行證”的數(shù)量為0,并把它理解為籌碼。每當(dāng)寫入方在共享內(nèi)存區(qū)中寫入數(shù)據(jù)以后就對信號量執(zhí)行一次V操作,而每當(dāng)讀出方想要讀出新的數(shù)據(jù)時就先執(zhí)行一次P操作。所以,許多別的進程間同步機制其實只是“信號量”的變種,是由“信號量”派生、演變而來的。
注意在條件不具備時進入睡眠、以及在條件具備時加以喚醒,是P操作和V操作固有的一部分,否則就退化成對于標(biāo)志位或數(shù)值的測試和設(shè)置了。當(dāng)然,“測試和設(shè)置”也是進程間同步的手段之一,但是那只相當(dāng)于輪詢,一般而言效率是很低的。所以,離開(睡眠)等待和喚醒,就不足于構(gòu)成高效的進程間通信機制。但是也有例外,那就是如果能肯定所等待的條件在一個很短的時間內(nèi)一定會得到滿足,那就不妨不斷地反復(fù)測試直至成功,這就是“空轉(zhuǎn)鎖(SpinLock)”的來歷。不過空轉(zhuǎn)鎖一般只是在內(nèi)核中使用,而不提供給用戶空間。
信號量對象的創(chuàng)建和打開是由NtCreateSemaphore()和NtOpenSemaphore()實現(xiàn)的,我們看一下NtCreateSemaphore():
[code]NTSTATUS
STDCALL
NtCreateSemaphore(OUT PHANDLE SemaphoreHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN LONG InitialCount,
IN LONG MaximumCount)
{
. . . . . .
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
. . . . . .
if(PreviousMode != KernelMode) {
_SEH_TRY . . . . . ._SEH_END;
if(!NT_SUCCESS(Status)) return Status;
}
. . . . . .
/* Create the Semaphore Object */
Status = ObCreateObject(PreviousMode, ExSemaphoreObjectType,
ObjectAttributes, PreviousMode, NULL,
sizeof(KSEMAPHORE), 0, 0, (PVOID*)&Semaphore);
/* Check for Success */
if (NT_SUCCESS(Status)) {
/* Initialize it */
KeInitializeSemaphore(Semaphore, InitialCount, MaximumCount);
/* Insert it into the Object Tree */
Status = ObInsertObject((PVOID)Semaphore, NULL,
DesiredAccess, 0, NULL, &hSemaphore);
ObDereferenceObject(Semaphore);
/* Check for success and return handle */
if(NT_SUCCESS(Status)) {
_SEH_TRY . . . . . ._SEH_END;
}
}
/* Return Status */
return Status;
}[/code]
這個函數(shù)可以說是對象創(chuàng)建函數(shù)的樣板(Template)。一般而言,創(chuàng)建對象的過程總是涉及三步主要操作:
1. 通過ObCreateObject()創(chuàng)建對象,對象的類型代碼決定了具體的對象種類。對于信號量,這個類型代碼是ExSemaphoreObjectType。所創(chuàng)建的對象被掛入內(nèi)核中該種類型的對象隊列(類似于文件系統(tǒng)的目錄),以備別的進程打開。這個函數(shù)返回一個指向具體對象數(shù)據(jù)結(jié)構(gòu)的指針,數(shù)據(jù)結(jié)構(gòu)的類型取決于對象的類型代碼。
2. 類似于KeInitializeSemaphore()那樣的初始化函數(shù),對所創(chuàng)建對象的數(shù)據(jù)結(jié)構(gòu)進行初始化。
3. 通過ObInsertObject()將所創(chuàng)建對象的數(shù)據(jù)結(jié)構(gòu)指針填入當(dāng)前進程的打開對象表,并返回相應(yīng)的Handle。所以,創(chuàng)建對象實際上是創(chuàng)建并打開一個對象。
對于信號量,ObCreateObject()返回的是KSEMAPHORE數(shù)據(jù)結(jié)構(gòu)指針:
[code]typedef struct _KSEMAPHORE {
DISPATCHER_HEADER Header;
LONG Limit;
} KSEMAPHORE, *PKSEMAPHORE, *RESTRICTED_POINTER PRKSEMAPHORE;[/code]
這里的Limit是信號量數(shù)值的上限,來自前面的調(diào)用參數(shù)MaximumCount。而參數(shù)InitialCount的數(shù)值、即信號量的初值,則記錄在DISPATCHER_HEADER內(nèi)的SignalState字段中。所以,信號量對象將其頭部的SignalState字段用作了“信號量”。
NtOpenSemaphore()的代碼就不看了,所有的打開對象操作都是一樣的,基本上就是通過內(nèi)核函數(shù)ObOpenObjectByName()根據(jù)對象名(路徑名)找到目標(biāo)對象,然后將它的數(shù)據(jù)結(jié)構(gòu)指針填入本進程的打開對象表,并返回相應(yīng)的Handle。
讀者可能已經(jīng)在急切想要知道信號量的P/V操作是怎么實現(xiàn)的。也許會使讀者感到意外,Windows并沒有專為信號量的P操作而設(shè)的系統(tǒng)調(diào)用,信號量的P操作就是通過NtWaitForSingleObject()或NtWaitForMultipleObjects()實現(xiàn)的。事實上,所有與P操作類似、會使調(diào)用者阻塞的操作都是由這兩個函數(shù)實現(xiàn)的。讀者已經(jīng)在上一篇漫談中看過NtWaitForSingleObject()的代碼,這里就不重復(fù)了。
信號量的V操作倒是有專門的系統(tǒng)調(diào)用,那就是NtReleaseSemaphore()。
[code]NTSTATUS
STDCALL
NtReleaseSemaphore(IN HANDLE SemaphoreHandle,
IN LONG ReleaseCount,
OUT PLONG PreviousCount OPTIONAL)
{
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
. . . . . .
if(PreviousCount != NULL && PreviousMode == UserMode) {
_SEH_TRY . . . . . ._SEH_END;
if(!NT_SUCCESS(Status)) return Status;
}
. . . . . .
/* Get the Object */
Status = ObReferenceObjectByHandle(SemaphoreHandle,
SEMAPHORE_MODIFY_STATE,
ExSemaphoreObjectType, PreviousMode,
(PVOID*)&Semaphore, NULL);
/* Check for success */
if (NT_SUCCESS(Status)) {
/* Release the semaphore */
LONG PrevCount = KeReleaseSemaphore(Semaphore, IO_NO_INCREMENT,
ReleaseCount, FALSE);
ObDereferenceObject(Semaphore);
/* Return it */
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -