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零基础入门到精通,高手进阶之路

相关推荐
simplepeng3 小时前
Room 3.0 KMP Alpha-01
android·kotlin·android jetpack
Lei活在当下3 小时前
Windows 下 Codex 高效工作流最佳实践
android·openai·ai编程
fatiaozhang95273 小时前
基于slimBOXtv 9.19.0 v4(通刷晶晨S905L3A/L3AB芯片)ATV-安卓9-完美版线刷固件包
android·电视盒子·刷机固件·机顶盒刷机·晶晨s905l3ab·晶晨s905l3a
私房菜4 小时前
Selinux 及在Android 的使用详解
android·selinux·sepolicy
一只特立独行的Yang4 小时前
Android中的系统级共享库
android
两个人的幸福online5 小时前
php开发者 需要 协程吗
android·开发语言·php
修炼者6 小时前
WindowManager(WMS)构建全局悬浮窗
android
xiaoshiquan12067 小时前
Android Studio里,SDK Manager显示不全问题
android·ide·android studio
Lstone73647 小时前
Bitmap深入分析(一)
android
一起搞IT吧8 小时前
Android功耗系列专题理论之十四:Sensor功耗问题分析方法
android·c++·智能手机·性能优化