Android IPC 之Binder分析

      接触AIDL有很长时间了,对进程间通信一直没有一个全面深入的了解,最近学习了一下Binder,根据自己的理解和参考网络上的大神写的文章,现将其整理一下,以便于后面学习。

一.Binder是什么

      Binder就是Android中的血管,遍布与Android中的任何地方,在Android中所用的Activity、Service等组件都需要和AMS(system_server)通信,这种跨进程的通信都是通过Binder完成。
      Binder是Android系统进程间通信(IPC)方式之一,根据不同的场景,对其定义不同。

场景 从机制、模型角度 从模型的结构、组成 从Android代码实现
定义 一种Android中实现IPC的方式
即Binder机制模型
一种虚拟的物理设备驱动
即Binder驱动
一个类,实现了IBinder接口
即Binder类
作用 在Android中实现跨进程通信 连接server进程、client进程、servermanager进程 将Binder机制以代码的形式具体实现在Android中

二.基础知识

      a.一个进程空间分为用户空间 & 内核空间(Kernel),即把进程内用户&内核隔离开来;
      二者区别:
      进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
      进程间,内核空间的数据可共享,所以内核空间 = 可共享空间
      所有进程共用1个内核空间
      进程内用户空间&内核空间进行交互需通过系统调用,两者之间传递数据需要进行数据拷贝,主要通过函数:
      copy_from_user():将用户空间的数据拷贝到内核空间
      copy_to_user():将内核空间的数据拷贝到用户空间
      b.为了保证 安全性 & 独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的。
      c.内存映射(mmap):mmap() 是操作系统中一种内存映射的方法,可以将两个虚拟内存地址空间(不同进程)映射到同一物理内存段上。实现多进程(或者内核与进程)之间共用一块内存,减少数据拷贝次数,内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
      内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

三.Binder通信模型

a.模型组成

      Binder框架定义了四个角色:Server进程、Client进程、ServiceManager进程以及Binder驱动。

角色 作用 备注
Server进程 提供服务的进程 服务端
Client进程 使用服务的进程 客户端
servicemanager进程 管理service的注册与查询(将字符形式的Binder名字转化成Client中对该Binder的引用) 类似路由器
Binder驱动 一种虚拟设备驱动,连接client进程、service进程、servicemanager进程
具体作用为:
1.传递服务进程信息
2.传递进程间需要的数据,通过内存映射
3.实现线程控制,使用Binder线程池,并用Binder驱动进行管理
Binder驱动持有每个service在内核中的Binder实体,并给Client进程提供Binder实体的引用
b.通信原理

      Binder有驱动程序,所有数据传输和接收,都是通过Binder驱动来操作的。
      数据接收进程,内核与用户内存空间,通过mmap映射到了同一块物理内存上。也就是说对该块物理内存的修改,将会体现到数据接收进程的用户空间和内核空间。
      数据发送进程,只是将内核的内存空间映射到了物理内存上。
      当数据发送进程需要向数据接收进程传递数据时,数据只需要从数据发送进程的用户内存空间执行copy_from_user()将数据拷贝到发送进程的内核内存空间,因为数据发送进程的内核内存空间与物理内存进行了映射,而数据接收进程的用户内存空间与内核内存空间同时都映射到了同一块物理内存上,所以此次拷贝,直接将数据发送进程的用户空间数据,拷贝到了数据接收进程的用户内存空间,这样便完成了一次通信。


binder-ipc数据传输图.jpg
c.通信流程

      1.首先一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
      2.Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManager 将其填入查找表。
      3.Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。


Binder通信流程.jpg
d.交互图
android-binder机制.jpeg
e.驱动内部图
binder引用及实体对象.png

四.Binder机制在Android中的具体实现

a.注册服务

      过程描述:Server进程 通过Binder驱动 向 Service Manager进程 注册服务
      代码实现:Server进程 创建 一个 Binder 对象
      1.Binder 实体是 Server进程 在 Binder 驱动中的存在形式
      2.该对象保存 Server 和 ServiceManager 的信息(保存在内核空间中)
      3.Binder 驱动通过 内核空间的Binder 实体 找到用户空间的Server对象

public class DataService extends Service {
    private static final String TAG = DataService.class.getSimpleName();
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return Service.START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        FLog.d(TAG, "data service is bound");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private IData.Stub mBinder = new IData.Stub() {
        @Override
        public int getSharedDisplayNum(int currDisplay, int appType) {
            if (mService != null) {
                return mService.getInSharedDisplayNum(currDisplay, appType);
            }
            return 0;
        }
    };
}

IData.aidl如下:

interface IFlowDropData {
    int getSharedDisplayNum(int currDisplay, int appType);
}

接着说一下在AIDL文件中支持的数据类型包括:
      1.基本数据类型
      2.String和CharSequence
      3.List:只支持ArrayList,里面的元素都必须被AIDL支持
      4.Map:只支持HashMap,里面的元素必须被AIDL 支持
      5.实现Parcelable接口的对象
      6.所有AIDL接口

b.获取服务
过程 代码实现 备注
1.Client向Binder驱动发起获取服务的请求,并传递要获取的服务名称 bindService()
2.Binder驱动将请求转发给servicemanager进程
3.servicemanager进程将client进程需要的server对应的Binder实体的Binder引用返回给Binder驱动
调用server进程的onBind()方法返回IBinder
4.Binder驱动将Binder代理对象返回给Client进程 client进程通过onServiceConnected()活动server进程Binder对象的代理对象BinderProxy 1.Client进程获得是Binder代理对象
2.Client进程操作的是server的Binder代理对象,代理对象通过Binder驱动最终让Binder对象来执行操作

      根据AIDL生产的文件可以看到:

public interface IData extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    //Stub继承了Binder,实现了IData接口,内部的方法需要在Service创建Stub时实现
    public static abstract class Stub extends android.os.Binder implements com.hly.learn.IData {
        public static com.hly.learn.IData asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
             //去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个binder本身就是 Binder本地对象,可以直接使用
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof com.hly.learn.IData))) {
                return ((com.hly.learn.IData)iin);
             }
            //否则返回的是实现了IData接口的Proxy对象,但不是Binder
            return new com.hly.learn.IData.Stub.Proxy(obj);
       }
       @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        //client调用后,server端需要的逻辑处理
        ........
        }
        //代理对象是IData,不是Binder
        private static class Proxy implements com.hly.learn.IData {
             private android.os.IBinder mRemote;
             //client获取到这个代理对象时,传参是Binder引用,最终是通过该引用来与server进行通信
             Proxy (android.os.IBinder remote) {
                   mRemote = remote;
             }
             .......
             //由于是实现了IData接口,需要实现其方法
            @Override public int getSharedDisplayNum(int currDisplay, int appType) throws   android.os.RemoteException {
            ........
            }
    }
    //本地Stub需要实现的方法
    public int getSharedDisplayNum(int currDisplay, int appType) throws android.os.RemoteException;
}

      Stub继承了Binder,实现了IData接口,asInterface()根据是否是同一个进程返回本地对象还是代理对象;代理对象类Proxy实现了IData,不是Binder;

c.使用服务

      Client端调用Server端的方法

@Override public int getSharedDisplayNum(int currDisplay, int appType) throws  android.os.RemoteException {
    //Client进程创建传输数据的Parcel对象
    android.os.Parcel _data = android.os.Parcel.obtain();
    //client进程创建返回数据的Parcel对象,目标方法执行后的结果
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //1.将需要传送的数据写入到Parcel对象中
        _data.writeInt(currDisplay);
        _data.writeInt(appType);
        //2.调用mRemote[Binder引用]的transact()将上述数据发送到Binder驱动
        mRemote.transact(Stub.TRANSACTION_getSharedDisplayNum, _data, _reply, 0);
        //在发送数据后,Client进程的该线程会暂时被挂起
        // 所以,若Server进程执行的耗时操作,请不要使用主线程,以防止ANR
        // 3. Binder驱动根据 mRemote 找到对应的真身Binder对象所在的Server 进程(系统自动执行)
        // 4. Binder驱动把数据发送到Server 进程中,并通知Server进程执行解包(系统自动执行)
        _reply.readException();
        _result = _reply.readInt();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
static final int TRANSACTION_getSharedDisplayNum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);

      Server端执行处理逻辑

// 1. 收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法
@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_getSharedDisplayNum:{
         // 解包Parcel中的数据
        data.enforceInterface(descriptor);
        // 获得目标方法的参数
        int _arg0;
        _arg0 = data.readInt();
        int _arg1;
        _arg1 = data.readInt();
        //调用本地已实现的方法
        int _result = this.getSharedDisplayNum(_arg0, _arg1);
        reply.writeNoException();
        //返回结果
        reply.writeInt(_result);
        return true;
    }
    default:{
        return super.onTransact(code, data, reply, flags);
    }
}

      Server进程将结果返回client进程

    mRemote.transact(Stub.TRANSACTION_getSharedDisplayNum, _data, _reply, 0);
        // 1. Binder驱动根据 代理对象 沿原路 将结果返回 并通知Client进程获取返回结果
        // 2. 通过代理对象接收结果(之前被挂起的线程被唤醒)
        _reply.readException();
        _result = _reply.readInt();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;

      bindService()详细流程可参考Android 进程通信bindService详解
进程间通信的过程如下:
      1.运行在Client进程中的Binder代理对象通过Binder驱动程序向运行在Server进程中的Binder本地对象发出一个进程间通信请求,Binder驱动程序接着就根据Client进程传递过来的Binder代理对象的句柄值来找到对应的Binder引用对象。
      2.Binder驱动程序根据前面找到的Binder引用对象找到对应的Binder实体对象,并且创建一个事务(binder_transaction)来描述该次进程间通信过程。
      3.Binder驱动程序根据前面找到的Binder实体对象来找到运行在Server进程中的Binder本地对象,并且将Client进程传递过来的通信数据发送给它处理。
      4.Binder本地对象处理完成Client进程的通信请求之后,就将通信结果返回给Binder驱动程序,Binder驱动程序接着就找到前面所创建的一个事务。
      5.Binder驱动程序根据前面找到的事务的相关属性来找到发出通信请求的Client进程,并且通知Client进程将通信结果返回给对应的Binder代理对象处理。
      从这个过程就可以看出,代理对象依赖于Binder引用对象,而Binder引用对象又依赖于Binder实体对象,最后,Binder实体对象又依赖于Binder本地对象。

五.Binder进程间通信优点

高效 安全行高 使用简单
数据拷贝只需一次
管道,消息队列,socket需要两次
通过驱动在内核空间拷贝数据,不需要额外的同步处理
Binder机制为每个进程分配了UID/PID来作为鉴别身份的标示,通信时会进行有效性检测 采用client-server架构,实现面向对象的调用方式,即使在使用binder时,跟调用本地对象一样

      与传统IPC对比

Binder 共享内存 Socket
性能 需要拷贝一次 无需拷贝 需要拷贝两次
特点 基于C/S架构,易用性高 控制复杂,易用性差 基于C/S架构,作为一款通用接口,其传输效率低,开销大
安全性 为每个App分配UID,同时支持实名和匿名 依赖上层协议,访问接入点是开放的,不安全 依赖上层协议,访问接入点是开放的,不安全

六.ServiceManager

      Binder进程间通信除了bindService之外,还可以通过ServiceManager.addService(String name, IBinder service)来注册,通过ServiceManager.getService(String name)来获取IBinder。
      通过类来一步一步看调用逻辑吧

public final class ServiceManager {
    private static IServiceManager sServiceManager;
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                //最终调用的是ServiceManagerProxy里面的getService
                return Binder.allowBlocking(getIServiceManager().getService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    public static void addService(String name, IBinder service) {
        try {
            //最终调用的是ServiceManagerProxy里面的addService
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

      上述在得到sServiceManager时,传递了BinderInternal.getContextObject()的参数,发现getContextObject()是native方法,那么会通过jni调用底层c/c++文件返回IBinder代理对象

public class BinderInternal {
    ......
    public static final native IBinder getContextObject();
   ......
}
public abstract class ServiceManagerNative extends Binder implements IServiceManager{
    /**
     * Cast a Binder object into a service manager interface, generating
     * a proxy if needed.
     */
    static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        //返回null
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        //返回的是ServiceManagerProxy
        return new ServiceManagerProxy(obj);
    }

class ServiceManagerProxy implements IServiceManager {
    public ServiceManagerProxy(IBinder remote) {
        //mRemote是从底层返回的代理对象
        mRemote = remote;
    }

    public IBinder asBinder() {
        return mRemote;
    }

    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

    public void addService(String name, IBinder service, boolean allowIsolated)
            throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        data.writeStrongBinder(service);
        data.writeInt(allowIsolated ? 1 : 0);
        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
        reply.recycle();
        data.recycle();
    }

      通过以上实现最终可以发现,通过addService也是通过Binder驱动在内核注册一个Binder实体,然后在servicemanager中管理Binder实体对象对应的应用,在getService时,会返回Binder实体对象对应的应用。
      ServiceManager.java是对底层service_manager.c的封装,最终的执行逻辑入口在frameworks/native/cmds/servicemanager/service_manager.c中:

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }
    //打开binder驱动,最终调用在binder.c中
    bs = binder_open(driver, 128*1024);
    //将自己注册为Binder驱动的大管家(其他进程根据引用编号0可以找到SM对应的Binder实体)
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    ......
    svcmgr_handle = svcmgr;
    //进入循环,不断从Binder驱动中读取消息(无消息被阻塞)
    //读取到消息之后处理消息
    //不断循环,永不退出
    binder_loop(bs, svcmgr_handler);
    return 0;
}
.......

      通过以下方法get及add服务端的service。

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply) {
    switch(txn->code) {
        //从servicemanager中国获取service对应的binder引用
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
            if (!handle)
                break;
            bio_put_ref(reply, handle);
            return 0;
        //将service的binder实体对应的binder引用加入servicemanager中
        case SVC_MGR_ADD_SERVICE:
            handle = bio_get_ref(msg);
            allow_isolated = bio_get_uint32(msg) ? 1 : 0;
            if (do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid))
                return -1;
            break;
}

      简单总结一下ServiceManager在启动时做的工作:


servicemanager启动.png

      binder_open打开bind驱动,并且分配128K大小。
      具体的相关操作在frameworks/native/cmds/servicemanager/binder.c中:

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    ......
    bs = malloc(sizeof(*bs));
    ......
    //打开binder驱动
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    //分配内存空间
    bs->mapsize = mapsize;
    ......
    //内存映射
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    .......
}
//注册成为0号引用
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

      通过frameworks/native/cmds/servicemanager/下的Android.bp可以看到,servicemanager最后编译为可执行的二进制程序,在system/core/rootdir/init.rc中启动:

cc_binary {
    name: "servicemanager",
    defaults: ["servicemanager_flags"],
    srcs: [
        "service_manager.c",
        "binder.c",
    ],
    shared_libs: ["libcutils", "libselinux"],
    init_rc: ["servicemanager.rc"],
}

      本文在写作过程中参考了很多文章和源码,其中有很多描述和图片都借鉴了下面的文章,在这里感谢大佬们的无私分享!
      https://www.jianshu.com/p/4ee3fd07da14
      https://blog.csdn.net/a740169405/article/details/94454223
      https://zhuanlan.zhihu.com/p/35519585

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容