文章转载自:liangkz
前面只是简单说到client EP发送注册消息给samgr EP然后获取handle就完成了EP的注册过程,这里我们来看看具体都做了哪些事情。
实际上client EP与Server(samgr EP)的所有IPC过程(如clien EP向server注册Feature、查询Feature等等)也是类似的。
首先从client EP的RegisterRemoteEndpoint()开始(详细的IPC信息封装和序列化处理,这里先不深入解析):
SvcIdentity samgr = {SAMGR_HANDLE, SAMGR_TOKEN, SAMGR_COOKIE}; //{0, 0, 0}
Transact(context, samgr, INVALID_INDEX, &req, &reply, LITEIPC_FLAG_DEFAULT, (uintptr_t *)&replyBuf);
这个Transact() 就是定义在liteipc_adapter.c中的SendRequest()函数,用来发送IPC请求,部分参数的意义如下:
- context: IPC通信通道的上下文
- samgr: IPC通信接收方的身份信息,handle标明哪个EP接收信息,token标明EP里的哪个router处理信息,这里知名EP的handle和token都是硬编码写死了的。
- req: IPC通信中传送的命令和重要参数,经过序列化处理了,接收端反序列化处理即可解析出来。
- reply:类似req,但是是信息接收端处理完消息后,反馈回来的应答信息,通过下面的identity->handle = IpcIoPopUint32(&reply); 看到能够从中解析出注册完成后返回来的handle。
再来看看samgr EP接收到这个IPC请求(注册EP)信息后如何处理。
Samgr EP的boss监控线程会在StartLoop循环的中收到发给SvcIdentity samgr = {0, 0, 0} 的IPC消息,通过Dispatch()函数来处理:
Dispatch()函数对IPC消息的处理,其实也算是很简单的,主要是根据IPC拿到的token,把需要处理这个IPC消息的router从Vector中取出来,首先确认router是否存在,不存在肯定就没法处理这个IPC消息了。router存在,就把相关信息填写到Request和Response里,再调用
SAMGR_SendSharedDirectRequest(&router->identity, &request, &resp, &ref, HandleIpc);
向router->identity 中的Qid队列发送direct request消息,处理完消息需要返回应答到resp里,消息指定要用HandleIpc() 函数来进行处理,相当于借Qid对应的那个service的线程,执行一下HandleIpc()函数。
实际处理IPC信息的是HandleIpc()函数:
最终,调用router->proxy->invoke()接口来处理消息,并返回应答。
实际上,所有EP的boss线程,对收到别的EP发过来的IPC消息,都是上面的处理过程,Dispatch()收到信息,转发信息,HandleIpc()处理消息,最终都是由消息接收者EP通过token指定的router->proxy->invoke()来处理。
对应samgr这个EP来说,这个router->proxy->invoke(),就是g_server.Invoke(),它们是怎么对应到一起的?
- static SamgrServer g_server = //samgr server
- {
- .GetName = GetName,
- .Initialize = Initialize,
- .GetTaskConfig = GetTaskConfig,
- .MessageHandle = MessageHandle,
- SERVER_IPROXY_IMPL_BEGIN,
- .Invoke = Invoke,
- IPROXY_END,
- };
复制代码
把这个g_server的接口部分展开:
它在通过
SAMGR_AddRouter(server->samgr, &saName, &server->identity,
GET_IUNKNOWN(*server)); 添加router时,所用的第四个参数GET_IUNKNOWN(*server),就是获得g_server的 .iUnknown 字段的地址,以此地址作为SAMGR_AddRouter(......,IUnknown *proxy) 的proxy参数,然后:
IServerProxy *serverProxy = NULL;
proxy->QueryInterface(proxy, SERVER_PROXY_VER, (void *)&serverProxy); //0x80
在QueryInterface()时,实际执行 IUNKNOWN_QueryInterface(),会把 IUnknown 类型的*proxy(第一个参数),重新下转换成IUnknownEntry类型的指针*entry:
IUnknownEntry *entry = GET_OBJECT(proxy, IUnknownEntry, proxy);
判断它的版本entry->ver是否符合要求,符合要求的话,它的引用entry->ref+1,然后返回proxy给第三个参数(void*) serverProxy,再经过类型转换到IServerProxy *serverProxy(也就是SamgrProxy的对象指针),然后:
router->proxy = serverProxy;
这就对应起来了。从log中调用的Invoke函数的地址也可确认是同一个函数。
其他所有进程EP内的routers中的router,都以类似的方式将Invoke函数映射起来,也是这样方式调用的。
到了g_server.Invoke()函数内,就开始比较明朗了。g_server有一个函数指针数组g_functions[]:
- static ProcFunc g_functions[] = {
- [RES_ENDPOINT] = ProcEndpoint,
- [RES_FEATURE] = ProcFeature,
- [RES_SYSCAP] = ProcSysCap,
- };
复制代码
g_server.Invoke()函数内通过对IPC过来的参数作为分类参数,以此选择对应的函数来分别做处理。
对EP注册类消息,调用ProcEndpoint()来处理:
对Feature类消息,调用ProcFeature()来处理,
又分为注册和查询两种,通过参数分别调用ProcPutFeatur()和ProcGetFeature()来处理。
注册Feature消息的IPC发送端,在client EP启动和注册过程中,通过调用SAMGR_ProcPolicy()或者RegisterRemoteFeatures()来向samgr EP注册Feature。请去这两个函数去看代码应该就可以理解了。
查询Feature消息的IPC发送端,是在尝试使用别的进程/EP提供的服务/功能前,先向samgr EP查询并获取Feature接口,才能去使用。
注册和查询Feature,都需要涉及service/feature在g_server内的存储方式,我们先来看一下它的SAStore store字段。
SAStore 结构体及其相关的其他结构体,定义在sa_store.h 内,这是一个链表+向量的结构:
- struct SAStore {
- int saSize;
- ListNode *root; //a list of services with their features
- int16 mapSize;
- int16 mapTop;
- PidHandle *maps; //a vector
- };
复制代码
ListNode *root; 是一个链表头部指针,链表的每一个节点是client EP注册进来的一个service,每个service节点,又包含一个子链表,子链表每个节点又是这个service提供的一个feature,没有提供feature的service,其子链表也会有一个名字为NULL,token为0的feature节点。
PidHandle *maps则是一个简化版本的向量,每一个element是一个指向PidHandle结构体的指针,每个PidHandle结构体则记录了向samgr EP注册过来的其他client EP的id信息:
- struct PidHandle {
- pid_t pid; //client EP 所在进程的 pid
- uid_t uid; //client EP 所在进程拥有者的 uid
- uint32 handle; //client EP 的boss线程的tid 或者 tid经过内核映射的编号
- uint32 deadId;
- };
复制代码
整个SAStore 结构体展开如上图,需要注意的是,client EP是先注册EP,在maps向量中记录了handle,然后才会注册Feature,往list中添加节点的时候,新节点的ServiceInfo中的handle,一定是与maps中对应的pid中的handle匹配的。而FeatureNode中的token字段,与这个Feature在client EP中的routers向量中的index也是匹配的。
ProcPutFeatur()函数的处理过程如下:
ProcGetFeature()函数的处理过程如下:
对于
SysCap类的IPC消息,调用ProcSysCap()来处理,
分为三类:添加一项SysCap、获取一项SysCap、获取所有SysCap。
在SYS_SERVICE_INIT(InitializeRegistry)阶段,就通过调用ParseSysCap()来读取和分析“/etc/system_capability.json”文件,将系统的Capabilitys,按格式添加到g_server的Vector sysCapabilitys中去了。sysCapabilitys的结构如下:
所以ProcSysCap()对三类操作,都按向量的操作方法,对sysCapabilitys操作即可。
当目前所有的client EP向g_server注册完EP/service/feature等等信息之后,g_server的完全展开信息如下:
系统启动之初通过init创建的3~11号线程中的3/4/8三个进程,不需要通过g_server的管理再向外提供服务,也可以看出这三个进程的特殊性。
List列出来的service/feature,是目前系统内不同进程之间能够相互访问的服务和特性,它们之间是如何互访的,我们下一节来详细分析。