关于Binder 优先级继承的一些QA

342 阅读5分钟

最近项目上遇到一个binder 优先级继承的问题,所以对binder 优先级这块做一个简单梳理。

这里顺便提下:
Binder driver 后续会逐渐使用Rust Binder,不过对于上层开发不会有影响,driver 侧的逻辑也基本没变,只是用rust 重写了而已。

Q: 设置优先级的核心函数?

A:

关键函数:binder_transaction_priority

  • 功能:将发起事务的线程的实时优先级(RT priority)传递给目标线程。
  • 实现:
    • 保存原始调度策略和优先级到 t->saved_priority
    • 比较目标 node 的最小优先级(min_priority)和事务设置的优先级,选择更高优先级的调度策略。
    • 调用 binder_set_priority 实际设置线程的优先级

真正实际set prio 的函数是:binder_do_set_priority

Q: 何时设置优先级?

A:
在Server 线程被唤醒之前,就提前提升目标线程(server)的优先级
binder_proc_transaction()

if (thread) {
    target_list = &thread->todo;
    binder_transaction_priority(thread->task, t, node_prio);
}

Server 处理完发出 reply 后,需要调用binder_restore_priority 恢复之前自己的优先级

if (reply) {
    ...
    binder_restore_priority(...);
}

binder_restore_priority 调用的是binder_do_set_priority

注:oneway binder 是没有reply 的,这里只针对同步binder即reply=1 的情况

Q: 什么时候需要RT 优先级继承?

A:

For latency sensitive operations such as sensor events, Bluetooth audio and rendering, inheriting the (real-time) priority of the caller is crucial to meet requirements.

对于那些 对延迟非常敏感的操作(比如传感器事件、蓝牙音频、界面渲染等),服务端必须继承客户端的(实时)优先级,才能满足实时性要求。

Q: 如何让Server 支持RT 优先级继承?

A:
通过inherit_rt 这个flag

	/**
	 * @FLAT_BINDER_FLAG_INHERIT_RT: whether the node inherits RT policy
	 *
	 * Only when set, calls into this node will inherit a real-time
	 * scheduling policy from the caller (for synchronous transactions).
	 */
	FLAT_BINDER_FLAG_INHERIT_RT = 0x800,

上层有接口setInheritRt 暴露出来 比如

// AudioFlingerServerAdapter
AudioFlingerServerAdapter::AudioFlingerServerAdapter(
        const sp<AudioFlingerServerAdapter::Delegate>& delegate) : mDelegate(delegate) {
    setMinSchedulerPolicy(SCHED_NORMAL, ANDROID_PRIORITY_AUDIO);
    setInheritRt(true);
}

Q: Server 不支持inherit_rt 为何优先级还会变成CFS 120?

对应下图中的#1 部分

image.webp

简而言之:

如果server 没有显示支持inherit_rt,被caller(RT) 同步binder 后就会被改为CFS 120

其实这段code 由来已久,一直存在争议

优先级倒置:被阻塞的实时线程 image.png

为了避免出现阻塞情况,您可以在 binder 线程处理来自 RT 客户端的请求时,使用优先级继承暂时将 binder 线程升级到 RT 线程。请注意,RT 调度的资源有限,应谨慎使用。在具有 n 个 CPU 的系统中,当前 RT 线程的数量上限也为 n;如果所有 CPU 均已被其他 RT 线程占用,那么超出的那些 RT 线程可能需要等待(因此将超出其截止时间)。

(ref: source.android.com/docs/core/t…)

要解决所有可能出现的优先级倒置问题,您可以针对 binder 和 hwbinder 使用优先级继承。不过,由于 binder 广泛用于整个系统,因此为 binder 事务启用优先级继承可能会使系统中的 RT 线程数超过其所能处理的线程数。

原因很简单,inherit_rt 没有显示支持的话

  • 一个 RT 线程发出的请求,会被一个低优先级的 CFS 线程处理;
  • 由于服务线程优先级低,响应延迟就高,影响性能。

还有一个影响就是:

  • Binder 的线程池是 LIFO 的,也就是说最近的”空闲binder 线程”更容易被选中处理事务;
  • 这些线程又因为 CFS 运行时间被“用完”,导致短时间无法调度;

这也是为何我们经常会看到SurfaceFlinger 的binder 线程池中的那几个binder 被调度时总是挂着一些Runnable,原因正是由于其优先级不高。

换句话如果这里不是120,而是100的话,那么就可以让它们更快重新被调度,从而 降低事务延迟。

事实上,Google 将一些实时性要求较高的场景下显示设置了inherit_rt 为true 比如HWC、audio 等。

但是凡事也没有绝对:

“RT 数量一多,就会影响到选核,同时也会影响到整机性能(吞吐)”

Q: 不希望优先级被一个RT binder 后变为120,该怎么做?

A: 对于5.10 及之后的kernel,理论上都是GKI 内核,可以通过hook 或者显示支持inherit rt

如果通过hook 的话,那就是下面这个接口 image.png

不过这一hook 接口在kernel 6.6 上不存在,所以只有在低版kernel 上搞搞了

Q: Rust Binder 还保留C Binder的优先级继承功能吗?

A:
先说下为何要用rust rewrite
下面内容汇总自A Rust implementation of Android's Binder [LWN.net]

Binder C 实现存在大量技术债务和安全漏洞(每千行代码约 3.1 个漏洞)。由于其可被低权限应用访问,安全隐患严重。C 实现中错误处理混乱、代码庞杂,维护困难。
为此,Android 开发者正在用 Rust 重写 Binder,利用 Rust 的类型系统和内存安全特性,避免常见漏洞如 use-after-free 和越界访问,并简化错误处理流程,提高代码可维护性和安全性。
Rust 实现了所有C Binder的特性,这自然也包括了优先级继承。

感兴趣可以看下Rust Binder 的实现
android-review.googlesource.com/c/kernel/co…

最终是作为一个rust_binder.ko 形式存在

小结

等后面有空再仔细看看