在了解 Binder 跨进程通信原理之前, 我们先了解一下 Linux 传统的进程间通信的概念和基本原理, 这样有助于我们更好的理解 Binder 的通信原理. 这个部分基本都是理论, 基础不是很好的同学, 还是要耐着性子看完. 最起码,我看完后受益匪浅.
(如有侵权, 请联系删除)
1.基本概念
Linux 中跨进程通信有一些几个基本的概念
- 进程隔离
- 进程空间划分: 用户空间(User Space) & 内核空间(Kernel Space)
- 系统调用: 用户态 & 内核态
1.1 进程隔离
- 简单的说就是操作系统中, 进程与进程间的内存是不共享的. 两个进程就像两个平行的世界, A进程没办法直接访问 B 进程的数据.这就是进程隔离的通俗解释. A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制: 进程间通信(IPC)
1.2 进程空间划分
- 现在操作系统都是采用的虚拟存储器, 对 32 位系统而言, 它的寻址空间 (虚拟存储空间) 就是 2 的 32 吃饭,也就是 4GB. 操作系统的核心是内核,独立于普通的应用程序, 可以访问受保护的内存空间, 也可以访问底层硬件设备的权限. 为了保护用户进程不能直接操作内核, 保证内核的安全, 操作系统从逻辑上将虚拟空间划分为用户空间(User Space) 和内核空间(Kernel). 针对 Linux 操作系统而言, 将最高的 1GB 直接供内核使用. 称为内核空间. 较低的 3GB 直接供各进程使用,称为用户空间. 简单来说就是, 内核空间是系统内核运行的空间, 用户空间是用户程序运行的空间. 为了保证安全性, 他们之间是隔离的.
1.3 系统调用: 用户态 & 内核态
Linux 使用两级保护机制: 0 级供系统内核使用, 3 级供用户程序使用
- 当一个任务(进程)执行系统调用而陷入内核代码中执行时, 称进程处于内核运行态(内核态). 此时处理器处于特权级最高的(0 级)内核代码中执行. 当进程处于内核态时, 执行的内核代码会使用当前进程的内核栈. 每个进程都有自己的内核栈.
- 当进程在执行用户自己的代码的时候, 我们称其处于用户运行态(用户态). 此时处理器在特权级最低的(3 级) 用户代码中运行.
- 系统调用, 主要通过如下两个函数来实现.
-
copy_from_user()
//将数据从用户空间 copy 到内核空间 -
copy_to_user()
//将数据从内核空间 copy 到用户空间
-
2. 传统 IPC 通信的基本原理.
通常做法是将消息发送方要发送的数据放在内存缓存区中, 通过系统图调用进入内核态, 然后内核程序在内核空间分配内存, 开辟一块内核缓存区, 调用 copy_from_user()
函数将数据从用户空间的内存缓存区 copy 到内核空间的内核缓存区中. 同样的接收方进程在接收数据时在自己的用户空间开辟一块内存缓存去, 然后内核程序调用 copy_to_user()
函数将数据从内核缓存区 copy 到接收进程的内存缓存区. 这样数据发送方进程和数据接收方进程就完成了一次数据传输. 也就是完成了一次进程间通信.
传统的 IPC 通信方式有两个问题
- 性能低下, 一次数据传递需要经历: 内存缓存区(发送方) -> 内核缓存区 -> 内存缓存区(接收方). 两次的数据 copy
- 接收数据的缓存区由数据接收进程提供, 但是接收进程并不知道需要多大的空间来存放将要传递过来的数据, 因此只能开辟尽可能大的内存控件或者先调用 API 接收消息头来获取消息体的大小, 但是这两种做法不是浪费空间就是浪费时间.