Android 应用工程师的 Binder 原理剖析(二)如何设计一个binder

1.Binder的设计方案

在理解Binder架构前,我们来考虑下,如果是你,该如何设计一个Binder的进程间通信机制。

要实现一个IPC通信那么需要几个核心要素:

1)发起端:肯定包括发起端所从属的进程,以及实际执行传输动作的线程

2)接收端:接收发送端的数据。

3)待传输的数据

4)内存映射,内核态

首先先画一个最简单的IPC通信图:

进程Process1和进程Process2 通过IPC通信机制进行通信。

再进行扩展调整,把IPC机制换成Binder机制,那么就变成如下的图形:

由于Android存在进程隔离,那么两个进程之间是不能直接传输数据的,Process1需要得到Process2的代理,Process2需要一个实体。

为了实现RPC,我们的代理都是提供接口,称为"接口代理",实体需要提供"接口实体",如下图所示:

我们把代理改成BpBinder,实体改成BBinder,接口代理改成BpInterface,接口实现体改成BnInterface。

我们都知道两个进程的数据共享,需要陷入内核态,那就需要一个驱动设备"/dev/binder",同时需要一个守护进行来进行service管理,我们成为ServiceManager。

进一步演变为:

假如我们想要把通过Process1 的微信信息发送给Process2的微信,我们需要做下面几步:

0)Process2在腾讯服务器中进行注册(包括微信名称、当前活动的IP地址等)

1)Process1从朋友列表中中查找到 Process2的名称,这就是Process2的别名:"service_name"

2)Process1 编写消息消息内容,点击发送。 这里的消息内容就是IPC数据

3)数据会发送到腾讯的服务器,服务器理解为Binder驱动

4)服务器从数据库中解析出IPC数据,找到Process2信息,转到Process2注册的地址, 数据库理解为ServiceManager

5)把数据发给Process2,完成Process1和Process2的通信

我们可以简单的把上面的顺序内容进行转换:

1)Binder驱动---腾讯服务器

2)数据库--ServiceManager

3)Service_name: Process2的微信名称

4)IPC数据:Process1 发送的微信消息

Native C/C++和内核进行通信需要通过系统调用,ServiecManager的主要用来对Service管理,提供了add\find\list等操作。Native进程的数据直接可以通过系统调用陷入内核态,进入图像转换,变为如下:

上面列举的是Native C/C++空间的进程进行Binder通信机制,那么JAVA层是如何通信的呢,Native层的Binder提供的是libbinder.so,那么从JAVA到Native,需要经过JNI、Framework层的封装,

JNI层的命名通常为android_util_xxx,我们这里是binder机制,那么JNI层的文件为 android_util_binder,同时Native的BBinder不能直接传给JAVA层,在JNI里面转换了一个JavaBBinder对象。

Framework层给应用层提供时,其实提供的也是一个代理,我们也称之为BinderProxy。

在JAVA侧要对应一个Binder的实体,称之为Binder。

JAVA侧的服务进行也需要一个管理者,类似于Native,创建了JAVA的ServiceManager,那么设计如下:

2.Binder何时初始化

我们已经讲了这么多关于binder的通信的案例了,那么Binder到底是在什么时候初始化呢?

Binder初始化一般是指binder驱动的初始化,大家在使用binder的过程中,我们从来没有执行过new Binder的方式来实现Binder初始化,原因很简单:binder初始化有它自身独立的特点。

每一个应用进程启动的时候,都是通过zygote fork产生的,所以,当fork产生进程后app进程的代码就开始执行,就开始运行的地方如下:

arduino 复制代码
public static final Runnable zygoteInit(int targetSdkVersion,
                                          long[] disabledCompatChanges,
                                          String[] argv, ClassLoader classLoader) {
      if (RuntimeInit.DEBUG) {
          Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
      }

      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
      RuntimeInit.redirectLogStreams();

      RuntimeInit.commonInit();//初始化运行环境
      ZygoteInit.nativeZygoteInit();//启动Binder ,方法在 androidRuntime.cpp中注册
      // 通过反射创建程序入口函数的 Method 对象,并返回 Runnable 对象
      //ActivityThread.main();
      return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
              classLoader);
  }

大家可以看到,会执行ZygoteInit.nativeZygoteInit()函数,而nativeZygoteInit函数执行appRuntime的onZygoteInit代码,也就是App_main.cpp中的 onZygoteInit()函数,函数如下:

scss 复制代码
virtual void onZygoteInit()
{
  sp<ProcessState> proc = ProcessState::self();
  ALOGV("App process: starting thread pool.\n");
  proc->startThreadPool();
}

在ProcessState的self函数里面就会初始化ProcessState(),而这个初始化的一个非常重要的动作就是启动binder驱动和并构建binder的Map映射。具体代码如下:

scss 复制代码
ProcessState::ProcessState(const char *driver)
  : mDriverName(String8(driver))
  , mDriverFD(open_driver(driver)) //打开binder的虚拟驱动
  , mVMStart(MAP_FAILED)
  , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
  , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
  , mExecutingThreadsCount(0)
  , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
  , mStarvationStartTimeMs(0)
  , mBinderContextCheckFunc(nullptr)
  , mBinderContextUserData(nullptr)
  , mThreadPoolStarted(false)
  , mThreadPoolSeq(1)
  , mCallRestriction(CallRestriction::NONE)
{

// TODO(b/139016109): enforce in build system
#if defined(__ANDROID_APEX__)
   LOG_ALWAYS_FATAL("Cannot use libbinder in APEX (only system.img libbinder) since it is not stable.");
#endif

   if (mDriverFD >= 0) {

//调用mmap接口向Binder驱动中申请内核空间的内存
       // mmap the binder, providing a chunk of virtual address space to receive transactions.
       mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
       if (mVMStart == MAP_FAILED) {
           // *sigh*
           ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
           close(mDriverFD);
           mDriverFD = -1;### 1.Binder的设计方案

在理解Binder架构前,我们来考虑下,如果是你,该如何设计一个Binder的进程间通信机制。

要实现一个IPC通信那么需要几个核心要素:

1)发起端:肯定包括发起端所从属的进程,以及实际执行传输动作的线程

2)接收端:接收发送端的数据。

3)待传输的数据

4)内存映射,内核态

首先先画一个最简单的IPC通信图:

![1.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/67cf292805cf4d78ad91a76b3394c904~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=720&h=514&s=31134&e=jpg&b=fefefe)

进程Process1和进程Process2 通过IPC通信机制进行通信。

再进行扩展调整,把IPC机制换成Binder机制,那么就变成如下的图形:

![2.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/512c70f9d16b466ea54792509a1d9689~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=290&h=142&s=5844&e=jpg&b=fefefe)

由于Android存在进程隔离,那么两个进程之间是不能直接传输数据的,Process1需要得到Process2的代理,Process2需要一个实体。

![2.png]()

为了实现RPC,我们的代理都是提供接口,称为"接口代理",实体需要提供"接口实体",如下图所示:

![3.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3bd8ab751aca4353bf6e4e05a6d4b004~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=293&h=244&s=10955&e=jpg&b=fefefe)

我们把代理改成BpBinder,实体改成BBinder,接口代理改成BpInterface,接口实现体改成BnInterface。

我们都知道两个进程的数据共享,需要陷入内核态,那就需要一个驱动设备"/dev/binder",同时需要一个守护进行来进行service管理,我们成为ServiceManager。

进一步演变为:

![4.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea710668c1f44738a5588b0e2d731832~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=314&h=387&s=16740&e=jpg&b=fefefe)

假如我们想要把通过Process1 的微信信息发送给Process2的微信,我们需要做下面几步:

0)Process2在腾讯服务器中进行注册(包括微信名称、当前活动的IP地址等)

1)Process1从朋友列表中中查找到 Process2的名称,这就是Process2的别名:"service_name"

2)Process1 编写消息消息内容,点击发送。 这里的消息内容就是IPC数据

3)数据会发送到腾讯的服务器,服务器理解为Binder驱动

4)服务器从数据库中解析出IPC数据,找到Process2信息,转到Process2注册的地址, 数据库理解为ServiceManager

5)把数据发给Process2,完成Process1和Process2的通信

**我们可以简单的把上面的顺序内容进行转换:**

1)Binder驱动---腾讯服务器

2)数据库--ServiceManager

3)Service_name: Process2的微信名称

4)IPC数据:Process1 发送的微信消息

Native C/C++和内核进行通信需要通过系统调用,ServiecManager的主要用来对Service管理,提供了add\find\list等操作。Native进程的数据直接可以通过系统调用陷入内核态,进入图像转换,变为如下:

![5.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6ba190bb43c4739a2cc4cb269c62c7f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=493&h=404&s=26828&e=jpg&b=fefefe)

上面列举的是Native C/C++空间的进程进行Binder通信机制,那么JAVA层是如何通信的呢,Native层的Binder提供的是libbinder.so,那么从JAVA到Native,需要经过JNI、Framework层的封装,

JNI层的命名通常为android_util_xxx,我们这里是binder机制,那么JNI层的文件为 android_util_binder,同时Native的BBinder不能直接传给JAVA层,在JNI里面转换了一个JavaBBinder对象。

Framework层给应用层提供时,其实提供的也是一个代理,我们也称之为BinderProxy。

在JAVA侧要对应一个Binder的实体,称之为Binder。

JAVA侧的服务进行也需要一个管理者,类似于Native,创建了JAVA的ServiceManager,那么设计如下:

![6.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/484f2267e477410788c0f1f5daf55c4e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=543&h=499&s=40136&e=jpg&b=fdfdfd)

### []()2.Binder何时初始化

我们已经讲了这么多关于binder的通信的案例了,那么Binder到底是在什么时候初始化呢?

Binder初始化一般是指binder驱动的初始化,大家在使用binder的过程中,我们从来没有执行过new Binder的方式来实现Binder初始化,原因很简单:binder初始化有它自身独立的特点。

每一个应用进程启动的时候,都是通过zygote fork产生的,所以,当fork产生进程后app进程的代码就开始执行,就开始运行的地方如下:

public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); }

scss 复制代码
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
  RuntimeInit.redirectLogStreams();

  RuntimeInit.commonInit();//初始化运行环境
  ZygoteInit.nativeZygoteInit();//启动Binder ,方法在 androidRuntime.cpp中注册
  // 通过反射创建程序入口函数的 Method 对象,并返回 Runnable 对象
  //ActivityThread.main();
  return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
          classLoader);

}

scss 复制代码
大家可以看到,会执行ZygoteInit.nativeZygoteInit()函数,而nativeZygoteInit函数执行appRuntime的onZygoteInit代码,也就是App_main.cpp中的 onZygoteInit()函数,函数如下:

virtual void onZygoteInit() { sp proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); }

javascript 复制代码
在ProcessState的self函数里面就会初始化ProcessState(),而这个初始化的一个非常重要的动作就是启动binder驱动和并构建binder的Map映射。具体代码如下:

ProcessState::ProcessState(const char *driver) : mDriverName(String8(driver)) , mDriverFD(open_driver(driver)) //打开binder的虚拟驱动 , mVMStart(MAP_FAILED) , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER) , mThreadCountDecrement(PTHREAD_COND_INITIALIZER) , mExecutingThreadsCount(0) , mMaxThreads(DEFAULT_MAX_BINDER_THREADS) , mStarvationStartTimeMs(0) , mBinderContextCheckFunc(nullptr) , mBinderContextUserData(nullptr) , mThreadPoolStarted(false) , mThreadPoolSeq(1) , mCallRestriction(CallRestriction::NONE) {

// TODO(b/139016109): enforce in build system #if defined(ANDROID_APEX) LOG_ALWAYS_FATAL("Cannot use libbinder in APEX (only system.img libbinder) since it is not stable."); #endif

if (mDriverFD >= 0) {

//调用mmap接口向Binder驱动中申请内核空间的内存 // mmap the binder, providing a chunk of virtual address space to receive transactions. mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); if (mVMStart == MAP_FAILED) { // sigh ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str()); close(mDriverFD); mDriverFD = -1; mDriverName.clear(); } }

#ifdef ANDROID LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver '%s' could not be opened. Terminating.", driver); #endif }

arduino 复制代码
所以,总的来说,Binder的初始化是在进程已创建就完成了。创建进程后会第一时间为这个进程打开一个binder驱动,并调用mmap接口向Binder驱动中申请内核空间的内存。
           mDriverName.clear();
      }
  }

#ifdef __ANDROID__
   LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver '%s' could not be opened. Terminating.", driver);
#endif
}

所以,总的来说,Binder的初始化是在进程已创建就完成了。创建进程后会第一时间为这个进程打开一个binder驱动,并调用mmap接口向Binder驱动中申请内核空间的内存。

今日分享到此结束,对你有帮助的话,点个赞再走呗,如遇侵权联系删除
关注公众号:Android老皮

解锁 《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路

相关推荐
约翰先森不喝酒6 小时前
Android RecyclerView 实现 GridView ,并实现点击效果及方向位置的显示
android
wk灬丨7 小时前
Android Choreographer 监控应用 FPS
android·kotlin
大胃粥7 小时前
Android U WMS : Activity 冷启动(2) 添加启动窗口
android
魏大橙8 小时前
长亭WAF绕过测试
android·运维·服务器
志尊宝8 小时前
Android 中使用高德地图实现根据经纬度信息画出轨迹、设置缩放倍数并定位到轨迹路线的方法
android
吾爱星辰8 小时前
Kotlin while 和 for 循环(九)
android·开发语言·kotlin
我命由我123458 小时前
Kotlin 极简小抄 P3(函数、函数赋值给变量)
android·开发语言·java-ee·kotlin·android studio·学习方法·android-studio
大风起兮云飞扬丶10 小时前
Android——内部/外部存储
android
niurenwo11 小时前
Android深入理解包管理--记录存储模块
android
文 丰11 小时前
【Android Studio】使用雷电模拟器调试
android·ide·android studio