3

Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击

 1 year ago
source link: https://blog.csdn.net/yiranfeng/article/details/105233936
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

摘要:本节主要来讲解Android10.0 Binder的数据是如何完成定向打击

阅读本文大约需要花费30分钟。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

欢迎关注我的公众号!

20200113052246361.jpg

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

《日志系统篇》

《Binder通信原理》

  《HwBinder通信原理》

《编译原理》

前面介绍了Binder驱动的机制、ServiceManager加载的流程、Native层服务注册、获取的流程,但是这些都是浮在表面的接口,真正的数据时如何传输的?协议码如何转向,让我们这一节深入来分析一下    

2.Binder的线程与进程

对于底层Binder驱动,通过binder_procs链表记录所有创建的binder_proc结构体,binder驱动层的每一个binder_proc结构体都与用户空间的一个用于binder通信的进程一一对应,且每个进程有且只有一个ProcessState对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个IPCThreadState对象,IPCThreadState对象也是单例模式,即一个线程对应一个IPCThreadState对象,在Binder驱动层也有与之相对应的结构,那就是Binder_thread结构体。在binder_proc结构体中通过成员变量rb_root threads,来记录当前进程内所有的binder_thread。

如下图所示:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

Binder线程池:每个Server进程在启动时会创建一个binder线程池,并向其中注册一个Binder线程;之后Server进程也可以向binder线程池注册新的线程,或者Binder驱动在探测到没有空闲binder线程时会主动向Server进程注册新的的binder线程。对于所有Client端进程的binder请求都是交由Server端进程的binder线程来处理的。

3 Binder传输过程

Binder-IPC机制,就是指在进程间传输数据(binder_transaction_data),一次数据的传输,称为事务(binder_transaction)。

对于多个不同进程向同一个进程发送事务时,这个同一个进程或线程的事务需要串行执行,在Binder驱动中为binder_proc和binder_thread都有todo队列。

也就是说对于进程间的通信,就是发送端把binder_transaction节点,插入到目标进程或其子线程的todo队列中,等目标进程或线程不断循环地从todo队列中取出数据并进行相应的操作。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

在Binder驱动层,每个接收端进程都有一个todo队列,用于保存发送端进程发送过来的binder请求,这类请求可以由接收端进程的任意一个空闲的binder线程处理。

接收端进程存在一个或多个binder线程,在每个binder线程里都有一个todo队列,也是用于保存发送端进程发送过来的binder请求,这类请求只能由当前binder线程来处理。

binder线程在空闲时进入可中断的休眠状态,当自己的todo队列或所属进程的todo队列有新的请求到来时便会唤醒,如果是由所需进程唤醒的,那么进程会让其中一个线程处理响应的请求,其他线程再次进入休眠状态。

4 Binder协议的演变

下面展示了Binder协议码的演变过程,在Android9.0之前,当client向Binder驱动发送BC_TRANSACTION,Binder驱动唤醒Server进程时,会向client进程发送BR_TRANSACTION_COMPLETE,现在这一步被移到了 唤醒Client之后再做,减少了数据延迟。

Android9.0之前的协议码流程:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

Android9.0及之后的协议码流程:

上面第5步的 BR_TRANSACTION_COMPLETE 被延迟到 第 10步 ,Android做了deferred_thread_work,延迟 TRANSACTION_COMPLETE,因此不会立即返回到用户空间;这允许目标进程立即开始处理此事务,从而减少延迟。然后,当目标回复(或出现错误)时,我们将返回TRANSACTION_COMPLETE。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

5.通信流程-binder如何定向打击

在 《Native-C\C++实例分析》 一节中,我们知道了Native层的服务注册和获取流程,我们以服务注册为示例来继续分析

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

5.1 talkWithDriver()

当BpBinder 调用transact()准备进行事务处理,其中发送的语义是BC_TRANSACTION.

IPCThreadState waitForResponse()中启动了while循环来和binder驱动进行通信,进行事务处理。通过talkWithDriver进行通信.

talkWithDriver()用来不停的和Binder驱动进行通信,ioctl()函数在传递BINDER_WRITE_READ语义时,既会使用“输入buffer”,也会使用“输出buffer”,所以IPCThreadState专门搞了两个Parcel类型的成员变量:mIn和mOut。mOut中的内容发出去,发送后的回复写进mIn。

BINDER_WRITE_READ的命令发给Binder驱动后,ServiceManager有个循环不停的解析Binder驱动的BINDER_WRITE_READ信息,调用binder_parse()进行解析,我们前面addService()时,传入的code为ADD_SERVICE_TRANSACTION,在ServiceManager中解析后,对应的值为SVC_MGR_ADD_SERVICE,最终把服务的name和handle(对象) 存入到svclist中,ServiceManager再通过binder_send_reply()把返回的数据发送出来,在talkWithDriver()中填入mIn中,waitForResponse()进行最终的语义处理,发送出去。

第一次mOut里面存的是BC_TRANSACTION及binder_transaction_data的值,mIn没有值,talkWithDriver()中此时,bwr.write_size >0 ; bwr.read_size = 0,调用ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) 和binder驱动进行交互。在Binder驱动一节,我们了解到,当write_size>0, read_size=0时,会进入binder_thread_write()

流程转换:binder_ioctl()->binder_ioctl_write_read()->binder_thread_write()

5.2 binder_thread_write

主要是从用户空间拷贝binder_transaction_data的数据,并传给binder_transaction()函数进行实际的传输。

5.3 binder_transaction

流程:

  1. 我们向ServiceManager注册服务时时,Native中 new BpBinder(0,xxx),这里的handle=0,在binder驱动中找到了目标binder_node--target_node, 指向ServiceManager; 如果此时的handle 不为0,会根据handle 找到对应的binder_ref节点,最终找到binder_node;
  2. 根据target_node找到目标binder_proc--target_proc;
  3. 创建binder_transaction节点,并将其插入目标进程的todo列表;
  4. 尝试唤醒目标进程。

处理Binder实体与Handle转化的时候,有下面几点注意的:

  1. 第一次注册Binder实体的时候,是向别的进程注册的,ServiceManager,或者SystemServer中的AMS服务
  2. Client请求服务的时候,一定是由Binder驱动为Client分配binder_ref,如果本进程的线程请求,fp->type = BINDER_TYPE_BINDER,否则就是fp->type = BINDER_TYPE_HANDLE。
  3. Android中的Parcel里面的对象一定是flat_binder_object

本地是从用户空间进入binder_transaction(),因此这里的proc、thread为前面IPCThreadState 所在进程的内容。

整理了一张binder_transaction的示意图:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

Client的数据传来后,进行事务处理后,唤醒Server端,Server端进行数据读取。假设我们这里的Server端为ServiceManager,在ServiceManager中有一个循环,不停的向Binder驱动发送读写的信息,读到内容后调用binder_parse()进行解析。

binder_loop()中请求时,write_size=0,到binder驱动中会进入binder_thread_read

5.3 binder_thread_read

         由于本次是通过Servicemanager进入了binder_thread_read(),因此,这里的proc,thread都是ServiceManager的内容。

当目标进程ServiceManager被唤醒时,会接着执行自己的binder_thread_read(),尝试解析并执行那些刚收来的工作。

流程:

  1. 利用wait_event_xxxx()让自己挂起,等待下一次被唤醒;
  2. 唤醒后找到合适的待处理的工作节点,即binder_transaction节点;
  3. 把binder_transaction中的信息整理到一个binder_transaction_data中;
  4. 整理一个cmd整数值,具体数值或者为BR_TRANSACTION,或者为BR_REPLY;
  5. 将cmd数值和binder_transaction_data拷贝到用户态;
  6. 如有必要,将得到的binder_transaction节点插入目标端线程的transaction_stack堆栈中。

如果没有工作需要做,binder_thread_read()函数就进入睡眠或返回,否则binder_thread_read()函数会从todo队列摘下了一个节点,并把节点里的数据整理成一个binder_transaction_data结构,然后通过copy_to_user()把该结构传到用户态。

5.4 目标端事务处理

5.4.1.当ServiceManager为Server端时

  1. 当Binder驱动把 BR_TRANSACTION 放入用户空间后,其实此时ServiceManager为Server端,那么进入ServiceManager,binder_parse()解析BR_TRANSACTION 调用svcmgr_handler处理,code=SVC_MGR_ADD_SERVICE, 然后调用binder_send_reply() cmd_reply=BC_REPLY,cmd_free=BC_FREE_BUFFER, 发给Binder驱动,其中write_size > 0
  2. Binder驱动读取ServiceManager传来的数据,进入binder_transaction(),cmd=BC_REPLY,唤醒Client进程,把BR_TRANSACTION_COMPLETE分别发给ServiceManager 和Client进程
  3. IPCThreadState 收到BR_TRANSACTION_COMPLETE后,把数据写入mIn,mOut移除内容,继续调用talkWithDriver(),向Binder驱动发起BINDER_WRITE_READ请求,此时mOut无值,mIn有内容
  4. Binder驱动进入binder_thread_read(),根据ServiceManager发来的reply数据,发送BR_REPLY给client
  5. IPCThreadState waitForResponse()收到BR_REPLY后,释放内存空间

5.4.2 当ProcessState中的服务为Server端时

  1. 当Binder驱动把 BR_TRANSACTION 放入用户空间后,waitForResponse()读到CMD=BR_TRANSACTION,会调用executeCommand()进行处理,最终调用到BBinder的transact(),最终会调到onTransact()
  2. 在调用完transact()动作后,executeCommand()会判断tr.flags有没有携带TF_ONE_WAY标记,如果没有携带,说明这次传输是需要回复的,于是调用sendReply()进行回复

6.总结:

6.1 服务注册

服务注册过程(addService)核心功能:在服务所在进程创建binder_node,在servicemanager进程创建binder_ref。其中binder_ref的desc在同一个进程内是唯一的:

  1. 每个进程binder_proc所记录的binder_ref的handle值是从1开始递增的;
  2. 所有进程binder_proc所记录的handle=0的binder_ref都指向service manager;
  3. 同一个服务的binder_node在不同进程的binder_ref的handle值可以不同;

   在《Native-C\C++实例分析》一节,我们说到了 MediaPlayerService 向ServiceManager 进行服务注册的流程,其中 MediaPlayerService是Client,ServiceManager是Server进程,通信流程图如下所示:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

过程分析:

  1. MediaPlayerService进程调用ioctl()向Binder驱动发送IPC数据,该过程可以理解成一个事务binder_transaction(记为T1),执行当前操作的线程binder_thread(记为thread1),则T1>from_parent=NULL,T1>from=thread1,thread1>transaction_stack=T1。
  2. Binder驱动收到该Binder请求,生成BR_TRANSACTION命令,选择目标处理该请求的线程,即ServiceManager的binder线程(记为thread2),则 T1>to_parent = NULL,T1>to_thread = thread2。并将整个binder_transaction数据(记为T2)插入到目标线程的todo队列;
  3. ServiceManager不断地从todo链表检查是否有节点,如果有的话就会摘取下来并解析、执行,ServiceManager的线程thread2收到T2后,调用服务注册函数将服务”media.player”注册到服务目录中。当服务注册完成后,生成IPC应答数据(BC_REPLY),T2>form_parent = T1,T2>from = thread2, thread2>transaction_stack = T2。
  4. Binder驱动收到该Binder应答请求,生成BR_REPLY命令,T2>to_parent = T1,T2>to_thread = thread1, thread1>transaction_stack = T2。在MediaPlayerService收到该命令后,知道服务注册完成便可以正常使用。

整个过程中,BC_TRANSACTION和BR_TRANSACTION过程是一个完整的事务过程;BC_REPLY和BR_REPLY是一个完整的事务过程。到此,其他进行便可以获取该服务,使用服务提供的方法。

6.2获取服务

获取服务(getService)过程,就是向servicemanager进程查询指定服务,当执行binder_transaction()时,会区分请求服务所属进程情况:

  1. 当请求服务的进程与服务属于不同进程,则为请求服务所在进程创建binder_ref对象,指向服务进程中的binder_node;最终readStrongBinder(),返回的是BpBinder对象
  2. 当请求服务的进程与服务属于同一进程,则不再创建新对象,只是引用计数加1,并且修改type为BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER。最终readStrongBinder(),返回的是BBinder对象的真实子类

至此,Binder的定向打击,IPC数据流转就介绍完成了,下一节我们一起来看看Binder在Framework层是如何工作的。

我的微信公众号:IngresGe

20200113052246361.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK