简单说说Binder


Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群


作者丨Silhouette 
来源丨安卓哥(WebExcavator

Binder这个东西在Android圈还挺红的,进程之间说话都要找他,这里就简单说说Binder这个东西。由于Binder这家伙还真不简单,我对他的认识还不算全面,这里只是简单说说。

Binder是什么东西?
Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现。( OpenBinder :一个提供简单的进程间互相通讯的途径,是一个系统级的组件架构,旨在提供更丰富的高层次、抽象的架构于传统和现代操作系统之上的服务。目前已在Linux上实现,但代码已可以运行在一个品种多样的平台,包括BeOS,Windows,和PalmOS Cobalt。)Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。有时这些组件运行在同一进程,有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。不仅如此,Android 系统对应用层提供的各种服务如:ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。

为什么需要Binder?
这里涉及到一个概念:进程隔离
进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。 进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B。正是由于进程隔离的存在,所以需要进程间通信(IPC)机制实现进程间的交流,Binder就是其中一种IPC机制。

为什么使用Binder而非其他IPC机制?
众所周知,Android系统是基于Linux内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?固然是因为Binder具有其他IPC方式没有的优势。见下表:

性能
安全性
稳定性
管道
采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。

除Socket外都不支持Client-Server的通信方式。
消息队列
共享内存
无需拷贝,但控制复杂,难以使用。
Socket
通用接口,传输效率低,开销大
Binder
一次数据拷贝,性能较优。
为每个进程分配UID,进程的UID是区分进程身份的重要标志
基于C/S架构,职责明确,架构清晰,稳定性好

原理
Linux传统IPC通信原理
这里先说明一个概念:用户空间(User Space)和内核空间(Kernel Space)
为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。用户空间需要访问内核资源时,需要通过系统调用来实现,这也是唯一的方式。
系统调用主要通过如下两个函数来实现:
copy_from_user() //将数据从用户空间拷贝到内核空间copy_to_user() //将数据从内核空间拷贝到用户空间
那么这两个函数到底什么时候用呢?接下来就说传统IPC的通信过程:
消息发送进程将要发送的数据存放在用户空间的内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,创建一个内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。然后再调用 copy_to_user() 函数将数据从内核缓存区拷贝到数据接收进程的用户空间的内存缓存区,通信完成。如下图:

由此可见,这个通信过程中,一共经历了2次数据拷贝,耗时,性能低。同时,数据接收进程对即将到来的数据大小一无所知,只能创建尽可能大的内存空间,浪费空间;或者先调用 API 接收消息头来获取消息体的大小,浪费时间。

Binder IPC原理
Binder IPC 利用内存映射(mmap)进行优化。mmap() 是操作系统中一种内存映射的方法。内存映射就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。内存映射能减少数据拷贝次数,提高用户空间和内核空间的通信效率。Binder使用mmap并不是像多数情况下为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间
这种情况下,Binder IPC的过程一般是这样:
(1)Binder驱动在内核空间创建一个数据接收缓存区,再创建一个内核缓存区,并建立这两者之间的映射关系;
(2)建立内核中数据接收缓存区和接收进程用户空间地址的映射关系;
(3)数据发送进程通过系统调用 copy_from_user() 将数据拷贝到内核中的内核缓存区,之后,由于之前建立的两个映射关系,数据可以直接传递到数据接收进程的用户空间,通信完成。整个过程只需要1次拷贝。

Binder IPC实现
说完原理之后,来看看Binder IPC是怎么实现的。先来看看Binder的架构,或者说通信模型。
如图,Binder由几个重要组件组成:Client、Server、Service Manager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。Service Manager和Binder驱动已经由系统提供,我们只需要实现Client、Server端。我们逐一来看看这几个组件:
Client:客户端,请求服务的一方
Server:服务端,提供服务的一方
Service Manager:负责管理系统中的各种服务
Binder驱动:运行在内核空间,通信的核心,提供open(),mmap(),poll(),ioctl()等标准文件操作,以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。驱动负责进程之间Binder通信的一系列底层支持。
那么这些组件是如何合作实现通信的呢?
  1. ServiceManager的诞生
    一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
  2. Service注册到ServiceManager

    Server将其提供的Service注册到ServiceManager中,即Binder驱动在内核空间为Service创建一个Binder实体,并创建一个Binder引用提供给ServiceManager。ServiceManager将维护一个Map,该Map会对注册的Service及对应Binder引用生成handle值进行保存;

  3. Client获取ServiceManager的引用

    Client要对ServiceManager发起请求,就需要首先找到ServiceManager。为了找到ServiceManager,Client构建一个handle值为0的Binder引用。Binder驱动在内核中创建相应的Binder引用,并指向ServiceManager在内核空间的Binder实体,值得注意的是handle值为0的Binder引用在Binder驱动中将自动对应到ServiceManager的Binder实体。
  4. Client对ServiceManager发起请求获取Service

    Client通过这个引用向内核空间发送希望请求Service的标志名。ServiceManager会根据Client提供的Service的标志名在Map中进行检索,获取到Service的Binder引用,将数据写回到内核空间。Binder驱动为Client进程创建一个Service端的Binder引用(蓝色波浪形)返回包含内核空间的Binder引用的handle值的数据给Client,在Client端创建一个Binder引用至此Client进程与Service进程建立联系。
  5. Client端与Server端的通信

    在Client端和Server端建立通信以后,Client持有自己的Binder实体以及Server的Binder引用,同样Server也持有自己的Binder实体及Client的Binder引用。至此Client端与Server端成功建立通信,并通过持有的引用进行数据传输。
过程比较复杂,结合下图理解:
网上有更简洁的总结,这里也放上来:
  • 首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
  • Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
  • Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

Binder IPC代码实例
说了这么多,Talk is cheap,show me the code! OK,现在来看一个基于Binder实现的AIDL跨进程通信的例子。
(1)新建一个AIDL文件,Android Studio会自动帮你创建一个aidl文件夹,并把这个文件放进去。这个过程即定义一个AIDL接口。
interface IMyAidlInterface { String show();}

(2)在AS中Build -> Make Module 或 Rebuild Project,成功之后在相应目录下会自动生成一个同名的.java文件
代码可以自己查看。
(3)编写客户端代码
public class MainActivity extends AppCompatActivity { private IMyAidlInterface mService;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(getApplicationContext(),MyService.class); bindService(intent,connection, Context.BIND_AUTO_CREATE); }
@Override protected void onDestroy() { super.onDestroy(); unbindService(connection); }
private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IMyAidlInterface.Stub.asInterface(service); }
@Override public void onServiceDisconnected(ComponentName name) { mService = null; } };
public void get(View view) { try { ((Button)view).setText(mService.show()); } catch (RemoteException e) { e.printStackTrace(); } }}

(4)编写服务端代码
public class MyService extends Service {
@Override public IBinder onBind(Intent intent) { return new IMyService(); }
public class IMyService extends IMyAidlInterface.Stub { @Override public String show() throws RemoteException { return "Hello world"; } }}

这就是一个简单的实例。关于Binder的介绍也先到这里,之后有时间会继续补充。

参考资料

[1]《写给 Android 应用工程师的 Binder 原理剖析》 

链接:https://www.jianshu.com/p/429a1ff3560c

[2]《Android Binder机制浅析》
链接:https://www.jianshu.com/p/c18173e3079e
[3]《【Android】Binder的结构》
链接:https://www.jianshu.com/p/17365aad2f69
[4]《Android Bander设计与实现 - 设计篇》
链接:https://blog.csdn.net/universus/article/details/6211589
[5]《在Android Studio中使用AIDL》
链接:https://www.jianshu.com/p/114ae69f4325
程序员专栏
 扫码关注填加客服 
长按识别下方二维码进群

近期精彩内容推荐:  

 从培训机构出来的程序员,后来都怎么样了?

 今年51长假给HR表白了,结果...

 再见了, VS Code !

 大牛:你真的懂反射吗?




在看点这里好文分享给更多人↓↓