IBinder对象在进程间传递的形式(一)

摘要:
在研究上述实现代码之前,我们先介绍一下IBinder作为參数使用IPC进程间传递时的状态变化,事实上这个就是我们本篇文章的核心内容,理解了这个机制,我们就会非常easy理解我们上述的那个命题的原理了。模型IBinder作为參数在IPC通信中进行传递,可能会使某些人困惑,IBinder不就是IPC通信的媒介吗?

命题

当service经常被远程调用时,我们经常常使用到aidl来定一个接口供service和client来使用,这个事实上就是使用Binder机制的IPC通信。当client bind service成功之后,系统AM会调用回调函数onServiceConnected将service的IBinder传递给client, client再通过调用aidl生成的asInterface()方法获得service的调用接口,此时一个bind过程结束了,我们在client端就能够远程调用service的方法了。比如

            public void onServiceConnected(ComponentName className,
                    IBinder service) {
                mSecondaryService = ISecondary.Stub.asInterface(service);
            }

我们再看aidl生成的asInterface()的定义

	public static com.example.android.apis.app.ISecondary asInterface(android.os.IBinder obj)
	{
	if ((obj==null)) {
	return null;
	}
	android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
	if (((iin!=null)&&(iin instanceof com.example.android.apis.app.ISecondary))) {
	return ((com.example.android.apis.app.ISecondary)iin);
	}
	return new com.example.android.apis.app.ISecondary.Stub.Proxy(obj);
	}

首先,asInterface()会去query传入的IBinder对象是否有LocalInterface,这里的LocalInterface是指传入的IBinder是service本身的引用还是代理。
1.当asInterface的输入的IBinder为server的引用(Binder类型)时,则直接返回该引用,那么此时调用server的方法不为IPC通信,而是直接的函数调用;
2.当asInterface的输入的IBinder为server的代理(BinderProxy类型)时,则须要创建该server的代理并返回,此时调用server的方法为IPC通信。

那么以上两种情况发生的条件是什么呢?这里我们先给出答案,然后再深入到代码中去研究2种不同的情况。
1.当client和service处在同样进程中的话,asInterface的输入的IBinder为server的引用时;
2.当client和service处在不同进程中的话,asInterface的输入的IBinder为server的代理。

在研究上述实现代码之前,我们先介绍一下IBinder作为參数使用IPC进程间传递时的状态变化,事实上这个就是我们本篇文章的核心内容,理解了这个机制,我们就会非常easy理解我们上述的那个命题的原理了。

模型

IBinder作为參数在IPC通信中进行传递,可能会使某些人困惑,IBinder不就是IPC通信的媒介吗?怎么还会被作为參数来传递呢,这样理解就有点狭隘了,拿native层的IPC来说,client从SM(service manager)中获取service端的Interface,这个Interface同一时候也是IBinder类型,当C/S两端须要双工通信时,即所谓的Service端须要反过来调用Client端的方法时,就须要client通过前述的那个Interface将Client端的IBinder传递给Service。
拿Java应用层的Service来说更是如此,如本文的这个命题,以下我们会分析,首先来介绍原理性的知识。
Binder IPC通信中,Binder是通信的媒介,Parcel是通信的内容。方法远程调用过程中,其參数都被打包成Parcel的形式来传递的。IBinder对象也不例外,我们看一下Parcel类中的writeStrongBinder()(因为java层和native层的方法是相相应的,java层仅仅是native的封装,因此我们仅仅须要看native的就可以),

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

status_t flatten_binder(const sp<ProcessState>& proc,
 const sp<IBinder>& binder, Parcel* out)
{
 flat_binder_object obj;
 obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
 if (binder != NULL) {
 IBinder *local = binder->localBinder();
 if (!local) {
 BpBinder *proxy = binder->remoteBinder();
 if (proxy == NULL) {
 LOGE("null proxy");
 }
 const int32_t handle = proxy ? proxy->handle() : 0;
 obj.type = BINDER_TYPE_HANDLE;
 obj.handle = handle;
 obj.cookie = NULL;
 } else {
 obj.type = BINDER_TYPE_BINDER;
 obj.binder = local->getWeakRefs();
 obj.cookie = local;
 }
 } else {
 obj.type = BINDER_TYPE_BINDER;
 obj.binder = NULL;
 obj.cookie = NULL;
 }
 return finish_flatten_binder(binder, obj, out);
}

上面代码分以下2种情况
1. 假设传递的IBinder为service的本地IBinder对象,那么该IBinder对象为BBinder类型的,因此上面的local不为NULL,故binder type为BINDER_TYPE_BINDER。
2. 假设传递的IBinder对象代理IBinder对象,那么binder type则为BINDER_TYPE_HANDLE。
client端将方法调用參数打包成Parcel之后,会发送到内核的Binder模块,因此以下我们将分析一下内核的Binder模块的处理。

kernel/drivers/staging/android/Binder.c中的函数binder_transaction()

switch (fp->type) {
  case BINDER_TYPE_BINDER:
  case BINDER_TYPE_WEAK_BINDER: {
   struct binder_ref *ref;
   struct binder_node *node = binder_get_node(proc, fp->binder);
   if (node == NULL) {
    node = binder_new_node(proc, fp->binder, fp->cookie);
    if (node == NULL) {
     return_error = BR_FAILED_REPLY;
     goto err_binder_new_node_failed;
    }
    node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
    node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
   }
   if (fp->cookie != node->cookie) {
    binder_user_error("binder: %d:%d sending u%p "
     "node %d, cookie mismatch %p != %p
",
     proc->pid, thread->pid,
     fp->binder, node->debug_id,
     fp->cookie, node->cookie);
    goto err_binder_get_ref_for_node_failed;
   }
   ref = binder_get_ref_for_node(target_proc, node);
   if (ref == NULL) {
    return_error = BR_FAILED_REPLY;
    goto err_binder_get_ref_for_node_failed;
   }
   if (fp->type == BINDER_TYPE_BINDER)
    fp->type = BINDER_TYPE_HANDLE;
   else
    fp->type = BINDER_TYPE_WEAK_HANDLE;
   fp->handle = ref->desc;
   binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
     &thread->todo);
   binder_debug(BINDER_DEBUG_TRANSACTION,
     " node %d u%p -> ref %d desc %d
",
     node->debug_id, node->ptr, ref->debug_id,
     ref->desc);
  } break;
  case BINDER_TYPE_HANDLE:
  case BINDER_TYPE_WEAK_HANDLE: {
   struct binder_ref *ref = binder_get_ref(proc, fp->handle);
   if (ref == NULL) {
    binder_user_error("binder: %d:%d got "
     "transaction with invalid "
     "handle, %ld
", proc->pid,
     thread->pid, fp->handle);
    return_error = BR_FAILED_REPLY;
    goto err_binder_get_ref_failed;
   }
   if (ref->node->proc == target_proc) {
    if (fp->type == BINDER_TYPE_HANDLE)
     fp->type = BINDER_TYPE_BINDER;
    else
     fp->type = BINDER_TYPE_WEAK_BINDER;
    fp->binder = ref->node->ptr;
    fp->cookie = ref->node->cookie;
    binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
    binder_debug(BINDER_DEBUG_TRANSACTION,
      " ref %d desc %d -> node %d u%p
",
      ref->debug_id, ref->desc, ref->node->debug_id,
      ref->node->ptr);
   } else {
    struct binder_ref *new_ref;
    new_ref = binder_get_ref_for_node(target_proc, ref->node);
    if (new_ref == NULL) {
     return_error = BR_FAILED_REPLY;
     goto err_binder_get_ref_for_node_failed;
    }
    fp->handle = new_ref->desc;
    binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
    binder_debug(BINDER_DEBUG_TRANSACTION,
      " ref %d desc %d -> ref %d desc %d (node %d)
",
      ref->debug_id, ref->desc, new_ref->debug_id,
      new_ref->desc, ref->node->debug_id);
   }
  } break;

上面代码也分为了2种不同的分支:
1. 传来的IBinder类型为BINDER_TYPE_BINDER时,会将binder type值为BINDER_TYPE_HANDLE;
2. 传来的IBinder类型为BINDER_TYPE_HANDLE时,会推断该IBinder的实体被定义的进程(也就是该IBinder代表的server被定义的进程)与目标进程(也即IBinder被传递的目标进程)是否同样,假设同样,则将该IBinder type转化为BINDER_TYPE_BINDER,同一时候使其变为IBinder本地对象的引用。
通过上述的处理,我们能够得出以下结论:
1.不同进程间传递的IBinder本地对象引用(BINDER_TYPE_BINDER类型),在内核中均会被转化为代理(BINDER_TYPE_HANDLE类型,眼下仅仅是改变其类型,在IBinder接收方会依据其类型转化为代理);
2.因为仅仅有不同进程间传递才会将IBinder发送到Binder模块,所以IBinder在多级传递的过程中,有以下2种可能 进程A-->进程B-->进程A,进程A-->进程B-->进程C;其相应的IBinder类型就是BINDER_TYPE_BINDER-->BINDER_TYPE_HANDLE-->BINDER_TYPE_BINDER,BINDER_TYPE_BINDER-->BINDER_TYPE_HANDLE-->BINDER_TYPE_HANDLE。
依据上述结论,我们就会明确Binder IPC通信过程中,同样进程间的IBinder本地对象,假设不经过不同进程的传递,那么IBinder就不会传给内核的Binder模块,因此它一直是IBinder的本地对象;假设在进程间传递,即使通过再多的进程间的传递,仅仅要最后的目标是同一个进程的component,那么他得到的IBinder对象就是本地的对象。

IBinder对象在进程间传递的形式(一)第1张

套用模型

理解了上面的这个模型,我们再回过头看最開始的那个命题结论就非常好理解了
1.当client和service处在同样进程中的话,asInterface的输入的IBinder为server的引用时;
2.当client和service处在不同进程中的话,asInterface的输入的IBinder为server的代理。
假如某一个component(比如一个Acitivity)处在进程A,它须要bind一个service,而该service处在进程B中,我们简介一下bind的过程。
1. 进程A向AM(进程system_server)发出Bind请求,并将回调ServiceConnection提供给AM(传递给AM的也是一个接口(IServiceConnection),由于AM与A之间是双工通信,所以A须要向AM提供IBinder);
2. AM启动进程B并创建service,进程B将service的IBinder对象传递给AM,AM再通过IServiceConnection传递给进程A。所以service的IBinder对象的传递路径为:进程B-->进程system_server(AM)-->进程A。

套用上面的模型,就会得出本文最開始命题的结论。

便于理解下图给出里bind 一个service的过程

IBinder对象在进程间传递的形式(一)第2张

免责声明:文章转载自《IBinder对象在进程间传递的形式(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇10分钟学会React Context API内网开发机虚拟化(kvm+qemu)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

linux c编程:FIFO

前面介绍的pipe属于匿名管道 管道的主要局限性正体现在它的特点上: 只支持单向数据流;  只能用于具有亲缘关系的进程之间;  没有名字;  管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);  管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等...

消息队列和管道的区别(转载)

转载自:http://bbs.chinaunix.net/viewthread.php?tid=265266作者:beginner-bj请问管道和消息队列有什么不同 管道通信(PIPE) 管道通信方式的中间介质是文件,通常称这种文件为管道文件。两个进程利用管道文件进行通信时,一个 进程为写进程,另一个进程为读进程。写进程通过写端(发送端)往管道文件中写入信...

Domain Socket本地进程间通信

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答...

Linux C/C++编程之(十七)进程间通信

文章目录 一、概述 二、进程间通信概念及方法 1. 管道的概念 2. pipe 3. 管道的读写行为 4. 管道缓冲区大小 5. 管道优劣 6. FIFO 7. 共享存储映射 8. mmap函数 9. munmap函数 10. mmap九问 11. mmap父子进程间通信 12. 匿名映射 13. mmap无血缘关系进程间通信 3. 练习...

彻底理解Android Binder通信架构

copy from : http://gityuan.com/2016/09/04/binder-start-service/ 基于Android 6.0的源码剖析, 本文深度剖析Binder IPC过程, 这绝对是一篇匠心巨作,从Java framework到Native,再到Linux Kernel,带你全程看Binder通信过程. 一. 引言 1.1...

System IPC 与Posix IPC(semaphore信号灯)

POSIX下IPC主要包括三种: posix message queue posix semaphores posix shared memory sysytem v IPC包括: system v message queue system v semaphores system v shared memory 1.Semaphores 在POSIX...