??
字號(hào):
漫談兼容內(nèi)核之十六:Windows的進(jìn)程間通信
[i]毛德操[/i]
對(duì)于任何一個(gè)現(xiàn)代的操作系統(tǒng),進(jìn)程間通信都是其系統(tǒng)結(jié)構(gòu)的一個(gè)重要組成部分。而說(shuō)到Windows的進(jìn)程(線程)間通信,那就要看是在什么意義上說(shuō)了。因?yàn)檎纭癢indows的跨進(jìn)程操作”那篇漫談中所述,在Windows上一個(gè)進(jìn)程甚至可以“打開”另一個(gè)進(jìn)程,并在對(duì)方的用戶空間分配內(nèi)存、再把程序或數(shù)據(jù)拷貝過(guò)去,最后還可以在目標(biāo)進(jìn)程中創(chuàng)建一個(gè)線程、讓它為所欲為。顯然,這已經(jīng)不只是進(jìn)程間的“通信”,而是進(jìn)程間“操縱”了。但是這畢竟屬于另類,我們?cè)谶@里要談?wù)摰氖恰罢?guī)”的進(jìn)程間通信。
不管是兩個(gè)甚么樣的實(shí)體,凡是要通信就得滿足一個(gè)必要條件,那就是存在雙方都可以訪問(wèn)的介質(zhì)。顧名思義,進(jìn)程間通信就是在不同進(jìn)程之間傳播或交換信息,那么不同進(jìn)程之間存在著什么雙方都可以訪問(wèn)的介質(zhì)呢?進(jìn)程的用戶空間是互相獨(dú)立的,一般而言是不能互相訪問(wèn)的,唯一的例外是共享內(nèi)存區(qū)。但是,系統(tǒng)空間卻是“公共場(chǎng)所”,所以內(nèi)核顯然可以提供這樣的條件。除此以外,那就是雙方都可以訪問(wèn)的外設(shè)了。在這個(gè)意義上,兩個(gè)進(jìn)程當(dāng)然也可以通過(guò)磁盤上的普通文件交換信息,或者通過(guò)“注冊(cè)表”或其它數(shù)據(jù)庫(kù)中的某些表項(xiàng)和記錄交換信息。廣義上這也是進(jìn)程間通信的手段,但是一般都不把這算作“進(jìn)程間通信”。因?yàn)槟切┩ㄐ攀侄蔚男侍土?,而人們?duì)進(jìn)程間通信的要求是要有一定的實(shí)時(shí)性。
但是,對(duì)于實(shí)際的應(yīng)用而言,光有信息傳播的實(shí)時(shí)性往往還不夠。不妨以共享內(nèi)存區(qū)(Section)為例來(lái)說(shuō)明這個(gè)問(wèn)題。共享內(nèi)存區(qū)顯然可以用作進(jìn)程間通信的手段,兩個(gè)進(jìn)程把同一組物理內(nèi)存頁(yè)面分別映射到自己的用戶空間,然后一個(gè)進(jìn)程往里面寫,另一個(gè)進(jìn)程就可以讀到所寫入的內(nèi)容。從信息傳播的角度看,這個(gè)過(guò)程是“即時(shí)”的,有著很高的實(shí)時(shí)性,但是讀取者怎么知道寫入者已經(jīng)寫入了一些數(shù)據(jù)呢?要是共享內(nèi)存區(qū)的物理頁(yè)面能產(chǎn)生中斷請(qǐng)求就好了,可是它不能。讓讀取者輪詢、或者定時(shí)輪詢、那當(dāng)然也可以,但是效率就降下來(lái)了。所以,這里還需要有通信雙方行為上的協(xié)調(diào)、或稱進(jìn)程間的“同步”。注意所謂“同步”并不是說(shuō)雙方應(yīng)該同時(shí)讀或同時(shí)寫,而是讓雙方的行為得以有序、緊湊地進(jìn)行。
綜上所述,一般所說(shuō)的“進(jìn)程間通信”其實(shí)是狹義的、帶限制條件的。總的來(lái)說(shuō),對(duì)于進(jìn)程間通信有三方面的要求:
l 具有不同進(jìn)程之間傳播或交換信息的手段
l 進(jìn)程間傳播或交換信息的手段應(yīng)具有一定程度的實(shí)時(shí)性
l 具有進(jìn)程間的協(xié)調(diào)(同步)機(jī)制。
此外,“進(jìn)程間通信”一般是指同一臺(tái)機(jī)器上的進(jìn)程間通信。通過(guò)網(wǎng)絡(luò)或通信鏈路進(jìn)行的跨主機(jī)的通信一般不歸入進(jìn)程間通信的范疇,雖然這種通信通常也確實(shí)是發(fā)生于進(jìn)程之間。不過(guò)網(wǎng)絡(luò)通信往往也可以作用于本機(jī)的不同進(jìn)程之間,這里并沒(méi)有明確的界線。這樣一來(lái)范圍就廣了,所以本文在介紹Windows的進(jìn)程間通信時(shí)以其內(nèi)核是否專門為具體的機(jī)制提供了系統(tǒng)調(diào)用為準(zhǔn)。這樣,例如用于網(wǎng)絡(luò)通信的Winsock機(jī)制是作為設(shè)備驅(qū)動(dòng)實(shí)現(xiàn)的,內(nèi)核并沒(méi)有為此提供專門的系統(tǒng)調(diào)用,所以本文就不把它算作進(jìn)程間通信。
先看上面三方面要求的第一項(xiàng),即同一機(jī)器上的不同進(jìn)程之間傳播或交換信息的手段,這無(wú)非就是幾種可能:
l 通過(guò)用戶空間的共享內(nèi)存區(qū)。
l 通過(guò)內(nèi)核中的變量、數(shù)據(jù)結(jié)構(gòu)、或緩沖區(qū)。
l 通過(guò)外設(shè)的存儲(chǔ)效應(yīng)。但是一般所講操作系統(tǒng)內(nèi)核的“進(jìn)程間通信”機(jī)制都把這排除在外。
由于通過(guò)外設(shè)進(jìn)行的進(jìn)程間通信一般而言實(shí)時(shí)性不是很好,所以考慮到上述第二方面的要求就把它給排除掉了。
再看進(jìn)程間的同步機(jī)制。如前所述,進(jìn)程間同步的目的是要讓通信的雙方(或多方) 行為得以有序、緊湊地進(jìn)行。所以本質(zhì)上就是雙方(或多方)之間的等待(睡眠)/喚醒機(jī)制,這就是為什么要在上一篇漫談中先介紹等待/喚醒機(jī)制的原因。注意這里的“等待”意味著主動(dòng)進(jìn)入睡眠,一般而言,所謂“進(jìn)程間同步”就是建立在(主動(dòng))睡眠/喚醒機(jī)制基礎(chǔ)上的同步。不主動(dòng)進(jìn)入睡眠的同步也是有的,例如“空轉(zhuǎn)鎖(Spinlock)”就是,但是那樣太浪費(fèi)CPU資源了。再說(shuō),在單CPU的系統(tǒng)中,如果是在調(diào)度禁區(qū)中使用Spinlock,還會(huì)引起死鎖。所以,一般不把Spinlock算作進(jìn)程間同步手段。在操作系統(tǒng)理論中,“信號(hào)量(Semaphore)”是基本的進(jìn)程間同步機(jī)制,別的大都是在此基礎(chǔ)上派生出來(lái)的。
另一方面,進(jìn)程間同步的實(shí)現(xiàn)本身就需要有進(jìn)程間的信息傳遞作為基礎(chǔ),例如“喚醒”這個(gè)動(dòng)作就蘊(yùn)含著信息的傳遞。所以,進(jìn)程間同步其實(shí)也是進(jìn)程間通信,只不過(guò)是信息量比較小、或者很小的進(jìn)程間通信。換言之,帶有進(jìn)程間同步的進(jìn)程間通信,實(shí)際上就是先以少量信息的傳遞使雙方的行為得到協(xié)調(diào),再在此基礎(chǔ)上交換比較大量的信息。如果需要傳遞的信息量本來(lái)就很小,那么這里的第二步也就不需要了。所以,進(jìn)程間同步就是(特殊的)進(jìn)程間通信。
注意這里所說(shuō)的進(jìn)程間通信實(shí)際上是線程間通信,特別是分屬于不同進(jìn)程的線程之間的通信。因?yàn)樵赪indows中線程才是運(yùn)行的實(shí)體,而進(jìn)程不是。但是上述的原理同樣適用于同一進(jìn)程內(nèi)部的線程間通信。屬于同一進(jìn)程的線程共享同一個(gè)用戶空間,所以整個(gè)用戶空間都成了共享內(nèi)存區(qū)。如果兩個(gè)線程都訪問(wèn)同一個(gè)變量或數(shù)據(jù)結(jié)構(gòu),那么實(shí)際上就構(gòu)成了線程間通信(或許是在不知不覺(jué)間)。這里仍有雙方如何同步的問(wèn)題,但是既然是共享用戶空間,就有可能在用戶空間構(gòu)筑這樣的同步機(jī)制。所以,一般而言,進(jìn)程間通信需要內(nèi)核的支持,而同一進(jìn)程中的線程間通信則也可以在用戶空間(例如在DLL中)實(shí)現(xiàn)。在下面的敘述中,“進(jìn)程間通信”和“線程間通信”這兩個(gè)詞常常是混用的,讀者應(yīng)注意領(lǐng)會(huì)。
Windows內(nèi)核所支持的進(jìn)程間通信手段有:
l 共享內(nèi)存區(qū)(Section)。
l 信號(hào)量(Semaphore)。
l 互斥門(Mutant)。
l 事件(Event)。
l 特殊文件“命名管道(Named Pipe)”和“信箱(Mail Slot)”。
此外,本地過(guò)程調(diào)用、即LPC,雖然并非以進(jìn)程間通信機(jī)制的面貌出現(xiàn),實(shí)際上卻是建立在進(jìn)程間通信的基礎(chǔ)上,并且本身就是一種獨(dú)特的進(jìn)程間通信機(jī)制。還有,Windows的Win32K模塊提供了一種線程之間的報(bào)文傳遞機(jī)制,一般用作“窗口”之間的通信手段,顯然也應(yīng)算作進(jìn)程間通信,只不過(guò)這是由Win32K的“擴(kuò)充系統(tǒng)調(diào)用”支持的,而并非由基本的系統(tǒng)調(diào)用所支持。所以,還應(yīng)增加以下兩項(xiàng)。
l 端口(Port)和本地過(guò)程調(diào)用(LPC)。
l 報(bào)文(Message)。
注:“Undocumented Windows 2000 Secrets”書中還列出了另一組用于“通道(Channel)”的系統(tǒng)調(diào)用,例如NtOpenChannel()、NtListenChannel()、NtSendWaitReplyChannel()等等,從這些系統(tǒng)調(diào)用函數(shù)名看來(lái),這應(yīng)該也是一種進(jìn)程間通信機(jī)制,但是“Windows NT/2000 Native API Reference”書中說(shuō)這些函數(shù)均未實(shí)現(xiàn),調(diào)用后只是返回出錯(cuò)代碼“STATUS_NOT_IMPLEMENTED”,“Microsoft Windows Internals”書中則并未提及。在ReactOS的代碼中也未見實(shí)現(xiàn)。
下面逐一作些介紹。
1. 共享內(nèi)存區(qū)(Section)
如前所述,共享內(nèi)存區(qū)是可以用于進(jìn)程間通信的。但是,離開進(jìn)程間同步機(jī)制,它的效率就不會(huì)高,所以共享內(nèi)存區(qū)單獨(dú)使用并不是一種有效的進(jìn)程間通信機(jī)制。
使用的方法是:先以雙方約定的名字創(chuàng)建一個(gè)Section對(duì)象,各自加以打開,再各自將其映射到自己的用戶空間,然后就可以通過(guò)常規(guī)的內(nèi)存讀寫(例如通過(guò)指針)進(jìn)行通信了。
要通過(guò)共享內(nèi)存區(qū)進(jìn)行通信時(shí),首先要通過(guò)NtCreateSection()創(chuàng)建一個(gè)共享內(nèi)存區(qū)對(duì)象。從程序的結(jié)構(gòu)看,幾乎所有對(duì)象的創(chuàng)建、即所有形似NtCreateXYZ()的函數(shù)的代碼都是基本相同的,所以下面列出NtCreateSection()的代碼,以后對(duì)類似的代碼就不再列出了。
[code]NTSTATUS STDCALL
NtCreateSection (OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection OPTIONAL,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL)
{
. . . . . .
PreviousMode = ExGetPreviousMode();
if(MaximumSize != NULL && PreviousMode != KernelMode)
{
_SEH_TRY
{
ProbeForRead(MaximumSize,
sizeof(LARGE_INTEGER),
sizeof(ULONG));
/* make a copy on the stack */
SafeMaximumSize = *MaximumSize;
MaximumSize = &SafeMaximumSize;
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
if(!NT_SUCCESS(Status))
{
return Status;
}
}
/*
* Check the protection
*/
if ((SectionPageProtection & PAGE_FLAGS_VALID_FROM_USER_MODE) !=
SectionPageProtection)
{
return(STATUS_INVALID_PAGE_PROTECTION);
}
Status = MmCreateSection(&SectionObject, DesiredAccess, ObjectAttributes,
MaximumSize, SectionPageProtection,
AllocationAttributes, FileHandle, NULL);
if (NT_SUCCESS(Status))
{
Status = ObInsertObject ((PVOID)SectionObject, NULL,
DesiredAccess, 0, NULL, SectionHandle);
ObDereferenceObject(SectionObject);
}
return Status;
}[/code]
雖然名曰“Create”,實(shí)際上卻是“創(chuàng)建并打開”,參數(shù)SectionHandle就是用來(lái)返回打開后的Handle。參數(shù)DesiredAccess說(shuō)明所創(chuàng)建的對(duì)象允許什么樣的訪問(wèn),例如讀、寫等等。ObjectAttributes則說(shuō)明對(duì)象的名稱,打開以后是否允許遺傳,以及與對(duì)象保護(hù)有關(guān)的特性、例如訪問(wèn)權(quán)限等等。這幾個(gè)參數(shù)對(duì)于任何對(duì)象的創(chuàng)建都一樣,而其余幾個(gè)參數(shù)就是專為共享內(nèi)存區(qū)的特殊需要而設(shè)的了。其中MaximumSize當(dāng)然是共享內(nèi)存區(qū)大小的上限,而SectionPageProtection與頁(yè)面的保護(hù)有關(guān)。AllocationAttributes通過(guò)一些標(biāo)志位說(shuō)明共享區(qū)的性質(zhì)和用途,例如可執(zhí)行映像或數(shù)據(jù)文件。最后,共享緩沖區(qū)往往都是以磁盤文件作為后盾的,為此需要先創(chuàng)建或打開相應(yīng)的文件,然后把FileHandle作為參數(shù)傳給NtCreateSection()。不過(guò)用于進(jìn)程間通信的共享內(nèi)存區(qū)是空白頁(yè)面,其內(nèi)容并非來(lái)自某個(gè)文件,所以FileHandle為NULL。
顯然,創(chuàng)建共享內(nèi)存區(qū)的實(shí)質(zhì)性操作是由MmCreateSection()完成的。對(duì)于其它的對(duì)象,往往也都有類似的函數(shù)。我們看一下MmCreateSection()的代碼:
[code][NtCreateSection() > MmCreateSection()]
NTSTATUS STDCALL
MmCreateSection (OUT PSECTION_OBJECT * SectionObject,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL,
IN PFILE_OBJECT File OPTIONAL)
{
if (AllocationAttributes & SEC_IMAGE)
{
return(MmCreateImageSection(SectionObject, DesiredAccess, ObjectAttributes,
MaximumSize, SectionPageProtection,
AllocationAttributes, FileHandle));
}
if (FileHandle != NULL)
{
return(MmCreateDataFileSection(SectionObject, DesiredAccess, ObjectAttributes,
MaximumSize, SectionPageProtection,
AllocationAttributes, FileHandle));
}
return(MmCreatePageFileSection(SectionObject, DesiredAccess, ObjectAttributes,
MaximumSize, SectionPageProtection, AllocationAttributes));
}[/code]
參數(shù)AllocationAttributes中的SEC_IMAGE標(biāo)志位為1表示共享內(nèi)存區(qū)的內(nèi)容是可執(zhí)行映像(因而必需符合可執(zhí)行映像的頭部結(jié)構(gòu))。而FileHandle為1表示共享內(nèi)存區(qū)的內(nèi)容來(lái)自文件,既然不是可執(zhí)行映像那就是數(shù)據(jù)文件了;否則就并非來(lái)自文件,那就是用于進(jìn)程間通信的空白頁(yè)面了。最后一個(gè)參數(shù)File的用途不明,似乎并無(wú)必要。我們現(xiàn)在關(guān)心的是空白頁(yè)面的共享內(nèi)存區(qū),具體的對(duì)象是由MmCreatePageFileSection()創(chuàng)建的,我們就不往下看了。注意這里還不涉及共享內(nèi)存區(qū)的地址,因?yàn)樯形从成洹? 參與通信的雙方通過(guò)同一個(gè)共享內(nèi)存區(qū)進(jìn)行通信,所以不能各建各的共享內(nèi)存區(qū),至少有一方需要打開已經(jīng)創(chuàng)建的共享內(nèi)存區(qū),這是通過(guò)NtOpenSection()完成的:
[code]NTSTATUS STDCALL
NtOpenSection(PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes)
{
HANDLE hSection;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status = STATUS_SUCCESS;
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
_SEH_TRY. . . . . . _SEH_END;
}
Status = ObOpenObjectByName(ObjectAttributes, MmSectionObjectType,
NULL, PreviousMode, DesiredAccess,
NULL, &hSection);
if(NT_SUCCESS(Status))
{
_SEH_TRY
{
*SectionHandle = hSection;
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
}
return(Status);
}[/code]
這里實(shí)質(zhì)性的操作是ObOpenObjectByName(),讀者想必已經(jīng)熟悉。
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -