Android开发之——Binder

前言

最近沉迷于Android源码中无法自拔,只是苦于内功浅薄,很多东西看得云里雾里。起初对Android图形系统比较感兴趣,一头扎进去,不到片刻,就再也找不到“线头”。原因之一在于Android在启动一个activity到从LCM中显示出来图像,使用了大量的Binder通讯,各种IXXX,BpXXX,BnXXX蜂拥而出,各种类派生,看得头皮发麻。于是将图形系统放在一边,转战Binder通讯。

概述

Android系统中充满各种各样的进程,每个进程肩负着不同的任务。当某个进程处理不了的事情,则可以丢给其他能够进行处理的进程进行处理,比如AudioTrack本身不具备播放音频的能力,它就是向上给应用提供播放的接口,向下就让AudioFlinger播放播放。由于AudioTrack和AudioFlinger处于不同的进程,所以这个过程就发生了跨进程通讯。

跨进程通讯需要采用特殊的通讯机制,也就是IPC(进程间通讯)。Android系统是基于Linux系统的,而Linux系统本身已经提供了共享内存,socket,管道等IPC机制。但Android却没有采纳这些机制,而是自己开发了一个IPC机制:Binder通讯。Binder通讯性能好,稳定性和安全性高。

所有的IPC都是基于Linux内核实现的,Binder也不例外,Binder驱动就是整个Binder通讯的核心。那么Binder驱动是如何实现进程间通讯的?看图说话:

 

image

 

 

进程B作为服务端,在进程启动的时候,就会打开一个Binder设备,并调用mmap方法,将用户空间和内核空间(会将此空间跟Binder驱动节点绑定)都映射到同一块物理内存上。我们且先不管进程A调用什么接口跟Binder驱动通讯,最终就是Binder驱动通过copy_from_user函数将A进程想要发给B进程的数据从用户空间拷贝到了内核中Binder驱动的缓冲区。由于此时缓冲区的与物理内存有映射关系,而进程B的用户空间也映射到了物理内存同样的地址,所以此时进程B就能直接读取到进程A发过来的数据了。这就是一次Binder通讯的大概框架。

如何找到目标binder节点

每个进程的用户空间自成一体,进程之间想要进行通讯,就只能通过内核空间进行数据共享。从如上概述中,我们已经清楚,内核空间通过映射的方法达到数据共享的目的,但这里有个前提,就是从用户空间拷贝到内核间的数据,需要拷贝到服务端进程对应的Binder驱动对应的内核缓冲区,才能让服务端进程的用户空间访问到数据。Binder驱动节点众多,是如何将数据从用户空间拷贝到内核空间中对应Binder节点的缓冲区内的?

Binder驱动会维护一张表,这张表由各个Binder节点组成,每当进程打开Binder设备,就会创建一个Binder节点,然后将该节点保存到这张表中。 作为服务端的进程在启动的时候,必须要打开Binder设备,创建一个Binder节点,然后往Binder大本营中添加该节点。并进行mmap操作,完成虚拟内存和物理内存的映射。当客户端通过ioctl方法跟binder驱动通讯时,会封装此次通讯的数据,目标handle就是数据之一,这个数据就是用来查找目标节点(服务端在binder驱动创建的binder节点)的关键。

 

image

 

 

handle的保存者--ServiceManager

既然handle如此重要,那它是从哪来的?答案在servicemanager(下面简称SM)中。这家伙是Android系统服务的管家,保存着所有在SM中注册的服务的handle。当客户端想要调用到服务端某个方法时,首先得找到这个远程服务,方法是通过servicemanager查询对应服务的handle。然后客户端再拿着这个handle(handle只是其中一个数据,会和其他的数据一起被封装)去和binder驱动通讯。所有东西都很完美,但这时有个问题需要被解决,那就是客户端跟SM的通讯也是跨进程,也就是说客户端需要先有SM对应的handle,这样才能进行通讯。

将SM对应的handle固定为某个值,这样客户端默认就知道了SM的handle值。Android也正是这样处理的,SM的handle值为0,唯一且永不改变。

那SM上各个系统服务的handle是怎么来的?总不能是凭空出现的。每个系统服务需要在SM中进行注册,在注册的过程中,会由驱动生成一个handle值,该handle保存到系统服务对应的binder节点上,且将handle通过svcinfo(包含服务名)的方式保存到SM的svclist列表中。这样每当有客户端向SM查询服务时,SM就会通过服务名来获取到相应的handle,再通过此handle去获取具体的BpBinder(客户端和服务端不在一个进程)或者BBinder(客户端和服务端在同一个进程)

 

image

 

 

客户端调用远程服务的方法

Android淡化了进程这个概念,我们即使在使用远程进程的时候,表面上就是获取某个远程对象,然后调用该对象的方法,实现我们的功能。但其实本质上是数据的打包发送和接收解析。所谓远程服务的方法,其实也就是一个个的code编号。客户端和服务端约定好编号的意义即可。

通过向SM查询到的服务,其实是一个远程代理,一般为Bpxxx。Bpxxx有我们需要的远程服务的接口,当我们调用了该接口的时候,实际上发生了Binder通讯,将打包好的数据通过IPCThread发送出去(调用ioctl,与Binder驱动通讯)。负责打包的是Parcel。服务端通过Binder驱动拿到客户端发过来的数据,并进行解析,通过code编号,知道客户端需要调用到自己的某个方法,于是开始执行该方法,并将结果通过Binder驱动发回给客户端。

结语

在阅读Binder的源码过程中,如果能有意去将Binder业务层和通讯层区分开,那么可能相对会好理解些。每次阅读都有不同的理解,也都能够纠错以前的一些错误认知。

Android系统很庞大,很多东西都是一知半解。最近有个计划,那就是分模块来进行学习。学习的方法主要还是以问题为驱动,然后针对这一块阅读不同的博客文章和书籍,再进行内化写文章。

在这里我也分享自己收录整理的Android学习PDF,里面对Binder有详细的讲解,希望可以帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,可以分享给身边好友一起学习

有需要的朋友可以私信我【666】获取;

感兴趣的可以加下粉丝裙哦:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章