IPC工具介绍
Binder作为Android 众多的IPC通讯手段之一,在Framework的数据传输中起到极为关键的作用。为什么Google需要重新创造Binder这么一个IPC工具,使用linux默认提供的Pipe、Socket、共享内存、信号、消息队列等IPC工具不行吗?
答案是 这些传统的linux IPC工具有一部分android也在使用,只是在某些场合下它们无法满足需求,所以才创造了Binder这么一个工具。 为了更好地向各位读者解释为什么需要Binder,我们先来简单地认识一下linux传统的IPC工具,让大家对它们的优势和劣势有一个更为直观的认识。
Pipe
管道是一种最基本的IPC工具,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。它有如下特质:
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端。
- 规定数据从管道的写端流入管道,从读端流出,一般只能单向通信,双向通信需建立两个管道。
- 只能用于父子、兄弟进程(有共同祖先)间通信。
- 数据一旦被读走,便不在管道中存在,不可反复读取。 因此,管道的局限性表现得非常明显,它并不适合一对多的方式建立通讯(尽管技术上能够实现),原因在于第5条,管道中的数据无法反复读取。类似的还有FIFO(命名管道),它在管道的基础上做了升级,摆脱了第4条的共同祖先的限制,但仍要面临一对多通讯的困境。
framework中有没有使用Pipe进行通讯?答案是有,但是用的很少,相比之下用的更多的是FIFO!!各位读者如果感兴趣的话,可以在源码中搜一下 TransferPipe
这个类,在其中可以找到Pipe的痕迹。
Sign
信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。linux系统已经预置了一部分信号标识,它们都有着特殊的含义,部分信号如下所示:
- SIGHUP:本信号在用户终端结束时发出,通常是在终端的控制进程结束时,通知同一会话期内的各个作业,这时他们与控制终端不在关联。比如,登录Linux时,系统会自动分配给登录用户一个控制终端,在这个终端运行的所有程序,包括前台和后台进程组,一般都属于同一个会话。当用户退出时,所有进程组都将收到该信号,这个信号的默认操作是终止进程。此外对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
- SIGINT:程序终止信号。当用户按下CRTL+C时通知前台进程组终止进程。
- SIGQUIT:Ctrl+\控制,进程收到该信号退出时会产生core文件,类似于程序错误信号。
- SIGILL:执行了非法指令。通常是因为可执行文件本身出现错误,或者数据段、堆栈溢出时也有可能产生这个信号。
- SIGTRAP:由断点指令或其他陷进指令产生,由调试器使用。
- SIGABRT:调用abort函数产生,将会使程序非正常结束。
- SIGBUS:非法地址。包括内存地址对齐出错。比如访问一个4个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法地址的非法访问触发。
- SIGFPE:发生致命的算术运算错误。
- SIGKILL:用来立即结束程序的运行。不能被捕捉、阻塞或忽略,只能执行默认动作。
信号只能起到对进程的通知作用,它无法发送复杂的数据类型,不适合用于进程间的数据交换。
信号在整个framework中也扮演了极为重要的角色,各位读者可以通过搜索sigemptyset
、sigaddset
等关键字,在源码中找到它们的身影。
message queue
消息队列,Unix的通信机制之一,可以理解为是一个存放消息(数据)容器。将消息写入消息队列,然后再从消息队列中取消息,一般来说是先进先出的顺序。消息队列本质上是位于内核空间的链表,链表的每个节点都是一条消息。每一条消息都有自己的消息类型,消息类型用整数来表示,而且必须大于 0。每种类型的消息都被对应的链表所维护。
其中数字 1 表示类型为 1 的消息,数字2、3、4 类似。彩色块表示消息数据,它们被挂在对应类型的链表上。
消息队列的缺陷在于:容量受到系统限制;消息队列的发送方与接收方没有强关联性,容易造成发送方往消息队列中存放了消息,没有接收方来取消息或接收方没有及时取消息的问题,消息的及时性无法保障。
目前在Android 10的非内核源码范围内,没有发现使用消息队列。
shared memory
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
共享内存利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决,开发上手难度较高,容易出错,且存在数据安全隐患。
目前在Android 10的非内核源码范围内,没有发现使用共享内存。
Socket
Socket这个不需要特别介绍了,不管是做C++开发还是Java开发,都会涉及到套接字编程。相对其他的IPC方式,Socket是最适合做一对多这种通讯需求的。它的问题在于数据需要经过两次拷贝,通讯效率相对低下。这个问题在电脑等设备上都不是什么特别大的问题,但考虑到Android搭载的移动设备,尤其是早期的移动设备,这个问题就很致命了。
framework中当然也存在Socket的使用痕迹,比如 system/core/init/init.cpp
这个文件中就采用epoll机制,实现init进程与其子进程的通讯。
Android更看重的是效率和一对多通讯的问题,无法采用传统的IPC工具实现,所以只能考虑自己另起炉灶。除此之外,传统的IPC无法获得对方进程的PID\UID,从而无法鉴别对象的身份,从而会使Android系统的安全性无法得到保证(ps:无法获得对方进程的身份指的是Linux默认没有提供获取通讯进程的身份的接口,并不是说采用传统IPC没有办法实现这样的安全管控需求,只是谷歌在综合考虑了上述所有的因素的情况下,在共享内存的基础上做了一套新的解决方案)。
这里给各位读者留个思考题,有兴趣的读者可以自己动手去实验一下:
在一对多通讯的场景下,Binder的传输效率一定会比Socket高吗?(提示:Socket包括BIO、NIO、NIO2、epoll等,请不要局限在BIO的通讯方式)
AIDL
AIDL 是 Android interface definition Language 的英文缩写, 意思Android 接口定义语言,它与Binder有着千丝万缕的联系。
AIDL是谷歌使用Java 编程语言的语法定义的专门服务于Binder IPC通讯的脚本语言,推出的根本原因是为了避免Binder通讯中大量模板代码的书写。AIDL脚本会在编译期间,由Android SDK 工具生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 generated/ 目录中。
我们来看一个简单的aidl文件:
packageackage com.example.commonservice;
// Declare any non-default types here with import statements
interface ITtsService {
void showTts(in String uid,in int textId,in boolean toPlayTts,in int type);
boolean isShowing();
}
它生成的java文件如下所示:
package com.example.commonservice;
// Declare any non-default types here with import statements
public interface ITtsService extends android.os.IInterface
{
public static class Default implements com.example.commonservice.ITtsService
{
@Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
{
}
@Override public boolean isShowing() throws android.os.RemoteException
{
return false;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static final java.lang.String DESCRIPTOR = "com.example.commonservice.ITtsService";
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
public static com.example.commonservice.ITtsService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.commonservice.ITtsService))) {
return ((com.example.commonservice.ITtsService)iin);
}
return new com.example.commonservice.ITtsService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_showTts:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
boolean _arg2;
_arg2 = (0!=data.readInt());
int _arg3;
_arg3 = data.readInt();
this.showTts(_arg0, _arg1, _arg2, _arg3);
reply.writeNoException();
return true;
}
case TRANSACTION_isShowing:
{
data.enforceInterface(descriptor);
boolean _result = this.isShowing();
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.commonservice.ITtsService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(uid);
_data.writeInt(textId);
_data.writeInt(((toPlayTts)?(1):(0)));
_data.writeInt(type);
boolean _status = mRemote.transact(Stub.TRANSACTION_showTts, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().showTts(uid, textId, toPlayTts, type);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public boolean isShowing() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_isShowing, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().isShowing();
}
_reply.readException();
_result = (0!=_reply.readInt());
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.commonservice.ITtsService sDefaultImpl;
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.example.commonservice.ITtsService impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.commonservice.ITtsService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}
虽然是简短的一个aidl文件,但生成的模板代码却极为复杂,为了整理清楚这段代码的结构,笔者先隐藏其部分内容:
public interface ITtsService extends android.os.IInterface
{
public static class Default implements com.example.commonservice.ITtsService
{
}
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static class Proxy implements com.example.commonservice.ITtsService
{
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}
可以看到,代码的结构如同套娃一样,一层接一层。各位读者不妨思考一下,如果不这样套娃,把ITtsService
里的Default
和Stub
类移到外面来,这样做可不可以?
答案是,可以的,不过类的命名方式可能要稍微做一下修改,如ITtsService_Defalut
、ITtsService_Stub
,以便于引用上的区分。当然,代码的结构不是重点,虽然谷歌的方式可读性会差一点,但开发人员不需要直接和这些源码打交道,也不是不可以接受。
Binder不一定都是跨进程通讯,同样也支持同进程通讯,比如 Activity绑定Service,通过Binder实现数据传输。在同一进程通讯的情况下,Stub
类身兼两职,因其implements了ITtsService
,它可以作为客户端的调用方;同时,它也是服务端的实现方。而在跨进程通讯的情况下,则由Proxy
来担任客户端的调用方。
至于Default
这个类的作用,暂时不明,无法找到相关的资料得知为什么谷歌要生成这么一个类。
在此,AIDL的介绍先告一段落,其中更多的细节将放到后续的文章中再做补充。
HIDL
HIDL的生命周期及其短暂,它从Android 8引入,然后在Android 10 立马被 Stable AIDL 所取代,虽然没啥存在感,但还是简单地提及一下吧。
HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是指用于在可以独立编译的代码库之间进行通信的系统。
HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。
更多HIDL相关的资料,可以参考 source.android.google.cn/docs/core/a…
以上就是Android10 Binder原理概述深入解析的详细内容,更多关于Android10 Binder原理的资料请关注编程网其它相关文章!