(6) Android中Binder调用流程 --- Binder驱动总结

        经过前面几节学习,我们对Android的Binder机制的相关环境、类有了比较详细的理解,对其周边涉及到的流程也做了讲解,这一节除了对Binder周边相关的类做个整体的总结之外,我们主要说明如下几个问题。

  • 客户端调用远程服务时是怎么样定位到远程服务的?
  • 服务端怎么样获取客户端的进程ID和用户ID来进行权限验证?
  • 服务端怎么样接收并处理客户端的远程调用的?
  • 客户端怎么样接收远程服务的返回结果的?

        在回顾前面章节和解答上述问题之前,我们先给出客户端调用远程服务并获取返回结果的整体流程图:

        首先我们先简单介绍下和Binder通讯相关的类(前面章节已经介绍过,这里总结下)。

1) Binder相关类介绍

        BinderProxy:BpBinder在JAVA层的类,实际实现通过本地的BpBinder实现对远程服务的代理;

        Binder:JAVA层真正实体服务的对象类,其实现了JAVA层的IBinder接口,通过关联本地类BBinder来调用JAVA层的服务实现类;

        本地Parcel:同名类在JAVA也有,不过只是个容器,真正实现的是本地的这个Parcel,这个类是Android特有的用来实现数据系列化和反系列化的关键类,其数据操作都是在内存中完成的;

        本地BBinder:JAVA层的服务对象在本地的代理,其主要功能就是通过其持有的JAVA层的服务类来实现Binder驱动层到JAVA层实际服务的回调用;

        本地BpBinder:远程服务在客户端的本地代理类,其实现主要通过和远程服务绑定的handle来完成的;

2)Binder通讯相关结构介绍

        flat_binder_object:这个结构是服务对象的二进制描述,Android就是通过这个结构来实现服务对象在多进程之间的传送;

        binder_transaction_data:封装请求数据以及包含的IBinder对象的信息
        binder_write_read:BINDER_WRITE_READ驱动层命令对应的封装信息
        binder_node:在Binder驱动中记录了实体服务信息
        binder_ref:保存实体IBinder对象的引用,用来在客户端查询实体IBinder用
        binder_proc:和Binder驱动关联的进程信息(调用binder_open就会生成)

        接下来我们一一解答上面提出的问题。

     客户端调用远程服务时是怎么样定位到远程服务的?

        在解答这个问题之前我们先看下两个流程图,第一个是系统服务怎么加到ServiceManager里的;第二个是客户端怎么样通过ServiceManager来获取远程系统服务的,首先看下ServiceManager.addService的处理流程。

        通过前面对系统的管理进程ServiceManager的介绍,我们知道系统服务一般是在SystemServer进程启动时初始化并通过调用ServiceManager.addService来加入的,而且ServiceManager其实就是一个IBinder远程服务的实现,其主要功能就是实现IServiceManager接口。

        1)SystemServer调用ServiceManager.addService注册系统服务;

        2)系统服务被封装为flat_binder_object结构,此结构的type设置为BINDER_TYPE_BINDER,binder为服务实体的引用,cookie为服务实体的地址(这里的服务实体是一个BBinder对象);

        3)通过BpBinder->IPCThreadState->ioctl把请求发送到Binder驱动层,由驱动层处理flat_binder_object(处理过程看上面流程图的注释说明),最终由Binder驱动生成服务实体对应的handle并把对应的binder_ref插入到ServiceManager进程对应的binder_proc,SystemServer进程对应的binder_proc也插入了服务实体对应的binder_node节点信息;

        4)最后Binder驱动把处理后的数据请求发送到ServiceManager进程进行处理;

        5)ServiceManager解析请求后,根据服务名称把SystemService中实体服务对应的handle(这个handle是存储在ServiceManager进程对应的binder_proc里的)保存为一个列表条目;

        从上面的流程我们知道,在SystemServer进程中增加的服务实体保存如下:

        1)在SystemServer进程对应的binder_proc中保存了服务实体节点binder_node,此节点有服务真正的信息;

        2)在ServiceManager进程对应的binder_proc保存了服务实体的引用binder_ref,这个引用包括以node和handle索引的两个红黑树链表,而且引用里面生成了远程服务对应的handle,注意这里的handle是保存在ServiceManager进程对应的binder_proc结构里,具体就是在节点binder_ref里;

        3)在ServiceManager进程(用户空间)里保留一个列表,这个列表是以远程服务的名称为索引的,列表项信息包括binder_ref里生成的handle值;

       然后我们再看看客户端怎么样通过ServiceManager来获取远程系统服务的?

       

        客户端通过IServiceManager获取系统服务,上图已经说得很清楚了,这里我们主要说下客户端获取的远程系统服务对应的handle是怎么生成的,注意,这个handle和ServiceManager进程持有系统服务的handle是不同的(虽然它们的handle对应到系统服务都是同一个)。

        1)ServiceManager收到getService请求后,会从自己的列表里通过name找到对应的远程系统服务的handle值;

        2)ServiceManager用这个handle值来填充flat_binder_object结构,然后把请求发送到Binder驱动;

        3)Binder驱动收到ServiceManager返回的数据后先用这个handle在ServiceManager进程对应的binder_proc里查询到系统服务在ServiceManager的引用(binder_ref),通过binder_ref获取得到真正的远程系统服务实体binder_node;

        4)通过真正的远程系统服务实体binder_node在客户端进程里查询是否有这个binder_node的引用,如果没有就会生成binder_ref,同时插入客户单进程对应的binder_proc,到这里远程系统服务在客户端进程里有了一个对应的handle;

        5)最后Binder驱动把客户端生成的handle替换flat_binder_object结构里的handle值,然后发送给客户端;

        最后客户端进程获取到的远程系统服务对应的代理handle就是客户端进程自己生成的,结合前面章节,我们知道客户端拿到的代理在JAVA层对象是BinderProxy,在native层是BpBinder。

        看完这个两个流程图的介绍,相信大家已经知道第一个问题的答案了,接下来看看第二个问题。

        服务端怎么样获取客户端的进程ID和用户ID来进行权限验证?

        在Framework源码中,经常看到Binder.getCallingUid()这样的调用,从字面意思来看就是获取接口调用者所在进程的用户ID的,而且这是个native方法,其在如下文件定义:

        frameworks/base/core/jni/android_util_Binder.cpp

        其JNI实现函数为:android_os_Binder_getCallingUid,一路跟踪过去,发现最终调用的是IPCThreadState::getCallingUid()这个方法,这个方法实现如下:

363uid_t IPCThreadState::getCallingUid() const
364{
365    return mCallingUid;
366}

        就是返回其成员变量mCallingUid而已,这个变量在方法clearCaller()有赋值,这个方法是在IPCThreadState的构造函数里调用的,从这里我们知道一开始这个值被初始化为调用线程所在进程的用户ID。

401void IPCThreadState::clearCaller()
402{
403    mCallingPid = getpid();
404    mCallingUid = getuid();
405}

        接下来我们看看第二处赋值的地方。

957status_t IPCThreadState::executeCommand(int32_t cmd)
958{
959    BBinder* obj;
960    RefBase::weakref_type* refs;
1036    case BR_TRANSACTION:
1037        {
......
1051            const pid_t origPid = mCallingPid; // (1)
1052            const uid_t origUid = mCallingUid;
......
1056            mCallingPid = tr.sender_pid; // (2)
1057            mCallingUid = tr.sender_euid;
......
1075            if (tr.target.ptr) {
......
1078                if (reinterpret_cast<RefBase::weakref_type*>(
1079                        tr.target.ptr)->attemptIncStrong(this)) {
1080                    error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
1081                            &reply, tr.flags);
1082                    reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);  // (3)
1083                } else {
1084                    error = UNKNOWN_TRANSACTION;
1085                }
1102            mCallingPid = origPid;  // (4)
1103            mCallingUid = origUid;
......

        上述代码片段里(1)保存当前调用进程ID和用户ID,(2)把调用服务接口所在进程的ID和用户ID保存到mCallingPidmCallingUid,(3)调用服务接口处理业务逻辑,(4)恢复进程ID和用户ID。

        这里大家要注意,代码片段(2)里从tr取得的进程ID和用户ID是在Binder驱动层里设置的,取得就是调用服务接口方的进程ID和用户ID,而这里调用方可能是不同的进程,也可能是相同的进程。

        到这里我们知道这里的mCallingUid指的是调用服务接口调用方所在的进程的用户ID,所以,这个用户ID所在进程可能和服务接口不在同一个进程,也可能在同一个进程,比如客户端调用AMS的startActivity接口时,在AMS的startActivity接口实现里获取的用户ID就是客户端所在进程的用户ID,而AMS实现里调用比如PMS接口时,它们就在同一个进程(SystemServer进程)里,所以在PMS接口实现里获取的用户ID和AMS的一样(这里仅仅是指AMS里调用PMS的接口这种相同进程的情况)。

        通过上面的说明,我们知道在服务实现接口获取的进程ID和用户ID就是调用服务接口的调用方所在进程ID和用户ID。

        最后两个问题这里就不说了,大家看下开始的Binder整体调用流程图就明白了。

        本系列文章均为原创,主要总结作者多年在软件行业的一些经验,和大家共同学习、进步,转载请注明出处,谢谢!

猜你喜欢

转载自blog.csdn.net/china0851/article/details/87945740