前言
本文可以视为 Binder 学习指南 和 为什么Android 要采用 BInder 作为 IPC 机制 两篇文章的学习笔记,因此文章仅为笔者个人复习使用 ,同时希望能给 对以上两篇文章有所了解的朋友 提供参考。
一、概述
1.Binder的含义
binder 是 Android 系统实现IPC(跨进程通信)的机制,它沟通和联系了各个组件,是Android系统最为重要的成员之一。
2.进程隔离
进程:
进程是操作系统中的一个重要概念,基本的 CS 课程都会讲到,不同进程之间的数据是不共享的(内存隔离),那么进程之间要进行数据传递,就需要有一套对应的系统机制了。
进程隔离:
进程隔离使用了虚拟地址空间(这个概念就不引申了),A 进程和B进程的虚拟地址不同,以此避免 A进程的数据写入B进程。
3.用户空间 与 内核空间:
内核:
Linux Kernel ,它是系统运行的核心,独立于普通的应用程序。它可以访问受到保护的内存空间,同时也有权限访问所有硬件资源。
用户:
这里的用户就是上层的普通应用程序的意思
为什么区分用户空间 与 内核空间?
从内核的设定可以看出,它是非常重要的,也拥有极高的权限,因此不可以让上层应用程序随意的调用和访问它,所以需要一套保护机制,告诉应用程序,你只可以访问哪些特定的资源。所以就从逻辑上抽象出用户空间和内核空间,将它们隔离开来。
4.用户态 与 内核态
系统调用
虽然说抽象出了 用户空间 和 内核空间将它们隔离开,但是不可避免的,应用程序会有需要访问内核的时候,例如访问文件、使用网络。这时候,就有了系统调用的概念。
系统调用是用户空间访问内核空间的唯一入口接口,通过这种方式,所有资源的访问都会在内核的控制之下了,安全性自然大大增加。
用户态:
当在执行上层应用程序自己的代码的时候,就称之处于用户态,这时候进程的特权等级最低(3级)
内核态:
当通过系统调用,执行内核代码的时候,就称之处于内核态,这时候进程的特权等级最高(0级)。只有在满足一定特权等级的时候,CPU才能执行对应的特权指令。
5.内核模块 与 binder驱动:
用户空间与用户空间通信
用户空间与用户空间之间想要通信,第一时间想到的当然是让内核提供支持,毕竟不同的用户空间都可以访问同一个内核空间。
linux内核中的socket和管道都是解决用户空间之间通信问题的,但是Binder并不是Linux内核的一部分啊,这时候就用到了Linux的动态可加载内核模块机制(LKM)
内核模块
在Linux中,模块不能单独运行,但是它可以单独编译,内核模块就是在运行时再 连入 链接到内核中,作为内核的一部分 ,在内核空间中运行。
在Android系统里,为了解决用户进程之间通信问题而引入的内核模块就是:binder驱动。
binder 驱动:
驱动就是操作硬件的接口,为了实现binder通信的过程,binder使用了一种“硬件”,因此这个模块被叫做binder驱动。
二、为什么是 Binder
1.简单描述
性能更好:
在移动设备上(尤其是早期的Android设备,硬件水平太差了),性能毫无疑问是非常重要的一项指标,binder机制比起传统的socket和管道等有更好的性能:Binder数据拷贝只需要一次(匿名共享内存),而管道、消息队列、Socket都需要2次。
安全性更高:
传统的跨进程通信机制,通常都没有针对通信的两端做身份上的校验(例如socket的IP地址完全可以伪造),而Android系统作为一个开放的生态,必然对安全性有更高的要求。
binder机制从协议上就 ~~设置了 ~~ 支持了对通信双方的身份校验,提高了安全性。这也是Android权限模型的基础
2.详细解释(暂且留空,等仔细看完gityuan大佬那个回答再来补充)
三、Binder 通信模型
1.通信过程中的角色
通信的双方:
Binder通信的双方一般称为:Server端 和 Client端,可以类比于网络通信中的服务端和客户端,实际上它们往往就是两个用户进程
通信录:
就像打电话时候需要的电话簿一样,通信录记录了 Server 端的地址信息,这样 Client 端才能找到对应的 Server 端,并向它发送消息。
通信录如何知道 Server 端的信息呢?
这就要求Server端自己要去通信录里面注册了,Binder通信的通信录工作,是由 ServiceManager 来完成的,也可以简称为 SM(不要想歪了,这是失眠的意思
基站:
同样以打电话来类比,一次通信的过程,当然少不了基站的支持,只有依靠它才能真正实现在通信双方之间传递消息
Binder通信的基站工作,是由Binder驱动来完成的
来人,给公子上图
2.一次基本的通信过程
- 首先是建立通信录(SM):一个进程申请成为ServiceManager,Binder驱动同意之后,SM进程就开始工作了,管理 Service 。原文中特意说明这里是Service,不是Server,我个人理解意思是说一次通信的 Client 进程,可能再跟另一个进程通信时,它又是 Sever 了,也就是一个进程会扮演 Client 和 Server 两个角色。
- 在通信录中注册(Server -> SM):如前文所说,SM也没办法凭空知道所有Server的信息,所以需要各个Server 自己在 SM 中注册,告知 SM 自己的名字和对应的地址信息
- 开始通信(Client -> SM -> Server):终于到了发送消息的这一步了,Client 想跟某个 Server 通信,就会先去 SM 中查询对应 Server 的信息,然后通过 binder 驱动,与对应的 Server 联系上,开始打电话了(通信是一来一回的,这里用发消息来类比就不合适了)。
四、Binder 机制跨进程原理
1.有关于通信
常规的进程间通信方式:
对于两个进程 A 和 B 来说,内核可以访问它们两个的数据,最常见的跨进程通信方式,当然就是利用内核。
例如 A 要向 B 发送一段数据,那么 A 就可以将数据拷贝到内核空间,然后内核再将数据拷贝到 B。
前面说到,用户空间操控内核空间需要通过系统调用,刚好Linux也提供了对应的系统调用:copy_from_user, copy_to_user。
Binder 机制跨进程通信的定义:
通信这个概念其实是比较宽泛的
Binder 机制实现跨进程通信,其实是指 Client 进程可以访问 Server 进程中的对象及其方法,这当然也是通信的一种方式
Binder 并没有用上述的常规的进程间通信方式
2.Binder 机制
隐藏步骤:一个进程申请成为ServiceManager,也就是SM进程
-
Server 端向 SM 注册:Server 端要向 SM 注册,告知 SM 自己的信息和自己有什么能力(提供什么方法)。例如:进程名是 Zhangsan ,拥有一个 Object 对象,它有个 add 方法。
-
Client 端从 SM 查询:Client 端想要访问 Zhangsan的这个 Object 对象,就需要联系 SM 查询,让SM给它返回对应的对象,但是注意,这里返回的对象不是原对象。
-
Binder 驱动对查询的中间处理:
数据在内核空间传递的时候,会经过 Binder 驱动,Binder 驱动判断到这是一个跨进程的调用,它不会将真正的 Object 对象返回给 A ,而是会返回一个 ObjectProxy 对象。
这个对象的方法和 Object 对象一模一样,它也有 add 方法,但是它不具备真正 Object 对象所拥有的能力,也就是它是一个代理对象,是假的。 -
Client 端调用:Client 拿到 ObjectProxy 对象之后,并不会感知到它和 Object 对象不一样,它还是照常当作在调用 Object 的 add 方法。
-
Binder 驱动对调用的中间处理:
这个调用当然又要经过 Binder 驱动,Binder 驱动 通过查表判断到 这是 ObjectProxy 对象的调用,就会去调用真正的 Object 对象的 add 方法,并且将得到的结果,通过 ObjectProxy.add 方法返回给进程 A。由此就完成了一次跨进程的调用,而 A 进程的调用方在此过程中其实是完全无感知的。 -
隐藏的内容 – Server 与 SM 的通信:
上述过程其实省略了很多细节,例如 Server 进程与 SM 大多数时候肯定也不是同一个进程的,那么 Server 进程向 SM 注册的过程,本身也是一次跨进程通信,自然也是利用了 Binder 机制
SM 中持有的 Object 对象,自然也不是真正的 Object 对象,其实它也是一个 代理对象,是一个 ObjectProxy 。
Server 进程的本地对象仅有一个,只有这个是有真正的能力的,其他进程所拥有的全部都是它的代理 -
总结:Client 进程持有的只是 Server 端的代理,代理对象协助Binder驱动完成了跨进程通信。