本文共 5766 字,大约阅读时间需要 19 分钟。
本文只作为了解 Binder机制的私人笔记,拜读了很多大神的博客,从中提炼总结如下文:
参考自大神
参考自大神因为很多人说 Binder就是跨进程通信方式,但是 Android 又是基于Linux的操作系统,为什么舍弃了Linux已经很成熟的管道、消息队列、共享内存和 Socket 等IPC 机制 而使用所谓的Binder机制呢? 所以在了解 Binder机制之前,我简单了解了一下Linux 的 IPC 机制(Linux传统的进程间通信原理 )作为对比。
在了解 Linux传统的进程间通信原理 之前先了解一下Liunx 中跨进程通信主要有三个关键信息
关键信息1 : 进程隔离
关键信息2 : 进程空间划分:用户空间(User Space)/内核空间(Kernel Space) 关键信息3 : 系统调用:用户态/内核态关键信息1- 进程隔离 简述:
关键信息2-进程空间划分 简述:
关键信息3-系统调用
虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。Linux 使用级别保护机制:0 级供系统内核使用,3 级供用户程序使用。当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。系统调用主要通过如下两个函数来实现:
copy_from_user() //将数据从用户空间拷贝到内核空间 copy_to_user() //将数据从内核空间拷贝到用户空间
三个关键信息汇总如下图:
理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。
通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copyfromuser() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copytouser() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。如下图:
理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。
正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。
那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?答案是否定的。我们直接对比一下各类IPC的实现方式。
问题 :发现 Binder 机制 的数据拷贝次数是0,那么 Binder 是怎么实现 0拷贝次数的呢?
这就就要说到 Linux 的另一个概念:内存映射。
Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
步骤1 : 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
步骤2: 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
步骤3: 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
如图:
Binder 通信模型是由一系列组件组成的,具体包括:Client、Server、ServiceManager、Binder 驱动四个组件。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。通常我们访问一个网页的步骤是这样的:
步骤1:首先在浏览器输入一个地址,如 然后按下回车键。
步骤2:由于没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,DNS 域名服务器中保存了 对应的 ip 地址 10.249.23.13,所以得到DNS服务器返回的IP地址:10.249.23.13。
步骤3:向IP为 10.249.23.13的服务器发起请求, 才能访问到 对应的服务器。
步骤4:应用服务器向客户端返回请求的数据
说到底 Binder 跨进程通信本质来讲就是能够快速的实现进程之间的通讯,本着这一思想,下面列出 Binder 跨进程通信步骤
步骤一:注册Server进程,即注册服务。
步骤二:获取服务
步骤三:使用服务
步骤1:Binder驱动为跨进程通信做准备,实现内存映射。即调用 mmap()系统函数
步骤2:Client进程 发送数据到 Server进程,即请求服务
步骤3:Server进程 根据 Client进程请求,调用目标方法
步骤4:Server进程 将目标方法的执行结果返还给 Client进程
过程如图所示:
注意1:
Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了),而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。注意2:
由于进程隔离,Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。如下图,虚线表示并非直接交互。转载地址:http://mmme.baihongyu.com/