前言
复习、复习、复习
你是否了解Binder机制?
Binder这么好用,那为什么Zygote的IPC通信机制用Socket而不用Binder?
为什么说Binder是安全的?
Intent跨进程传大图为什么会崩溃?
AIDL的oneWay和非oneway有什么区别?
本文将针对以上问题进行原理分析
目录
一、IPC机制
Inter-Process Communication 简称 IPC ,即为进程间通信。Android与Liunx都有自己的IPC机制。虽然说Android是继承自Linux的,但是IPC通信机制并不是完全继承的。如果要你设计一套进程间通信,你会如何设计,数据简单传递流程是如何的?
Linux
-
管道
继承自Unix,它是 Unix早期的一个重要通信机制。主要思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个文件本质是内核缓冲区( 4k ),通过pipe系统函数调用即可得到描述符,创建管道,一个用来读,一个用来写。通信方式为半双工,数据自己读不能写,数据读走后,便不在管道中,不能反复读取。模型如下:
-
信号
举个例子,我们在电脑上按 Ctrl + C 会中断程序,这是通过信号机制来停止了程序。信号就是一种异步通信方式,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,但是不适用于信息交换。每个信号都有一个名字和编号,名字一般都以SIG开头。例如Android里面的杀掉进程方法。
-
信号量
信号量是一个计数器,用来控制多个进程对共享资源的访问,它作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源,主要作为进程间以及同一进程内不同线程之间的同步手段,通过控制这种资源的分配来实现互斥和同步的。
-
消息队列
双链表结构,存在内存中,由一个或多个进程读写信息,信息会复制两次,对于频繁、信息量大的通信不宜使用消息队列。
-
共享内存
多个进程可以直接读写的一块内存,优点在于内核空间专门留出了一块内存可以由需要访问的进程映射到自己的私有地址空间,进程就可以不需要进行数据拷贝,而直接读写数据。
-
套接字
套接字可用于不同机器之间的进程通信,例如Socket,全双工的,即可读又可写,可以用在两个无亲缘关系的进程间。
Android
-
AIDL
Android interface definition Language 即 Android 接口定义语言,专门用来处理进程间通信
-
Messenger
以串行的方式来处理客户端发来的消息,如果有大量消息发送到服务端,服务端仍然一个一个的处理再相应客户端显然不合适,并且在进程间只能用于数据传递,不能像AIDL一样,支持方法调用。
-
Bundle
Bundle实现了Parcelable 接口,所以它可以方便的在不同进程间传输。Activity、Service、Receive都是在Intent中通过Bundle来进行数据传递的。
-
文件共享
-
ContentProvider
ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder。例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
-
Binder
对于消息队列、Socket、管道而言,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共拷贝两次,如下图
对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到一块物理地址的(其实是拿到描述符后同时映射到两个进程的空间,一个进程写,另一个进程就能读到)。如下图
这个就体现出了Binder的性能比Socket好,少了一次数据拷贝,那Binder的安全体现在哪呢?
首先Socket或命名管道,别人知道IP地址或者管道名就可以和它进行连接,写数据,这里容易被恶意使用,没有对使用者的信息进行验证。这种信息要想不出问题,只能由IPC机制本身在内核态添加,例如Binder,它就是这么做的,Android里面这种身份信息标识其实就是UID。
这里要留意一下,一般Binder不会传递太大的数据,然而通过文件跨进程传递数据,效率有点低,作为内存优化手段可以选择使用MemoryFile,这个类是Android匿名共享内存里面的高性能文件类。
-
Scoket
常见于Zygote 和 AMS通信,Zygote接受AMS的请求就是用的Scoket,init进程 fork 出 zygote进程后,会执行Zygote的main函数,内部会注册一个本地Scoket,然后进入循环来处理数据。
二、Binder驱动
1、Android 启动时 Binder 通信流程
-
Android 底层 Linux启动之后,用户空间启动的第一个进程为 init 进程
-
init 进程 加载 init.rc 配置,通过 fork + exec 系统启动配置文件定义的系统服务,包括 Zygote进程、ServiceManager进程、SurfaceFlinger进程等
-
Zygote进程启动 (
frameworks/base/cmds/app_process/app_main.cpp
)。 启动完虚拟机、注册好JNI 函数后,会继续启动SystemServer
进程,也就是说会执行SystemServer
进程的main函数com.android.server.SystemServer.java public static void main(String[] args) { new SystemServer().run(); } //这里就会启动 AMS WMS PMS 等服务 private void run() { ..... // 开机引导服务、核心服务、其他服务 startBootstrapServices(); startCoreServices(); startOtherServices(); ..... } 复制代码
这里以
AMS
为例,创建好AMS实例后会调用它的以下方法// ActivityManagerService extends IActivityManager.Stub public void setSystemProcess() { try { ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true); ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats); ServiceManager.addService("meminfo", new MemBinder(this)); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(this)); } ..... } 复制代码
可以看到这里会将
AMS
和一些其它服务注册到ServiceManager
里面我们继续看一下是如何注册的
android.os.ServiceManager.java //addService public static void addService(String name, IBinder service) { try { getIServiceManager().addService(name, service, false); } catch (RemoteException var3) { Log.e("ServiceManager", "error in addService", var3); } } //getService private static IServiceManager getIServiceManager() { if (sServiceManager != null) { return sServiceManager; } else { //重点 这里通过 BinderInternal 拿到一个Native的 BpBinder,然后转换成Java层的BinderProxy对象 sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); return sServiceManager; } } //ServiceManagerNative.asInsterface static public IServiceManager asInterface(IBinder obj) { if (obj == null) { return null; } IServiceManager in = (IServiceManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ServiceManagerProxy(obj); } 复制代码
看到这里,大概就清楚了,从底层拿到BpBinder,然后转换成BinderProxy对象,调用
addService
方法,放入到ServiceManager 中管理。addService 方法对应于 native 层的frameworks/native/libs/IServicManager.cpp
中的 addService 方法,后面在对SurfaceFlinger
注册服务 会分析此方法。 -
ServiceManager进程启动。我们直接看它的main函数,里面会打开Binder驱动、映射内存,开启
Binder Loop
循环,等待Client 和 Service 的请求 (这里说的Service是指系统服务)frameworks/native/cmds/sedrvicemanager/service_manager.c int main(int argc, char** argv) { .... if (argc > 1) { driver = argv[1]; } else { driver = "/dev/binder"; } .... //1.打开 Binder 驱动 映射内存 128 k bs = binder_open(driver, 128*1024); ..... // 2\. Binder 成为了上下文的管理者,告诉Binder驱动,我就是ServiceManager,注册、查询都找我 if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; } .... //3.开启binder loop循环 处理消息 binder_loop(bs, svcmgr_handler); return 0; } frameworks/native/cmds/sedrvicemanager/binder.c void binder_loop(struct binder_state *bs, binder_handler func) { ...... readbuf[0] = BC_ENTER_LOOPER; binder_write(bs, readbuf, sizeof(uint32_t)); //4.for 循环 把 binder驱动发来的数据读过来 ,然后 binder_parse 处理请求 for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); if (res < 0) { ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno)); break; } res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); ....... } } 复制代码
这里说明一下,
binder_open
中会通过mmap
内存映射 128 k 的内存空间,但是这仅仅在ServiceManager
进程中,其它Binder 服务进程BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(SC_PAGE_SIZE) * 2)
的内存空间,
SC_PAGE_SIZE
为 一个page
页大小, 估摸着为1M - 8K的样子。那为什么网上有人说Binder内存限制是1M - 8K,而一次调用最大传输数据只有 507 K呢?
这是因为 Binder 线程池数量默认是 15 个 (不计 某个指令创建的 Binder主线程,否则为 16 个),15 个线程共享这么多内存空间,所以实际传输大小并没有这么大。
-
SurfaceFlinger 进程启动
frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp int main(int, char**) { startHidlServices();