进程隔离
在操作系统中,进程与进程之间的内存和数据都是不共享的,称之为进程隔离机制。如果进程之间需要进行通信,那么需要使用IPC机制,操作系统中的IPC机制很多,比如共享内存,socket,管道等,在安卓系统中,最常见的进程间通信就是binder了。
虚拟地址空间
进程隔离机制的主要实现方式是使用虚拟地址空间,以32位操作系统为例,总共有4G虚拟地址空间,操作系统将虚拟地址空间划分为2部分,最高的1G字节(0xC0000000~0xFFFFFFFF
)为内核空间,较低的3G字节(0x00000000~0xBFFFFFFF
)由各个进程使用,称为用户空间,如下图所示:
为什么会划分这些空间
操作系统主要是为了系统的安全性考虑,规定内核空间可以执行CPU的任何指令,而普通进程运行的用户空间则受到很多限制。
在CPU的指令中,有些指令是非常危险的,如果调用出错,可能导致系统崩溃。CPU指令分为特权指令和非特权指令,对于特权指令,只允许操作系统和相关模块使用,对于普通应用程序,只能使用那些不会造成灾难的指令。CPU特权等级分为4个级别:Ring0~Ring4。Linux系统中只使用了Ring0和Ring3两个级别。
binder驱动
传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;但是 Binder 并不是 Linux 内核的一部分,它是怎么做到访问内核空间的呢? Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。这样,Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。
所以,在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动,对应的文件系统路径为:/dev/binder
关键函数
binder机制中关键函数如下表如示:
方法 | 描述 | 头文件 | 使用方式 |
---|---|---|---|
open() | 打开设备文件,返回文件描述符 | fcntl.h | |
mmap() | 内存映射方法,实现文件系统到内存共享 | sys/mman.h | |
ioctl() | 对打开的驱动程序进行读,写等一些扩展功能 | sys/ioctl.h |
一次拷贝优势
binder作为安卓系统中最常用的IPC技术,其中比较重要的一个优势在于进程间通信时数据的一次拷贝。
传统的IPC方式中的做法:
- 发送方将准备好的数据存放在缓存区中,通过系统调用进入内核中,内核服务程序在内核空间分配内存,将数据从发送方缓存区复制到内核缓存区中
copy_from_user()
。 - 接收方读数据时也要提供一块缓存区,内核将数据从内核缓存区拷贝到接收方提供的缓存区中并唤醒接收线程
copy_to_user()
。
这种机制需要做两次拷贝:用户空间->内核空间->用户空间,效率低下。
而binder使用了内存映射(memory map)技术,只拷贝一次就可以达到用户进程之间传递数据的目的。如下图所示:
系统架构
Binder通信采用C/S架构(Client-Server),从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。无论是注册服务和获取服务的过程都需要ServiceManager,它是Android进程间通信机制Binder的守护进程。
类比到网络请求中,可以理解为:Server是服务器,Client是客户端,ServiceManager是域名服务器(DNS),驱动是路由器。ServiceManager作用是将字符形式的Binder名字转化成Client中对该Binder的引用。
伪代码
- ServiceManager
main(){
//打开Binder驱动
int binderFd = open("/dev/binder");
//通过 ioctl 告诉 Binder 驱动,自己为 ServiceManager
ioctl(binderFd , BINDER_SET_CONTEXT_MGR_EXT, &obj)
while(true){
//读驱动
read();
//解析数据
parseData();
//判断调用的操作
if(){
//注册服务
addService()
}else if(){
//获取服务
getService();
}
}
}
- Servie
//打开驱动
int binderFd = open("/dev/binder");
//获取ServiceManager
IBinder* serviceManager = defalutServiceManager();
//注册服务
serviceManager -> addService("Test");
while(true){
//读取数据
read();
//解析数据
parseData();
//判断调用的操作,执行对应方法
invokeMethod();
}
- Client
//打开驱动
int binderFd = open("/dev/binder");
//获取ServiceManager
IBinder* serviceManager = defalutServiceManager();
//获取服务
IBinder* service = serviceManager.getService("Test");
//发送数据
service -> sendData()