6.5 拓展思考

这一节,让我们进一步讨论和思考几个与Binder有关的问题。这几个问题和Binder的实现有关系。先简单介绍一下与Binder驱动相关的内容:

Binder的驱动代码在kernel/drivers/staing/android/binder.c中,另外该目录下还有一个binder.h头文件。Binder是一个虚拟设备,所以它的代码相比而言还算简单,读者只要有基本的Linux驱动开发方面的知识就能读懂它。

/proc/binder目录下的内容可用来查看Binder设备的运行状况。

6.5.1 Binder和线程的关系

以MS为例,如果现在程序运行正常,此时MS则:

1)通过startThreadPool启动一个线程,这个线程在talkWithDriver。

2)主线程通过joinThreadPool也在talkWithDriver。

至此我们知道,有两个线程在和Binder设备打交道。这时在业务逻辑上需要与ServiceManager交互,比如要调用listServices打印所有服务的名字,假设这是MS中的第三个线程。按照之前的分析,它最终会调用IPCThreadState的transact函数,这个函数会talkWithDriver并把请求发到ServiceManager进程,然后等待来自Binder设备的回复。那么现在一共有三个线程(不论是在等待来自其他Client的请求,还是在等待listService的回复)都在talkWithDriver。

ServiceManager处理完了listServices,把回复结果写回Binder驱动,那么,MS中哪个线程会收到回复呢?此问题如图6-6所示:

6.5 拓展思考 - 图1

图 6-6 本问题的示意图

显而易见,当然是调用listServices的那个线程会得到结果。为什么?因为如果不这么做,则会导致下面的情况发生:

如果是没有调用listServices的线程1或线程2得到回复,那么它们应该唤醒调用listServices的线程3。因为这时已经有了结果,线程3应该从listServices函数调用中返回。

这其中的线程等待、唤醒、切换会浪费不少宝贵的时间片,而且代码逻辑会极其复杂。

看来,Binder设备把发起请求的线程牢牢地拴住了,必须收到回复才会放它离开。这种一一对应的方式极大简化了代码层的处理逻辑。

说明 想想socket编程吧,同一时刻不能有多个线程操作socket的读/写,否则数据会乱套。

另外,我们在分析executeCommand的时候,特别提到了BR_SPAWN_LOOPER这个case的处理,它用于建立新的线程来和Binder通信。什么会收到这个消息呢?请读者研究Binder的驱动。如果有新发现,请告诉我,大家再一起学习。