?? 漫談兼容內核之十八:windows的lpc機制.txt
字號:
ExFreePool(CReply);
ObDereferenceObject(NamedPort);
return (STATUS_SUCCESS);
}
/* Prepare the connection. */
if (WriteMap != NULL)
{
PSECTION_OBJECT SectionObject;
LARGE_INTEGER SectionOffset;
Status = ObReferenceObjectByHandle(WriteMap->SectionHandle,
SECTION_MAP_READ | SECTION_MAP_WRITE,
MmSectionObjectType, UserMode, (PVOID*)&SectionObject, NULL);
. . . . . .
SectionOffset.QuadPart = WriteMap->SectionOffset;
WriteMap->TargetViewBase = 0;
CReply->ReceiveClientViewSize = WriteMap->ViewSize;
Status = MmMapViewOfSection(SectionObject, CRequest->ConnectingProcess,
&WriteMap->TargetViewBase, 0, CReply->ReceiveClientViewSize,
&SectionOffset, &CReply->ReceiveClientViewSize,
ViewUnmap, 0, PAGE_READWRITE);
. . . . . .
WriteMap->ViewBase = 0;
Status = MmMapViewOfSection(SectionObject, PsGetCurrentProcess(),
&WriteMap->ViewBase, 0, WriteMap->ViewSize,
&SectionOffset, &WriteMap->ViewSize,
ViewUnmap, 0, PAGE_READWRITE);
. . . . . .
ObDereferenceObject(SectionObject);
}
if (ReadMap != NULL && CRequest->SendSectionObject != NULL)
{
LARGE_INTEGER SectionOffset;
SectionOffset = CRequest->SendSectionOffset;
ReadMap->ViewSize = CRequest->SendViewSize;
ReadMap->ViewBase = 0;
Status = MmMapViewOfSection(
CRequest->SendSectionObject, PsGetCurrentProcess(),
&ReadMap->ViewBase, 0, CRequest->SendViewSize,
&SectionOffset, &CRequest->SendViewSize,
ViewUnmap, 0, PAGE_READWRITE);
. . . . . .
}
/* Finish the reply. */
if (ReadMap != NULL)
{
CReply->SendServerViewBase = ReadMap->ViewBase;
}
else
{
CReply->SendServerViewBase = 0;
}
if (WriteMap != NULL)
{
CReply->ReceiveClientViewBase = WriteMap->TargetViewBase;
}
CReply->MaximumMessageSize = PORT_MAX_MESSAGE_LENGTH;
/* Connect the two ports */
OurPort->OtherPort = ConnectionRequest->Sender;
OurPort->OtherPort->OtherPort = OurPort;
EiReplyOrRequestPort(ConnectionRequest->Sender,
(PLPC_MESSAGE)CReply, LPC_REPLY, OurPort);
ExFreePool(ConnectionRequest);
ExFreePool(CReply);
ObDereferenceObject(OurPort);
ObDereferenceObject(NamedPort);
return (STATUS_SUCCESS);
}[/code]
如果接受連接請求,那么服務方也要創建一個通信端口,因為原來的連接端口是專門用來接收連接請求的。第一個參數ServerPortHandle就是用來返回新建通信端口的Handle。而NamedPortHandle當然就是連接端口的Handle,這是本次操作的目標對象。
參數AcceptIt表示是否接受連接請求。
參數WriteMap和ReadMap與NtConnectPort()中所用者相同。同樣,如果預期需要發送的數據量較大的話,服務方也要為此提供一個共享內存區。
先看不接受連接請求時的情況,因為這比較簡單。這就是條件語句if (!AcceptIt)里面的操作:先將一個“拒絕連接”報文、即類型為LPC_CONNECTION_REFUSED的報文、通過EiReplyOrRequestPort()掛入對方端口的報文隊列,然后在對方端口的“信號量”上執行一次V操作,以喚醒正在等待的對方線程。這樣就行了。
接受連接請求時的情況就比較復雜一點:
1. 先創建一個通信端口,就是類型為EPORT_TYPE_SERVER_COMM_PORT的端口。
2. 然后為應答報文LpcMessage準備好一個內核版本、就是類型為EPORT_CONNECT_REPLY_MESSAGE的數據結構Creply。
3. 處理共享內存區的映射。注意這里做了三次映射:
? l 把由服務方提供的共享內存區映射到客戶進程的用戶空間,這是客戶方的接收區。“連接請求”報文中的ConnectingProcess提供了指向客戶進程的EPROCESS數據結構的指針。
? l 把由服務方提供的共享內存區映射到服務方自己的用戶空間,這是服務方進程的寫入區。
? l 把由客戶方提供的共享內存區映射到服務方的用戶空間,這是服務方進程的讀出區。
注意這里在調用MmMapViewOfSection()時所給定的地址都是0,表示聽從分配。所分配的地址要通過WriteMap和ReadMap返回到用戶空間,特別是替客戶方進程代為映射的地址要通過應答報文發送給對方。
4. 使服務方通信端口和客戶方通信端口的指針OtherPort互相指向對方,即建立連接。
5. 通過EiReplyOrRequestPort()將應答報文掛入客戶方端口的報文隊列,但是并不喚醒客戶方線程。
在完成了NtAcceptConnectPort()以后,服務方線程還需要對新創建的通信端口執行一下另一個系統調用NtCompleteConnectPort()。目的在于喚醒客戶方線程。注意此時的操作對象已經是新創建的通信端口,而不再是連接端口。
[code]NTSTATUS STDCALL
NtCompleteConnectPort (HANDLE hServerSideCommPort)
{
NTSTATUS Status;
PEPORT ReplyPort;
. . . . . .
/* Ask Ob to translate the port handle to EPORT */
Status = ObReferenceObjectByHandle (hServerSideCommPort, PORT_ALL_ACCESS,
LpcPortObjectType, UserMode, (PVOID*)&ReplyPort, NULL);
. . . . . .
/* Verify EPORT type is a server-side reply port; otherwise tell the caller
the port handle is not valid. */
if (ReplyPort->Type != EPORT_TYPE_SERVER_COMM_PORT)
{
ObDereferenceObject (ReplyPort);
return STATUS_INVALID_PORT_HANDLE;
}
ReplyPort->State = EPORT_CONNECTED_SERVER;
/* Wake up the client thread that issued NtConnectPort. */
KeReleaseSemaphore(&ReplyPort->OtherPort->Semaphore,
IO_NO_INCREMENT, 1, FALSE);
/* Tell Ob we are no more interested in ReplyPort */
ObDereferenceObject (ReplyPort);
return (STATUS_SUCCESS);
}[/code]
前面,在NtAcceptConnectPort()的代碼中,雖然已經將應答報文掛入了客戶方端口的接收隊列,卻并未喚醒客戶方線程。現在就通過對其信號量的V操作將其喚醒。
這樣,就建立起了客戶方與服務方的一對通信端口的連接。以后就可以通過這個連接通信了。一般總是服務方線程先通過NtReplyWaitReceivePort()或NtReplyWaitReceivePortEx()等待對方發來報文,由于Port機制實際上只用于LPC,客戶方發往服務方的一般都是服務請求報文,而服務方則根據具體的請求提供服務,然后發回應答報文、一般是返回結果。不過,也并沒有規定必須是服務方等待客戶方的報文,反過來也并無不可。
不管是那一方,需要向對方發送一個報文時可以通過系統調用NtRequestPort()發送。
[code]NTSTATUS STDCALL
NtRequestPort (IN HANDLE PortHandle, IN PLPC_MESSAGE LpcMessage)
{
. . . . . .
Status = ObReferenceObjectByHandle(PortHandle, PORT_ALL_ACCESS,
LpcPortObjectType, UserMode, (PVOID*)&Port, NULL);
. . . . . .
Status = LpcRequestPort(Port->OtherPort, LpcMessage);
ObDereferenceObject(Port);
return(Status);
}[/code]
顯然,具體的操作是由LpcRequestPort()完成的。區別在于LpcRequestPort()要求使用指向EPORT數據結構的指針,而傳給NtRequestPort()的是Handle,需要加以轉換。Handle本質上是數組下標,所以從Handle到結構指針的轉換開銷并不大。
[code][NtRequestPort() > LpcRequestPort()]
NTSTATUS STDCALL LpcRequestPort (IN PEPORT Port,
IN PLPC_MESSAGE LpcMessage)
{
NTSTATUS Status;
DPRINT("LpcRequestPort(PortHandle %08x, LpcMessage %08x)\n", Port, LpcMessage);
#ifdef __USE_NT_LPC__
/* Check the message's type */
if (LPC_NEW_MESSAGE == LpcMessage->MessageType)
{
LpcMessage->MessageType = LPC_DATAGRAM;
}
else if (LPC_DATAGRAM == LpcMessage->MessageType)
{
return STATUS_INVALID_PARAMETER;
}
else if (LpcMessage->MessageType > LPC_CLIENT_DIED)
{
return STATUS_INVALID_PARAMETER;
}
/* Check the range offset */
if (0 != LpcMessage->VirtualRangesOffset)
{
return STATUS_INVALID_PARAMETER;
}
#endif
Status = EiReplyOrRequestPort(Port, LpcMessage, LPC_DATAGRAM, Port);
KeReleaseSemaphore(&Port->Semaphore, IO_NO_INCREMENT, 1, FALSE );
return(Status);
}[/code]
可見,NtRequestPort()只是發送,而并不等待對方的回應。如果需要等待回應的話可以采用另一個系統調用NtRequestWaitReplyPort()。
需要向對方發送應答報文時可以用NtReplyPort()。
[code]NTSTATUS STDCALL
NtReplyPort (IN HANDLE PortHandle, IN PLPC_MESSAGE LpcReply)
{
NTSTATUS Status;
PEPORT Port;
DPRINT("NtReplyPort(PortHandle %x, LpcReply %x)\n", PortHandle, LpcReply);
Status = ObReferenceObjectByHandle(PortHandle, PORT_ALL_ACCESS,
LpcPortObjectType, UserMode, (PVOID*)&Port, NULL);
. . . . . .
Status = EiReplyOrRequestPort(Port->OtherPort, LpcReply, LPC_REPLY, Port);
KeReleaseSemaphore(&Port->OtherPort->Semaphore, IO_NO_INCREMENT, 1, FALSE);
ObDereferenceObject(Port);
return(Status);
}[/code]
當然,這是純粹的發送應答報文,如果是發送應答報文并且等待下一個請求,那就要用NtReplyWaitReceivePort(),這讀者已經在前面看到了。
可見,Port是一種功能相當強、相當齊全、結構又相當完整的綜合性的進程間通信機制,這樣的機制理應提供給應用軟件的開發者,或者在Win32 API上提供相應的庫函數,或是把有關的系統調用公諸于世。但是微軟卻并不這么干,倒是一方面諱莫如深,一方面供自己的軟件內部使用。這樣,如果都來開發應用軟件,那別的公司如何能與微軟公平競爭呢?正因為如此,美國一直有人在呼吁甚至提起訴訟,要把操作系統和應用軟件的開發分拆開來,不能讓同一家公司既做操作系統又做應用軟件。另一方面,這也可以解釋為什么總是有這許多人熱衷于探究Windows和相關產品的“Undocumented…”、“…Internals”、“Inside…”。
最后還要提一下,有些資料中還提到Windows有一種“快捷LPC(QuickLPC)”機制。這就是建立在上一篇漫談中講到的“事件對”基礎上的LPC。早期Windows上的csrss通信太頻繁了,需要有一種非常輕快的進程間通信手段,所以才有了QuickLPC。現在,一方面是csrss的功能大都移到了內核中,一方面是處理器的速度也有了量級的提高,QuickLPC就變得不那么重要了。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -