最近,看了不少Android内核分析的书籍、文章及Android源程序。感觉自己对Android Binder的工作机制算是有了个彻底的理解。
但是,自己是花了很多时间和精力之后才达到这一点的。对于大多数人来说,恐怕不会象我这样愿意花这么大的代价就为了弄明白这一点东西。所以,我想尽可能简要的介绍一下Android Binder驱动的工作机制原理,以使大家不必花太多时间也能弄明白其要旨。
一、我们先介绍一下Binder Driver所要解决的问题。
Binder driver所要解决的是运行在同一个Android操作系统中的不同进程之间的小规模数据的传递问题。
注意,对于不同进程之间的大规模数据的传递问题,Android是用Android Shared Memory Driver来解决的。Binder Driver是针对小规模的数据传递的(比如几十字节到几十KB的数据)。
Android Driver是整个Android Binder系统的内核部分。Android Binder系统的目标是提供一个运行在同一个Android操作系统中的不同进程间通信的面向对象的API,类似于Microsoft DCOM或者CORBA。为了提供这样的面向对象的API, Android需要一种高效的进程间通信机制,这就是Android driver存在的目的。
二、那么,为什么不直接用Linux的共享内存机制来在进程间传递数据呢?
这是因为,除了传递数据之外,Android还需要这个Driver支持以下特性。
1. 安全机制:只有特定的用户和进程才能访问这块数据。Linux共享内存不支持这样的机制。
2. 引用计数:只有Client进程不再需要了,其对应的Server进程才能释放相应的内存。Linux共享内存也不支持这样的机制。
3. 另外,Android Binder driver还支持夸进程的描述符传递。这样,一个进程可以直接读另一个进程打开的文件,而不需要将文件内容读到内存后再复制给另一个进程。
三、Linux进程的虚拟内存空间
下面以x86 CPU的Linux实现为例,回顾一下Linux进程的虚拟内存空间的相关知识。
1. 每个进程有4G的虚拟内存空间
2.这4G空间分为两部分。0~3G为用户态空间(User Space),3~4G为内核态空间(Kernel Space)。
3.对于0~3G的用户态空间,对于不同的进程,Linux会将其映射到不同的物理内存。比如,进程A的虚拟地址0x01234567所指向的物理地址与进程B的虚拟地址0x01234567所指向的物理地址是不同的。这就是所谓的进程隔离。
4.对于3~4G的内核态空间,对于所有的进程,Linux会将其映射到相同的物理内存。换句话说,进程A的虚拟地址0xC1234567与进程B的虚拟地址0xC1234567指向的是同一个物理地址。这部分虚拟内存空间,用户态的进程是不能访问的,只有切换到内核态的进程才能访问一个进程的3~4G的内核态空间。
四、将数据从进程A传递到进程B的确切含义
所谓将数据从进程A传递到进程B,就是将进程A的某块用户空间(0~3G)的内容复制到进程B的某块用户空间(0~3G)的内存中。
因此,为了将数据从一个进程A复制到另一个进程B,我们需要:
1. 切换到内核态。因为只有内核态才能访问所有进程的用户态空间。
2. 将数据从进程A的用户态空间读出。
3. 在进程B的用户态空间中找一块内存空间,为其分配物理内存,然后将数据复制到该物理内存处。
4. 然后,进程B就可以从其用户空间读到数据了。
五、Binder Driver是如何在进程间传递数据的
其实,Binder driver要做的事情就是上面我们所列出的那几个步骤。
不过,对于每个进程,Binder driver会在进程打开binder driver后,通过ioctl系统调用为每个进程预先找一块用户态和内核态的内存空间,做为Binder buffer使用。
请注意,对于一个进程而言,其Binder buffer在用户态和内核态的内存被映射到了同样的物理内存地址。
下面我们看看利用Binder进行通信的两个进程之间是如何传递数据的:
1. Client进程有一块内存内容需要传递给Server进程
2. 切换到内核态
3. Binder driver读Client进程的用户态空间(0~3G)的这块内存内容
4. Binder driver将读出的内容复制到Server进程的Binder buffer中
5. 然后,当Server进程执行时,就能够在其用户态空间(0~3G)读传过来的内容了。因为Binder Buffer也被映射到了Server进程的用户态空间。
6. 当Server进程处理完读出的消息后,然后需要将处理的结果存放在一块用户态空间的内存中,希望将其返回给Client进程
7. 切换到内核态
8. Binder driver读Server进程的用户态空间(0~3G)的处理结果
4. Binder driver将读出的内容复制到Client进程的Binder buffer中
5. 然后,当Client进程执行时,就能够在其用户态空间(0~3G)读传过来的内容了。因为Binder Buffer也被映射到了Client进程的用户态空间。
这就是Binder driver如何实现进程间通信的。
至于其中的细节,比如安全机制的实现,就不多说了。感兴趣的请参考老罗的鸿篇巨著《Android系统源代码情景分析》,里边有两百余页的篇幅讲Binder系统。不过,要看懂这些内容,需要有较深的Linux Kernel知识。