什么是Binder
Binder,Android系统IPC机制的一种。Android APP的四大组件之间所涉及的进程间通信底层基本都依赖于Binder IPC机制。此外,在整个Android系统的架构中,也有大量采用Binder IPC机制的方案。
Question:
- Android系统除了Binder之外,还有哪些其他IPC机制
- 各IPC机制之间的优劣对比
Linux IPC机制
Interprocess Communication,进程间通信,在多任务和多进程的操作系统环境中,进程可能需要相互协作、共享信息或者传递数据。
- 管道(Pipes): 管道是一种单向通信机制,用于父子进程或兄弟进程之间传递数据( 比如fork或exec创建的新进程)。其中一个进程写入管道,而另一个进程从管道读取数据。
- 消息队列(Message Queues): 进程通过消息队列发送和接收消息,可以实现双向通信。消息队列通常用于在不同进程之间传递数据块。
- 共享内存(Shared Memory): 多个进程可以访问同一块内存区域,从而实现数据的共享。这是一种高效的IPC机制,但需要确保进程之间的同步和互斥。
- 信号(signal): 信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等
- 信号量(Semaphores): 信号量是一种用于控制多个进程对共享资源的访问的同步机制。它可以用于防止多个进程同时访问关键资源。
- 套接字(Socket): 在网络编程中,套接字是一种用于进程间通信的机制。套接字机制不但可以单机的不同进程通信,而且使得跨网机器间进程可以通信。
- 文件共享:两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本、XML、JOSN。文件共享适用于对数据同步要求不高的进程间通信。通过文件进行多进程通信用法简单,但不适合高并发情况
这些IPC机制使得进程能够以协同的方式工作,完成复杂的任务。选择合适的IPC机制取决于具体的应用场景和需求。
优劣对比:Socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
Linux IPC原理
-
进程隔离:进程与进程间内存是不共享的
-
进程空间划分:用户空间(User Space)/内核空间(Kernel Space)。
现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

-
系统调用:用户态/内核态
虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。Linux 使用两级保护机制: 0 级供系统内核使用,3 级供用户程序使用。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。
系统调用主要通过如下两个函数来实现:
copy_from_user() //将数据从用户空间拷贝到内核空间 copy_to_user() //将数据从内核空间拷贝到用户空间
Linux为每个进程维护一个单独的虚拟地址空间。Linux的进程有32位和64位,32位进程的虚拟地址空间是232=4G
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。进程空间分为用户空间和内核空间,对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。

理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。
以讲程A发送数据到进程B为例,需要经历如下步骤:
- A进程的内核空间开辟一块内核缓存区,通过copy_from_user()系统调用,将用户空间的数据拷贝到内核缓冲区;
- 进程B的内核空间也开辟了一块内核缓存区,因为不同进程的内核空间共享的,此时进程B就可以通过copy_to_user()系统调用将内核缓存区的数据拷贝到自己的用户空间;
如此就完成了一次进程间通讯,上面说到的管道、消息队列都是基于这种方式

这种传统的 IPC 通信方式有两个问题:
- 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;
- 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

Binder IPC原理
Binder Driver
跨进程通信是需要内核空间做支持。linux传统IPC机制诸如管道、Socket都是内核的一部分,自然支持通过内核来实现进程间通信。 然而Binder并不是linux内核的一部分,它利用了linux的LKM机制,作为一个特殊的设备(/dev/binder)运行在内核空间。
动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。
在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。
Binder IPC原理实现
内存映射 :内存映射(Memory Mapping)是一种文件I/O的处理方式,它将文件或其他对象与内存区域建立一个映射关系。通过内存映射,程序可以像访问普通内存一样直接访问文件内容,而无需传统的read和write系统调用进行数据传输, 因此可以提高文件处理速度。
Binder IPC 正是基于内存映射mmap(mmap()是操作系统中一种内存映射的方法)来实现的。mmap() 通常是用在有物理介质的文件系统上的。比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的Binder IPC调用过程:
-
Binder驱动在内核空间创建一个数据接收缓存区;
-
在内核空间创建一个内核缓存区,建立内核缓存区与数据接受缓存区的内存映射关系、建立内核中数据接收缓存区与接受数据进程的用户空间地址的映射关系
-
发送方进程通过系统调用copyfromuser()将数据copy到内核中的内核缓存区,由于内核缓存区与接收进程的用户空间存在内存映射,所以就相当于直接把数据发送到了接收进程的用户空间,只通过一次数据拷贝就完成了进程间通信。

Refs
linux 各IPC机制
https://blog.csdn.net/sinat_36118270/article/details/73118277
https://blog.csdn.net/a987073381/article/details/52006729
https://zhuanlan.zhihu.com/p/430557625


被折叠的 条评论
为什么被折叠?



