Binder机制牵涉到进程的内核空间地址和用户空间地址,所以这里先简单介绍一下Linux进程中的内存管理的概念。内存通常
被组织为一个由N个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址,作为到数组的索引。CPU访问内存最简单直接的方法就是使用物理地址,这种寻址方式被称为物理寻址。但是,在现代操作系统中,处理器使用的是一种称为虚拟寻址的寻址方式。使用虚拟寻址,CPU在获得虚拟地址之后,需要通过MMU将虚拟地址翻译为物理地址,这样才能访问到真实的物理内存。而在翻译的过程中还需要借助页表,所谓页表就是一个存放在物理内存中的数据结构,它记录了虚拟页与物理页的映射关系。在32位的操作系统中,每当创建一个进程时,就会为该进程分配一个 4GB 大小的虚拟地址空间,之所以是 4GB ,是因为在 32位的操作系统中(64位是不同的),一个指针长度是 4 字节,而 4 字节指针的寻址能力是从 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即为 4GB 大小的容量。虚拟内存好处是可以为每个进程提供了一个一致的地址空间,从而降低了程序员对内存管理的复杂性,其次它还保护了每个进程的地址空间不会被其他进程破坏。每个进程只能访问自己虚拟地址空间中的数据,无法访问别的进程中的数据,通过这种方法实现了进程间的地址隔离。
一个进程中的4GB 虚拟内存空间又可以分为两种类型,0~3GB为用户空间地址,3GB~4GB为内核空间地址。用户空间包括
代码、数据、堆、共享库以及栈等区;内核空间保存的是系统线程调度、内存管理、设备驱动等数据,这部分数据供所有的进程
共享,但应用程序是不能直接访问这个内核空间地址,只能通过系统调用来访问。当程序运行在用户空间时,我们称为程序运行
在进程的用户态,当程序运行在内核空间时,我们称为程序运行在进程的内核态。虽然用户态下和内核态下工作的程序有很多差
别,但最重要的差别就在于CPU特权级的不同,即权力的不同。运行在用户态的程序不能直接访问操作系统内核数据结构和程序,而程序处于内核态时,是可以随意进入用户态的。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如常见的是通过系统调用进入内核态。
Binder机制的跨进程通信就是使用的内核空间与用户空间共享数据的原理来实现跨进程的通信,一 个进程在使用Binder进程
间通信机制之前,首先要调用函数open打开设备文件/dev/binder来获得一个文件描述符, 然后才能通过这个文件描述符来和
Binder驱动程序交互, 继而和其他进程执行Binder进程间通信。
进程执行mmap函数会通过系统调用执行binder驱动的binder_mmap函数,其内部主要为进程的用户空间和内核空间分别分配一段连续的虚拟地址空间(两个地址范围虽不一样,但大小是相同,地址之间相差一个固定值)。接下来会为这两个虚拟地址空间分配内核缓存区,也就是内存映射到同一个物理页,binder的物理页由binder驱动负责分配,然后这些物理页的访问,将分为进程的用户空间和进程内核空间。并且在映射建立初始阶段,只是映射了一个物理页,而不是为整个虚拟地址空间都建立相应的物理影射,目的是充分高效的利用内存,只是到需要用到的时候才建立映射,用完后马上释放映射,这样可以充分高效的利用系统的物理内存页。内核刚开始只是分配了一个物理页,并且分别将这个物理页映射到进程的内核虚拟地址空间和进程的用户虚拟地址空间。在用户空间访问内核空间和在内核空间访问用户空间,其实都是访问的是同一个物理内存块,从而实现进程的内核和用户空间共享同一块物理内存的目的。这样binder驱动在内核空间,将一段数据拷贝到这个物理页,则该进程的用户空间则不需要拷贝即可以同步看到内核空间的修改,并能够访问这段物理内存。
当 一 个进程使用命令协议BC_TRANSACTION或者BC_REPLY向另外 一 个进程传递数据时,Binder驱动程序就需要将这些数据从用户空间拷贝到目标进程的内核空间。这时候Binder驱动程序就需要在目标进程的内存池中分配出一 小块内核缓冲区来保存这些数据,以便可以传递给它使用。在Binder驱动程序中,内核缓冲区的动态分配操作是由函数binder_alloc_buf实现的,
因为进程的内核空间和进程的用户空间针对同一块物理内存建立映射,这样只需要一次拷贝就能完成进程间信息的传递。下面是
被组织为一个由N个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址,作为到数组的索引。CPU访问内存最简单直接的方法就是使用物理地址,这种寻址方式被称为物理寻址。但是,在现代操作系统中,处理器使用的是一种称为虚拟寻址的寻址方式。使用虚拟寻址,CPU在获得虚拟地址之后,需要通过MMU将虚拟地址翻译为物理地址,这样才能访问到真实的物理内存。而在翻译的过程中还需要借助页表,所谓页表就是一个存放在物理内存中的数据结构,它记录了虚拟页与物理页的映射关系。在32位的操作系统中,每当创建一个进程时,就会为该进程分配一个 4GB 大小的虚拟地址空间,之所以是 4GB ,是因为在 32位的操作系统中(64位是不同的),一个指针长度是 4 字节,而 4 字节指针的寻址能力是从 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即为 4GB 大小的容量。虚拟内存好处是可以为每个进程提供了一个一致的地址空间,从而降低了程序员对内存管理的复杂性,其次它还保护了每个进程的地址空间不会被其他进程破坏。每个进程只能访问自己虚拟地址空间中的数据,无法访问别的进程中的数据,通过这种方法实现了进程间的地址隔离。
一个进程中的4GB 虚拟内存空间又可以分为两种类型,0~3GB为用户空间地址,3GB~4GB为内核空间地址。用户空间包括
代码、数据、堆、共享库以及栈等区;内核空间保存的是系统线程调度、内存管理、设备驱动等数据,这部分数据供所有的进程
共享,但应用程序是不能直接访问这个内核空间地址,只能通过系统调用来访问。当程序运行在用户空间时,我们称为程序运行
在进程的用户态,当程序运行在内核空间时,我们称为程序运行在进程的内核态。虽然用户态下和内核态下工作的程序有很多差
别,但最重要的差别就在于CPU特权级的不同,即权力的不同。运行在用户态的程序不能直接访问操作系统内核数据结构和程序,而程序处于内核态时,是可以随意进入用户态的。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如常见的是通过系统调用进入内核态。
Binder机制的跨进程通信就是使用的内核空间与用户空间共享数据的原理来实现跨进程的通信,一 个进程在使用Binder进程
间通信机制之前,首先要调用函数open打开设备文件/dev/binder来获得一个文件描述符, 然后才能通过这个文件描述符来和
Binder驱动程序交互, 继而和其他进程执行Binder进程间通信。
进程打开了设备文件/dev/binder之后, 还必须要调用函数mmap把这个设备文件映射到进程的地址空间, 然后才可以使用Binder进程间通信机制。设备文件/dev/binder对应的是一个虚拟的设备, 将它映射到进程的地址空间的目的并不是对它的内容感兴趣, 而是为了为进程分配内核缓冲区, 以便它可以用来传输进程间通信数据。
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
每个进程都有属于自己的一块binder内存,这块内存的大小就是在进程调用mmap系统调用时分配的,其返回值是就是为用户空间分配的虚拟地址的起始地址,分配的内存大小则由BINDER_VM_SIZE指定,但不能超过4M,从这里就可以看出, Binder驱动程序最多可以为进程分配4M内核缓冲区来传输进程间通信数据。PROT_READ属性表示用户空间的进程对内核分配的这个内核缓冲区的内容只有读的权限,没有写的权限。
进程执行mmap函数会通过系统调用执行binder驱动的binder_mmap函数,其内部主要为进程的用户空间和内核空间分别分配一段连续的虚拟地址空间(两个地址范围虽不一样,但大小是相同,地址之间相差一个固定值)。接下来会为这两个虚拟地址空间分配内核缓存区,也就是内存映射到同一个物理页,binder的物理页由binder驱动负责分配,然后这些物理页的访问,将分为进程的用户空间和进程内核空间。并且在映射建立初始阶段,只是映射了一个物理页,而不是为整个虚拟地址空间都建立相应的物理影射,目的是充分高效的利用内存,只是到需要用到的时候才建立映射,用完后马上释放映射,这样可以充分高效的利用系统的物理内存页。内核刚开始只是分配了一个物理页,并且分别将这个物理页映射到进程的内核虚拟地址空间和进程的用户虚拟地址空间。在用户空间访问内核空间和在内核空间访问用户空间,其实都是访问的是同一个物理内存块,从而实现进程的内核和用户空间共享同一块物理内存的目的。这样binder驱动在内核空间,将一段数据拷贝到这个物理页,则该进程的用户空间则不需要拷贝即可以同步看到内核空间的修改,并能够访问这段物理内存。
当 一 个进程使用命令协议BC_TRANSACTION或者BC_REPLY向另外 一 个进程传递数据时,Binder驱动程序就需要将这些数据从用户空间拷贝到目标进程的内核空间。这时候Binder驱动程序就需要在目标进程的内存池中分配出一 小块内核缓冲区来保存这些数据,以便可以传递给它使用。在Binder驱动程序中,内核缓冲区的动态分配操作是由函数binder_alloc_buf实现的,
因为进程的内核空间和进程的用户空间针对同一块物理内存建立映射,这样只需要一次拷贝就能完成进程间信息的传递。下面是
内存的拷贝图
进程A向进程B传递数据时,这些数据会被驱动从进程A 拷贝到进程B的内核缓存区中,这个内核缓存区已经映射过地址,
进程B就可以直接读取用户空间内存里面的数据,就不需要再做一次拷贝动作了。