Binder跨进程通信的本质是依赖内核驱动将属于不同Binder进程的数据,从原始进程复制到目标进程,这样就完成了跨进程通信了。

好了就这些,简单吧?

逗我玩全国人民都爱的毛爷爷说过一句至理名言:我们要从战略上藐视敌人,从战术上重视敌人!虽然Binder跨进程通信方式跟Linux系统其它跨进程通信方式一样,都是把数据从一个进程发送到另一个进程,但是在具体的实现方式必须独树一帜!Binder确实做到了。

一、传输效率高

Binder通过独特的内存映射机制,在跨进程通信时,可以做到一次拷贝,两个空间同时使用!如下图:

图1.内存拷贝图进程A向进程B传递数据时,Binder驱动通过一次拷贝,将进程A用户空间的数据拷贝到进程B内核空间的共享内存中。由于Binder进程的内存分配机制是,每一个Binder进程中内核空间和用户空间针对同一段物理内存建立映射,那么你懂的!放在同一块物理地址中的数据自然可以被内核空间和用户空间同时访问。但是面试官可能会问你内核空间和用户空间的内存地址是否一样,告诉他不一样,两者之间存在一个固定的差值。

至于内核空间和用户空间是Linux内核地址空间的内存划分的范畴,有些专家岗对这个颇在乎,我会专门学习~

对了,关于进程B的拷贝响应数据到进程A的过程,也是这种机制。值得注意的是,Binder驱动要单独申请物理内存完成映射。试想,如果两次拷贝使用相同的物理内存地址,就跟shared memory共享内存这种跨进程通信方式完全一样了,也就无法避免共享内存借助于其他通信方式完成多进程同步的弊端。

Binder跨进程通信的效率是相对的,比管道/消息队列等IPC效率高,因为管道/消息队列不但要复制到内核空间还要复制到用户空间。比共享内存安全性好,不必考虑进程间同步问题。

二、虚拟硬件映射内存mmap

Binder跨进程通信是要传递数据的,既然有数据必然要占用内存空间,Android系统规定每一个进程都有一块Binder内存区,也就是图1中的共享内存,系统最多只能给该区域分配4M的物理内存,由于申请这块内存是通过系统的mmap函数完成的,所以整个映射机制又被称为mmap机制

为了把这部分说明白,就再盗图一张,命名图2吧!

图2.内存映射机制mmap机制是Linux系统为具有物理存储介质的文件系统提供的映射函数,简言之,它服务于内核空间,而内核空间是可以跨进程通信的。Android系统为了使用这种机制,通过Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制,LKM在运行时被链接到内核作为内核的一部分在内核空间运行。这样,Android系统可以通过LKM添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,完成通信。在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动。驱动就是操作硬件的接口,为了支持Binder通信过程,Binder 成为了一种“硬件”,因此这个模块被称之为Binder驱动。

图2中通信时,先调用binder_open函数打开Binder驱动,然后通过binder_mmap申请一块共享内存,然后将这块共享内存映射到接收进程的内核空间和用户空间。每当发送进程向接收进程传递数据时,Binder驱动程序就在共享内存中根据最优算法申请一块区域,并将数据从发送进程对应的共享内存拷贝过来。

但是Binder通信采用的消息+数据的格式传递信息,仅仅将数据拷贝过来拿到映射是没有用的,消息结构体如何调度呢?

Linux内核并没有从一个用户空间到另一个用户空间直接拷贝的函数,而是先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。这样用户空间就可以通过mmap映射读取内核空间存储的数据了。Binder驱动承担了有效负荷的分配和释放任务,由于采用mmap机制提升了一倍性能,消息头结构体还是要接收方来存储的,但是其大小可控,属于辅助。

三、Binder进程和线程

Binder进程和线程

对于Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。

Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。

四、参考博客