概述
LPC(本地过程调用)服务
本地过程调用(LPC,Local Procedure Call),主要用于操作系统各个组件之间进行通信,或者用户模式程序与系统组件之间通信。
LPC工作方式时消息传递,允许两个进程进行双向通信,在Windows的主要应用如下:
- Windows 应用程序与系统进程,包括Windows环境子系统之间的通信。
- 用户模式程序与内核模式组件之间的通信。
- 当RPC(远程过程调用,Remote Procecure Call)的两端在同一个系统时,RPC通信转化位LPC通信。
1 LPC结构模型
LPC 通信模型,如图:
LPC允许一对多通信模型。LPC连接端口只接受连接请求,通信端口只接受数据请求和服务。
LPC进程双方建立连接后,可以给对方发送消息。每个端口对象都有一个消息队列,用来保存发送给端口对象的消息。LPC允许使用两种方法来传输数据:
- 当最大消息长度不超过256字节时,要传输的数据之间在消息头后面。发送进程将数据拷贝到系统地址空间中,然后接收进程将数据拷贝到它的进程地址空间中。
- 若最大消息长度超过256字节,那么客户在连接到端口对象时,可以指定一个内存区对象,并且在客户进程中映射一个视图,以用于向服务器发送大数据。服务器也可以指定一个内存区对象,用于向客户发送大数据。
Windows的LPC使用消息队列来保存发送给一个端口对象的消息,使用信号量对象来同步发送和接收操作。
LPC系统服务(适用于Windows 2000以后的版本)
系统服务 | 简要说明 |
NtAcceptConnectPort | 服务器进程利用该服务来接受或拒绝一个连接请求 |
NtCompleteConnectPort | 服务器进程在调用了NtAcceptConnectProt后,在调用该服务以便唤醒客户线程 |
NtConnectPort | 通过该服务,客户进程可通过名称来连接一个服务器进程 |
NtCreatPort | 服务器进程利用该服务创建一个LPC连接端口 |
NtCreateWaitablePort | 服务器进程利用该服务来创建一个LPC连接端口,它允许异步方式等待LPC消息,即等待客户连接请求的到来 |
NtListenProt | 利用NtReplyWaitReceivePort服务来等待来自客户端的连接请求 |
NtReplyPort | 发送一个应答消息 |
NtReplyWaitReceivePort | 发送一个应答消息,等待接收一个客户消息 |
NtReplyWaitReceivePortEx | 功能同NtReplyWaitReceivePort,可指定超时值 |
NtReplyWaitReplyPort | 发送一个应答信息,并等待此应答消息的应答消息 |
NtRequestPort | 发送一个请求消息 |
NtRequestWaitReplyPort | 发送一个请求信息,并等待此请求的应答消息 |
NtSecureConnectPort | 通过此服务,客户进程可通过名称来连接到一个服务器进程,它允许指定服务器进程的安全标识符 |
2 LPC端口和LPC消息
typedef struct _LPCP_PORT_OBJECT
{
struct _LPCP_PORT_OBJECT *ConnectionPort;
struct _LPCP_PORT_OBJECT *ConnectedPort;
LPCP_PORT_QUEUE MsgQueue;
CLIENT_ID Creator;
PVOID ClientSectionBase;
PVOID ServerSectionBase;
PVOID PortContext;
PETHREAD ClientThread; //仅适用于服务器通信端口
SECURITY_QUALITY_OF_SERVICE SecurityQos;
SECURITY_CLIENT_CONTEXT StaticSecurity;
LIST_ENTRY LpcReplyChainHead;//仅适用于通信端口
LIST_ENTRY LpcDataInfoChainHead; //仅适用于通信端口
union {
PEPROCESS ServerProcess;// 仅适用于服务器连接端口对象
PEPROCESS MappingProcess;//仅适用于通信端口
};
ULONG MaxMessageLength;
ULONG MaxConnectionInfoLength;
ULONG Flags;
KEVENT WaitEvent;//仅适用于可等待的端口
} LPCP_PORT_OBJECT, *PLPCP_PORT_OBJECT;
ConnectionPort : 指向当前端口对象的连接对象,对于服务器通信端口或客户通信端口,指向相关联的连接端口对象;连接端口的ConnectionPort 指向其自身。
ConnectedPort: 指向通信的对方。
MsgQueue: 表示端口对象的消息队列。
Creator : 表示创建线程的ID。
ClientSectionBase: 指向客户内存区对象的视图基地址。
ServerSectionBase : 指向服务器内存区对象的视图基地址
PortContext : 是一个由服务器进程管理和解释的指针域。
ClientThread : 仅用于服务器通信端口对象,在建立连接过程中用于记录客户线程对象。
SecurityQos 、StaticSecurity : 用于建立端口对象的安全环境。
LpcReplayChainHead 、LpcDataInfoChainHead : 两个链表头,用于通信端口存放应答消息。
ServerProcess : 仅用于连接端口,以便销毁端口对象时可以解除映射。
MaxMessageLength 、MaxConnectionInfoLength :表示该端口的最大消息长度和最大连接信息长度。
Flags : 表示该端口对象的类型和状态标志,其最低4位标识端口的类型。
WaitEvent : 仅用于可等待的端口,此事件的信号状态标识该端口是否有消息到达。
LPC子系统在初始化时,创建了名称为”Port”和“WaitablePort”的两种LPC端口对象类型。两者的主要区别是,后者包含了LPCP_PORT_OBJECT 的WaitEvent,并且从非换页内存池分配,前者从换页内存池分配。
LPC通信的消息结构:
typedef struct _LPCP_MESSAGE
{
union
{
LIST_ENTRY Entry;
struct
{
SINGLE_LIST_ENTRY FreeEntry;
ULONG Reserved0;
};
};
PVOID SenderPort;
PETHREAD RepliedToThread; //发送应答时填充,因而接收方可以解除对该线程的引用
PVOID PortContext; //从发送方的通信端口中获得
PORT_MESSAGE Request;
} LPCP_MESSAGE, *PLPCP_MESSAGE;
Entry : 是一个消息被插入消息队列时的链表节点对象。
SenderPort : 指向发送方的端口对象。
Request : 一个PORT_MESSAGE 结构,指定了消息的长度、类型、消息ID等信息。
3 LPC通讯模型的实现
LPC通信过程分为两个阶段: 建立连接阶段和数据传输阶段。
在建立连接阶段,首先服务器进程调用NtCreateProt 或NtCreateWaitablePort 创建一个LPC端口,原型如下:
NTSYSAPI
NTSTATUS
NTAPI
NtCreatePort(
OUT PHANDLE PortHandle,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG MaxConnectInfoLength,
IN ULONG MaxDataLength,
IN OUT PULONG Reserved OPTIONAL );
NTSYSAPI
NTSTATUS
NTAPI
NtCreateWaitablePort(
__out PHANDLE PortHandle,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in ULONG MaxConnectionInfoLength,
__in ULONG MaxMessageLength,
__in_opt ULONG MaxPoolUsage
);
两个函数都是调用LpcpCreatePort函数。LpcpCreatePort根据参数名称,创建一个LPC端口对象。它首先检查参数,然后调用ObCreateObject 创建一个LpcPortObjectType 或LpcWaitablePortObjectType 类型的内核对象。然后初始化对象中的域,包括最大消息长度和最大连接信息长度。最大消息长度不得超过PORT_MAXIMUM_MESSAGE_LENGTH,即256字节。如果调用者指定了名称,则此端口对象为连接端口对象(SERVER_CONNECTION_PORT),否则是非连接得通信端口对象(UNCONNECTED_COMMUNICATION_PORT)。最后,调用ObInsertObject 将新建得端口对象插入到当前进程得句柄表中。
服务器进程调用NtListenPort 监听连接请求,NtListen的监听是一个同步过程,它调用NtReplyWaitReceivePort ,直至该函数接收到一个LPC连接请求,或者返回不成功。函数原型如下:
NTSYSAPI
NTSTATUS
NTAPI
NtListenPort(
IN HANDLE PortHandle,
OUT PLPC_MESSAGE ConnectionRequest );
NTSYSAPI
NTSTATUS
NTAPI
NtReplyWaitReceivePort(
IN HANDLE PortHandle,
OUT PHANDLE ReceivePortHandle OPTIONAL,
IN PLPC_MESSAGE Reply OPTIONAL,
OUT PLPC_MESSAGE IncomingRequest );
LPC服务器和客户进程都可以使用NtReplyWaitReceivePort来接收消息。
NtReplyWaitReceivePort调用NtReplyWaitReceivePortEx ,NtReplyWaitReceivePortEx 首先检查其参数,尤其是ReplyMessage 和PortHandle。然后确定接收者的端口对象(即ReceivePort)。如果指定了ReplyMessage参数,则从此消息中定位到目标线程,并调用KeReleaseSemaphore,唤醒正在等待的目标线程。NtListenPort 将ReplyMessage 参数设置为0,所以,服务器监听连接请求时不需要处理ReplyMessage 参数。待完成参数处理后,NtReplyWaitReceivePortEx 在接收端口对象的消息队列的信号量对象上等待,即
ReceivePort->MsgQueue.Semaphore对象。等待成功以后从ReceivePort 的消息队列中读取消息,并复制到参数ReceiveMessage指定的消息对象中。
NtListen 直接在LPC连接端口对象的消息队列信号量上等待,直到有客户向此端口对象发送连接请求信息。在客户进程中,通过NtConnectPort 来完成。
NTSYSAPI
NTSTATUS
NTAPI
NtConnectPort(
OUT PHANDLE ClientPortHandle,
IN PUNICODE_STRING ServerPortName,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,
IN OUT PLPC_SECTION_OWNER_MEMORY ClientSharedMemory OPTIONAL,
OUT PLPC_SECTION_MEMORY ServerSharedMemory OPTIONAL,
OUT PULONG MaximumMessageLength OPTIONAL,
IN ConnectionInfo OPTIONAL,
IN PULONG ConnectionInfoLength OPTIONAL );
NtConnectPort 调用NtSecureConnectPort。NtSecureConnectPort 首先检查参数。然后根据PortName 获得目标连接对象,然后调用ObCreateObject 创建一个新的LPC端口对象。如果ClientView 指定了内存区对象,则在当前进程中映射一个视图。然后初始化新的LPC端口对象,并构造一个请求连接的LPC消息,将它插入到LPC连接对象的消息队列中。服务器连接对象的消息队列的信号量对象调用KeReleaseSemaphore,唤醒服务器进程正在监听连接对象的线程。然后再当前线程的LpcReplySemaphore 信号量上等待。
服务器进程会向客户线程的LpcReplySemaphore 信号量发送信号。
NtSecureConnectPort 的到服务器对象的信号通知后,将客户的通信端口对象插入到进程的句柄表中。
服务器进程接下来调用NtAcceptConnectPort 和NtCompleteConnectPort ,接受或拒绝客户的连接请求。原型如下:
NTSTATUS
NtAcceptConnectPort(
__out PHANDLE PortHandle,
__in_opt PVOID PortContext,
__in PPORT_MESSAGE ConnectionRequest,
__in BOOLEAN AcceptConnection,
__inout_opt PPORT_VIEW ServerView,
__out_opt PREMOTE_PORT_VIEW ClientView
);
NTSYSAPI
NTSTATUS
NTAPI
NtCompleteConnectPort(
IN HANDLE PortHandle );
ConnectionRequest 是NtListenPort 返回的连接请求消息。
AcceptConnection 表明应该接受还是拒绝此请求。
ServerView 和ClientView 用于内存区对象的视图映射。
至此,客户进程与服务器进程之间的LPC连接已经建立起来。建立LPC连接的交互过程,如图:
两个进程建立连接后,双方可以发送或接收应答消息。LPC通信的函数简要说明:
- NtRequestPort ,向指定的端口发送一个请求消息。该函数首先找到目标端口,将消息插入到该端口的消息队列中,然后调用KeReleaseSemaphore 使消息队列的信号量计数器增1。
- NtRequestWaitReplyPort,向指定的端口发送一个请求消息,然后等待应答。该函数首先检查要发送的消息,并利用消息中的信息找到要唤醒的线程,将请求消息告诉对方,并唤醒它,然后等待对方的应答。
- LpcRequestPort,直接使用端口对象的地址
- LpcRequestWaitReplyPort/LpcRequestWaitReplyPortEx 直接使用端口对象的地址
- NtReplyWaitReceivePort/NtReplyWaitReceivePortEx , 如果参数中指定了应答消息,则首先根据应答消息中的信息,向应答目标方传送应答消息,并唤醒对方。然后再端口对象的消息队列信号量上等待,以接收消息。NtListenPort 使用此函数来监听客户进程的连接请求。
- NtReplyPort ,发送一个应答消息。参数PortHandle 指定了原来接收到请求消息的端口对象,参数ReplyMessage 指定了要送回的应答消息。
- NtReplyWaitReplyPort, 发送一个应答消息,并且等待对此应答消息的应答。
以下几点请留意:
- 在传输消息时,不管是请求消息还是应答消息,都要进行消息拷贝,是通过LpcpMoveMessage 完成的。
- 应答消息必定是针对前一个已经传输的消息,线程对象ETHREAD的LpcReplyMessageId 记录了该消息的ID。
- 消息ID通过一个简单的计数器来产生,参加LpcpGenerateMessageId。
- 如果一个函数要等待应答消息,则在等待之前,要将当前线程的LpcReplyChain 节点加入到适当端口对象的LpcReplyChanHead链表中。
- LPC使用了全局锁LpcpLock。
- 如果这些函数的调用者是用户模式代码,则必须严格检查参数的有效性。
LpcpClosePort 关闭端口连接,LpcpDeletePort 删除端口对象。
最后
以上就是称心故事为你收集整理的windows 内核原理与实现读书笔记之LPC的全部内容,希望文章能够帮你解决windows 内核原理与实现读书笔记之LPC所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复