要想了解这个问题,首先需要对Linux进程间通讯机制有一定的了解。
1.Linux进程间通讯机制
https://www.zhihu.com/question/39440766/answer/89210950
Linux现有的所有进程间IPC方式:
1.管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
2.消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
3.共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
4.套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
5.信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
6.信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;
Binder机制基于开源的OpenBinder。http://www.angryredplanet.com/~hackbod/openbinder/docs/html/BinderIPCMechanism.html
2. socket 与 binder 对比
先来看看 socket与binder 两种进程间通讯机制 对比。
https://www.jianshu.com/p/066d99da7cbd http://gityuan.com/2015/11/21/binder-framework/
接下来回顾下 Zygote启动和Android启动。
Zygote启动过程的函数调用类大致流程:
http://gityuan.com/2016/02/13/android-zygote/#jnistartreg
http://gityuan.com/2016/02/13/android-zygote/#jnistartreg
Zygote启动过程:
1.解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法;
2.调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数;
3.通过JNI方式调用ZygoteInit.main(),第一次进入Java世界;
4.registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求;
5.preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率;
6.zygote完毕大部分工作,接下来再通过startSystemServer(),fork得力帮手system_server进程,也是上层framework的运行载体。
7.zygote功成身退,调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。
下面来看一张图,标示了调用startService之后的通讯流程。
图中涉及3种IPC通信方式:
Binder
、Socket
以及Handler
,在图中分别用3种不同的颜色来代表这3种通信方式。一般来说,同一进程内的线程间通信采用的是 Handler消息队列机制,不同进程间的通信采用的是binder机制,另外与Zygote
进程通信采用的Socket
。
Android系统启动流程如下:
https://www.jianshu.com/p/3e033aeb44f8
现在回到问题,为什么Zygote通信为什么用Socket,而不是Binder?
具体可看这篇:
android中AMS通知Zygote去fork进程为什么使用socket而不使用binder?
可从以下五个方面分析:
1.先后时序问题:
binder驱动是早于init进程加载的。而init进程是安卓系统启动的第一个进程。
安卓中一般使用的binder引用,都是保存在ServiceManager进程中的,而如果想从ServiceManager中获取到对应的binder引用,前提是需要注册。虽然Init进程是先创建ServiceManager,后创建Zygote进程的。虽然Zygote更晚创建,但是也不能保证Zygote进程去注册binder的时候,ServiceManager已经初始化好了。注册时间点无法保证,AMS无法获取到Zygote的binder引用,这是原因之一。
2.多线程问题
Linux中,fork进程是不推荐fork一个多线程的进程的,因为如果存在锁的情况下,会导致锁异常。
而如果自身作为binder机制的接收者,就会创建一个额外的线程来进行处理(发送者进程是无影响的)。
所以,如果使用binder机制,就会导致去fork一个多线程的进程,这是原因之二。
3.效率问题
AMS和Zygote之间使用的LocalSocket,相对于网络Socket,减少了数据验证等环节,所以其实效率相对于正常的网络Socket会大幅的提升。虽然还是要经过两次拷贝,但是由于数据量并不大,所以其实影响并不明显。
所以,LocalSocket效率其实也不低,这是原因之三。
4.安全问题
LocalSocket其实也有权限校验,并不意味着可以被所有进程随意调用,这是原因之四。
5.Binder拷贝问题
如果使用binder通讯机制的话,从Zygote中fork出子进程会拷贝Zygote中binder对象。所以就凭白多占用了一块无用的内存区域。而Binder对象不能释放。Binder的特殊性在于其是成对存在的,其分为Client端对象和Server端对象。假设我们使用binder,如果要释放掉APP的Server端binder引用对象,就必须释放掉AMS中的Client端binder对象,那这样就会导致AMS失去binder从而无法正常向Zygote发送消息。
而使用socket通讯机制的话,fork出APP进程之后,APP进程会去主动的关闭掉这个socket,从而释放这块区域。相关代码在ZygoteConnection的processCommand方法中:
try {
if (pid == 0) {
// in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, childPipeFd,
parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, serverPipeFd);
return null;
}
}
所以,使用binder会造成额外的内存占用,这是原因之五。
另外,binder调用一般是同步阻塞的。如果使用oneway,是非阻塞(像一些系统服务调用应用进程的时候就会使用 oneway,比如 AMS 调用应用进程启动 Activity,这样就算应用进程中做了耗时的任务,也不会阻塞系统服务的运行。)。但是,binder驱动对于oneway的调用是类似于handler sendmessage那样的,挨个处理,所以如果服务端的oneway接口处理太慢而客户端调用太多的话,来不及处理的调用会占满binder驱动的缓存,导致其他调用抛出上面的transaction failed。
oneway 方法的隐患具体参考这篇。
AIDL oneway 方法的隐患
socket也不是单独使用的,I/O模型中的epoll是一种高效的管理socket的模型,epoll机制相对成熟,是同步非阻塞。
socket(套接字)是对 TCP/IP 或者UDP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。
而Binder很负复杂。
简单的设计,减少出问题的几率,会让系统更加稳定。
Zygote通信为什么用Socket,而不是Binder?
说了这么多,其实是通过对比加深对Android进程间通讯Socket和Binder两种机制的理解。
3.Zygote 处理 socket消息代码分析
具体代码流程分析可看这篇:
app_process: zygote处理socket消息请求(5)
参考链接:
为什么 Android 要采用 Binder 作为 IPC 机制?
为什么Android的Zygote与SystemServer通信采用Socket,而不是Binder?
[026]Zygote中Socket通信能否替换成Binder通信?
Android系统启动-zygote篇
Android10.0系统启动之Zygote进程-[Android取经之路]
app_process: zygote处理socket消息请求(5)
Android Framework层学习——为什么SystemServer进程与Zygote进程通讯采用Socket而不是Binder
android中AMS通知Zygote去fork进程为什么使用socket而不使用binder
网友评论