先来看一些技术名词基础1、虚拟内存地址。虚拟内存地址是操作系统为每个进程创建的独立的、连续的内存地址范围从 0 到最大值。进程“看到”和使用的都是这个虚拟地址。操作系统配合 MMU 硬件负责把虚拟地址动态翻译成真实的物理内存地址。如此每条进程都会有一块自己的内存地址虚拟的当然最后会有对应的真实的内存地址它们所使用的内存地址不重叠一条进程的运行不会影响另一条进程确保了进程安全必要时操作系统还可以把硬盘也映射成虚拟内存。虚拟内存这里面就用到了内存映射的技术。这种内存映射技术需要同时用到操作系统和MMU硬件。2、用户态/内核态用户态和内核态是CPU运行的两种模式对应着用户权限、内核权限两种权限模式由一个硬件寄存器记录着也就是硬件寄存器里的这个值控制着CPU处于哪种状态模式。内核态内核权限下可以执行任何命令用户态用户权限则只能执行常规操作。当CPU通电后启动芯片被触发引导程序会将系统内核加载到内存系统内核运载起来此时CPU会默认给操作系统最高权限即控制权限切换的权力操作系统也会采取一系列手段让自己独占控制权以后我说升到内核态就升到内核态我说降到用户态就降到用户态其他人谁也别想染指并且这种切换还伴随着硬件开关前面说的那个寄存器的变动普通进程想要执行一些敏感操作、危险操作必须通过调用系统接口的方式间接调用系统收到调用后会先将CPU对于多核CPU来说就是当前所在的核升级到内核态然后再执行。执行完再降回用户态。那么既然都通过系统接口调用了系统接口肯定是值得信赖的为什么非要升级到内核态呢因为硬件设计规定这些敏感、危险的操作必须在内核态寄存器里记录显示处于内核态下才能执行硬件又为什么要这样设计呢如果不这样设计那么任何进程都可以直接执行危险命令比如把硬件烧毁、让CPU关机等等必然不可但是操作系统是可值得信任的它留出的调用接口势必不会威胁到内核安全。对于硬件来说操作系统和普通程序没有区别都是软件都可以调用危险命令能不能执行是另一回事但是安全也是必须要保证的那咱们重新梳理一下就能理清了CPU通电后引导程序把系统内核加载了进来系统启动后夺取了最高控制权切换内核/用户的权限成为硬件的控制者也是硬件的信任者操作系统做什么硬件都执行此时若有其他进程程序想执行危险命令硬件首先就会判断出它没有最高控制权不值得信任拒绝执行甚至杀死该进程这样就保证了安全。如果引导程序引入的是一段恶意程序最高控制权被这段恶意程序夺取了那么是不是就危险了是的此时原本的操作系统如果启动了也只是会被当做普通程序而这段恶意程序就变成了新的操作系统的角色。3、用户空间(User Space)/内核空间(Kernel Space)两种空间都是说内存的但是乍看是不是觉得就是真实物理内存上的两块不同的区域它们确实都有对应的真实内存地址但是它们其实也都是指的虚拟内存地址而且它们之间的虚拟内存地址不重叠。用户空间是供普通进程使用的内核空间是供系统内核使用的。每个进程有自己的独立用户空间而所有进程共享同一个内核空间。4、设备文件在Windows上 /dev/binder 这样的写法会被认为是一个目录但是在Linux上它代表一个设备叫设备文件你可以认为它是操作设备的一个接口大致示意如下int fd open(/dev/binder, O_RDWR); //打开该设备write(fd, data, len); // 向设备里写数据read(fd, buf, size); // 读取设备里的数据close(fd);Binder1、普通跨进程通信在讲Binder之前先来看一下一次普通的进程间通信大致是怎样的。比如进程1作为客户端有数据要发送给进程2作为服务端进程1先把数据从它的用户空间复制到内核空间进程2再把数据从内核空间复制到它的用户空间经过两次数据复制数据就由1传到了2。如下所示。再次提醒用户空间和内核空间都是虚拟内存地址。2、Binder基本组件Binder的机制也是类似的。Binder通信架构是典型的C/S架构由Client、Server、ServiceManager、Binder 驱动四大组件组成。Binder驱动是整个机制的“物理基础”它运行在内核空间是内核的一部分随着内核的加载而一起启动本质上是一个混合设备驱动通过 /dev/binder 设备节点为上层提供服务。ServiceManager是所有服务的登记中心每条服务都要向它注册报告自己的地址、名字等信息。ServiceManager 靠 Binder 驱动收发消息Client/Server 也都靠 Binder 驱动收发消息Binder 驱动是所有人的唯一通信通道。3、Binder背后的基础机制当系统启动的时候init进程会创建出一条进程叫ServiceManager它是整个系统里的第一条服务它通过open打开 /dev/binder 就和Binder驱动建立了连接Binder 驱动会给 ServiceManager 创建系统的第一个Binder实体节点不过这个节点会比较特殊它的句柄是0全系统约定只要发给句柄 0的消息就是发给 ServiceManager的你可以认为全系统都知道ServiceManager家在哪以及怎样才能联系到它此后ServiceManager 进入死循环等待 Binder 驱动转发过来的请求。Binder实体节点binder_node是一种数据结构存放于Binder驱动内部记录着server的身份证真身地址凡是客户端发给Binder驱动的请求Binder驱动都可以根据binder_node找到对应的server然后把请求转交给server去处理。日常接触到的几乎所有 Android 进程APP、系统服务、system_server、ServiceManager 等都会自动 open binder只有底层极少数原生守护进程不会。也就是说几乎所有进程在启动的时候系统Android 系统底层 libbinder 库代码都会自动让它通过open()打开 /dev/binder 驱动文件建立与内核Binder驱动的连接。之所以设计成这样是因为在 Android 里没有任何一个应用 / 系统进程能 “独善其身”全都必须频繁和其他进程、系统服务打交道不连 Binder进程直接就是个 “孤岛”根本跑不起来。即使你仅仅只是展示一个Activity静态页面也需要和AMS打交道不过这种open建立连接的方式只在进程启动时执行一次全程复用这条连接不需要多次建立连接直至下次进程重启。连接建立后紧接着系统就会继续让进程通过mmap()执行内存映射Binder驱动收到mmap()后会给进程分配一块虚拟内存地址作为进程使用的用户空间数据缓冲区暂称user_buffer但是驱动同时也会开辟出另一块内核空间的虚拟内存地址暂称kernel_buffer最终把这两块虚拟内存地址映射到同一块物理内存地址上。为什么要这么做后面讲。深度思考每个进程都有一块自己的用户空间数据缓冲区那进程多了之后岂不占用很多内存不会。这块缓冲区是虚拟的当用不到的时候并没有给分配真实的内存只在使用时才会临时给其映射到真实的内存地址使用完毕后就又把真实内存地址收回。而且这块区域并不大一般不会超过1MB系统也会限制进程总数不会无限创建。这里1MB的限制正是造成Intent传带数据超过1MB就会报TransactionTooLargeException的原因。4、服务上线前面已经讲过进程一启动系统就会自动让它跟Binder驱动建立连接并完成内存映射这是所有进程都会经历的不管是否作为Server。如果作为Server还有更多的工作要做Server 端必须创建实现了 IBinder 接口的服务实例对象。对于系统服务来说大部分系统服务在创建完实例后会由system_server进程少数是其他机制触发触发它们去执行ServiceManager.addService(服务名, IBinder对象)以便把自己的服务名称和IBinder实例打包成带有句柄0的 Binder 请求发送给Binder驱动。Binder 驱动收到这个请求后为它们创建各自的binder_node发现它们的binder第一次跨进程传递就会为其生成binder_node封装后把请求直接转发给 ServiceManager 进程。ServiceManager 收到注册请求后会在内部维护一张系统服务注册表把「服务名 ↔ 对应 binder_node」的映射关系永久保存下来。注册完成后这个 Server 就正式在全系统 “挂牌上线” 了只要其他进程知道服务名就能找到它。完成注册的 Server 并不会主动做任何操作而是和 ServiceManager 一样进入阻塞等待状态静静等候 Binder 驱动转发来自客户端的调用请求。5、客户端使用服务查询服务客户端通过 getService(服务名字) 经由Binder驱动向ServiceManager查询服务。ServiceManager找到后会返回一个该服务的Binder代理对象Proxy给客户端。客户端拿到这个代理对象就相当于拿到了与远程服务通信的“遥控器”。发起请求客户端通过代理对象发起方法调用请求中带有目标句柄 方法号 参数 回复缓冲区。底层通信与处理客户端请求会被发送到Binder驱动由于Binder驱动作为中介全程参与了双方的沟通它掌握的信息足够知道该把请求转交给谁处理服务端处理完毕后将结果保存在它的普通用户空间里Binder驱动将结果从普通用户空间再复制到内核空间kernel_buffer里而kernel_buffer由于内存映射其实和客户端的用户空间是同一块物理内存所以也只用了一次复制就把结果传回给了客户端。当进程A发送数据给B时数据由A的用户空间复制到B的内核接收缓冲区但是由于内存映射关系B的内核接收缓冲区其实和B的用户空间里的数据缓冲区是同一块物理内存所以B可以直接读取反过来B处理请求完毕后回复结果则把结果数据直接复制到A的内核里的接收缓冲区仍然由于内存映射关系进程A也可以直接读取所以请求数据、回复数据都只需要复制一次数据。6、AIDLAIDL也使用的是Binder但是调用不到ServiceManager.addService它是hide方法所以普通应用不需要也不能注册到全局 ServiceManager。客户端bindService后由于需要用到AMS所以底层会先去ServiceManager处查询到AMS查到AMS后就把请求交给AMS然后AMS负责让服务端启动服务端会执行onBind()把Binder交给AMS此时AMS又经过Binder驱动给服务端创建binder_node将服务端的代理交给客户端。客户端拿到代理之后和服务端之后的交互就直接由Binder驱动来传递了不再需要AMS、ServiceManager的参与。7、Binder机制的优势除了前面所说的数据复制相比传统方式少了一次性能高以外也更安全。UID是应用的身份证如果两个应用共用UID就会被认为是同一个APP就可以互相访问对方的数据所以伪造UID如果成功骗过了系统就可以访问其他应用的数据。Socket、管道、队列等方式都可以自己声明UID也就有伪造UID的可能Binder机制直接从内核里查询进程的UID进程无法伪造UID而且Binder机制也不使用进程自己设置的UID即使进程伪造了也没用。