这是一系列的 Binder 文章,会从内核层到 Framework 层,再到应用层,深入浅出,介绍整个 Binder 的设计。详见《图解 Binder:概述》。

本文根据 Android platform 分支 android-13.0.0_r1 和内核分支 common-android13-5.15 解析。

一些关键代码的链接,可能会由于源码的变动,产生位置偏移、丢掉等现象。能够查找函数名,重新进行定位。

每个运用 Binder 通讯的进程,都会在 Framework 层建立自己的线程池,处理来自不同进程的业务。内核层也会为线程池线程保护相应的数据结构。

Binder 线程

Binder 里的线程能够分成两类:一般的用户线程、Binder 线程池里的线程。

下图展现了在 Binder 业务中,触及的几个与 Binder 线程相关的数据结构:

图解 Binder:线程池

  • ① Thread 指的是一般的用户线程,也就是建议 Binder 业务的线程。

  • ② PoolThread 指的是 Binder 线程池里的线程,用来处理发送给当时进程的 Binder 音讯。

  • ③ binder_thread 是 Binder 驱动里在内核保护的一个数据结构,用来描绘一个用户空间线程的信息,如线程 pid、作业行列 todo 等。binder_thread 是经过 binder_get_thread() 创立的。

  • ④ 每当一个用户空间线程经过 Binder 驱动进行进程间通讯时,都会有一个对应的 binder_thread 方针在内核中表明它,即 binder_thread 与 Thread 或 PoolThread 是1对1的关系。

  • ⑤ proc、target_proc 都是结构体 binder_proc 的方针,别离对应客户端进程、服务端进程。binder_proc 是 Binder 驱动在内核保护的一个数据结构,代表一个运用 Binder 通讯机制的进程,记载着进程相关信息。它保护两个与 binder_thread 相关的数据结构:

    • threads:一棵红黑树,记载了该进程的正在 Binder 驱动里进行通讯的所有用户空间线程对应的 binder_thread,依照线程的 pid 巨细排序。目的是为了后续能用线程的 pid 在 O(logn) 的时间复杂度内找到对应的 binder_thread,或许在用户空间的线程销毁时,在 O(logn) 的时间复杂度内移除对应的 binder_htread。
    • waiting_threads:一个双向链表,记载了当时所有闲暇的线程池线程 PoolThread。
  • ⑥ Thread、PoolThread 每次进入 Binder 驱动时,首要都会经过 binder_get_thread() 查询当时线程 binder_proc 里的红黑树 threads 是否有对应的 binder_thread,没有则创立一个,并插入 threads 中。

  • ⑦ Binder 线程池的线程在创立的时分或许在刚完结上一个作业使命时,一旦进程的作业行列里,没有新的使命,就会进入 waiting_threads 的链表头里,并挂起。当进程的作业行列有新的使命到来时,就会移除 waiting_threads 的链表头的 binder_thread,唤醒对应的线程,处理使命。(一般的用户线程 Thread 是不会进入 waiting_threads 的)

在 Binder 业务中,从线程的角度来看,其实就是由 Thread 提交一个业务,经过 Binder 驱动转化处理后,挑选线程池中的一个 PoolThread 处理。

Binder 作业行列

Binder 的作业行列有三种:线程作业行列、进程作业行列和待处理的异步使命作业行列。这三种作业行列,都是双向链表。

作业行列里使命的类型,最常见的是业务,但不是只要业务。作业使命类型由 binder_work_type 界说。

线程作业行列和进程作业行列

线程作业行列,是指 binder_thread 里的 todo。

进程作业行列,是指 binder_proc 里的 todo。

下图展现了 Binder 线程池里的线程处理这两个作业行列里的作业使命的流程和优先级

图解 Binder:线程池
  • 1)PoolThread 会先判别 binder_thread 的 todo 是否为空,不为空则从该 todo 中出队一个作业使命,并进行处理。为空则进行过程 2)
  • 2)PoolThread 判别 binder_proc 的 todo 是否为空,不为空则从该 todo 中出队一个作业使命,并进行处理。为空则进行过程 3)
  • 3)PoolThread 发现当时没有可处理的作业使命,进入到 binder_proc 的 waiting_threads 里,并挂起自己。

留意,上面说到的是 PoolThread。一般的用户线程的话,不太一样。一般的用户线程 Thread,是不会处理进程作业行列的使命的,只会处理自身 todo。并且它在等候业务回复的过程中,也不会入队到 waiting_threads 中,只是简单地挂起。

图解 Binder:线程池

如上图,一般的用户线程 Thread 提交一个同步业务后:

  • 先检测方针进程的 wait_threads:
    • 假如 wait_threads 不为空,则会从方针进程的 wait_threads 出队一个闲暇线程,并将该业务入队到该线程的 todo 里,最终唤醒该线程,进行处理。
    • 假如 wait_threads 为空,则会将该业务入队到方针进程的 todo 里,等候服务端有闲暇的线程。
  • 用户线程 Thread 挂起,等候服务端处理该业务。
  • 服务端处理完结业务后,PoolThread 会提交回复到 Thread 的 todo 里。
  • 最终,唤醒 Thread,读取服务端的回复。

待处理的异步使命作业行列

待处理的异步使命作业行列,是指 binder_node 里的 async_todo。一个 binder_node 最多只能够有一个异步使命,多余的会放进 binder_node 的 async_todo 行列中,异步使命处理完,才会从 async_todo 行列中出队一个到 binder_proc 里的 todo。

这样的设计保证了每个 binder_node 在任何时刻都只要一个活泼的异步业务,这关于防止并发问题十分有用。同时,经过运用 async_todo 行列,Binder 能够保证异步业务的顺序性:先恳求的异步业务会先得到处理。

图解 Binder:线程池

如上图,两个用户线程一前一后,经过一个 Binder 代理,即 BpBinder,建议异步业务:

  • Thread1 的异步业务来到 Binder 驱动时,发现 binder_node 当时没有异步业务,所以走了途径 ①,直接提交业务到 binder_thread 的 todo 中,或许 binder_proc 的 todo 中。
  • Thread2 的异步业务来到 Binder 驱动时,发现 binder_node 当时有正在处理的异步业务(Thread1 的),所以走了途径 ②,将业务入队到 binder_node 的 async_todo 中。直到 Thread1 的异步业务被处理完,就会从 async_todo 出队 Thread2 的异步业务,并提交到到 binder_thread 的 todo 中,或许 binder_proc 的 todo 中。

Binder 线程池

Binder 线程池在进程发动时创立,用来接受、处理来自 Binder 驱动(源自其他进程)的音讯。这时分,Binder 线程池所在进程为服务端,其他进程为客户端。

线程池里的线程分为两类:

  • 主线程:线程池第一个创立的线程。
    • 经过 BC_ENTER_LOOPER 告诉 Binder 驱动,自己进入了轮询状况。
    • 经过 BC_EXIT_LOOPER 告诉 Binder 驱动,自己退出了轮询状况。
  • 作业线程:后续创立的线程池线程。
    • 经过 BC_REGISTER_LOOPER 告诉 Binder 驱动,自己进入了轮询状况。
    • 经过 BC_EXIT_LOOPER 告诉 Binder 驱动,自己退出了轮询状况。

主线程和作业线程发动后,都会在 joinThreadPool() 里堕入轮询,不断轮询处理 Binder 音讯,不同的是主线程不会因超时退出轮询,而子线程会(但从源码来看,会触发 TIMED_OUT 的,只要 BR_FINISHED 音讯,但现在在 binder.c 现已没发送该音讯的代码,所以主线程、作业线程现在其实差异不大)。

IPCThreadState

每个 Binder 线程在 Framework 层都有一个 IPCThreadState 实例。 它用于办理 Binder IPC(进程间通讯)的线程状况,担任接纳和处理来自 Binder 驱动的命令,包含传递的数据和文件描绘符等。

当 IPCThreadState 初始化时,会设置输入和输出缓冲区的容量:

mIn.setDataCapacity(256);
mOut.setDataCapacity(256);

这里,mIn 和 mOut 都是 Parcel 类的实例,别离表明输入和输出缓冲区。setDataCapacity(256) 将缓冲区的容量设置为 256 字节。

设置初始容量是为了在 IPC 调用过程中供给足够的缓冲区空间。在大多数情况下,这个初始容量现已足够应付大部分的数据传输需求。然而,在某些情况下,可能需要传输更大的数据块,这时分缓冲区的容量会根据需要动态增加。

在实践运行过程中,IPCThreadState 可能会处理许多不同巨细的数据块。设置一个合适的初始缓冲区容量有助于进步系统性能,由于它能够削减在数据传输过程中频频调整缓冲区巨细所带来的开销。

ProcessState

每个进程在 Framework 层都有一个 ProcessState 实例,它担任保护与 Binder 驱动程序之间的通讯,如打开 Binder 驱动程序设备、初始化线程池、办理线程池等。ProcessState 类与 IPCThreadState 类密切相关,后者担任办理每个线程的 Binder 通讯状况。

以下是 ProcessState 的一些重要函数:

  • startThreadPool():用于发动 Binder 线程池。线程池中的线程担任处理进程间的 Binder 通讯。一开始,线程池只要一个主线程,用于处理与进程关联的所有 Binder 恳求。根据需要,线程池还能够包含其他作业线程,以帮忙主线程处理并发恳求。
  • spawnPooledThread():用于在 Binder 线程池中创立并发动新的线程。有一个入参 isMain,为 true 时是创立主线程,为 false 时是创立作业线程。
  • setThreadPoolMaxThreadCount():设置 Binder 线程池的最大线程数。默认的 Binder 线程池的最大线程数是 15,由 DEFAULT_MAX_BINDER_THREADS 界说。留意,这个最大线程数,是不包含主线程的,约束的是作业线程数。

Binder 线程池发动

Zygote 进程在 processCommand() 接受到创立子进程的音讯并 fork 出子进程后,在履行子进程的初始化时,就会创立并发动 Binder 线程池。

图解 Binder:线程池

相关调用链路如下:

ZygoteConnection.java

└─processCommand()

└──handleChildProc()

└───ZygoteInit.java

└────zygoteInit()

└─────nativeZygoteInit()

└──────AndroidRuntime.cpp

└───────com_android_internal_os_ZygoteInit_nativeZygoteInit()

└────────app_main.cpp

└─────────onZygoteInit()

└──────────ProcessState.cpp

└───────────startThreadPool()

└────────────spawnPooledThread()

└─────────────Threads.cpp

└──────────────run()

└───────────────_threadLoop()

└────────────────PoolThread.cpp

└─────────────────threadLoop()

└──────────────────joinThreadPool()

Binder 线程池创立新的线程

当进程的作业线程的等候行列,即 binder_proc 的 waiting_threads 现已为空,并且当时 Binder 线程池里的作业线程还没有到达最大线程数的时分,Binder 驱动会发送一条 BR_SPAWN_LOOPER 音讯,告诉进程创立一个新的作业线程。

图解 Binder:线程池