进程A中的线程Aa(也就是service Aa)想要调用某个service/feature的接口,这个接口可能是进程A自己的线程Ab(也就是service Ab)提供的,也可能是进程B的线程Bc(也就是service Bc)提供的。
如果是同进程的不同服务提供的接口,那Aa只需要向自己进程的SamgrLiteImpl g_samgrImpl查询service/feature名字就可以拿到对应的IUnknown *iUnknown接口,因为是同进程内的相同虚拟地址空间,所以可以直接跨线程调用。如《系统服务框架子系统-4-面向服务架构的实现》中的测试程序所做的那样。
也就是通过调用SAMGR_GetInstance()->GetDefaultFeatureApi(SERVICE_NAME) 或者SAMGR_GetInstance()->FeatureApi(SERVICE_NAME,FEATURE_NAME)来查询接口。
如果Aa想调用的service/feature接口是B进程提供的,那首先这个service,肯定在Aa所在进程的SamgrLiteImpl g_samgrImpl里面是找不到记录的,也就是serviceImpl = NULL:
所以,跨进程的服务/特性的调用,入口就在上面的SAMGR_FindServiceApi(service, feature)。
结合我们的log,搜索一下“%%%%%%%%%%%”这样一个字符串,这是我标记的一处入口(实际上搜索“SAMGR_FindServiceApi”也差不多)。
看一下log:
- {[bundle_daemon_client]} Initialize: GetDefaultFeatureApi(bundle_daemon)
这是“bundle_daemon_client”在Initialize的时候,向自己所在进程的SamgrLiteImpl g_samgrImpl查询名字为“bundle_daemon”的服务的DefaultFeatureApi。
通过前后的log,我们可以知道bundle_daemon_client所在进程的信息“pid[ 5]/uid[ 7]/(*)handle[61](*)”,5号进程是“foundation”,而“bundle_daemon”服务是6号进程提供的。
所以会通过SAMGR_FindServiceApi(S[bundle_daemon],F[(null)])来查询“bundle_daemon”服务提供的接口。
SAMGR_FindServiceApi()及其辅助函数,涉及到了g_remoteRegister.clients 这个字段,我们先来理解一下。
这是一个向量,在本进程的 g_remoteRegister 初始化的时候,也对这个向量做了配置:
- g_remoteRegister.clients = VECTOR_Make((VECTOR_Key)SAMGR_GetSAName, (VECTOR_Compare)SAMGR_CompareSAName);
向量的element:data[x]是一个IUnknown *proxy类型的指针,指向的是一个客户端代理的接口entry.iUnknown,通过这个指针,可以转换回IClientProxy类对象、IClientEntry类对象、IDefaultClient类对象,再通过IClientProxy类对象指针去访问后面的Invoke函数,或者通过IDefaultClient类对象指针去访问上面的IClientHeader header字段内的key、target等字段。
向量中每一个element,记录了本进程EP调用过的别的进程提供的服务接口相关的重要身份信息如名字、handle、token等,这样下次再调用的时候,直接在这里查询就能获得这些信息,不需要再次通过IPC去向samgr EP查询了,它的展开图如下:
SAMGR_FindServiceApi()的流程如下:
我们接着上面的log往下看一下,
这是第一次bundle_daemon_client 向samgr EP查询FeatureAPI,但是返回的 handle 是 -1,从接下来的DbgParse_g_server的信息来看,这个时候提供bundle_daemon服务的6号进程,还没有向samge EP注册EP和Feature,所以samgr EP自己也查不到。
再看第二次bundle_daemon_client 向samgr EP查询FeatureAPI,这回查到了,handle是38,token是0,也就是说,SAMGR_CreateIProxy()的第一步,拿到了SvcIdentity identity= {38, 0, 0},跟着接下来的流程跑,就会得到一个完整有效的IDefaultClient对象,把这个对象中的 .iUnknown 地址先保存到g_remoteRegister.clients 向量中去,然后通过这个.iUnknown 地址,就可以调用客户端代理的Invoke()接口,从而再通过IPC去远程调用bundle_daemon服务了。
Privider: Pid[6]/Uid[8]/handle[38]:bundle_daemon -> (null)Consumer: Pid[5]/Uid[7]/Tid[53]
从这两句log可以看出,6号进程的bundle_daemon 服务是provider,查询并使用该服务的5号进程是consumer,这也是面向服务的架构的一种实现。
我们再看另外一个更明显的跨进程调用服务的例子:第一个应用进程launcher的孵化启动。
Log搜索“AppAppAppApp”,这是5号进程通过app_manager准备调用7号进程提供的“appspawn”服务来孵化launcher应用,它拿到了“appspawn”的handle 74、token 0和.iUnknown地址,并把该地址以及相关的重要信息添加到自己的clients向量中。
接下来app_spawn_client调用上面的Invoke{0x249cc84c},这个Invoke{0x249cc84c}就是ProxyInvoke()函数,通过它发送IPC消息给IpcMsg Rceiver::handle[74],token[0],让它的EP收到和处理该消息,这就到了上一小节提到的Dispatch()和HandleIpc()对IPC消息的处理流程了。
经过消息的转发和处理,最后是appspawn_service的Invoke()函数开始孵化launcher应用进程,桌面应用开始提供服务。
跨进程的服务接口调用到此结束。
刚好刚才去浏览标准系统的 //base/startup/appspawn_standard/目录,看了一下README_zh,看到下面这张图就贴过来了,除了通信方式不一样之外,大体流程是一样的。