介绍

IPC全称为Inter-Process Communication,含义为进程间通信,是指两个进程之间进行数据交换的过程。IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制,比如Windows上可以通过剪贴板等进行进程间通信;Linux上可以通过管道(Pipe)、信号(Sinal)、信号量(Semophore)、消息队列(Message)、共享内存(Share Memory)和套接字(Socket)等来进行进程间的通信。可以看到不同的操作系统有着不同IPC机制,对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间通信方式就是Binder了,通过Binder可以轻松实现进程间的通信。本文主要介绍Linux中的IPC机制,以及相关的使用场景。

1.管道

管道是Linux有UNIX继承过来的进程间的通信机制,它是UNIX早期的一个重要通信机制,管道的主要思想是在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个共享文件比较特殊,他不属于文件系统并且只存在于内存中。另外管道采用半双工通信方式,数据只能在一个方向流动。管道的简单模型如下图:

通过简单的模型图,相信应该比较容易理解通过管道通信,传递数据,需要两次拷贝,进程A将数据拷贝至管道,进程B再从管道拷贝。

1.1管道应用场景之父子进程

管道应用场景一般在父子进程之间使用,当然如果是有名管道,只要通信的两个进程知道管道名称就可以通信了。父进程孵化克隆子进程时,就是使用的(无名)管道,通过管道将父进程的数据资源拷贝到子进程,通过调用pipe(fd)生成一对描述符,一个用来写,另一个用来读。fd[0]是用来读的,fd[1]是用来写的,通过fork()创建子进程,子进程会继承父进程的一对管道描述符。现在的需求是只是希望父进程往子进程里面写东西,那就可以把父进程的读描述符关了,子进程的写描述符关了。现在父进程往写描述符里面写入数据字符串,子进程就可以从读描述符把字符串读出来,读到buf里面,也对应了前面提到的半双工通信方式,数据只能在一个方向流动/传输,要么读,要么写,即管道一端写入数据,另一端读取数据。

int main(void) {int n,fd[2];char buf[SIZE];pipe(fd);pid_t pid = fork();if(pid == 0) {//关闭子进程写描述符close(fd[1]);//子进程向读描述符fd[0],读取数据到buf里面read(fd[0],buf,SIZE);} else if(pid > 0) {//关闭父进程读描述符close(fd[0]);//父进程向写描述符fd[1],写入字符串"Hello"write(fd[1],"Hello",5);}
}

对应的模型图如下:

1.2管道应用场景之Looper

管道在FrameWork中还有哪些应用场景呢?java层的loop在native层会有对应的Looper类,它的构造里面就会创建一个管道,Android4.4 用的是管道,在更高的版本如6.0,管道被换成了eventFd。现在主要是了解一下管道的用法。通过pipe创建管道,有读端,有写端。创建epoll,注册监听事件,监听读事件,即读描述符的读事件,如果有另一个进程拿到这个管道的写描述符,往里面写入数据的话,那么读端就能收到通知了。

Looper::looper(bool allowNonCallbacks){int wakeFds[2];//创建管道,生成对应的描述符int result = pipe(wakeFds);//读描述符mWakeREadPipeFd = wakeFds[0];//写描述符mWakeWritePipeFd = wakeFds[1];...mEpollFd = epoll_create(EPOLL_SIZE_HINT);//监听(读描述符)的读事件struct epoll_EVENT eventItem;eventItem.events = EPOLLIN;eventItem.data.fd = mWakeReadPipeFd;epoll_ctl(mEpollFd, EPOLL_CTL_ADD,mWakeReadPipeFd, &eventItem);
}

接下来看epoll是如何监听读端事件的

int Looper::pollInner(int timeoutMillis) {struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd,eventItems,...);for(int i=0;i<eventCount;i++){int fd = eventItems[i].data.fd;uint32_t epollEvents = eventItems[i].events;if (fd == mWakeReadPipeFd) {if (epollEvents & EPOLLIN) {awoken();}}}...return result;
}

epoll_wait阻塞在这里,等待事件。等到它返回时,返回值eventCount表示被触发事件的数量。然后在循环里面依次处理,if fd== mWakeReadPipeFd, 如果事件是可读的,就调用awoken(); 将管道里面的数据读出来,不然管道满了,写端就不能往管道写入数据了。并不关注往管道写入的是什么,只要往往管道写入数据,就会唤醒读端阻塞的线程,就可以去处理消息了。当别的线程给我们线程发消息时,就会通过wake函数往管道里面写入数据。

void Looper::wake() {nWrite = write(mWakeWritePipeFd,"W",1);
}

管道使用还是挺方便的,主要是它可以和epoll相结合,监听读写事件,它在进程里面可以用,跨进程也可以用,在数据量不大的跨进程通信中还是不错的。

2.Socket

套接字是更基础的进程间的通信机制,与其他通信机制不同的是,套接字可用于不同机器之间的进程间通信。它是全双工的,即在任何时候,通信两端都可以发送或接收数据,即读写操作。在Android中有没有socket的应用场景呢?答案是有的,在Zygote的Java框架层中会创建一个Server端的Socket,这个Socket用来等待AMS请求Zygote来创建新的应用程序进程。

public static void main(String argv[]) {......registerZygoteSocket(socketName);......runSelectLoop(abiList);
}

在Zygote的main函数中,通过registerZygoteSocket创建一个本地socket,做好一些准备工作之后,就进入runSelectLoop循环,去检测socket有没有新过来的连接或数据。

void runSelectLoop(String abiList) {......while(true) {Os.poll(pollFds, -1);for (int i = pollFds.length - 1; i >= 0; --i) {if (i == 0) {//处理新过来的连接} else {//处理发过来的数据peers.get(i).runOnce();}} }
}

runOnce处理数据是通过Socket读取发送过来的参数,根据参数去执行相关指令,这里的应用场景执行的就是创建新的应用进程,创建之后,将进程的pid通过socket写给对方。

Tips:问题来了,我们知道Binder是Android较为高效的跨进程通信机制,此处为何会采用Socket通信呢?

Zygote进程创建/孵化子进程,fork做了如下事情:

  • 父进程的内存数据会原封不动的拷贝到子进程中
  • 子进程在单线程状态下被生成

不采用binder通信,是为了避免binder线程有锁,然后子进程的主线程一直等待子线程(从父进程拷贝过来的子线程)的资源,但其实父进程的子线程没有拷贝过来,容易造成死锁。所以fork不允许在多线程环境,即不采用binder通信。

3.共享内存

共享内存的多个进程可以直接读写一块内存空间,是针对其他通信机制运行效率低而设计的,为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。这样,进程就可以直接读写这一块内存而不需要进行数据的复制了,从无需数据拷贝的角度来看,提高了效率,但是要避免一方修改了数据,另一方读取的数据还是之前的数据,就需要同步机制,开销大。同步机制容易造成数据不同步和死锁等问题。所以说它内存管理机制比较复杂。

Ashmem是一种共享内存的机制,它利用了Linux的mmap系统调用,将不同进程中的同一段物理内存映射到进程各自的虚拟地址空间,从而实现高效的进程间共享。而MemoryFile就是对Ashmem(匿名共享内存)做了封装。

native_open调用ashmem_create_region创建一块匿名共享内存,返回描述符mFD , native_mmap调用native层的mmap将描述符mFD映射到当前进程的内存空间,mAdress就是内存空间的地址。接下来是MemoryFile的读和写方法:

jint android_os_MemoryFile_read(JNIEnv* env,jobject clazz,...) {......env-> SetByteArrayRegion(buffer,destOffset,count,...);......return count;
}
jint android_os_MemoryFile_write(JNIEnv* env,jobject clazz,...) {......env-> SetByteArrayRegion(buffer,srcOffset,count,...);......return count;
}

read是将共享内存数据读到应用层的buffer里面,通过setByteArrayRegion方法将native buffer的数据拷贝到java的数组里面,write就是反过来了。

4.信号

​ 信号是原件层次上对中断机制的一种模拟,信号是一种异步通信方式,进程不必通过任何操作来等待信号的到达。信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些事件。具有以下特点:

  • 单向的,发出去之后怎么处理是别人的事
  • 只能带个信号,不能带别的参数
  • 知道进程pid就能发信号了,也可以一次给一群进程发信号(root权限或者和其他进程uid相同,才能发信号)

从以上特点可知,信号不适用于信息交换/数据传递,比较适用于单纯的通知预警作用,比如进程中断控制。

有时候要杀掉应用进程调用killProcess函数,就是给进程发送SIGNAL_KILL信号,pid就是进程pid,不是想杀谁就能杀谁的,受权限控制的。两个进程的userID相同才能给别人发送信号,应用进程都是zygote进程fork出来的,uid默认都是和zygote相同的。但是进程启动之后都会马上重新设置自己的uid,所以不能随便给别人发送信号的。zygote进程会关注SIGCHLD信号,zygote启动子进程之后需要关注子进程退出了没有,如果退出了,需要及时把它的资源回收掉。

public class Process {public static final void killProcess(int pid) {sendSignal(pid,SIGNAL_KILL);}
}

Linux中传统的IPC机制相关推荐

  1. Linux中的线程同步机制-futex

    Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...

  2. 解析linux中的vfs文件系统机制,解析Linux中的VFS文件系统机制

    解析Linux中的VFS文件系统机制 本文阐述 Linux 中的文件系统部分,源代码来自基于 IA32 的 2.4.20 内核.总体上说Linux下的文件系统主要可分为三大块:一是上层的文件系统的系统 ...

  3. Linux内核中的vfs,解析 Linux 中的 VFS 文件系统机制

    在Linux系统中,每个分区都是一个文件系统,都有自己的目录层次结构.Linux的最重要特征之一就是支持多种文件系统,这样它更加灵 活,并可以和许多其它种操作系统共存.由于系统已将Linux文件系统的 ...

  4. Linux中的中断管理机制

    1.中断相关基础知识介绍 1.1.中断产生背景 假设现在CPU需要去获取一个键盘的时间,如果处理器发出一个请求信号之后一直在轮询键盘的响应,由于键盘响应速度比处理器慢得多并且需要等待用户输入,这对于C ...

  5. 解析 Linux 中的 VFS 文件系统机制

    简介: 本文阐述 Linux 中的文件系统部分,源代码来自基于 IA32 的 2.4.20 内核.总体上说 Linux 下的文件系统主要可分为三大块:一是上层的文件系统的系统调用,二是虚拟文件系统 V ...

  6. Linux中select IO复用机制

    函数作用: 系统提供select函数来实现多路复用输入/输出模型.select系统调用是用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发 ...

  7. 解析Linux中的VFS文件系统机制

    文阐述 Linux 中的文件系统部分,源代码来自基于 IA32 的 2.4.20 内核.总体上说Linux下的文件系统主要可分为三大块:一是上层的文件系统的系统调用,二是虚拟文件系统 VFS(Virt ...

  8. 解析 Linux 中的 VFS 文件系统机制(1)

    住:这里只转载一篇,还有几篇没有转载,但这里给出源地址: http://www.51cto.com/art/200803/67283.htm 摘要:本文阐述 Linux 中的文件系统部分,源代码来自基 ...

  9. linux中vfs和fs区别,解析Linux中的 VFS 文件系统机制(rootfs)一

    本文阐述 Linux 中的文件系统部分,源代码来自基于 IA32 的 2.4.20 内核.总体上说 Linux 下的文件系统主要可分为三大块:一是上层的文件系统的系统调用,二是虚拟文件系统 VFS(V ...

最新文章

  1. 一分钟详解PCL中点云配准技术
  2. 图说二叉树添加数据原理以及遍历原理
  3. 大数据中常见的端口号 总结汇总大全(最新)
  4. redis——内存概述
  5. java mongo 查询数组_MongoDB查询(数组、内嵌文档)
  6. android壁纸居中,Android Launcher 如何实现壁纸居中
  7. sqlserver 递归查询
  8. vue watch 经常监听不到_VUE处理 组件赋值 watch 监听不到赋值问题
  9. python遥感数据有偿处理_地质男转行学遥感Python——DMSP数据预处理一
  10. 数据结构---哈希表
  11. ios模拟器安装app
  12. zcu106 固化_xilinx zcu106 vcu demo
  13. 波士顿动力SpotMini改造有胳膊半人马,这家意大利创企打造极致机械手臂
  14. 静态网页之--小说阅读网
  15. 中小科技企业新蓝图,抓住资本新机遇!北京证券交易所要来了
  16. MATLAB特殊矩阵的构造
  17. 编写测试用例的基本方法之边界值
  18. 搭建高性能计算环境(四)、应用软件的安装之VASP
  19. 用python语言实现喇叭发声原理_用Python实现喇叭天线设计小工具(三)
  20. misc与简单的密码知识(ctfwiki

热门文章

  1. 2012年最新的12款超棒jQuery插件
  2. 高德地图之周边信息查询
  3. linux 命令:top 详解
  4. Scrapy搜狗微信:使用cookies登录并使用打码平台自动输入验证码
  5. MCS-51实现静态数码管的显示
  6. 币圈拉盘是什么意思?
  7. 天津师范大学计算机专业排名,天津最好的10所大学公布:天津师范大学第三,各校就业率相差较大...
  8. 美国芯源系统(MPS)校招笔试经历
  9. IOS 应用内打开三方地图app直接导航(高德坐标)
  10. 图片添加文字水印,自动换行,左右留白