在了解Android的Binder通信机制之前,我们来看下Linux现有的进程间通信的方式,然后简要分析Android为什么要另起炉灶,设计一套新的通信机制Binder,以及Binder通信机制在Android这种嵌入式平台具有何种优越性等,首先看Linux下的进程间通信方式,有管道、消息队列、共享内存、套接字、信号量、信号,起初我们一一回顾下这几种通信方式的实现原理和适用场景

1.管道

比较好理解概念的就是进程间通信就是在不同进程之间传播或交换信息。管道通信方式是Linux继承了早期的Unix进程间通信的方式,管道又分为无名管道和有名管道,一般没有刻意描述称管道为无名管道

1.1管道的特点

Ø  管道是半双工的,数据只能向一个方向流动,一端输入,另一端输出。需要双方通信时,需要建立起两个管道。

Ø  管道分为普通管道和命名管道。普通管道位于内存,只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。命名管道位于文件系统,没有亲缘关系的进程间只要知道管道名也可以通讯。

Ø  单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中

Ø  一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

Ø  管道满时,写阻塞;管道空时,读阻塞。

1.2 管道实现机制

管道是由内核管理的一个缓冲区,管道的一端连接着进程的输出,一端连接进程的输入,一个缓冲区不需要很大,它被设计成为环形的数据结构,类似于数据结构中的循环队列;以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。

管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1],其中fd[0]用于读取,fd[1]用于写

管道的通信模型图如下所示:

Android的进程间通信机制之Binder初探_第1张图片

管道的创建过程这里以父进程创建子进程的方式为例,当父进程调用fork()函数时候,子进程也拥有一对描述符(复制父进程的)链接到管道上,如图所示:

Android的进程间通信机制之Binder初探_第2张图片

随后,当两个进程进行通信时关闭不需要的一个链接,图中所示,process1关闭写,process2关闭读描述符

 

#include #include #include #include #include int main(int argc, char *argv[]){    int pfd[2]; //保存进程间通信的管道文件描述符    pid_t pid;  //保存子进程 描述符    char buf;    if(argc != 2)//判断命令行参数是否符合    {        fprintf(stderr,"Usage: %s \n",argv[0]);        exit(0);    }    // 先建立管道文件描述符    if (pipe(pfd) == -1)    {        perror("pipe");        exit(EXIT_FAILURE);    }    if((pid = fork()) == 0) {        // 关闭子进程的管道写描述符        close(pfd[1]);        printf("get message from parent\n");        while (read(pfd[0], &buf, 1) > 0)   //从管道循环读取数据            write(STDOUT_FILENO, &buf, 1);  //输出读到的数据        write(STDOUT_FILENO, "\n", 1);      //输出从管道读取的数据        close(pfd[0]);         //关闭管道读        exit(0);    }else if(pid > 0){        //关闭管道读        close(pfd[0]);        printf("send message to child\n");        //向管道写入命令行参数1        write(pfd[1], argv[1], strlen(argv[1]));        close(pfd[1]);        //等待子进程退出        wait(NULL);        exit(0);    }else{         perror("fork");//fork()失败        exit(EXIT_FAILURE);    }}

1.3 命名管道

由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(namedPIPE)。

FIFO (First in,First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接


2.共享内存


共享内存可以说是进程间通信最快的方式,两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以来保证数据的一致性。

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据

这四次数据拷贝是指:

写的时候

1)用户空间的进程将buf中将数据拷贝到内核空间中

2)内核将数据拷贝到内存中

读的时候

1)读数据从内存到内核空间

2)内核空间到用户空间的buf

共享内存的2次是指:

一次从输入文件到共享内存区,另一次从共享内存区到输出文件


Android的进程间通信机制之Binder初探_第3张图片

Linux的2.2.x内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。这里以system V为例子

 

1.创建共享内存,这里用到的函数是shmget,也就是从内存中获得一段共享内存区域;

2.映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中去,这里使用的函数是shmat;

3.使用不带缓冲的I/O读写命令对其进行操作

4.撤销映射的操作,其函数为shmdt。

具体的实现参见博客http://blog.csdn.net/lzjsqn/article/details/53863283


3.信号量


主要作为进程间以及同一进程不同线程之间的同步手段。信号量是用来解决进程之间的同步与互斥问题的一种进程之间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。其中信号量对应于某一种资源,取一个非负的整型值。信号量值指的是当前可用的该资源的数量,若它等于0则意味着目前没有可用的资源。

什么是PV操作

P操作:如果有可用的资源(信号量值>0),则占用一个资源(给信号量值减去一,进入临界区代码);如果没有可用的资源   (信号量值等于0),则被阻塞到,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。

V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程。如果没有进程等待它,则释放一个资源(给信号量值加一)。

 

4.信号


信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

 

5.消息队列


消息队列是消息的链表,存放在内核中并由消息队列标识符标识。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。这跟管道和FIFO是相反的,对后两者来说,除非读出者已存在,否则先有写入者是没有意义的。包括Posix消息队列systemV消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。


6.套接字


套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

 

7. Binder的机制


Android的内核也是基于Linux内核,为何不直接采用Linux现有的进程IPC方案呢,这个问题众说纷纭,下面我们从Binder的基本特性和已有Linux进程间通信的方式进行对比,说明采用Binder机制的合理性

Ø  通信效率

Android作为嵌入设备的代表,性能异常重要,不会像PC那样拥有更多的内存和CPU资源,因此设计一套高效的通信方式是必须的,前面讲的管道、消息队列、Socket通信的时候都需要进行2次内存拷贝数据(注意是内存拷贝,不是总的拷贝次数),共享内存不需要内存拷贝,Binder机制需要一次,从性能上讲Binder机制在通信效率上仅此于内存共享。

Ø  安全性

Binder机制对于通信双方的身份是由内核进行校检支持的,传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,无法鉴别对方身份。Android系统为每个应用程序都分配相应的UID,Binder机制是基于C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断相应的通信双方是否合法。使用用户空间无法访问的内存空间来交换数据,保证了IPC的安全性

Ø  易用性

共享内存通信不需要copy,性能够高,可是使用复杂,要使用复杂的同步互斥方式实现进程间通信,保证共享内存的数据一致有效,Binder采用面向对象的设计实现,将进程间通信的过程好比为调用对象的方法一样.


Binder机制的具体实现原理,,,未完待续

更多相关文章

  1. 【腾讯Bugly干货分享】Android 进程保活招式大全
  2. Android 外接USB转串口设备开发笔记(Android与单片机通过usb转串
  3. Android AIDL-跨进程
  4. Android 9(P)应用进程创建流程大揭秘
  5. Android中的多进程开发以及多进程的使用场景
  6. Android中实现跨进程通信(IPC)的方式(三)之观察者模式
  7. Android 蓝牙通信开发(一) 搜索蓝牙设备
  8. Android线程通信机制-Handler(Java层)

随机推荐

  1. Android学习札记22:ThumbnailUtils
  2. Android(安卓)Studio 快捷键
  3. android studio 3.1 Android(安卓)Device
  4. 关于listView设置背景引起StackOverflowE
  5. android 中常用的权限
  6. 【Android】【Lottie】在Android中使用Lo
  7. 如何去掉ListView底部的ListDivider
  8. Android(安卓)gradle 命令行打包
  9. 启动模式详解
  10. Android文件下载使用Http协议