Linux 中跨进程通信方式有很多种,安卓framework中主要使用到了四种。
-
管道
-
Socket
-
内存共享
-
信号
一、管道
1、特点
a、半双工的,即单项的。
管道描述符数据只能往一个方向流,要么是读,要么是写。
如果需要既能读又能写,那么管道需要两个文件描述符。Linux还是很人性化的,提供了api pipe(fds),这个api可以生成一对描述符,一个用于读,一个用于写。
int result = pipe(fd) // 创建管道
fd[0] // 数组代表读端
fd[1]// 数组代表写端
b、有名管道&无名管道
管道分为有名管道,无名管道。
无名管道一般是在父子进程之间进行通信。有名管道只要双方都知道管道名字也可以进行通信。
c、可以和epoll相结合监听管道读写事件。
d、进程内可以用(无名管道),进程间也可以用(有名管道),在数据不大的跨进程通信时非常好用。
2、 Android framework中的使用
Looper的native层使用到了管道, 但是在安卓4.0之后换成了evendfd。
管道使用起来还是很方便的,主要可以可和epoll结合起来监听读写事件。
>1、通过pipe创建一对管道描述符读、写。
2、创建epoll监听读的文件描述符
3、其他线程往当前线程写信息时当前线程读描述符就会监听到。
epoll对读端事件的监听:
1、epoll_wait 阻塞等待事件,返回值是触发事件的个数。
2、for循环中处理事件,我们这里最关注的是读端事件。
3、如果事件是可读事件,通过awoken吧事件从管道读出。唤醒当前线程。
ps:这里不管写端写了什么,都全部从管道读出,否则管道数据满了其他端往管道写数据失败。至于怎么往管道写数据呢,是其他线程调用wake函数通过write 往写端描述符写数据。
二、本地Socket
1、特点
a、全双工的。
即可读,又可写,不用再区分读端,写端。
b、可以用在两个无亲缘关系的进程间。
创建时指定路径,只需要把路径公开给别人,就可以给别人通信了。
2、 framework中的使用
Zygote进程创建Sever端Socket,不断loop循环,去检测有无ams端发送过来新的socket连接请求,或者检测有无ams 发送来的socket消息。
1、poll用来检测有无我们关注的事件
2、有关注的事件分两种情况:1、有新的连接:处理新的连接
2、有新的消息:处理发送过来新的数据。具体是如何处理的呢?首先从socket中读取参数,然后根据参数执行相应指令。主要是创建应用进程,进程创建成功后吧进程pid写给对方。
三、共享内存
管道、Socket都有个缺点,传输的数据量不能太大,否则性能会很糟,二者都涉及到两次拷贝。
1、共享内存特点
a、很快,不需要多次拷贝。
拿到描述符,吧描述符同时映射到两个进程的内存空间即可。这样一个进程往描述符写数据,另一个进程就能读到。
b、进程间无需存在亲缘关系。
只要拿到文件描述符即可,文件描述符可以跨进程传递的。
2、 framework中的使用
一般用在进程间大数据传输的如图片的相关,如有个memoryFile工具类。
1、通过native函数在native层 调用ashmem_create_regin创建匿名共享内存。返回个描述符。
2、通过native函数在native层 调用mmap 给描述符映射到当前进程空间。当前进程空间地址为mAddress
memoryFile的读写:
1、读:吧数据从共享内存读到应用层的buffer中。
ps:setByteArrayRegin就是把buffer的数据拷贝到运行时数组中。
2、写:功能与读相反。
四、信号
1、信号的特点
a、单向的。
信号发出后,其他进程是否处理发出信号的进程无法得知。
b、只能带个信号,不能带别的参数
c、知道进程的pid就可以给进程发信号,也可以一次给一群进程发信号。
给其他进程发信号是需要权限的,要么具有系统权限,要么两进程具有相同的uid.
这里需要注意,虽然所有的进程都是由zygote进程fork出来的,具有相同的uid,但是进程被创建之后一般都会被重新设置uid的。
2、信号的应用:通知杀死某一进程
zygote关注的sigchld 信号。子进程死亡后zygote进行清理。
1、给指定pid的进程发送SINGAL_KILL 信号
2、Zygote关注的信号SIGXHLD信号,当子进程退出时会发送这个信号,Zygote收到信号后会回收子进程资源表信息。防止僵尸进程产生。