【Binder系列课】二、Binder通信服务应知应会

Binder问答

概括Binder机制
1.整体业务层CS架构,BpBinder/BnBinder
2.通信层Binder驱动,writeStrongBinder/readStrongBinder完成路由
其他的代码都是业务逻辑,没必要剖析。

一次Binder请求的数据封装/传输流程

听说你Binder机制学的不错,来面试下这几个问题(二)
在这里插入图片描述

数据结构

Android Binder框架实现之Binder中的数据结构

binder_proc

binder_proc是内核中描述Binder进程上下文信息的结构体,其在用户空间相对应的是ProcessState

  • threads,它是包含该进程内用于处理用户请求的所有线程的红黑树。
  • nodes,它是包含该进程内的所有Binder实体所组成的红黑树。
  • refs_by_desc,它是包行该进程内的所有Binder引用所组成的红黑树。通过一个引用的desc可以在这里找到对应的ref
  • buffer,它是该进程内核虚拟内存的起始地址。而user_buffer_offset,则是该内核虚拟地址和进程虚拟地址之间的差值。在Binder驱动中,将进程的内核虚拟地址和进程虚拟地址映射到同一物理页面,该user_buffer_offset则是它们之间的差值;这样,已知其中一个,就可以根据差值算出另外一个。
  • todo wait,todo,是该进程的待处理事务队列,而wait则是等待队列。 没看懂什么区别。
  • struct list_head buffers; // 和binder_buffer->entry关联到同一链表,从而对Binder内存进行管理

binder_thread

binder_thread是内核层描述Binder线程的结构体,在用户层与其相对应的是IPCThreadState

binder_node

binder_node是内核层描述Binder实体的结构体,用户层与之相对应的是具体的Binder Server。

  • binder_uintptr_t ptr; // Binder实体在用户空间的地址(为Binder实体对应的Server在用户空间的本地Binder的引用)
  • binder_uintptr_t cookie; // Binder实体在用户空间的其他数据(为Binder实体对应的Server在用户空间的本地Binder自身)
  • proc,它是binder_proc(进程上下文信息)结构体对象。目的是保存该Binder实体的进程。

binder_ref

binder_ref是内核层描述Binder引用的结构体,用户层与之相对应的是某个具体服务的远程代理BpBinder或者BinderProxy。

  • struct rb_node rb_node_desc; //关联到所属进程binder_proc->refs_by_desc红黑树中
  • struct rb_node rb_node_node; // 关联到所属进程binder_proc->refs_by_node红黑树中
  • node是该Binder引用所引用的Binder实体。
  • proc,它是binder_proc(进程上下文信息)结构体对象。
  • desc是Binder引用的描述,实际上它就是Binder驱动为该Binder分配的一个唯一的int型整数。通过该desc,可以在binder_proc->refs_by_desc中找到该Binder引用,进而可以找到该Binder引用所引用的Binder实体等信息。
    在这里插入图片描述

binder_buffer

Binder驱动将Binder进程的内存分成一段一段进行管理,而这每一段则是使用binder_buffer来描述的。

  • struct list_head entry; // 和binder_proc->buffers关联到同一链表,从而使Binder进程对内存进行管理。

flat_binder_object

flat_binder_object是描述Binder对象信息的结构体。binder通过此结构体传输binder对象。

struct flat_binder_object {
	/* 8 bytes for large_flat_header. */
	__u32		type;		 		// 可以为BINDER_TYPE_BINDER或BINDER_TYPE_HANDLE等类型
	__u32		flags;		 		// 标记
	/* 8 bytes of data. */
	union {
		binder_uintptr_t	binder;		// 当type=BINDER_TYPE_BINDER时,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)的弱引用。 
		__u32			handle;			// 当type=BINDER_TYPE_HANDLE时,它等于Binder对象在Binder驱动中对应的Binder实体的Binder引用的描述。就是binder_ref->desc对象,可以通过desc在内核中找到binder_node
	};

	/* extra data associated with local object */
	binder_uintptr_t	cookie;			// 当type=BINDER_TYPE_BINDER时才有效,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)。
};

它在用户空间被使用时(例如,Server发送添加服务请求给ServiceManager),flat_binder_object就是记录的Server位于用户空间的Binder对象的信息的结构体;此时的type的值一般都是BINDER_TYPE_BINDER类型,对应的union中的binder的值是该Binder对象在用户空间的本地Binder(即BBinder对象)的引用;同时,cookie则是本地Binder自身。 而当flat_binder_object在Binder驱动中被使用(例如,当Binder驱动收到发送服务请求时),它会将type修改为BINDER_TYPE_HANDLE,然后将联合体中的handle修改为"该Server对应的Binder实体的Binder引用"的描述;根据Binder引用的描述就能找到该Server。

binder_write_read

binder_write_read是用来描述Binder在驱动层和用户层进行数据读写交互的的结构体。用户层和内核层均有此结构体。

  • write_size,是写内容的总大小;write_consumed,是已写内容的大小;write_buffer,是写的内容的虚拟地址。
  • read_size,是读内容的总大小;read_consumed,是已读内容的大小;read_buffer,是读的内容的虚拟地址。

binder_transaction_data

binder_transaction_data是描述Binder事务交互的数据格式的结构体。它也属于内核空间和用户空间的通信结构体。

struct binder_transaction_data {
	union {
		__u32	handle;		// 当binder_transaction_data是由用户空间的进程发送给Binder驱动时,
							// handle是该事务的发送目标在Binder驱动中的信息,即该事务会交给handle来处理;	
							// handle的值是目标在Binder驱动中的Binder引用,Binder的引用在代码中也叫句柄(handle)
		binder_uintptr_t ptr;	//当binder_transaction_data是由Binder驱动反馈给用户空间进程时
								//ptr是该事务的发送目标在用户空间中的信息,即该事务会交给ptr对应的服务来处理;
								// ptr是处理该事务的服务在用户空间的本地Binder对象,即指向Binder对象内存的指针
	} target;					//该事务的目标对象(即,该事务数据包是给该target来处理的,具体是由handle还是ptr来制定,根据实际情况来确定)
	binder_uintptr_t	cookie;	//只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象
	__u32		code;			//该成员存放收发双方约定的命令码,驱动完全不关心该成员的内容.如果是请求,则以BC_开头;如果是回复,则以BR_开头。

	__u32	        flags;
	pid_t		sender_pid;
	uid_t		sender_euid;
	binder_size_t	data_size;		//数据大小
	binder_size_t	offsets_size;	//数据包中包含的对象的个数

	union {
		struct {
			binder_uintptr_t	buffer;		//真正保存通信数据的缓冲区
			binder_uintptr_t	offsets;	//记录Binder对象偏移的数组	
		} ptr;
		__u8	buf[8];
	} data;							//数据
};

在这里插入图片描述

binder_write_read 和 binder_transaction_data分工不同

  • binder_transaction_data 记录了具体的数据
  • binder_write_read 记录了处理的状态 比如 write_consumed,是已写内容的大小

binder_transaction

binder_transaction用来描述进程间通信过程,这个过程又称为一个事务,进程通信事务。

struct binder_transaction {
	int debug_id;
	struct binder_work work;			//设置事务类型
	struct binder_thread *from;			//发起事务的线程,称为源线程
	struct binder_transaction *from_parent;	//该事务所依赖的事务
	struct binder_proc *to_proc;			//处理该事务的进程
	struct binder_thread *to_thread;		//处理该事务的线程
	struct binder_transaction *to_parent;	//目标线程的下一个事务
	unsigned need_reply:1;					//用来区分一个事务是同步的还是异步的
	/* unsigned is_dead:1; */	/* not used at the moment */

	struct binder_buffer *buffer;			//为该事务分配的一块内核缓冲区,该缓冲区保存了通信数据
	unsigned int	code;					//进程间通信代码
	unsigned int	flags;					//标志位,描述进程间通信行为的特征
	long	priority;						//发起事务的线程优先级
	long	saved_priority;					//保存处理该事务的线程原有优先级
	kuid_t	sender_euid;					//发起事务的euid
};

ServiceManager#binder_state

struct binder_state
{
    int fd;			//文件节点"/dev/binder"的句柄
    void *mapped;	//映射内存的起始地址
    size_t mapsize;	//映射内存的大小
};

ServiceManager#binder_io

binder_io是与binder_transaction_data对应的轻量级结构体。

不清楚具体的使用方式

ServiceManager#svcinfo

svcinfo是保存"注册到ServiceManager中的服务"的相关信息的结构体。它是一个单链表,

struct svcinfo
{
    struct svcinfo *next; //svcinfo中的next是指向下一个服务的节点
    uint32_t handle; //服务在Binder驱动中Binder引用的描述
    struct binder_death death;
    int allow_isolated;
    size_t len;
    uint16_t name[0]; //name则是服务的名称。
};

Parcel

Parcel是描述Binder在用户层服务代理端和服务端进行通信的信息的结构体。

    status_t            mError;                                   
    uint8_t*            mData;            // 数据
    size_t              mDataSize;        // 数据大小
    size_t              mDataCapacity;    // 数据容量
    mutable size_t      mDataPos;         // 数据指针的当前位置
    size_t*             mObjects;         // 对象在mData中的偏移地址
    size_t              mObjectsSize;     // 对象个数
    size_t              mObjectsCapacity; // 对象的容量

数据传输

Android Binder框架实现之Parcel详解之read/writeStrongBinder实现

Parcel负责Binder通信过程中的数据打包,在写入数据的时候 可以通过growData自动扩容。
简单的数据直接写入到mData里,IBinder打包成flat_binder_object。
上面的链接,详细讲述了IBinder打包成flat_binder_object的过程。
在这里插入图片描述

IPCThreadState会在 writeTransactionData 中将mOut(Parcel)打包到binder_transaction_data结构体中。然后在talkWithDriver中讲数据通过 ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr(binder_write_read)) 发送到驱动层
在这里插入图片描述

Parcel可以传递各种类型的数据对象:

基本数据类型:借助Parcel的方法writePrimitives将基本数据类型从用户空间(源进程)copy到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程)
复杂对象将经过序列化的数据借助Parcel的方法writeParcelable()/writeSerializable()从用户空间(源进程)copy到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程),然后再反序列化
大型数据(文件描述符):通过Parcel的方法writeFileDescriptor通过Binder传递匿名共享内存 (Ashmem)的FileDescriptor从而达到传递匿名共享内存的方式,即传递的是FileDescriptor而并不是真的是大数据,参见博客Android 匿名共享内存的使用
IBinder对象:通过Parcel的方法writeStrongBinder方法,然后经由Kernel binder驱动专门处理来完成IBinder的传递

APP的binder线程的如何启动的?

BootLoader-》Linux kernel—》Android init—》service_manager——》Zygote——》system_server

Android APP进程都是由Zygote进程孵化出来的。常见场景:点击桌面icon启动APP,或者startActivity启动一个新进程里面的Activity,最终都会由AMS去调用Process.start()方法去向Zygote进程发送请求,让Zygote去fork一个新进程,Zygote收到请求后会调用Zygote.forkAndSpecialize()来fork出新进程,之后会通过RuntimeInit.nativeZygoteInit来初始化Andriod APP运行需要的一些环境,而binder线程就是在这个时候新建启动的

RuntimeInit.nativeZygoteInit, 该方法经过 JNI 映射,最终会调用到 app_main.cpp 中的 onZygoteInit。
/base/cmds/app_process/app_main.cpp

    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        //这里会创建Binder的主线程。
        proc->startThreadPool();
    }

ProcessState::self()函数会调用open()打开/dev/binder设备,这个时候Client就能通过Binder进行远程通信;其次,proc->startThreadPool()负责新建一个binder线程,监听Binder设备,这样进程就具备了作为Binder服务端的资格。每个APP的进程都会通过onZygoteInit打开Binder,既能作为Client,也能作为Server,这就是Android进程天然支持Binder通信的原因。

Binder 线程的创建流程图:
在这里插入图片描述

每次由 Zygote fork 出新进程的过程中,伴随着创建 Binder 线程池,调用 spawnPooledThread 来创建 Binder 主线程。 当线程执行 binderthreadread 的过程中,发现当前没有空闲线程,没有请求创建线程,且没有达到上限,则创建新的 Binder 线程。

Binder 系统中可分为 3 类 Binder 线程:

  • Binder 主线程:进程创建过程会调用 startThreadPool() 过程中再进入 spawnPooledThread(true),来创建 Binder 主线程。 编号从 1 开始,也就是意味着 Binder 主线程名为 binder1,并且主线程是不会退出的。
  • Binder 普通线程:是由 Binder Driver 来根据是否有空闲的 Binder 线程来决定是否创建 Binder 线程,回调spawnPooledThread(false) ,isMain=false, 该线程名格式为 binderx。
  • Binder 其他线程:其他线程是指并没有调用 spawnPooledThread 方法,而是直接调用 IPC.joinThreadPool(),将当前线程直接加入 Binder 线程队列。 例如: mediaserver 和 servicemanager 的主线程都是 Binder 线程,但 system_server 的主线程并非 Binder 线程。

services_manager进程的启动

以下参考自:Android Binder框架实现之servicemanager守护进程
Kernel启动加载完驱动之后,会启动Android的init程序,init程序会解析init.rc,进而启动init.rc中定义的守护进程。而ServiceManager和zygote则正是通过注册在init.rc中,而被启动的。

//framework/native/cmds/servicemanager/service_manager.c
int main()
{
    struct binder_state *bs;

    bs = binder_open(128*1024);//open里进行了 open("/dev/binder", O_RDWR | O_CLOEXEC); mmap
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    binder_loop(bs, svcmgr_handler);

    return 0;
}

struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));
	...
    bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
	...
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
		...
    }

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
	...
    return bs;

    ...
}

open(“/dev/binder”) 如何关联到binder驱动中

Kernel启动后会调用到binder_init,init会将驱动注册到文件节点/dev/binder ,让我们执行open ioctl等操作时,便会执行到注册的fops函数中。

static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};

static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops
};

static int __init binder_init(void)
{
	...
	ret = misc_register(&binder_miscdev);
	...
}
  • Kernel启动后会调用到binder_init。
  • misc_register(&binder_miscdev)将Binder驱动注册到文件节点"/dev/binder",而该文件节点"/dev/binder"的设备信息是binder_miscdev这个结构体对象。
  • fops是该设备节点的文件操作对象,它是我们需要重点关注的!fops指向binder_fops变量。
  • binder_fops变量是struct file_operations类型。owner是标明了该文件操作变量的拥有者,就是该驱动;poll则指定了poll函数指针,当我们对/dev/binder文件节点执行poll()操作时,实际上就是调用的binder_poll()函数;同理,mmap()对应binder_mmap(),open()对应binder_open(),ioctl()对应binder_ioctl()…

binder_open()

当我们成功调用binder_open函数时,Binder驱动会在内核负责为用户层进程创建对应的进程上下文结构体binder_proc
将 binder_proc 设为filp文件描述符的私有成员 这样,在mmap(),ioctl()等函数中,我们都可以根据filp的私有成员来获取proc信息。

binder_mmap()

//[kernel/drivers/staging/android/binder.c]
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
  int ret;
  struct vm_struct *area;
  struct binder_proc *proc = filp->private_data;
  const char *failure_string;
   //内核进入这个函数时,就已经预先为此次映射分配好了调用进程在用户空间的虚拟地址范围
  //(vma->vm_start,vma->vm_end)
  struct binder_buffer *buffer;

  // 有效性检查:映射的内存不能大于4M
  if ((vma->vm_end - vma->vm_start) > SZ_4M)
      vma->vm_end = vma->vm_start + SZ_4M;

  ...

  vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

  mutex_lock(&binder_mmap_lock);

  //为进程所在的内核空间申请与用户空间同样长度的虚拟地址空间,这段空间用于内核来访问和管理binder内存区域
  area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
  ...

  // 将内核空间地址赋值给proc->buffer,即保存到进程上下文中,对应内核虚拟地址的开始,即为binder内存的开始地址
  proc->buffer = area->addr;
  // ** 计算 "内核空间地址" 和 "进程虚拟地址" 的偏移
  proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
  mutex_unlock(&binder_mmap_lock);

  //**  为proc->pages分配内存,用于存放内核分配的物理页的页描述指针:struct page,每个物理页对应这样一个struct page结构
  proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
  ...

  // 内核空间的内存大小 = 进程虚拟地址区域(用户空间)的内存大小
  proc->buffer_size = vma->vm_end - vma->vm_start;

  vma->vm_ops = &binder_vm_ops;
  // 将 proc(进程上下文信息) 赋值给vma私有数据
  vma->vm_private_data = proc;

  // 通过调用binder_update_page_range()来分配物理页面。
  // ** 即,将物理内存映射到内核空间 以及 用户空间
  if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
      goto err_alloc_small_buf_failed;
  }
  buffer = proc->buffer;
  INIT_LIST_HEAD(&proc->buffers);
  // 将物理内存添加到proc->buffers链表中进行管理。
  list_add(&buffer->entry, &proc->buffers);
  buffer->free = 1;
  // 把分配好内存插入到对应的表中(空闲内存表)
  binder_insert_free_buffer(proc, buffer);
  proc->free_async_space = proc->buffer_size / 2;
  barrier();
  proc->files = get_files_struct(proc->tsk);
  // 将用户空间地址信息保存到proc中
  proc->vma = vma;
  proc->vma_vm_mm = vma->vm_mm;

  return 0;
  ...
}
  • 第一,将指定大小的"物理内存" 映射到 “用户空间”(即,进程的虚拟地址中)
  • 第二,将该"物理内存" 也映射到 “内核空间(即,内核的虚拟地址中)”。简单来说,就是"将进程虚拟地址空间和内核虚拟地址空间映射同一个物理页面"
    binder_mmap()会将Server进程的虚拟地址和内核虚拟地址映射到同一个物理页面。那么当Client进程向Server进程发送请求时,只需要将Client的数据拷贝到内核空间即可!由于Server进程的地址和内核空间映射到同一个物理页面,因此,Client中的数据拷贝到内核空间时,也就相当于拷贝到了Server进程中。因此,Binder通信机制中,数据传输时,只需要1次内存拷贝!这就是Binder通信原理的精髓所在!

Binder如何精确找到目标Binder实体

struct binder_proc {
    struct hlist_node proc_node;
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    。。。
    struct list_head todo;
    wait_queue_head_t wait;
    。。。
};
  • 主要看binder_proc的结构体。
  • Service通过addService向ServiceManager注册的时候,ServiceManager会将服务相关的信息存储到自己进程的Service列表中去,同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,这样ServiceManager就能获取Service的Binder实体信息。
  • 当Client通过getService向ServiceManager请求该Service服务的时候,ServiceManager会在注册的Service列表中查找该服务,如果找到就将该服务返回给Client,在这个过程中,ServiceManager会在Client进程的binder_ref红黑树中添加binder_ref节点,可见本进程中的binder_ref红黑树节点都不是本进程自己创建的,要么是Service进程将binder_ref插入到ServiceManager中去,要么是ServiceManager进程将binder_ref插入到Client中去。之后,Client就能通过Handle句柄获取binder_ref,进而访问Service服务。
  • getService之后,便可以获取binder_ref引用,进而获取到binder_proc与binder_node信息,之后Client便可有目的的将binder_transaction事务插入到binder_proc的待处理列表,并且,如果进程正在睡眠,就唤起进程,其实这里到底是唤起进程还是线程也有讲究,对于Client向Service发送请求的状况,一般都是唤醒binder_proc上睡眠的线程:

binder的路由原理

**binder的路由原理:**BpBinder发送端,根据handler,在当前binder_proc中,找到相应的binder_ref,由binder_ref再找到目标binder_node实体,由目标binder_node再找到目标进程binder_proc。简单地方式是直接把binder_transaction节点插入到binder_proc的todo队列中,完成传输过程。

对于binder驱动来说应尽可能地把binder_transaction节点插入到目标进程的某个线程的todo队列,效率更高。当binder驱动可以找到合适的线程,就会把binder_transaction节点插入到相应线程的todo队列中,如果找不到合适的线程,就把节点之间插入binder_proc的todo队列。

MediaPlayerService 如何进入消息循环。

MediaPlayerService的main()函数

int main(int argc __unused, char **argv __unused)
{
	...
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm(defaultServiceManager());
	...
    MediaPlayerService::instantiate();
    ResourceManagerService::instantiate();
	...
	//startThreadPool 创建binder主线程, BC_ENTER_LOOPER 
    ProcessState::self()->startThreadPool();
    //将当前线程加入到binder处理线程池。 BC_REGISTER_LOOPER
    IPCThreadState::self()->joinThreadPool();
}

startThreadPool,创建了binder主线程,最后也会调用到joinThreadPool
joinThreadPool 会调用talkWithDriver()和Binder驱动进行交互。 循环读取binder的消息。

Binder线程启动 ProcessState 和 IPCThreadState

ProcessState是每个进程单例,记录进程信息。对应驱动层binder_proc;ProcessState::self()->startThreadPool(); 创建binder主线程,主线程不会退出。
IPCThreadState是每个线程单例,它负责和binder驱动的具体交互工作。 对应驱动层binder_thread;IPCThreadState在joinThreadPool后 会循环的读取binder驱动的信息。
IPCThreadState::self()->joinThreadPool();是将当前线程加入到Binder处理线程,非主线程空闲超时会退出。

joinThreadPool 就是循环的调用talkWithDriver和驱动进行交互。

server进程如何被回调到onTransact

1、server进程阻塞在binder_thread_read()方法等待客户端的请求。
2、收到事务类型BINDER_WORK_TRANSACTION,然后将事务数据binder_transaction_data复制到用户空间。
3、put了两个指令到用户空间BR_NOOP和BR_TRANSACTION。
4、binder驱动返回到IPCThreadState::talkWithDriver()
5、IPCThreadState::executeCommand() 处理BR_TRANSACTION。
6、通过mIn.read()读取事务数据。调用ipcSetDataReference()将事务数据解析出来。

 case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            ...

            Parcel buffer;
            buffer.ipcSetDataReference(
                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(size_t), freeBuffer, this);

            ...

            Parcel reply;
            ...
            //tr.target.ptr 驱动层找到的binder对象。
            if (tr.target.ptr) {
                sp<BBinder> b((BBinder*)tr.cookie);
                //通过BBinder.transact() 调用到onTransact 最终调用到MediaPlayerService.cpp的onTransact
                const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
                if (error < NO_ERROR) reply.setError(error);

            } else {
                ...
            }

在这里插入图片描述

binder一次copy的原理。

听说你Binder机制学的不错,来面试下这几个问题(一)

  • Binder的map函数,会将内核空间直接与用户空间对应,用户空间可以直接访问内核空间的数据
  • • A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)
  #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
  
  ProcessState::ProcessState()
      : mDriverFD(open_driver())
      , mVMStart(MAP_FAILED)
      , mManagesContexts(false)
      , mBinderContextCheckFunc(NULL)
      , mBinderContextUserData(NULL)
      , mThreadPoolStarted(false)
      , mThreadPoolSeq(1){
     if (mDriverFD >= 0) {
      ....
          // mmap the binder, providing a chunk of virtual address space to receive transactions.
          mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
     ...
   
      }
  }

当数据从用户空间拷贝到内核空间的时候,是直从当前进程的用户空间接拷贝到目标进程的内核空间,这个过程是在请求端线程中处理的,操作对象是目标进程的内核空间。看如下代码:

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply){
                   ...
        在通过进行binder事物的传递时,如果一个binder事物(用struct binder_transaction结构体表示)需要使用到内存,
        就会调用binder_alloc_buf函数分配此次binder事物需要的内存空间。
        需要注意的是:这里是从目标进程的binder内存空间分配所需的内存
        //从target进程的binder内存空间分配所需的内存大小,这也是一次拷贝,完成通信的关键,直接拷贝到目标进程的内核空间
        //由于用户空间跟内核空间仅仅存在一个偏移地址,所以也算拷贝到用户空间
        t->buffer = binder_alloc_buf(target_proc, tr->data_size,
            tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
        t->buffer->allow_user_free = 0;
        t->buffer->debug_id = t->debug_id;
        //该binder_buffer对应的事务    
        t->buffer->transaction = t;
        //该事物对应的目标binder实体 ,因为目标进程中可能不仅仅有一个Binder实体
        t->buffer->target_node = target_node;
        trace_binder_transaction_alloc_buf(t->buffer);
        if (target_node)
            binder_inc_node(target_node, 1, 0, NULL);
        // 计算出存放flat_binder_object结构体偏移数组的起始地址,4字节对齐。
        offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
           // struct flat_binder_object是binder在进程之间传输的表示方式 //
           // 这里就是完成binder通讯单边时候在用户进程同内核buffer之间的一次拷贝动作 //
          // 这里的数据拷贝,其实是拷贝到目标进程中去,因为t本身就是在目标进程的内核空间中分配的,
        if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
            binder_user_error("binder: %d:%d got transaction with invalid "
                "data ptr\n", proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_copy_data_failed;
        }

可以看到binder_alloc_buf(target_proc, tr->data_size,tr->offsets_size, !reply && (t->flags & TF_ONE_WAY))函数在申请内存的时候,是从target_proc进程空间中去申请的,这样在做数据拷贝的时候copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)),就会直接拷贝target_proc的内核空间,而由于Binder内核空间的数据能直接映射到用户空间,这里就不在需要拷贝到用户空间。这就是一次拷贝的原理。内核空间的数据映射到用户空间其实就是添加一个偏移地址,并且将数据的首地址、数据的大小都复制到一个用户空间的Parcel结构体,具体可以参考Parcel.cpp的Parcel::ipcSetDataReference函数。

在这里插入图片描述

binder传输的大小限制

Zygote孵化而来的用户进程,大小是 10241024 - 40962 也就是1M减去两页。

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))

内核中在mmap处限制了4M,用户自己调用mmap可以申请到4M。

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    //限制不能超过4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
    。。。
    }

ServiceManager进程,它为自己申请的Binder内核空间是128K

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

        // 仅仅申请了128k
    bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}   

系统服务与bindService等启动的服务的区别

bindService的普通服务对象是由AMS来管理的。bindService启动的服务,主要是去ActivityManagerService中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。

具体的需要再研究。

Android APP有多少Binder线程,是固定的么?

SystemServer 和 Media都是一开始的时候开启了两个线程。

       ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();

普通应用通过onZygoteInit开始只创建一个线程。

    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        //这里会创建Binder的主线程。
        proc->startThreadPool();
    }

binder驱动在binder_thread_read的时候,发下如果线程不够,则会发送BR_SPAWN_LOOPER命令,通知服务端的用户空间去创建新的线程。

普通的APP进程,在ProcessState::self()新建ProcessState单利对象的时候会调用ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);设置上限,可以看到默认设置的上限是15。

之所以采用动态新建Binder线程的意义有两点,第一:如果没有Client请求服务,就保持线程数不变,减少资源浪费,需要的时候再分配新线程。第二:有请求的情况下,保证至少有一个空闲线程是给Client端,以提高Server端响应速度。

异步请求(oneway)同一个线程中的Client请求的服务是一个耗时操作的时候,通过oneway的方式发送请求的话,如果之前的请求还没被执行完,则Service不会启动新的线程去响应,该请求线程的所有操作都会被放到同一个Binder线程中依次执行,客户端不等待他的返回。

Binder协议中BC与BR的区别

BC与BR主要是标志数据及Transaction流向,其中BC是从用户空间流向内核,而BR是从内核流线用户空间,比如Client向Server发送请求的时候,用的是BC_TRANSACTION,当数据被写入到目标进程后,target_proc所在的进程被唤醒,在内核空间中,会将BC转换为BR,并将数据与操作传递该用户空间。
在这里插入图片描述

Binder驱动传递数据的释放(释放时机)

Binder通信中内核数据的释放时机应该是用户空间控制的,内核中释放内存空间的函数是binder_free_buf,其他的数据结构其实可以直接释放掉,执行这个函数的命令是BC_FREE_BUFFER。上层用户空间常用的入口是IPCThreadState::freeBuffer:

void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t dataSize,
                                const size_t* objects, size_t objectsSize,
                                void* cookie)
{
    if (parcel != NULL) parcel->closeFileDescriptors();
    IPCThreadState* state = self();
    state->mOut.writeInt32(BC_FREE_BUFFER);
    state->mOut.writeInt32((int32_t)data);
}

Parcel 的ipcSetDataReference函数不仅仅能讲数据映射到Parcel对象,同时还能将数据的清理函数映射进来
Parcel的析构函数 会释放内存。也就是Parcel释放才会释放驱动映射的内存

//release_func relFunc参数 这里指定了IPCThreadState::freeBuffer函数
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
    const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
    

Parcel::~Parcel()
{
    freeDataNoInit();
}

为什么zygote使用socket创建进程,不使用Binder

因为zygote要fork出好多子进程,如果用binder的话,都fork出一个binder对象,无法做区分进程了。

binder通信是如何找到远端对应的方法的。

1、首先aidl生成的java文件会为每一个方法生成一个唯一的数字。
AIDL不能定义两个方法名字相同的函数。

interface IMyAidlInterface {
    int add(int a);
    oneway void addOneWay(int b);
    int addNormorl(int a);
}

   int FIRST_CALL_TRANSACTION  = 0x00000001;
   static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
   static final int TRANSACTION_addOneWay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
   static final int TRANSACTION_addNormorl = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
   

@Override public void addOneWay(int b) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(b);
          //在调用remote.transact方法的时候会传递方法的code  TRANSACTION_addOneWay
          boolean _status = mRemote.transact(Stub.TRANSACTION_addOneWay, _data, null, android.os.IBinder.FLAG_ONEWAY);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addOneWay(b);
            return;
          }
        }
        finally {
          _data.recycle();
        }
      }
      

2、binder请求的时候会将方法对应的数字传递进去。

3、中间省略数据打包和拆包的过程。

4、服务端根据此数字找到对应的方法。

asInterface的主要作用

首先通过ServiceManager.getService来获取IBinder对象
asInterface就是将远程的binder代理对象转换为本地的代理对象ActivityManagerProxy。ActivityManagerProxy通过持有IBinder,来调用transact方法实现binder的通信。


    //此处等价于IBinder b = new BinderProxy(new BpBinder(handle));
    IBinder b = ServiceManager.getService("activity");
            
	//此处等价于IActivityManager am = new ActivityManagerProxy(new BinderProxy(new BpBinder(handle)))
    IActivityManager am = asInterface(b);
    

	//注意此处我们的入参是BinderProxy类型,所以会走代理端
    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
		//即会走到此处
        return new ActivityManagerProxy(obj);
    }

bindService的流程

  • 发起端进程: 首先通过bindService携带接口回调类ServiceConnection传递到LoadedApk中,接着在该类中通过getServiceDispatcher获取发起端进程的匿名Binder服务端(此为第一处匿名Binder),即LoadedApk.ServiceDispatcher.InnerConnection,该对象继承于IServiceConnection.Stub;再通过bindService调用到system_server进程
    这里将ServiceConnection通过map将封装的Binder对象IServiceConnection.Stub形成一一对应的关系,然后传递到system_server。方便IServiceConnection的回调能够找到ServiceConnection

  • system_server进程: 依次通过scheduleCreateService和scheduleBindService方法, 远程调用到目的端进程进程(这里的前提是目的端进程已经创建,如果服务端进程还没有创建,则还牵涉到目的服务端进程的创建)

  • 目的端进程: 依次执行目的端Service的onCreate()和onBind()方法;,然后将onBind()方法的返回值IBinder(作为目的端进程匿名的Binder服务端,此处为第二处匿名Binder服务端),接着通过publishService将该匿名Binder服务端的代理端传递到system_server进程
    system_server将通过一些数据结构保存此Binder和服务端Service的对应关系

  • system_server进程: 经过上述的一系列处理以后,利用IServiceConnection代理对象向发起端进程发起connected()调用, 并把target进程的onBind返回Binder对象的代理端传递到发起端进程

  • 发起端: 回调到onServiceConnection()方法, 该方法的第二个参数便是目的端进程的Binder代理端. 到此便成功地拿到了目的端进程的代理, 可以畅通无阻地进行RPC交互了

bindService的实质就是匿名Binder跨进程的传递,客户端创建一个IServiceConnection传递给system_server,服务端Service通过onBinder返回一个IBinder对象,由AMS做处理将IServiceConnection和IBinder链接起来。AMS拿到IBinder之后回调IServiceConnection的connect接口,并将IBinder传递给客户端。此时客户端拿到IBinder就可以和服务端进行交互了。

Binder IPC的权限控制

calluid和callpid是由驱动层设置的,所以不会被篡改。
1、binder调用到远端,驱动层在执行binder_thread_read()过程会设置pid和uid
2、IPCThreadState的transact收到BR_TRANSACION则会设置mCallingPid、mCallingUid。

Binder的死亡通知原理

对于Binder IPC进程都会打开/dev/binder文件,当进程异常退出时,Binder驱动会保证释放将要退出的进程中没有正常关闭的/dev/binder文件,实现机制是binder驱动通过调用/dev/binder文件所对应的release回调函数,执行清理工作,并且检查BBinder是否有注册死亡通知,当发现存在死亡通知时,那么就向其对应的BpBinder端发送死亡通知消息。

死亡回调DeathRecipient只有Bp才能正确使用,因为DeathRecipient用于监控Bn端挂掉的情况, 如果Bn建立跟自己的死亡通知,自己进程都挂了,也就无法通知。

每个BpBinder都有一个记录DeathRecipient列表的对象DeathRecipientList。

**linkToDeath过程
**

  • requestDeathNotification过程向驱动传递的命令BC_REQUEST_DEATH_NOTIFICATION,参数有mHandle和BpBinder对象;
  • binder_thread_write()过程,同一个BpBinder可以注册多个死亡回调,但Kernel只允许注册一次死亡通知。
  • 注册死亡回调的过程,实质就是向binder_ref结构体添加binder_ref_death指针, binder_ref_death的cookie记录BpBinder指针。

unlinkToDeath过程

  • unlinkToDeath只有当该BpBinder的所有mObituaries都被移除,才会向驱动层执行清除死亡通知的动作, 否则只是从native层移除某个recipient。
  • clearDeathNotification过程向驱动传递BC_CLEAR_DEATH_NOTIFICATION,参数有mHandle和BpBinder对象;
  • binder_thread_write()过程,将BINDER_WORK_CLEAR_DEATH_NOTIFICATION事务添加当前当前进程/线程的todo队列

触发死亡回调

  • 服务实体进程:binder_release过程会执行binder_node_release(),loop该binder_node下所有的ref->death对象。 当存在,则将BINDER_WORK_DEAD_BINDER事务添加ref->proc->todo(即ref所在进程的todo队列)
  • 引用所在进程:执行binder_thread_read()过程,向用户空间写入BR_DEAD_BINDER,并触发死亡回调。
  • 发送死亡通知sendObituary

BpBinder->mHandler 和 this

很多地方会同事将BpBinder的mHandler和this 都传入,mHandler就已经可以查找到BnBinder了为什么还要传入this;

mHandler可以查找到对应的BnBinder这个在驱动中只有一份。但是BpBinder可能会有很多,传入this,在驱动层转换为cookie,可以根据cookie的不同判断是否是同一个BpBinder

self->clearDeathNotification(mHandle, this);

status_t IPCThreadState::clearDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_CLEAR_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writePointer((uintptr_t)proxy);
    return NO_ERROR;
}

    //驱动层
    get_user(target, (uint32_t __user *)ptr); //获取target mHandle
    ptr += sizeof(uint32_t);
    get_user(cookie, (void __user * __user *)ptr); //获取cookie,也就是BpBinder* proxy
    
    if (death->cookie != cookie) {
        break; //比较是否同一个BpBinder
    }
              

aidl关键方法

aidl接口 继承 IInterface
1、IInterface主要提供asBinder方法。

Stub类继承自Binder实现aidl接口
1、asInterface方法通过queryLocalInterface根据DESCRIPTOR查找是否是本地Binder
2、同一个进程,请求binder服务,不需要创建binder_ref,BpBinder等这些对象,但是是否需要经过binder call,取决于descriptor是否设置。
3、binder通过回调onTransact方法回调本地实现。

Proxy类实现aidl接口。
1、Proxy持有IBinder mRemote的引用。
2、实现asBinder方法 返回mRemote
3、代理的方法均通过mRemote.transact调用。

binder_transaction的分配

binder_proc 和 binder_thread都有todo队列,proc的队列都会交给thread来处理。

对于binder驱动来说应尽可能地把binder_transaction节点插入到目标进程的某个线程的todo队列,效率更高。当binder驱动可以找到合适的线程,就会把binder_transaction节点插入到相应线程的todo队列中,如果找不到合适的线程,就把节点之间插入binder_proc的todo队列。

queryLocalInterface

asInterface在native层是通过IInterface.h的模板方法实现的。

1、在BpBinder端,该方法是直接返回NULL。

sp<IInterface>  IBinder::queryLocalInterface(const String16& descriptor)
{   
    return NULL;
}   

2、在Stub端的继承至Binder的实现,也就是根据DESCRIPTOR返回自身


    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    
    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值