数据包接收队列与线程等待队列管理
数据传输的接收端通常维护两个队列:数据包接收队列和线程等待队列,核心目的是缓解供需矛盾。就像超市进货太多货物会堆积在仓库,购物的人太多则要在收银台排队一样。在驱动层面,每个进程维护一个全局接收队列(to-do 队列),存放非特定线程的数据包;同时有一个全局等待队列,所有等待从全局队列取数据的线程在此排队。
每个线程也有私有的 to-do 队列和私有等待队列。私有 to-do 队列专门存放发往该线程的数据包,而私有等待队列用于本线程等待接收自己的数据。虽然叫'队列',但线程私有等待队列中通常只容纳它自己。
既然发送时没有特殊标记,驱动如何判断数据包该进全局还是私有队列?这里有两条核心规则。
第一条规则是 Client 发给 Server 的请求数据包默认提交到 Server 进程的全局 to-do 队列。不过有个特例,Binder 对工作线程启动做了优化,来自 T1 的请求可能直接送入 T2 的私有 to-do 队列,而非 P2 的全局队列。
第二条规则针对同步请求的返回数据包(由 BC_REPLY 发送)。这类包必须发送到发起请求的线程的私有 to-do 队列。例如,P1 的线程 T1 发给 P2 的线程 T2 的是同步请求,T2 返回的数据包将直接进入 T1 的私有队列,而不是 P1 的全局队列。
这些机制决定了线程进入等待队列的逻辑:如果一个线程不接收返回包,它应该在全局等待队列中等待新任务;否则,它应在私有等待队列中等待 Server 的返回。比如 T1 向 T2 发送同步请求后,必须等待在私有队列中,否则收不到 T2 的返回包。
这实际上是驱动对通信双方施加的限制条件,体现在应用层就是同步请求中的线程一致性要求:
- Client 端:等待返回包的线程必须是发送请求的线程。不能由一个线程发包,另一个线程等包,否则无法匹配返回路径。
- Server 端:发送返回包的线程必须是收到请求包的线程。因为返回数据包的目的 Binder 不是用户指定的,而是驱动记录在收到请求的线程上下文里。如果发送返回包的线程不对,驱动将无法定位目标。
接下来看看驱动如何处理同步和异步交互。两者的区别在于,同步交互的发送端发出请求后需等待返回,而异步交互发出后即结束。对于请求包,驱动若简单粗暴地全部丢入接收端的 to-do 队列处理,可能会引发问题。因此,驱动对异步交互做了限流,优先保障同步交互。
具体做法是:对于某个 Binder 实体,只要有一个异步交互未处理完毕(正在被线程处理或在任意 to-do 队列中排队),后续发给该实体的异步交互包就不会投递到 to-do 队列,而是阻塞在该实体专用的异步交互接收队列(Binder 节点的 async_todo 域)中。期间同步交互不受限制,直接进入 to-do 队列获得处理。直到前一个异步交互完成,下一个异步交互才能脱离队列进入处理流程。
这么做的原因很实际:同步交互的请求端需要等待返回,必须迅速处理以免影响响应速度;而异步交互属于'发射后不管',稍微延时不会阻塞其他线程。通过专用队列暂存过多的异步交互,可以避免突发流量挤占 Server 端的处理能力或耗尽线程池,从而防止阻塞关键的同步交互。
总结
Binder 采用 Client-Server 通信模式,凭借安全性好、简单高效的特点,加上其面向对象的设计思想以及独特的接收缓存管理和线程池管理方式,成为了 Android 进程间通信的中流砥柱。

