📄
字号:
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 + -