Binder原理(三)之通信内核

118 阅读41分钟

大纲:

1.了解Binder的概念

2.Binder通信核心-拷贝,是如何拷贝的。

围绕了解 Native层源码 binder驱动层 源码

Android 为什么不用 linux 已有的 IPC 通信?

Android Binder 通信一次拷贝的原理_binder一次拷贝原理-CSDN博客

[Binder 一]Binder浅析

传统内核 IPC 通信

传统内核划分

在 linux 中,各进程 APP 的代码运行在用户空间,而内核层代码运行在内核空间。

传统内存调用

此时我们通过 Linux 系统调用进行内存调用:copy_from_user() 是一个 Linux 函数,作用是将数据从用户空间拷贝到内核空间;copy_to_user() 则是将数据从内核空间拷贝到用户空间。

所以当两个进程进行 IPC 通信时,流程如下:

  • 对于消息的发送端进程:
    • 通常传统的 IPC 通信(Socket,管道,消息队列)首先将消息发送方将要发送的数据存放在内核缓存区中。
    • 然后通过系统调用进入内核态。然后操作系统为 Linux 发送方进程在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。
  • 对于消息的接收端进程:
    • 首先在进行接收数据时在自己的用户空间开辟一块内存缓存区。
    • 然后操作系统为 Linux 接收方进程在内核空间中调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。

我们不难发现 Linux 传统跨进程 IPC 通信模型的缺点:

  1. 传统 IPC 通信模型传输效率比较低,拷贝次数较多,需要两次(这个仅是对于 Binder 和匿名共享内存而言),第一次是从发送方用户空间拷贝到内核缓存区,第二次是从内核缓存区拷贝到接收方用户空间。
  2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

Binder 概念

假设一个送快递的场景,客户 张三寄快递给到客户李四:那么张三要将苹果给到快递公司,之后快递公司再将苹果给到李四,这样就涉及到了两次拷贝。

但是在 Binder 呢,它就不是这样做的;首先由快递公司在李四家安装一个快递柜,这个快递柜属于快递公司,但是李四可以访问这个快递柜(快递柜是快递公司的,但是安装在李四家,两者都有访问权)。那么张三把苹果给到快递员(这里一次拷贝),快递员发现李四家有快递柜,那么直接将苹果放到快递柜(由于快递柜是快递公司的,这里不存在拷贝),而因为快递柜是归李四家里的,归李四管理,所以李四随用随取,不需要额外拷贝,本身就在自己家。所以一共就是一次拷贝。

Linux 中内存映射概念

映射主要是指内核将发送端的共享内存区域映射到接收端的地址空间中,这样接收进程可以直接访问发送进程的数据,而不需要拷贝数据。

内存映射是系统调用函数 mmap() 的中文翻译,其本质是一种进程虚拟内存的映射方法,它可以将一个文件、一段物理内存或者其它对象(也包括内核空间)映射到进程的虚拟内存地址空间。

实现这样的映射关系后,进程就可以采用指针的方式来读写操作这一段内存,进而完成对文件(或者其它被映射的对象)的操作,而不必再调用 read/write 等系统调用函数了。

所以正是因为有如上的特点内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。当映射成功以后,两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。其模型如下所示:

Bindert 通信流程

那么在 Binder 中,是怎样设计的呢?

一次完整的Binder IPC 通信过程通常是这样:

  • Binder 驱动在内核空间创建一个数据接收缓存区
  • 然后在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;
  • 发送方进程通过系统调用 copy_from_user() 将数据拷贝到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

内核缓存区(Kernel Buffer)

内核缓存区是 Binder 驱动在内核空间中用于存储数据的缓冲区。它负责在发送进程接收进程之间中转数据。

  • 当发送进程通过 Binder 向接收进程发送数据时,发送的数据首先会被复制到 Binder 驱动的内核缓存区中(通过 copy_from_user() 系统调用,将用户空间数据复制到内核空间)。
  • 内核缓存区暂存这些数据,等待调度将数据传递给接收进程。
  • 内核缓存区的大小是有限的,通常每个进程最多可以使用 1MB 的缓存空间。如果数据量超过了这个限制,Binder 会拒绝传输,或者数据需要通过分块处理。

数据接收缓存区(Data Receive Buffer)

数据接收缓存区是接收进程用于接收 Binder 驱动传来的数据的缓冲区。这是接收进程的用户空间内存区域,它从内核缓存区获取数据,并用于处理数据。

  • 当内核缓存区中的数据准备好后,Binder 驱动会将这些数据传递到接收进程的数据接收缓存区中。
  • 数据传递通过 copy_to_user() 系统调用实现,即将内核空间中的数据复制到接收进程的用户空间内存。

Binder 跨进程通信核心

我们在上一章了解了 Binder 的概念,当客户端给服务端发送数据时,发送进程将数据拷贝到内核空间接收进程通过在内核空间的映射获取内核空间数据;我们下面就将这个过程逐步分析。

Binder接收进程与内核进程的映射

首先是 接收进程 与 内核进程之间的映射实现,

先看 Binder 源码的分层;

Binder 源码结构分层📎Binder4层源码.zip

Binder 分成四层结构,分别为:Binder Framework应用层、Binder JNI 层、Binder native 层、Binder 驱动层。而前面三层主要用于调用服务注册与查找。

来到 Android 系统源码,这里是 Android8.0 版本的:

  • framework 应用层:实现了 Java 的 Service。
  • jni 层:android_util_Binder.cpp 中调用 native 方法。
  • native 层:service_manager.cProcessState.cpp。
  • 驱动层:驱动层源码并不属于 Android 系统源码,Binder 驱动源码不属于系统源码的一部分。

Binder.xmind

为什么内核缓存区的大小限制是 1M-8K 呢?

在任何一个 APP 启动过程中,它们一定有进程通信的需求(如 Activity 之间的跳转也是进程通信);
所以 APP 在启动时,必须在内核空间注册一个内存大小为1M-8K 的内存。而 APP 是由 zygote 启动的,之后 zygote 会执行启动一个 ProcessState 对象,在这个ProcessState对象中有个宏定义限制了 Binder 内核缓存区的大小为 1M-8K

这个 1M-8K 是如何去用的呢?
它在 ProcessStated 中通过 mmap()函数调用 实现 映射内存:

那为什么是 1M-8K 呢?
比如 Http 请求时,比如携带数据是 1M,但真实数据通常大于 1M,因为还有请求头,而 1M-8K 则是 google 限制大小为 1M,但是因为请求头的存在需要保留 8K,而 8K 即两页,所以这两页用于存放请求头的数据(请求头必须包含目的进程)。

mmap()函数

参数说明

mmap 是一种内存映射文件的方法,它一共有六个参数,前面四个参数和 内存 相关后面两个参数和 磁盘 相关

mmap 函数的目的是开辟一段物理内存,与磁盘建立关系。

void* _Nonnull mmap(void* _Nullable __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset);
  • void* _Nullable __addr:映射区的开始地址,设置为 0 时表示由系统决定映射区的起始地址。如果指定了物理地址,那么则是将磁盘的内容覆盖到指定地址,否则由系统分配。
    size_t __size: 映射区的长度。//长度是以字节为单位,不足一页按一页内存处理。
  • int __prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过 or 运算合理地组合在一起:
    • PROT_EXEC:页内容可以被执行。通常用于加载可执行文件或共享库的代码段,这样映射到内存的代码可以直接被CPU执行。
    • ★ PROT_READ:页内容可以被读取。
    • ★ PROT_WRITE:页可以被写入。
    • PROT_NONE:页不可访问。
  • int __flags: 指定映射对象的类型,映射选项和映射页是否可以共享。他的值可以是一个或多个以下位的组合体:
    • MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
    • ★ MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    • ★ MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    • MAP_DENYWRITE //这个标志被忽略。
    • MAP_EXECUTABLE //同上
    • ★ MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
    • MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
    • MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    • MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联
    • MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
    • MAP_FILE //兼容标志,被忽略。
    • MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
    • MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
    • MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
  • int __fd: 有效的文件描述符。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
  • off_t __offset:被映射对象内容的起点。
返回值说明

成功执行时,mmap()返回被映射区的指针(返回虚拟地址) 。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值:

EACCES:访问出错

EAGAIN:文件已被锁定,或者太多的内存已被锁定

EBADF:fd不是有效的文件描述词

EINVAL:一个或者多个参数无效

ENFILE:已达到系统对打开文件的限制

ENODEV:指定文件所在的文件系统不支持内存映射

ENOMEM:内存不足,或者进程已超出最大内存映射数量

EPERM:权能不足,操作不允许

ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志

SIGSEGV:试着向只读区写入

SIGBUS:试着访问不属于进程的内存区

使用 mmap 函数

mmap 函数不仅 Android 系统可以实现,我们自己也可以去调用,它不是独有的;我们自己也可以调用 mmap 函数:首先创建 jni,导入sys/mman.h头文件:

//引入mmap的头文件mman.h,  mmap是系统函数
#include "sys/mman.h"

引入头文件后,我们也可以使用 mmap 函数实现内存映射:

#include <jni.h>
#include <string>
#include <fcntl.h>

//引入mmap的头文件  mmap是系统函数
#include "sys/mman.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_binder_MainActivity_callNativeMmap
(JNIEnv *env, jobject) {
    std::string file = "/sdcard/binder";
    int m_fd=open(file.c_str(),O_RDWR|O_CREAT,S_IRWXU);
    /**
     * mmap函数的做了如下事情:
     * setup 1.在物理内存中开辟了4096个字节的内存大小
     * setup 2.将物理内存与磁盘进行关联
     * setup 3.MMU将mmap开辟的物理内存地址,转换成虚拟地址
     */
    //mmap函数返回的是虚拟地址
    mmap(0,4096,PROT_READ | PROT_WRITE,MAP_SHARED,m_fd,0);

    //假设APP2想要与APP1通信,一次通信超过1M-8K的数据,可以通信吗? 
    // 答:不能,因为Binder中mmap函数创建的物理内存大小只有1M-8K
}

那么,Android 中,假设APP2想要与APP1通信,一次通信超过1M-8K的数据,可以通信吗?
答:不能,因为Binder 源码中调用 mmap函数创建的物理内存大小只有1M-8K。

共享内存方式,使用 mmap 实现跨进程通信

实现过程

我们打算使用两个 Acitivity,并为他们指定不同进程后,使他们进行跨进程通信。

首先创建一个 jni 工程,创建工具类进行加载 so 库与调用编写的 native 函数:

package com.example.binder

class XunuaBinder {
    init {
        System.loadLibrary("binder")
    }
    external fun write()
    external fun read():String
}
#include <jni.h>
#include <string>
#include <fcntl.h>

//引入mmap的头文件  mmap是系统函数
#include "sys/mman.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_example_binder_XunuaBinder_write(JNIEnv *env, jobject thiz) {

}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_binder_XunuaBinder_read(JNIEnv *env, jobject thiz) {

}

接着创建 MainActivity 与 SecondActivity,为它两指定不同的进程如果需要跨进程通信,必须显式地通过 android:process 属性将它们分配到不同的进程。这一步我就在此省略;

<activity android:name=".MyActivity"
          android:process=":my_process" />

我们接下来要通过 mmap 函数开辟一个 4096 的物理内存空间,并对应一个同样大小的磁盘内存区域;

所以我们需要先开辟一个同样为 4096 字节大小的磁盘文件:

    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    //指定磁盘文件大小为4096,填充0。
    ftruncate(m_fd,4096);

调用 mmap 进行映射:

    //物理内存与磁盘家里映射关系,void* m_ptr为虚拟地址。
    void* m_ptr = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);

拿到虚拟地址,修改内部的值为10:

    int8_t* m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
    //拿到虚拟地址,修改内部的值为10,将10存储到物理内存
    *m_ptr=10;

也可以传一个字符串:

    std::string data("你妈让我给你带句话");
    memcpy(reinterpret_cast<void *const>(*m_ptr), data.data(), data.size());
extern "C"
JNIEXPORT void JNICALL
Java_com_example_binder_XunuaBinder_write(JNIEnv *env, jobject thiz) {
    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    //指定磁盘文件大小为4096,填充0。
    ftruncate(m_fd,4096);
    //物理内存与磁盘家里映射关系,*m_ptr 为虚拟地址。
    int8_t* m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
//    //拿到虚拟地址,修改内部的值为10
//    *m_ptr=10;

    std::string data("你妈让我给你带句话");
    memcpy(reinterpret_cast<void *const>(*m_ptr), data.data(), data.size());
}

接下来我们开启一个进程 2,即 SecondActivity,那么我们就可以通过*m_ptr这个虚拟地址来读取到进程 1 在里面存储的内容;注意,不要想着把m_ptr作为 jni 层的全局变量,之后在读的时候访问全局变量 m_ptr;这样是不行的,因为跨进程通信!!进程 1 和进程 2 的 jni 层他们的m_ptr不是同一个!

所以我们该如何得到写入时的m_ptr指针呢?

此时借助 mmap,在读取时,我们只需要传入相同文件大小、相同文件路径、相同入参时;mmap 会自动分配之前已经重复申请过的映射地址,并将同一个虚拟地址返回,所以这里写法很简单,将做写入时映射的参数重新执行一遍 mmap 就可以得到同一个虚拟地址了:

    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    //指定磁盘文件大小为4096,填充0。
    //read时可以不调用ftruncate,调用ftruncate是为了保险起见
    ftruncate(m_fd,4096);
    //因为起始地址一致,大小一致,文件fd一致,所以mmap会返回上次映射时的同一个虚拟地址
    void* m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));

接下来读取虚拟地址的数据:

    //读取数据
    char* buf= static_cast<char *>(malloc(100));
    //拷贝前100个字节
    memcpy(buf,m_ptr,100);
    std::string result(buf);

同时使用 munmap 取消mmap 内存映射:

//关闭虚拟地址m_ptr的映射
munmap(m_ptr,4096);
//关闭文件描述符
close(m_fd);

将读取到的内容通过 jni 返回:

std::string result(buf);
//关闭文件描述符
close(m_fd);
return env->NewStringUTF(result.c_str());

Java 层在进程 1 发送消息:

XunuaBinder().write()

进程 2 接收读取消息:

val readMsg = XunuaBinder().read()
完整代码

完整 JNI 代码:

#include <jni.h>
#include <string>
#include <fcntl.h>
#include <unistd.h>
//引入mmap的头文件  mmap是系统函数
#include "sys/mman.h"
#include "android/log.h"

// 定义日志标签
#define LOG_TAG "NativeLog"

// 定义日志宏
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)


extern "C" JNIEXPORT jstring JNICALL
Java_com_example_binder_MainActivity_callNativeMmap
        (JNIEnv *env, jobject) {
    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    /**
     * mmap函数的做了如下事情:
     * setup 1.在物理内存中开辟了4096个字节的内存大小
     * setup 2.将物理内存与磁盘进行关联
     * setup 3.MMU将mmap开辟的物理内存地址,转换成虚拟地址
     */
    //mmap函数返回的是虚拟地址
    mmap(0, 4096, PROT_EXEC, MAP_SHARED, m_fd, 0);

    //假设APP2想要与APP1通信,一次通信超过1M-8K的数据,可以通信吗? 答:不能,因为Binder中mmap函数创建的物理内存大小只有1M-8K
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_binder_XunuaBinder_write(JNIEnv *env, jobject thiz) {
    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    //指定磁盘文件大小为4096,填充0。
    ftruncate(m_fd,4096);
    //物理内存与磁盘家里映射关系,*m_ptr 为虚拟地址。
    int8_t* m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
//    //拿到虚拟地址,修改内部的值为10
//    *m_ptr=10;
    LOGE("打印日志: %p    m_fd:%d   错误信息:%s",m_ptr,m_fd);

    std::string data("你妈让我给你带句话:你真棒");
    memcpy(m_ptr, data.data(), data.size());

//    添加异常判断优化后的代码
//    std::string file = "/sdcard/binder";
//    // 打开文件
//    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
//    if (m_fd == -1) {
//        LOGE("Failed to open file: %s", strerror(errno));
//        return;
//    }
//    // 指定磁盘文件大小为4096,并填充0
//    if (ftruncate(m_fd, 4096) == -1) {
//        LOGE("Failed to set file size: %s", strerror(errno));
//        close(m_fd);
//        return;
//    }
//    // 进行内存映射,使用 PROT_READ | PROT_WRITE
//    int8_t* m_ptr = static_cast<int8_t*>(mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
//    if (m_ptr == MAP_FAILED) {
//        LOGE("mmap failed: %s", strerror(errno));
//        close(m_fd);
//        return;
//    }
//
//    LOGE("打印日志: %p    m_fd: %d", m_ptr, m_fd);
//
//    // 拿到虚拟地址,写入数据
//    std::string data("你妈让我给你带句话");
//    memcpy(m_ptr, data.data(), data.size());
//
//    // 解除内存映射和关闭文件
//    if (munmap(m_ptr, 4096) == -1) {
//        LOGE("munmap failed: %s", strerror(errno));
//    }
//
//    close(m_fd);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_binder_XunuaBinder_read(JNIEnv *env, jobject thiz) {
    std::string file = "/sdcard/binder";
    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
    //指定磁盘文件大小为4096,填充0。
    ftruncate(m_fd,4096);
    //因为起始地址一致,大小一致,文件fd一致,所以mmap会返回上次映射时的同一个虚拟地址
    void* m_ptr = static_cast<int8_t *>(mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));

    //读取数据
    char* buf= static_cast<char *>(malloc(100));
    //拷贝前100个字节
    memcpy(buf,m_ptr,100);
    //关闭虚拟地址m_ptr的映射
    munmap(m_ptr,4096);

    std::string result(buf);
    //关闭文件描述符
    close(m_fd);
    return env->NewStringUTF(result.c_str());
}
package com.example.binder

class XunuaBinder {
    init {
        System.loadLibrary("binder")
    }
    external fun write()
    external fun read():String
}
总结

其实这种实现方式效率比 Binder 还要更快一些;原因主要是我们没有存在拷贝,而是直接操作物理内存:进程 A 将内容放入物理内存,进程 B 从物理内存中读取,完全没有发生拷贝。所以我们将这个叫做 共享内存方式实现跨线程通信。

服务端用户进程 与 内核进程之间的映射

我们刚才写的跨进程通信方案是基于共享内存来实现的,进程 A 和进程 B 共享同一块内存,所以它们通信不需要拷贝。

Binder 一端是跟内核进程的内存进行映射共享的(大小 1M-8K);比如进程 B 中有 server 端,那么进程 B 会在内核进程的内存中开辟一块内存(1M-8K),之后内核进程和用户 B 进程拿到的都是虚拟地址,即 内核空间和进程 B 进行 mmap 映射,这样一来进程 B 和内核空间的这一块内存就不会存在拷贝了(类似我们前面的写的 进程 A 和进程 B 基于 mmap 共享内存实现跨进程通信)。

Binder发送端进程与内核进程的拷贝

AIDL 与 Binder 的关系

AIDL (Android Interface Definition Language)Binder 是 Android 系统中用于实现跨进程通信(IPC)的两大核心机制,它们是相辅相成的;

Binder 是基础通信机制

  • Binder 是 Android 系统底层的跨进程通信机制。它允许不同进程之间共享数据,并提供了一种高效的通信方式。每个应用都运行在自己的进程中,Binder 负责处理进程间的数据传输和远程方法调用。
  • Binder 本质上是一个驱动程序,直接与 Android 操作系统内核交互,确保数据能够安全、快速地在不同进程之间传递。

AIDL 是 Binder 的高级封装

  • AIDL 是基于 Binder 的高级抽象,用来简化跨进程调用的实现。编写 AIDL 文件可以定义不同进程之间需要通信的接口和方法,类似于定义一个远程服务接口。
  • AIDL 自动生成对应的 Binder 接口,将开发者从底层的复杂细节中解放出来。通过 AIDL,你定义的接口方法将由 Binder 机制在客户端和服务端之间传递和执行。

两者的关系:

  • AIDL 是基于 Binder 实现的工具:AIDL 文件定义了跨进程服务的接口,Android 编译器会生成相应的 Binder 代码,用以实现跨进程方法调用。
  • Binder 负责底层的数据传输:当使用 AIDL 定义接口后,实际的数据传输和方法调用是通过 Binder 完成的。Binder 是负责 IPC 的底层引擎,AIDL 只是帮助简化了跨进程调用的定义。

工作流程:

  1. 定义 AIDL 接口:开发者编写 AIDL 文件,描述进程之间需要调用的远程方法。
  2. 生成 Binder 代码:编译时,Android 自动生成包含 StubProxy 的 Binder 类。
    • Stub:服务器端的 Binder,负责接收客户端请求并处理。
    • Proxy:客户端的 Binder,负责将远程调用发送到服务器端。
  1. Binder 执行:当客户端调用 AIDL 定义的方法时,实际的数据传输通过 Binder 实现。

总结:

  • Binder 是底层的 IPC 框架,负责实际的数据传输和通信。
  • AIDL 是 Binder 的一种封装,用于定义和处理跨进程接口,更适合应用层的开发者使用。

两者配合,提供了 Android 跨进程通信的完整解决方案。

AIDL 生成

AIDL 是 Binder 的一种封装,所以我们拿 AIDL 为例进行学习。

我们先创建一个 AIDL,在 build.gradle 文件中声明允许 AIDL,然后创建 AIDL 文件;

buildFeatures{
    aidl true
}
// IMyAidlInterface.aidl
package com.example.binder;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    int add(int a,int b);
}

写 AIDL 文件的目的,主要是为了生成代码;编写完 AIDL 文件后,此时在其他 Java 中是无法调用的,AIDL 文件必须要先进行一次编译才会生成 AIDL 对应的代码;我们进行一次编译后,就可以调用到 IMyAidlInterface 了。

在我们 SDK 中,有一个叫做 aidl.exe可执行文件,

该可执行文件是将 我们写的 aidl 文件翻译成 Java 类:

Stub 与 Proxy

我们现在查阅 aidl 生成的代码,可以看见内部有 Default、Stub 这么两个类,我们先不看 Default,先看 Stub 和其内部的 Proxy

比如我们有两个码头,码头 A 和码头 B,我们要将货物从 A 运到 B,他们交换的物质是货物。在架构中,数据接收方和发送发是无法写在一起的,按照功能单一原则不能写在一起;所以写 ****Proxy 作为数据 发送 Stub 则作为数据 接收

我们先看数据发送方 Proxy 如何发送数据

我们创建一个 Service,并在其内部实现 IMyAidlInterface.Stub:

package com.example.binder

import android.app.Service
import android.content.Intent
import android.os.IBinder

class MyService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return object:IMyAidlInterface.Stub(){
            override fun add(a: Int, b: Int): Int {
                return a+b
            }
        }
    }
}

在清单文件声明 Service 在新的进程:

<activity
    android:name=".SecondActivity"
    android:exported="true"
    android:process=":second_process"
    />
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:process=":main_process"
    >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<service android:name=".MyService" android:process=":c"/>

在 Activity 中 bindMyService,并获取实现了 IMyAidlInterface 接口的 MyService 对象后,跨进程调用add(int a,int b)函数:

//参数1:被绑定 Service 的 Intent
//参数2:ServiceConnection实例
//参数3:Service的bind模式
bindService(Intent(this, MyService::class.java),
    object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service)
            Toast.makeText(this@MainActivity, "跨进程读取信息:${iMyAidlInterface.add(1,2)}", Toast.LENGTH_SHORT).show()
        }

        override fun onServiceDisconnected(name: ComponentName?) {

        }
    }, Context.BIND_AUTO_CREATE
)

我们看看在MainActivity 进程中如何跨进程发送数据;

在 MainActivity 的val iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service)位置;

我们进入IMyAidlInterface 看看它是个什么对象;可以看到, IMyAidlInterface 是一个接口类,他只有单一的接口方法,里面没有函数实现。

asInterface

在 Stub 中,有个 asInterface 的方法,这个方法的目的是 IBinder 对象转换为特定的 AIDL 接口 ( IMyAidlInterface ) 实现对象 Proxy,以便客户端可以使用这个接口与服务端进行通信:

public static com.example.binder.IMyAidlInterface asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    //queryLocalInterface:尝试为Binder对象检索接口的本地实现。如果返回null,则需要实例化一个代理类,以便通过transact()方法编组调用。
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.example.binder.IMyAidlInterface))) {
        return ((com.example.binder.IMyAidlInterface)iin);
    }
    return new com.example.binder.IMyAidlInterface.Stub.Proxy(obj);
}
  1. 输入参数 android.os.IBinder obj
  • IBinder:是 Android 中跨进程通信的核心接口,它代表了一个远程的服务对象。
  • 这个 IBinder 对象通常是服务端通过 onBind() 方法返回给客户端的,客户端可以通过它与服务端通信。
  1. if (obj == null) { return null; }
  • 检查 IBinder 对象是否为 null。如果为空,直接返回 null,表示没有可用的服务接口。
  1. obj.queryLocalInterface(DESCRIPTOR)
  • 这是关键的一步,它尝试从 IBinder 对象中检索服务端的本地接口实现。如果返回null,则需要实例化一个代理类,以便通过transact()方法编组调用。
  • queryLocalInterface() IBinder 接口的一个方法,用于在服务端和客户端位于同一进程时,返回服务端本地的接口实现对象。如果服务端和客户端位于同一进程中,直接返回这个接口实现,以避免进程间通信的开销。
  • DESCRIPTOR 是一个字符串常量,通常是 AIDL 文件中接口的名称(由 aidl.exe 生成),用来唯一标识接口。例如 com.example.binder.IMyAidlInterface
  1. if (((iin!=null)&&(iin instanceof com.example.binder.IMyAidlInterface)))检查 iin 是否为 null 和类型检查
  • 如果 queryLocalInterface() 返回的 IInterface 对象 iin 不为 null 且是 IMyAidlInterface 类型(即本地实现的接口对象),则直接将其转换为 IMyAidlInterface 并返回。这样,客户端可以直接调用本地的接口实现。
  • 同一进程优化:如果客户端和服务端在同一个进程中,这一步可以避免不必要的跨进程通信,直接返回本地对象来提高性能。
  1. 代理类 ( Stub.Proxy ) 的实例化
  • 如果 queryLocalInterface() 返回 null(意味着服务端和客户端不在同一个进程中),就需要创建一个 Proxy 对象。
  • Stub.Proxy:是 AIDL 自动生成的代理类,它实现了 IMyAidlInterface 接口,并通过 transact() 方法将客户端的请求封装成 IPC 调用,通过 Binder 发送给服务端。
  • 当服务端和客户端处于不同进程时,客户端的每次方法调用都通过代理类的 transact() 方法进行封装,传递给服务端处理。
  1. 返回代理类实例
  • 如果服务端在另一个进程中,asInterface() 会返回代理类的实例,通过它实现跨进程通信。
  • 代理类会通过底层的 Binder 机制将客户端的调用序列化,发送到服务端,再接收服务端的响应。
总结

return new com.example.binder.IMyAidlInterface.Stub.Proxy(obj);中可以看出,Proxy 是 Stub 的内部类,Proxy 代表着数据的发送方。我们在调用asInterface时,主要是做了实例化 Proxy 对象并 return 返回,通过 Proxy 对象进行通信。

Proxy 源码示例

    private static class Proxy implements com.example.binder.IMyAidlInterface
    {
      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 int add(int a, int b) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(a);
          _data.writeInt(b);
          boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }
    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
Binder 如何跨进程调用函数

当我们 Activity 跨进程调用iMyAidlInterface.add(1,2)时;如果 Activity 直接访问另一个进程的 add 方法地址去执行,可行吗?不可行,因为跨进程调用,有函数地址也是无法访问的。
那么此时客户端如何去请求到服务器呢?将自己的数据封装,如: 接口名称、接口参数、请求参数、类执行信息;之后将数据进行序列化(如:json 字符串)后传递到服务端。

客户端与服务端 ipc 通信规则

因为进程 A 如果想要调用进程 B 的方法,就需要进行序列化,所以接下来做的都是序列化的事情;

_data 变量用来存储发送端的请求数据;_reply 变量用来存储目标进程响应的数据。
android.os.Parcel 则是 Android 中用于进程间通信的数据容器,它相当于一个高效的序列化机制,可以将各种数据类型(如字符串、整数、数组等)打包并传输到其他进程或组件中。可以通过类似 writeInt()writeString() 等方法将数据写入 Parcel,然后通过 readInt()readString() 等方法在接收端读取数据。Parcel 是一种轻量级的序列化方式,但与传统的 Java 序列化不同,Parcel 没有内置的内存管理功能,需要开发者自己注意释放资源,避免内存泄漏。

序列化有两个地方:一是方法调用者将请求数据进行序列化;二是服务端方法执行完成后将结果序列化返回。

首先将请求数据序列化,操作 _data 变量;

因为发送端和客户端之间通过传递序列化数据来实现通信,那么发送端和客户端之间就需要制定一个公共协议(公共规则,如请求数据的顺序)才能通信;

所以我们先执行writeInterfaceToken函数,data.writeInterfaceToken(DESCRIPTOR);,在 _data 变量中先写入我要通信的目标类,在我们这里就是iMyAidlInterface类。之后写入要传入的参数,第一个参数 a ,第二个参数 b;将他们添加到 _data 请求数据中。

这样一来请求数据中就有了目标类参数信息,那么还需要待执行的目标函数信息;在 aidl 中,传递的不是目标函数名,而是函数索引;因为函数索引的传输比函数名更加的节约性能;函数索引是在 aidl.exe 转译时生成的(即发送端与服务端都约定了这个索引对应了某一个函数,从而保证根据所以可以执行正确函数);所以在mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0)中,传递的函数索引为Stub.TRANSACTION_addTRANSACTION_add的值为 常量+0,如果多个函数的情况下,依次递增:常量+1、常量+2。

transact 函数

mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0)中第一个参数是函数索引,第二个参数是请求数据,第三个是返回数据的容器,第四个是其他操作标志。普通RPC为0,单向RPC为FLAG_ONEWAY。

而在transact 中,最终是如何调用的呢?

在客户端 A 进程中,通过 android.os.IBinder mRemote变量的 Binder 接口来调用transact函数把请求数据发送出去;此时请求数据会来到内核进程;

Binder 驱动源码

我们可以简单了解下 Linux 下 Binder 驱动的源码,Binder 的驱动源码在 Linux 内核下,而不是在 Android 中;Binder 的驱动层源码在 linux-3.18/drivers/staging/android/ ****路径下。

Linux驱动内核源码下载地址

官方:mirrors.edge.kernel.org/pub/linux/k…

国内:mirror.bjtu.edu.cn/kernel/linu…

编译好的镜像文件:kernel.ubuntu.com/~kernel-ppa…

我们以 Linux3.18 版本源码为例,找到 /drivers/staging/android/ 路径,直接看 binder.c 文件

Binder 驱动层的binder_transaction函数

我们主要看 transact 的 native 层实现,当我们调用 transact 时,native 中就会调用 binder.c 的 binder_transaction 函数;

binder.c -> binder_transaction 函数源码

static void binder_transaction(struct binder_proc *proc,
				   struct binder_thread *thread,
				   struct binder_transaction_data *tr, int reply)
{
	//根据各种判定,获取以下信息:
	struct binder_transaction *t;
	struct binder_work *tcomplete;
	binder_size_t *offp, *off_end;
	//目标进程
	struct binder_proc *target_proc;
	//目标线程
	struct binder_thread *target_thread = NULL;
	//目标binder节点
	struct binder_node *target_node = NULL;
	//目标TODO队列
	struct list_head *target_list;
	//目标等待队列
	wait_queue_head_t *target_wait;
	struct binder_transaction *in_reply_to = NULL;
	struct binder_transaction_log_entry *e;
	uint32_t return_error;

	e = binder_transaction_log_add(&binder_transaction_log);
	e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY);
	e->from_proc = proc->pid;
	e->from_thread = thread->pid;
	e->target_handle = tr->target.handle;
	e->data_size = tr->data_size;
	e->offsets_size = tr->offsets_size;

	if (reply) {
		in_reply_to = thread->transaction_stack;
		if (in_reply_to == NULL) {
			binder_user_error("%d:%d got reply transaction with no transaction stack\n",
					  proc->pid, thread->pid);
			return_error = BR_FAILED_REPLY;
			goto err_empty_call_stack;
		}
		binder_set_nice(in_reply_to->saved_priority);
		if (in_reply_to->to_thread != thread) {
			binder_user_error("%d:%d got reply transaction with bad transaction stack, transaction %d has target %d:%d\n",
				proc->pid, thread->pid, in_reply_to->debug_id,
				in_reply_to->to_proc ?
				in_reply_to->to_proc->pid : 0,
				in_reply_to->to_thread ?
				in_reply_to->to_thread->pid : 0);
			return_error = BR_FAILED_REPLY;
			in_reply_to = NULL;
			goto err_bad_call_stack;
		}
		thread->transaction_stack = in_reply_to->to_parent;
		target_thread = in_reply_to->from;
		if (target_thread == NULL) {
			return_error = BR_DEAD_REPLY;
			goto err_dead_binder;
		}
		if (target_thread->transaction_stack != in_reply_to) {
			binder_user_error("%d:%d got reply transaction with bad target transaction stack %d, expected %d\n",
				proc->pid, thread->pid,
				target_thread->transaction_stack ?
				target_thread->transaction_stack->debug_id : 0,
				in_reply_to->debug_id);
			return_error = BR_FAILED_REPLY;
			in_reply_to = NULL;
			target_thread = NULL;
			goto err_dead_binder;
		}
		target_proc = target_thread->proc;
	} else {
		if (tr->target.handle) {
			struct binder_ref *ref;

			ref = binder_get_ref(proc, tr->target.handle);
			if (ref == NULL) {
				binder_user_error("%d:%d got transaction to invalid handle\n",
					proc->pid, thread->pid);
				return_error = BR_FAILED_REPLY;
				goto err_invalid_target_handle;
			}

			target_node = ref->node;
		} else {
			target_node = binder_context_mgr_node;
			if (target_node == NULL) {
				return_error = BR_DEAD_REPLY;
				goto err_no_context_mgr_node;
			}
		}
		e->to_node = target_node->debug_id;
		target_proc = target_node->proc;
		if (target_proc == NULL) {
			return_error = BR_DEAD_REPLY;
			goto err_dead_binder;
		}
		if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
			struct binder_transaction *tmp;

			tmp = thread->transaction_stack;
			if (tmp->to_thread != thread) {
				binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n",
					proc->pid, thread->pid, tmp->debug_id,
					tmp->to_proc ? tmp->to_proc->pid : 0,
					tmp->to_thread ?
					tmp->to_thread->pid : 0);
				return_error = BR_FAILED_REPLY;
				goto err_bad_call_stack;
			}
			while (tmp) {
				if (tmp->from && tmp->from->proc == target_proc)
					target_thread = tmp->from;
				tmp = tmp->from_parent;
			}
		}
	}
	if (target_thread) {
		e->to_thread = target_thread->pid;
		target_list = &target_thread->todo;
		target_wait = &target_thread->wait;
	} else {
		target_list = &target_proc->todo;
		target_wait = &target_proc->wait;
	}
	e->to_proc = target_proc->pid;

	/* TODO: reuse incoming transaction for reply */
	//分配两个结构体内存
	t = kzalloc(sizeof(*t), GFP_KERNEL);
	if (t == NULL) {
		return_error = BR_FAILED_REPLY;
		goto err_alloc_t_failed;
	}
	binder_stats_created(BINDER_STAT_TRANSACTION);
	//分配两个结构体内存
	tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
	if (tcomplete == NULL) {
		return_error = BR_FAILED_REPLY;
		goto err_alloc_tcomplete_failed;
	}
	binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);

	t->debug_id = ++binder_last_id;
	e->debug_id = t->debug_id;

	if (reply)
		binder_debug(BINDER_DEBUG_TRANSACTION,
				 "%d:%d BC_REPLY %d -> %d:%d, data %016llx-%016llx size %lld-%lld\n",
				 proc->pid, thread->pid, t->debug_id,
				 target_proc->pid, target_thread->pid,
				 (u64)tr->data.ptr.buffer,
				 (u64)tr->data.ptr.offsets,
				 (u64)tr->data_size, (u64)tr->offsets_size);
	else
		binder_debug(BINDER_DEBUG_TRANSACTION,
				 "%d:%d BC_TRANSACTION %d -> %d - node %d, data %016llx-%016llx size %lld-%lld\n",
				 proc->pid, thread->pid, t->debug_id,
				 target_proc->pid, target_node->debug_id,
				 (u64)tr->data.ptr.buffer,
				 (u64)tr->data.ptr.offsets,
				 (u64)tr->data_size, (u64)tr->offsets_size);

	if (!reply && !(tr->flags & TF_ONE_WAY))
		t->from = thread;
	else
		t->from = NULL;
	t->sender_euid = task_euid(proc->tsk);
	t->to_proc = target_proc;
	t->to_thread = target_thread;
	t->code = tr->code;
	t->flags = tr->flags;
	t->priority = task_nice(current);

	trace_binder_transaction(reply, t, target_node);
//从target_proc分配一块buffer
	t->buffer = binder_alloc_buf(target_proc, tr->data_size,
		tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
	if (t->buffer == NULL) {
		return_error = BR_FAILED_REPLY;
		goto err_binder_alloc_buf_failed;
	}
	t->buffer->allow_user_free = 0;
	t->buffer->debug_id = t->debug_id;
	t->buffer->transaction = t;
	t->buffer->target_node = target_node;
	trace_binder_transaction_alloc_buf(t->buffer);
	if (target_node)
		binder_inc_node(target_node, 1, 0, NULL);

	offp = (binder_size_t *)(t->buffer->data +
				 ALIGN(tr->data_size, sizeof(void *)));

	if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
			   tr->data.ptr.buffer, tr->data_size)) {
		binder_user_error("%d:%d got transaction with invalid data ptr\n",
				proc->pid, thread->pid);
		return_error = BR_FAILED_REPLY;
		goto err_copy_data_failed;
	}
	if (copy_from_user(offp, (const void __user *)(uintptr_t)
			   tr->data.ptr.offsets, tr->offsets_size)) {
		binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
				proc->pid, thread->pid);
		return_error = BR_FAILED_REPLY;
		goto err_copy_data_failed;
	}
	if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) {
		binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n",
				proc->pid, thread->pid, (u64)tr->offsets_size);
		return_error = BR_FAILED_REPLY;
		goto err_bad_offset;
	}
	off_end = (void *)offp + tr->offsets_size;
	for (; offp < off_end; offp++) {
		struct flat_binder_object *fp;

		if (*offp > t->buffer->data_size - sizeof(*fp) ||
			t->buffer->data_size < sizeof(*fp) ||
			!IS_ALIGNED(*offp, sizeof(u32))) {
			binder_user_error("%d:%d got transaction with invalid offset, %lld\n",
					  proc->pid, thread->pid, (u64)*offp);
			return_error = BR_FAILED_REPLY;
			goto err_bad_offset;
		}
		fp = (struct flat_binder_object *)(t->buffer->data + *offp);
		switch (fp->type) {
		case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {
			struct binder_ref *ref;
			struct binder_node *node = binder_get_node(proc, fp->binder);

			if (node == NULL) {
				node = binder_new_node(proc, fp->binder, fp->cookie);
				if (node == NULL) {
					return_error = BR_FAILED_REPLY;
					goto err_binder_new_node_failed;
				}
				node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
				node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
			}
			if (fp->cookie != node->cookie) {
				binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
					proc->pid, thread->pid,
					(u64)fp->binder, node->debug_id,
					(u64)fp->cookie, (u64)node->cookie);
				return_error = BR_FAILED_REPLY;
				goto err_binder_get_ref_for_node_failed;
			}
			ref = binder_get_ref_for_node(target_proc, node);
			if (ref == NULL) {
				return_error = BR_FAILED_REPLY;
				goto err_binder_get_ref_for_node_failed;
			}
			if (fp->type == BINDER_TYPE_BINDER)
				fp->type = BINDER_TYPE_HANDLE;
			else
				fp->type = BINDER_TYPE_WEAK_HANDLE;
			fp->handle = ref->desc;
			binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
					   &thread->todo);

			trace_binder_transaction_node_to_ref(t, node, ref);
			binder_debug(BINDER_DEBUG_TRANSACTION,
					 "        node %d u%016llx -> ref %d desc %d\n",
					 node->debug_id, (u64)node->ptr,
					 ref->debug_id, ref->desc);
		} break;
		case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct binder_ref *ref = binder_get_ref(proc, fp->handle);

			if (ref == NULL) {
				binder_user_error("%d:%d got transaction with invalid handle, %d\n",
						proc->pid,
						thread->pid, fp->handle);
				return_error = BR_FAILED_REPLY;
				goto err_binder_get_ref_failed;
			}
			if (ref->node->proc == target_proc) {
				if (fp->type == BINDER_TYPE_HANDLE)
					fp->type = BINDER_TYPE_BINDER;
				else
					fp->type = BINDER_TYPE_WEAK_BINDER;
				fp->binder = ref->node->ptr;
				fp->cookie = ref->node->cookie;
				binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
				trace_binder_transaction_ref_to_node(t, ref);
				binder_debug(BINDER_DEBUG_TRANSACTION,
						 "        ref %d desc %d -> node %d u%016llx\n",
						 ref->debug_id, ref->desc, ref->node->debug_id,
						 (u64)ref->node->ptr);
			} else {
				struct binder_ref *new_ref;

				new_ref = binder_get_ref_for_node(target_proc, ref->node);
				if (new_ref == NULL) {
					return_error = BR_FAILED_REPLY;
					goto err_binder_get_ref_for_node_failed;
				}
				fp->handle = new_ref->desc;
				binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
				trace_binder_transaction_ref_to_ref(t, ref,
									new_ref);
				binder_debug(BINDER_DEBUG_TRANSACTION,
						 "        ref %d desc %d -> ref %d desc %d (node %d)\n",
						 ref->debug_id, ref->desc, new_ref->debug_id,
						 new_ref->desc, ref->node->debug_id);
			}
		} break;

		case BINDER_TYPE_FD: {
			int target_fd;
			struct file *file;

			if (reply) {
				if (!(in_reply_to->flags & TF_ACCEPT_FDS)) {
					binder_user_error("%d:%d got reply with fd, %d, but target does not allow fds\n",
						proc->pid, thread->pid, fp->handle);
					return_error = BR_FAILED_REPLY;
					goto err_fd_not_allowed;
				}
			} else if (!target_node->accept_fds) {
				binder_user_error("%d:%d got transaction with fd, %d, but target does not allow fds\n",
					proc->pid, thread->pid, fp->handle);
				return_error = BR_FAILED_REPLY;
				goto err_fd_not_allowed;
			}

			file = fget(fp->handle);
			if (file == NULL) {
				binder_user_error("%d:%d got transaction with invalid fd, %d\n",
					proc->pid, thread->pid, fp->handle);
				return_error = BR_FAILED_REPLY;
				goto err_fget_failed;
			}
			target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
			if (target_fd < 0) {
				fput(file);
				return_error = BR_FAILED_REPLY;
				goto err_get_unused_fd_failed;
			}
			task_fd_install(target_proc, target_fd, file);
			trace_binder_transaction_fd(t, fp->handle, target_fd);
			binder_debug(BINDER_DEBUG_TRANSACTION,
					 "        fd %d -> %d\n", fp->handle, target_fd);
			/* TODO: fput? */
			fp->handle = target_fd;
		} break;

		default:
			binder_user_error("%d:%d got transaction with invalid object type, %x\n",
				proc->pid, thread->pid, fp->type);
			return_error = BR_FAILED_REPLY;
			goto err_bad_object_type;
		}
	}
	if (reply) {
		BUG_ON(t->buffer->async_transaction != 0);
		binder_pop_transaction(target_thread, in_reply_to);
	} else if (!(t->flags & TF_ONE_WAY)) {
		BUG_ON(t->buffer->async_transaction != 0);
		t->need_reply = 1;
		t->from_parent = thread->transaction_stack;
		thread->transaction_stack = t;
	} else {
		BUG_ON(target_node == NULL);
		BUG_ON(t->buffer->async_transaction != 1);
		if (target_node->has_async_transaction) {
			target_list = &target_node->async_todo;
			target_wait = NULL;
		} else
			target_node->has_async_transaction = 1;
	}
	t->work.type = BINDER_WORK_TRANSACTION;
	list_add_tail(&t->work.entry, target_list);
	tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
	list_add_tail(&tcomplete->entry, &thread->todo);
	if (target_wait)
		wake_up_interruptible(target_wait);
	return;

err_get_unused_fd_failed:
err_fget_failed:
err_fd_not_allowed:
err_binder_get_ref_for_node_failed:
err_binder_get_ref_failed:
err_binder_new_node_failed:
err_bad_object_type:
err_bad_offset:
err_copy_data_failed:
	trace_binder_transaction_failed_buffer_release(t->buffer);
	binder_transaction_buffer_release(target_proc, t->buffer, offp);
	t->buffer->transaction = NULL;
	binder_free_buf(target_proc, t->buffer);
err_binder_alloc_buf_failed:
	kfree(tcomplete);
	binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
err_alloc_tcomplete_failed:
	kfree(t);
	binder_stats_deleted(BINDER_STAT_TRANSACTION);
err_alloc_t_failed:
err_bad_call_stack:
err_empty_call_stack:
err_dead_binder:
err_invalid_target_handle:
err_no_context_mgr_node:
	binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
			 "%d:%d transaction failed %d, size %lld-%lld\n",
			 proc->pid, thread->pid, return_error,
			 (u64)tr->data_size, (u64)tr->offsets_size);

	{
		struct binder_transaction_log_entry *fe;

		fe = binder_transaction_log_add(&binder_transaction_log_failed);
		*fe = *e;
	}

	BUG_ON(thread->return_error != BR_OK);
	if (in_reply_to) {
		thread->return_error = BR_TRANSACTION_COMPLETE;
		binder_send_failed_reply(in_reply_to, return_error);
	} else
		thread->return_error = return_error;
}

binder.c -> binder_transaction_buffer_release 函数源码

static void binder_transaction_buffer_release(struct binder_proc *proc,
						  struct binder_buffer *buffer,
						  binder_size_t *failed_at)
{
	binder_size_t *offp, *off_end;
	int debug_id = buffer->debug_id;

	binder_debug(BINDER_DEBUG_TRANSACTION,
			 "%d buffer release %d, size %zd-%zd, failed at %p\n",
			 proc->pid, buffer->debug_id,
			 buffer->data_size, buffer->offsets_size, failed_at);

	if (buffer->target_node)
		binder_dec_node(buffer->target_node, 1, 0);

	offp = (binder_size_t *)(buffer->data +
				 ALIGN(buffer->data_size, sizeof(void *)));
	if (failed_at)
		off_end = failed_at;
	else
		off_end = (void *)offp + buffer->offsets_size;
	for (; offp < off_end; offp++) {
		struct flat_binder_object *fp;

		if (*offp > buffer->data_size - sizeof(*fp) ||
			buffer->data_size < sizeof(*fp) ||
			!IS_ALIGNED(*offp, sizeof(u32))) {
			pr_err("transaction release %d bad offset %lld, size %zd\n",
				   debug_id, (u64)*offp, buffer->data_size);
			continue;
		}
		fp = (struct flat_binder_object *)(buffer->data + *offp);
		switch (fp->type) {
		case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {
			struct binder_node *node = binder_get_node(proc, fp->binder);

			if (node == NULL) {
				pr_err("transaction release %d bad node %016llx\n",
					   debug_id, (u64)fp->binder);
				break;
			}
			binder_debug(BINDER_DEBUG_TRANSACTION,
					 "        node %d u%016llx\n",
					 node->debug_id, (u64)node->ptr);
			binder_dec_node(node, fp->type == BINDER_TYPE_BINDER, 0);
		} break;
		case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct binder_ref *ref = binder_get_ref(proc, fp->handle);

			if (ref == NULL) {
				pr_err("transaction release %d bad handle %d\n",
				 debug_id, fp->handle);
				break;
			}
			binder_debug(BINDER_DEBUG_TRANSACTION,
					 "        ref %d desc %d (node %d)\n",
					 ref->debug_id, ref->desc, ref->node->debug_id);
			binder_dec_ref(ref, fp->type == BINDER_TYPE_HANDLE);
		} break;

		case BINDER_TYPE_FD:
			binder_debug(BINDER_DEBUG_TRANSACTION,
					 "        fd %d\n", fp->handle);
			if (failed_at)
				task_close_fd(proc, fp->handle);
			break;

		default:
			pr_err("transaction release %d bad object type %x\n",
				debug_id, fp->type);
			break;
		}
	}
}

binder_transaction 函数中主要做的是数据拷贝工作,因为发送端的数据是走的拷贝,而接收端通过映射来接收。

那么拷贝使用的 Linux 函数是 copy_from_user 将数据从用户空间拷贝到内核空间;在 binder_transaction函数可以搜索到两处使用了 copy_from_user 调用;

第一次拷贝

第一处的copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)tr->data.ptr.buffer, tr->data_size)则是将用户层的数据包(tr->data.ptr.buffer)大小(tr->data.size) 通过系统调用copy_from_user 将数据拷贝到内核进程中开辟的一块内存空间里struct binder_transaction *t。内核空间的这块内存就是接收端与内核空间映射的那 1M-8K 的物理内存空间。

这一次是数据包的拷贝,如: 调用的方法、类、参数信息。

Binder 真的只调用一次拷贝吗?

在发送端数据拷贝到内核空间时,对于我们程序员来说,是做了一次拷贝,但是在 binder 驱动层来说,其内部调用了两次的copy_from_user ,所以其实是拷贝了两次;因为每调用一次copy_from_user 就意味着一次拷贝。

第一次拷贝是数据包的拷贝,第二次拷贝是数据头的拷贝。其实copy_from_user分两次 或 一次性拷贝,他们拷贝的数据量是不变的,所以性能区别相差不大。

第二次拷贝

第二处的copy_from_user(offp, (const void __user *)(uintptr_t)tr->data.ptr.offsets, tr->offsets_size)则是数据头的拷贝,如果只拷贝第一处的信息,是无法找到只根据 函数索引接口类名参数找到正确的服务端进程。

所以第二次拷贝需要拷贝的是tr->data.ptr.offsets目的进程号、目的线程、验证信息;

Question

  1. 所谓的复制主要是用户空间和内核空间的复制吗?
    是的。不管在所有的 Linux 或 Android,他们的复制都是内核空间和用户空间之间的复制,不存在进程 A 与进程 B 之间的直接复制。
  2. 实时音视频数据可以通过 AIDL 实现跨进程传输吗?
    不行。因为 Binder 机制主要是用于进程间通信的;进程间通信与进程间传文件是不等价的。这也是为什么通信限制大小为 1M-8K,因为它是拿来做通信而不是拿来传输文件的。
  3. bbbp 是框架层 framework 吗?
    是的。
  4. 内存和磁盘映射 mmap 也有存的过程,那么普通写文件和 mmap 是一样的吗?
    不是的,普通写文件是通过 fileinputstream、fileoutputstream 来写的。
  5. 1M-8K 是什么?
    一个程序能够实现进程间通信的最大数据量。
  6. Binder 跨进程通信,如果两个进程间,多个线程访问会阻塞吗?
    Binder 进程通信支持并发访问,它有线程数量级,这个数量级是 15,最多支持 15 个线程访问。