浅谈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驱动是如何实现进程间通讯的?看图说话:

在这里插入图片描述
进程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节点)的关键。
在这里插入图片描述

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(客户端和服务端在同一个进程)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Am63fF8B-1593398548582)(https://note.youdao.com/yws/api/personal/file/WEB12fd145fa54bd00a88801cba11428b8f?method=download&shareKey=9b9dd9c18cd4e4ca8b74f066dc69b553)]

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

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

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

结语

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

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

文章的更新周期相对来说也会变长,期待大家也能静下心来一起学习研究Android系统。

最后

我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值