前情回顾
在之前的文章中,我们介绍了鸿蒙系统框架中的主要构成者,server和client是进行往来的主要对象,而samgr则充当一个中介处,为某些特定的服务功能提供往来帮助,也分析了客户端与服务端的搭建流程,那么今天,我们将分析客户端与服务端之间的IPC来往机制。
IPC过程(client EP 与 Server)
简单介绍来往流程和相关重要函数介绍
首先我们要知道IPC来往的原理,无非就是client EP向Server注册、查询相关Feature等等。
所以今天我们从之前简单谈到的 RegisterRemoteEndpoint 函数开始分析。
static int RegisterRemoteEndpoint(const IpcContext *context, SvcIdentity *identity) //注册远程终端
// IpcContext是指函数中使用到的Ipc的上下文
// SvcIdentity是指客户端和服务端进行调用的使用者身份
该函数中用来实现Ipc过程的函数主要是这两句:
SvcIdentity samgr = {SAMGR_HANDLE, SAMGR_TOKEN, SAMGR_COOKIE};
int err = Transact(context, samgr, INVALID_INDEX, &req, &reply, LITEIPC_FLAG_DEFAULT, (uintptr_t *)&replyBuf);
Transact函数定义在liteipc_adapter.c中的SendRequest()函数,顾名思义,该函数的功能就是用来发送IPC请求的,其相关变量的定义如下:
1.req: IPC通信中传送的命令和重要参数,经过序列化处理了,接收端反序列化处理即可解析出来。
2.reply:类似req,但是是信息接收端处理完消息后,反馈回来的应答信息。
3.context: IPC通信通道的上下文。
在前面的学习中我们知道,在samgr中,IPC是通信接收方的身份信息;Handle的作用是标明用哪个EP(终端)来接收信息;token的作用是标明在该EP下用哪个router来处理信息。在服务端与客户端的IPC来往中很多Handle和token都是固定的、对应写好的。
所以接下来我们分析samgr EP
当Samgr EP接收到IPC请求时的处理
通过查询一些大牛的博客,我大概知道了Samgr EP的一些工作流程,首先就是它的boss监控线会在StartLoop循环中收到发给我们前面提到的SvcIdentity samgr的IPC消息,然后通过一个特定的函数——Dispatch()来对消息进行处理,我们来简单看看这个函数:
Dispatch()
static int Dispatch(const IpcContext *context, void *ipcMsg, IpcIo *data, void *argv)
{
if (argv == NULL || ipcMsg == NULL) {
return EC_INVALID;
}
//当前boss所在EP
Endpoint *endpoint = (Endpoint *)argv;
uint32_t token = (uint32_t)INVALID_INDEX;
//从ipcMsg消息中提取token,作用:通过token来判断用EP中的哪个router来处理信息
GetToken(ipcMsg, &token);
if (TB_CheckMessage(&endpoint->bucket) == BUCKET_BUSY) {
HILOG_WARN(HILOG_MODULE_SAMGR, "Flow Control <%u> is NULL", token);
goto ERROR;
}
//根据取出的token,取出对应的router
Router *router = VECTOR_At(&endpoint->routers, token);
if (router == NULL) {
HILOG_ERROR(HILOG_MODULE_SAMGR, "Router <%s, %u> is NULL", endpoint->name, token);
goto ERROR;
}
//通过Response的data字段来发送部分request
Response resp = {0};
resp.data = endpoint;
Request request = {0};
request.msgId = token;
request.data = ipcMsg;
request.msgValue = INVALID_INDEX;
GetCode(ipcMsg, &request.msgValue);
#ifdef LITE_LINUX_BINDER_IPC
HandleIpc(&request, &resp);
#else
uint32 *ref = NULL;
//发送共享直接请求
int ret = SAMGR_SendSharedDirectRequest(&router->identity, &request, &resp, &ref, HandleIpc);
if (ret != EC_SUCCESS) {
HILOG_ERROR(HILOG_MODULE_SAMGR, "Router[%u] Service<%d, %d, %p> is busy",
token, router->identity.serviceId, router->identity.featureId, router->identity.queueId);
goto ERROR;
}
#endif
return EC_SUCCESS;
ERROR:
if (ipcMsg != NULL) {
FreeBuffer(endpoint->context, ipcMsg);
}
return EC_INVALID;
}
从函数的构建中我们能够较为直接的看到其构建流程,该函数对IPC的处理也是比较简单直接的:
- 就是先根据IPC拿到了相关的token,然后把需要处理这个IPC消息的相关router从Vector中取出来,在这里需要注意,在提取router前首先需要判断该router是否存在,不存在自然就没法处理消息。
- 存在的话就把相关的信息写进Response和Request中,然后调用 SAMGR_SendSharedDirectRequest 函数向其中的router->identity发送所谓的direct request消息。
- 处理完消息之后需要返回到resp中,最后的消息处理会由另外一个函数HandleIpc()来处理。
用一个简单的流程图来描述该流程:
HandleIpc()
前面我们学习到,IPC消息在Dispatch函数中处理之后返回一个resp后,再由Handlie函数来处理
static void HandleIpc(const Request *request, const Response *response)
{
//在ipcMsg中包含了IPC发送者的所有信息
void *ipcMsg = (void *)request->data;
//处理该IPC的终端
Endpoint *endpoint = (Endpoint *)response->data;
//通过从IPC中获得的token来选择VECTOR中的router
Router *router = VECTOR_At(&endpoint->routers, request->msgId);
if (ipcMsg == NULL) {
return;
}
if (router == NULL || router->proxy == NULL || router->proxy->Invoke == NULL) {
FreeBuffer(endpoint->context, ipcMsg);
HILOG_ERROR(HILOG_MODULE_SAMGR, "Invalid IPC router<%p>!", router);
return;
}
//判断消息发送者是否具有权限来使用消息接收者的router来处理消息
uid_t uid = GetCallingUid(ipcMsg);
if ((strcmp(router->saName.service, SAMGR_SERVICE) != 0) &&
!JudgePolicy(uid, (const PolicyTrans *)(router->policy), router->policyNum)) {
FreeBuffer(endpoint->context, ipcMsg);
HILOG_ERROR(HILOG_MODULE_SAMGR, "Consumer uid<%d> has no permission to access<%s, %d, %d>!",
uid, router->saName.service, router->identity.serviceId, router->identity.featureId);
return;
}
IpcIo req;
IpcIoInitFromMsg(&req, ipcMsg);
IpcIo reply;
uint8 data[IPC_IO_DATA_MAX];
IpcIoInit(&reply, data, IPC_IO_DATA_MAX, MAX_OBJECT_NUM);
//该router为消息接收者的EP所对应的router,这里调用代理的Invoke API来处理IPC消息
router->proxy->Invoke(router->proxy, request->msgValue, ipcMsg, &req, &reply);
uint32_t flag = 0;
GetFlag(ipcMsg, &flag); //获取一个坐标
if (flag == LITEIPC_FLAG_DEFAULT) {
SendReply(endpoint->context, ipcMsg, &reply); //发送答复
} else {
FreeBuffer(endpoint->context, ipcMsg); //释放内存
}
}
最后的最后,处理消息的过程其实还是调用router->proxy->invoke()接口来处理消息,最后返回一个应答resp。
那么简单的总结一下,在所有的EP线程中,对于收到的那些来自别的EP发送的IPC消息,处理过程几乎都是以上这些流程:
- Dispatch()接收消息,然后转发消息
- HandleIpc()对消息进行处理
- 最后由消息接收者EP通过调用token指定的router->proxy->invoke()来处理
下期预告
在这篇博客中我们基本上了解到了服务端与客户端的联动机制,那么在最后提到的router->proxy->invoke()接口到底什么呢?
欲知后事如何,且听下回分解。