Binder驱动中的流程详解

news2024/9/17 7:36:16

1.前言

        作为安卓系统中重要的IPC通信机制,Binder通信使得组件之间的通信更加的高效和灵活。但在实现上,Binder通信横跨了整个Android系统架构,从应用层到frameworks,再到native,最后到底层kernel,这使得Binder流程会变得相对复杂。为了更好的理清Binder的流程,讲清楚它到底是如何通信的,本文将结合源码,完整的梳理一遍整个通信过程中的几个重要流程。因为主要的数据包转发、binder线程选取以及资源的调配管控都在binder驱动中实现,故分析将会重点放在内核部分的实现上。

        本文选择了比较新的安卓14源码+kernel_6.1版本进行分析,为了使整个binder驱动中通信流程的分析更连贯,代码将主要集中在native和kernel这两层进行讲解,不涉及java层。为了使行文更加高效,需要读者对binder架构和重要的数据结构有一定了解,文中不再重复。     

安卓14源码下载:https://source.android.com/docs/setup/download/downloading?hl=zh-cn

kernel_6.1源码:https://elixir.bootlin.com/linux/v6.1.74/source      

2.Binder通信概述

        binder通信是典型的C/S架构,即Client和Server通信,而servicemanager充当名字服务器的角色记录server的信息,Client和Server之间的依赖binder驱动进行交互。通信框架流程如下图:    

a0f3f8cdeb3151d7ad62ba6f5a3fb103.png

图1-binder通信框架

        从图上交互流程看,binder通信中重点分为注册(addservice)和请求服务(getservice)两大部分,server必须向servicemanager注册后,其它client才能去servicemanager中找到对应服务,并建立联系,从而实现binder请求。这个流程中涉及3个重要的信息,即如何找到对方、需要什么事情、传输的数据是什么。

        先解答第一点,如何找到对方。先回顾下binder通信中几个基础知识:    

21c136a26b30138593be5e7250da8a6b.png

图2-Client-Server查找流程图

lBpBinder是客户端的Binder代理对象,作用是通过它实现对server端方法的远程调用;它能通过在servicemanager查询到的handle,在内核中找到与之对应的binder_ref;

lBBinder是server端的服务对象,用来提供服务;它在内核中的是以binder_node的形式存在;

l每一个server都能提供多个services组件,每个services会有很多进程需要需要向它请求服务,在内核中表示为每个binder_node会有多个binder_ref引用,每个binder_ref都指向对应的binder_node;

lbinder通信中是通过handle来找到对方的,驱动中约定可以通过handle=0找到servicemanager。        

3.注册服务

3.1 总览

    注册服务其实就是将需要注册服务的server作为client端,通过handle=0,去找servicemanager的一次binder通信。通信的内容是,将server中的组件信息注册到servicemanager中的MAP中,便于其它client去跟它通信时,查找servicemanager获取handle而建立联系。同时,还需要在内核建立自己service对应的binder_node,建立联系就是通过handle找到binder_ref,从而找到binder_node的过程。

3.2 客户端流程

注册服务在客户端的流程如下:

9dd1eb4b577d24469c590111abca5eaa.png

图3-addservice客户端流程

     addService方法传入的是服务的名字和service组件对象实体,Android 11 开始,SM 放弃了较底层的接口,转向 libbinder 库和 AIDL。这里有两个 IServiceManager,一个在 libbinder 中,是 android 的 name space,直接被用户#include使用。 另一个是aidl自动生成的 android os 的 name space,被上面的 libbinder 所使用 。

BpServiceManager的接口在aidl中实现。

(代码位置:out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_*_shared/*/gen/aidl/android/os/IServiceManager.cpp)d437419159832385747a436c10f4f8ac.png

name和service在addService方法①和②处写入到parcel中,它是用于通信的数据包。这里需要重点讲下writeStrongBinder(service),它是实现图3中步骤④在内核中创建binder_node的关键。writeStrongBinder方法的实现在flattenBinder方法中。

(代码位置:frameworks/native/libs/binder/Parcel.cpp)

55706401a5432e39bfb22ea76741d828.png
0bdac4369daf944757b49519cffd297c.png

addservice是注册服务,走②处else分支,将obj.hdr.type设置成了BINDER_TYPE_BINDER,并将①处新生成的BBinder实体给到cookie,这些cmd和信息将在后面驱动中用于创建binder_node。接着来到了BpBinder的实现transact中。

(代码位置:frameworks/native/libs/binder/BpBinder.cpp)    

bea077766b2aa4cce313b76166584002.png

这里有一个重要的步骤,获取handle传递到IPCTHreadState中,去找servicemanager,handle必然为0,那这里binderhandle()的值是从哪来的呢?

(代码位置:frameworks/native/libs/binder/BpBinder.cpp)

61c31e981017a09c3e28abcfa20fe570.png601227be243b15a914690403d3594964.png

从这里可以看到handle来自于mHandle,在BpBinder创建实体是在构造函数中写入。这里就不得不说一个重要的函数defaultServiceManager(),这个方法主要用于获取ServiceManager的代理对象,用于和servicemanager通信,servicemanager创建BpBinder正是在这里做的。在创建BpBinder时写入的handle为0。这部分代码就本文先不分析了,可参考https://blog.csdn.net/tkwxty/article/details/103034523            

      终于来到了IPCThreadState中,libbinder和驱动沟通的地方在在此处,这里主要设置①写数据(writeTransactionData)和②等待响应(waitForResponse)。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)

eb2365708b31f9e67e59ad3175444ab6.png

而writeTransactionData中会创建binder_transaction_data类型的tr,①处将重要的handle/code/data等信息存进去,并在②处写入IPCThreadState类型的mOut中,用于后续ioctl真正向内核拷贝数据。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)    

dfa75a7f20364c3f2e997fece7400a73.png

waitForResponse中主要是通过talkWithDriver函数在①向驱动写入ioctl指令和传输buffer地址,用于数据拷贝。然后等待binder驱动的回应,处理BR指令。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)de76929f37c2f3d75514cf5f3dac0551.png             

这里涉及到另一个很重要的数据结构binder_write_read,它和binder_transaction_data结构体一样,是内核空间和用户空间通用的。binder_wirte_read数据结构用于内核空间和用户空间读写数据交换,binder_transaction_data数据结构则是交换数据中跟在BC/BR指令后的数据包。这里借用一张大佬的图来讲述它们之间的关系。binder驱动中也是按照这种关系读取上层写入的数据,稍后会介绍。    

a9a4d086a267306b8499c1644e78a08b.png

图4-binder_write_read数据结构组成(图片来源:参考3)

至此,上层Client到驱动写入数据的流程就结束了,总结一下,主要做了这几件事:

1.向servicemanager注册新的服务时,传入了服务名和一个新创建的组件对象;

2.新建flat_binder_object对象,写入组件对象的BBinder地址和指令BINDER_TYPE_BINDER;

3.新建binder_transaction_data对象,写入handle/code/cookie/传输data等信息;

4.新建binder_write_read对象,将BC_TRANSACTION和上面的binder_transaction_data数据封装好,写入binder_write_read的write_size中;

5.最后,通过ioctl向binder驱动写入BINDER_WRITE_READ命令和binder_write_read对象的地址,等待内核回应。

3.3 驱动中的流程

     接下来进入内核空间,binder_ioctl读取BINDER_WRITE_READ命令后,会进入binder_ioctl_write_read函数中,这个函数会首先根据传下来的binder_write_read地址,①处将数据拷贝到内核空间,这还不是真正意义上的binder通信数据拷贝。②处正式开始处理binder_write_read数据。    

(代码位置:kernel_6.1/drivers/android/binder.c)

1beb8fae58e54d7ec8a4516fe4ddc0a3.png

binder_write_read中根据writer_buffer的地址,循环去解析cmd和数据,直到全部解析完。write_buffer一次是可以装入多个命令和数据的。对于BC_TRANSACTION命令来说,会进入binder_transaction函数对其进行处理。

(代码位置:kernel_6.1/drivers/android/binder.c)    

16346bbbaa5339eda99cc2af05dae4f4.pngd0621ff80dbc19ee2ad2361973dc83bf.png

     binder_transaction是binder驱动中的核心函数,上面列出了注册服务对应BC_TRANSACTION命令在这个函数中走到的几个关键流程。①和②是根据上层传下来的handle值查找binder_ref,然后找到binder_node的流程。对于addService来说,传下来的handle=0,走else分支,可以看到这里直接找到的target_node就是servicemanager服务组件对应到内核的binder_node。根据这个binder_node即可确认target_proc为servicemanager。    

       接着④中会创建一个binder_transaction结构体,记录这一次binder通信的全部内容信息,包含发送端线程信息和接收端线程信息,以及这一次binder通信的类型(BINDER_WORK_TRANSACTION)。

    ⑤和⑥是一次重要的转换,即binder(代表binder_node)和handle(代表binder_ref)的转换,在上层介绍flattenBinder方法时,讲述了flat_binder_object结构体,它是是描述Binder对象信息的结构体,用于在两个进程之间进行传递。为什么要做转换呢?是因为这两者只在对应的Client和Server端才起作用,Server端对应binder_node,Client对应binder_ref,传递到对端是不起作用的,必须转换成两端分别能识别的类型才有意义。addservice传递的命令是BINDER_TYPE_BINDER,走binder_translate_binder创建binder_node,这之后必然会创建对应的servicemanager端对应的binder_ref。

     ⑦中主要用于给每个binder_transaction事务寻找一个binder_thread用于处理请求,后续的资源分配章节将会详细讲述这里面的流程。       

这里分析一下binder_translate_binder中的流程。

(代码位置:kernel_6.1/drivers/android/binder.c)    

cace9eaf1498e49e4e4958b195aedf7e.png

     ①中会根据flattenBinder方法中写入的service组件的BBinder地址,从已有的binder_node去查找,但因为是addservice注册服务,这里会走到②中创建新的binder_node,紧接着会去③中去增加binder_ref的强弱引用计数,用于binder对象生命周期的管控。但这里很显然需要新创建的binder_ref,主要是去创建了一个新的ref->data.desc(handle)数值,即这个binder_node在所属的进程servicemanager中的handle值。④则是对这个flat_binder_object进行转换,类型改为BINDER_TYPE_HANDLE,写入新创建的handle值,这个新的结构体将给到servicemanager那边使用。    

     到这里,binder驱动中Client端已经将数据准备好了,接下来需要服务端处理binder_work请求,并将数据重新打包发给servicemanager进程处理。唤醒服务端把数据读走主要是在binder_thread_read函数中做的。

(代码位置:kernel_6.1/drivers/android/binder.c)

beaef2e7242018347e7d33c98d9d2044.png6ea7cc0de8a8df78ec2765f7929b17c7.png3afa29c6933ebc171cf96d4d900984f1.png

      任何一个server在注册自己的service组件后,会在binder_thread_read中等待并处理事务,如果没有事务处理,则会进入阻塞状态。

      上面代码①中确认当前是否有工作(指binder_work)需要处理,即binder_transaction中的一个链表节点,这里是去check线程的传输栈是否为空以及thread->todo中是否有binder_work。如果线程空闲会进去②restore掉继承的priority,然后走到③中等待存在可用的binder_work。    

       之前在binder_transaction函数结尾处,我们已经走binder_proc_transaction函数去选择了线程,如果当前系统不忙,对应的binder_work会直接找到空闲binder线程并将事务插入到thread->todo中。所以这里④会获取list,并在⑤中取链表头的binder_work进行处理。binder_transaction创建的t是BINDER_WORK_TRANSACTION,与这里处理流程⑥对应,还原出t后,此时要做的事情便是⑧中打造一个binder_transaction_data数据结构,和cmd=BR_TRANSACTION一起,在⑨中重新将数据发给上层。注意这里将target_node的ptr和cookie给到了binder_transaction_data,还有code和data等。

       这里⑩是确认是否需要创建一个新的binder_thread,如果当前线程不足,且还未达到注册的max_threads时,可以给上层IPCThreadState发送BR_SPAWN_LOOPER命令新建一个binder_thread。

到这里,addservice注册服务在内核空间的事情处理完了,总结一下:

1.client通过拷贝binder_write_read对象地址到内核空间,确认bwr.write_buffer后,实现对BC协议码+数据的循环解析。BC_TRANSACTION对应的数据在binder_transaction_data中;

2.根据binder_transaction_data中的handle=0直接确认binder_node,找到对端servicemanager,并创建binder_transaction数据结构,赋值本次通信的全部信息;

3.根据上层写入的binder_object_header类型,为本次注册的service组件新建binder_node,并同步给servicemanager创建对应binder_ref;

4.为本次binder请求在对端寻找binder线程,用于处理binder_transaction事务,找到线程后将事务中binder_work节点挂入thread->todo队列处理;    

5.server端根据binder线程状态被唤醒,处理该请求,还原binder_transaction事务,并再次构建binder_transaction_data数据结构,将target_node中的BBinder指针和其它请求强相关的数据写入该结构;

6.server端将BR协议码(BR_TRANSACTION)和构造好的binder_transaction_data数据发给IPCThreadState处理。

3.4 服务端流程

注册服务在服务端的流程如下:

f271acba373bbe885cd99c8a31fd519d.png

图5-addService服务端流程

      数据兜兜转转,来到了用户空间(服务端)。3.3中驱动指令并非返回到Client端,而是返回到Server端,对应servicemanager。servicemanager进程在启动的时候,会通知驱动(BC_ENTER_LOOPER)自己已做好准备接收请求,监听驱动 fd ,有消息时回调到 handleEvent 处理binder 调用。

(代码位置:frameworks/native/cmds/servicemanager/main.cpp)    

32ca743676efe2eac8fa942e632ee8f3.png

之前讲waitForResponse方法时有提到,它在给驱动发完ioctl指令时(①处),会等待驱动回应,其实就是在等驱动发过来的BR指令。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)

a834bf7afa789400b4f71c0a5f516db3.png

驱动发到IPCThreadState的数据存在mIn中,此时在②中读出cmd=BR_TRANSACTION,进入③中executeCommand函数进行处理。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)    

3cffa5a7d28e1d33ed6fac50dcabe5c0.pngdfe127045fcb74b684e26624ae02ed26.png

executeCommand函数中根据①BR_TRANSACTION,对在内核构建的binder_transaction_data数据进行读取得到tr.cookie这个service组件BBinder的地址,并在③中调用transact方法,传递code/buffer等信息。这里transact的实现在Binder.cpp中,最终会调用onTransact(code, data, reply, flags)方法,onTransact是一个虚函数会调用到其子类BnServiceManager的onTransact实现。

IPCThreadState::executeCommand(int32_t cmd)

|---reinterpret_cast(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags);    

|    |---BBinder::transact(

    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

|    |    |---BnServiceManager::onTransact(uint32_t _aidl_code, ...)

现在我们终于来到了server端addservice实现的地方,前面提到的IServiceManager.cpp,由aidl生成。

(代码位置:out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_*_shared/*/gen/aidl/android/os/IServiceManager.cpp)

569fc959d536903c8cb4afbe536e8747.png5b3b05397112de6a65fd2bbc9aeeeb41.png

这里①中code就是之前Client端在调用BpServiceManager::addService方法时传入的参数TRANSACTION_addService。同样②中name和service分别为我们要注册的服务名和一个IBinder类型变量。以上信息经历了漫长的过程,传递到了server(servicemanager)端。此时在③中即可调用addservice的实现了。    

      这里需要重点看下_aidl_data.readStrongBinder(&in_service)这个方法,它去拿到了in_service这个IBinder类型的对象,这个对象是怎样产生的?接着往下看。

frameworks/native/libs/binder/Parcel.cpp

readStrongBinder(sp* val)

|---readNullableStrongBinder(val)

|    |---unflattenBinder(val)

|    |    |---getStrongProxyForHandle(flat->handle)

|    |    |---finishUnflattenBinder(binder, out)

(代码位置:frameworks/native/libs/binder/Parcel.cpp)

858622e524c199e6616561553dda916e.png

unflattenBinder方法先在①中读到flat_binder_object对象,还记得bp端的writeStrongBinder调用flattenBinder方法吗?在构造flat_binder_object时写入了BINDER_TYPE_BINDER和BBinder对象指针。而这里是反的过程,flat->hdr.type显然是BINDER_TYPE_HANDLE,然后进入流程②,因为在之前binder驱动的分析中,type在binder_translate_binder函数中创建binder_node和生成servicemanager对应的binder_ref后,经过了转换,handle被赋值成了binder_ref对应的desc描述符。这里③getStrongProxyForHandle方法会根据传入的handle,生成BpBinder。这里是去获取ServiceManager对应binder_ref(注册的Client的binder_node的引用)的desc作为handle的BpBinder对象。它会被当成一个IBinder对象通过addService的第二个参数传递进去。            

      接着看servicemanager服务端的addService实现,如下:

(代码位置:frameworks/native/cmds/servicemanager/ServiceManager.cpp)   

609d050e97b6ad5c27c5ac9e00cb551e.png

该方法中的实现相对简单,①去检查service name是否合法,然后在去②mNameToService中去检查是否有存在的服务。这里mNameToService 是一个 map,以服务的名称为 key,用来记录被注册的服务名和对应的 IBinder 信息等。③则是新建一个Service,将BpBinder存到服务名对应的Service中。

总结一下,离开binder驱动后,server端(servicemanager)主要在读取数据。

1.服务端servicemanager在进程启动时就会去监听fd等待消息,有消息会走到IPCThreadState中getAndExecuteCommand方法,接收BR指令+数据。本次通信中返回的是BR_TRANSACTION+binder_transaction_data;    

2.executeCommand函数中会处理BR_TRANSACTION指令,并获取data中的BBinder指针cookie,调用该类型中的transact方法,最终调用子类BnServiceManager的onTransact实现;

3.onTransact中调用readStrongBinder方法,根据传入的handle值,生成对应的BpBinder实例并返回;

4.servicemanager的addservice实现中,将注册的service名和这个IBinder类型的BpBinder对象,一起写入mNameToService这个Map中,供后续client端请求时查找对应的service信息。

3.5 总结

      上面将服务通过addservice去注册的流程全部梳理了一遍,简单概括这个流程:这是一次handle=0通往对端servicemanager的通信,其中在驱动中创建binder_node,在服务对端创建binder_ref,并将引用所对应的handle信息保存到servicemanager中的。        

4.获取服务

4.1 差异点

     获取服务的流程和注册服务的流程在client端的流程几乎是相同的,差异是getService传入的参数是service的名字,通过这个名字去serviceamanager中获取对应的IBinder对象,因为它不用像addService还需要传入service组件对象来注册自己。

      那么问题来了,我们通过service名获取的IBinder对象,从之前的分析可知它里面存的是servicemanager对注册服务binder_node的引用(对应handle),而当前我们去getService的进程和servicemanager是两个独立的进程,这个handle通用吗?我们是直接拿这个handle到client端使用的吗?    

4.2 获取服务在SM端流程

      带着上面的问题,我们从servicemanager端的getService实现部分开始分析,首先看aidl生成IServiceManager.cpp文件中的BnServiceManager::onTransact方法:

(代码位置:out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_*_shared/*/gen/aidl/android/os/IServiceManager.cpp)

0253e9e1e2dbb05bbc2dfe2ee9ed5ee2.png

①这里读到Client端getService传过来的service名,然后通过②传入servicemanager端getService的实现函数中。tryGetService在mNameToService中通过in_name查找到注册的服务后,返回service->binder,即IBinder类型的BpBinder对象。

frameworks/native/cmds/servicemanager/ServiceManager.cpp    

getService(const std::string& name, sp* outBinder)

|    tryGetService(name, true)

这里相对于addService多了一个步骤③,即将BpBinder传入writeStrongBinder中,这个函数之前也分析过(在Client端将的BBinder实体给到cookie,用于后续再驱动中创建binder_node),这里很显然不是这个用途。我们直接分析flattenBinder中的流程。

(代码位置:frameworks/native/libs/binder/Parcel.cpp)

453a629803c8450b807295a7a2f089cb.pngcdd56cad490f8648fb26c7991be46584.png    

因为我们传入的是IBinder类型BpBinder对象,①处肯定获取不到BBinder对象,local=NULL,所以会走到②分支。这里将flat_binder_object 类型的type设置成BINDER_TYPE_HANDLE,并赋值了BpBinder中的handle值。这些数据会存到_aidl_reply这个parcel类型的数据包中。

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)

0fdc73fc432bdd4db5333715ea63e769.png7cf68f6a2e5c4e099a1b3f9816653cd0.png

回到IPCThreadState的executeCommand方法,在执行完BR_TRANSACTION的流程①后,紧接着需要给驱动下发BC_REPLY指令,带上刚才的_aidl_reply数据。进入②处sendReply。    

(代码位置:frameworks/native/libs/binder/IPCThreadState.cpp)

2ad1d772e186fbf20aa2bdc52c751a23.png

sendReply方法中流程清晰,直接将BC_REPLY指令和刚才的reply数据包写入mOut中,reply数据包依然会将关键的信息存入binder_transaction_data用于和cmd形成一对组合用于驱动解析。接着通过②waitForResponse写入binder驱动。

4.3 驱动中的流程

(代码位置:kernel_6.1/drivers/android/binder.c)    

38205b8d94ed4a5ab11f554731fd67cb.png

binder_ioctl到BC_REPLY之间的逻辑关系和数据解析过程,之前已经详细分析过了,这里直接来到binder_transaction函数看差异部分。因为是BC_REPLY,所以走①处的reply分支,在addService的流程分析中,内核要找到对端借助了handle=0确认binder_node,然后找到binder_proc。但这里没有去查找Client端的binder_node,原因是Client端在发binder请求时,已经在binder_transaction这个事务中记录了发送端的进程和线程信息和接收端的进程和进程信息binder_thread和binder_proc,通过②处获取。直接找到此时的对端(之前的Client端)。

binder_get_txn_from_and_acq_inner(in_reply_to)

|---binder_get_txn_from(t)

|    |---from = t->from

③处是本次通信过程中很重要的一次转换,addService对应的驱动流程中是将flat_binder_object实体中BINDER_TYPE_BINDER类型的type转换成BINDER_TYPE_HANDLE类型,并写入新注册的binder_node所对应servicemanager的binder_ref的desc作为handle,传给上层;而这里是在怎样转换,做了哪些事情呢?    

(代码位置:kernel_6.1/drivers/android/binder.c)

589f1a7042b311b5bc7a7960238449b7.png3109d70a5ca8a7b8a0e4f1e3a5d8e2c3.png

binder_translate_handle函数中①首先去获取了node,传入的参数是flat_binder_object中的handle,这个handle反复说过是servicemanager相对于我们注册的新service组件的binder_ref应用的desc描述符,所以找到的binder_node肯定就对应注册的组件。

binder_get_node_from_ref(proc, fp->handle,fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata)

  ==》binder_get_ref_olocked(proc, desc, need_strong_ref)

    ==》遍历proc->refs_by_desc.rb_node,根据desc去找到对应的ref

    ==》node = ref->node

接着②处看node->proc是否就是target_node,这里如果发现此node对应的服务进程和目标进程是相同的,说明Client通过servicemanager去获取Client中的服务,此时只需要直接将flat_binder_object转换成实体binder,并赋值service组件的BBinder地址返回给Client端。    

      这里通常走的是else分支,即不是去获取同一个进程中的服务,因为不是同一个进程,而我们又先获取了服务的binder_node,那必然要为Client创建一个该binder_node的binder_ref引用,④处就是做这个事情。然后⑤处将这个binder_ref对应的desc给到flat_binder_object的handle中。

(代码位置:kernel_6.1/drivers/android/binder.c)

b91716d93b0982b797cf3d7323aecef6.png

binder_inc_ref_for_node函数的作用是增加对应binder_node的binder_ref的引用计数,用于生命周期的管控。但首先需要通过①去获取到binder_ref,对于第一次获取服务去建立的联系,肯定是无法在refs_by_node这颗红黑树中找到binder_ref的,所以会走②中的流程,去新建一个binder_ref,在binder_get_ref_for_node_olocked函数中,很重要的步骤是去创建一个新的new_ref->data.desc,就是最终的handle,该函数会去遍历refs_by_desc红黑树,寻找一个最小的且空闲的整数值赋值给这个新创建的binder_ref。    

(PS:这里遍历红黑树新建desc的过程相对比较耗时,但6.6内核即将上一种新的bitmap查找方式替代红黑树查找,测试提升性能巨大,https://lore.kernel.org/all/20240417191418.1341988-1-cmllamas@google.com/)

②中创建好新的binder_ref后,会走到③处增加ref的强弱引用计数。         

经过上面的分析,binder_translate_handle函数完成了flat_binder_object的转换,对于不是向同一个进程请求服务的情况,被成功赋予了Client端对应binder_ref desc作为handle的值。此时,这个BINDER_WORK_TRANSACTION类型的事务,带着BR_REPLY指令+data,会走到binder_thread_read端,挂入thread->todo上等待被处理。这部分在内容同之前addService的流程,这里就不再展开了。最终,BR_REPLY指令会和构造好的binder_transaction_data一起发给上层IPCThreadState中等待被处理。        

总结一下,获取服务在servicemanager端和binder驱动中的流程:

1.获取服务主要通过getService传递service名去servicemanager里面的mNameToService MAP中,查找到对应的servicemanager相对于注册service组件的BpBinder对象,这个对象中handle是servicemanager这个进程binder_ref引用的desc值;

2.BnServiceManager::onTransact中重新构建flat_binder_object对象,类型设置为BINDER_TYPE_HANDLE,并附上1中的desc值,传递给binder驱动;

3.binder驱动在binder_transaction中,对BC_REPLY命令进行处理,找到target_proc,并将flat_binder_object对象进行转换,即通过flat_binder_object中的handle找到service的binder_node,并对Client端进程新建一个改node的binder_ref引用,重新封装到flat_binder_object对象中;    

4.Client端在binder_thread_read中被唤醒,解析本次BC_REPLY所对应的binder事务(binder_work),并将上面的数据打包成cmd(BR_REPLY)+binder_transaction_data(包含flat_binder_object)的组合,发给上层IPCThreadState中进行解析。

4.4 客户端流程

      Client端在收到BR_REPLY指令后,会在waitForResponse函数中进行处理,并对binder_transaction_data中的数据进行解析,写入reply这个Parcel包中。然后层层返回到IServiceManager.cpp(aidl生成)的BpServiceManager::checkService方法中①处,_aidl_reply即为BR_REPLY返回的对应解析出的数据。

(代码位置:out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_*_shared/*/gen/aidl/android/os/IServiceManager.cpp)

62d86c41164856d571b05af0cca245da.png

接着进到②中,这个方法之前有分析过,它是Parcel这个类的成员,会从_aidl_reply中解析出flat_binder_object数据结构,并根据里面的handle创建新的BpBinder对象(见getStrongProxyForHandle方法中①处),并给到_aidl_return。    

(代码位置:frameworks/native/libs/binder/ProcessState.cpp)

04cc2aedebef52c7338ddcaff9e7b6ed.png

getService在Client端的流程如下,_aidl_return将作为ret返回给Client端。

sm->getService(String16("service"))

|---checkService(name)

|    |---mTheRealServiceManager->checkService(String8(name).c_str(), &ret)        

所以Client端在获取服务时,sp<IBinder >= sm->getService(String16("service"));可以转换成:sp<IBinder > = new BpBinder(handle); 这里的handle便是在binder驱动中,为Client端给服务端binder_node创建的binder_ref应用的desc值。返回Client后,根据这个handle创建BpBinder对象。之后,可以将这个binder对象转换为强引用类型的IService(自己定义的类)类型,并利用binder引用调用远端的方法,实现远程调用了。         

5.总结    

      从上面的addService和getService两个流程来看,Binder驱动主要帮助我们完成了从本地的一个整数值handle到远端实体服务BBinder的转换,大概流程可以用下图来呈现。

e899aafb210c339bd529ec7efd65cd0e.png

图6-binder通信流程(图片来源:参考5)

图中分为native和kernel两层,完整的将注册和获取服务中的关键流程和函数呈现了出来。黑色箭头代表client请求server的过程,绿色直线箭头代表server处理完毕返回结果给client的过程,红框代表数据的变化,展示了client请求server服务及server把结果返回给client的过程。希望可以加深读者看完代码解析后对整个流程的理解。              

参考:

1.http://www.wowotech.net/linux_kenrel/binder1.html Binder从入门到放弃(上)

2.http://www.wowotech.net/linux_kenrel/binder2.html Binder从入门到放弃(下)

3.http://gityuan.com/tags/#binder 袁辉辉的博客

4.Android系统源代码情景分析,罗升阳著

5.https://juejin.cn/post/7062529108219002894 通熟易懂的分析binder

一文读懂QUIC 协议:更快、更稳、更高效的网络通信

深入代码细节看f2fs在磁盘上的组织方式

ANR问题产生原理和分析思路总结

ece8cd259c39474322a67e258f94eb01.gif

长按关注内核工匠微信

Linux内核黑科技| 技术文章| 精选教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1923812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习工具和资源推荐:全面指南

今天我们来聊聊深度学习的工具和资源。要学好深度学习&#xff0c;除了理论知识&#xff0c;还需要掌握一些强大的工具和找到好的资源。以下是我在学习过程中发现的一些非常有用的工具和资源&#xff0c;希望对你们有帮助。 目录 工具推荐 1. Python编程语言 2. TensorFlow…

前端Vue组件化实践:打造灵活可维护的地址管理组件

随着前端技术的不断演进&#xff0c;复杂度和开发难度也随之上升。传统的一体化开发模式使得每次小小的修改或功能增加都可能牵一发而动全身&#xff0c;严重影响了开发效率和维护成本。组件化开发作为一种解决方案&#xff0c;通过模块化、独立化的开发方式&#xff0c;实现了…

zigbee开发工具:3、驱动安装与程序下载(更新中...)

zigbee开发工具前两篇讲解了IAR开发工具的安装与注册&#xff0c;还介绍了新建一个cc2530开发工程的建立与配置。在进行zigbee开发&#xff0c;代码编写编译好后还需要下载到zigbee节点设备上进行调试与验证&#xff0c;那么就需要安装SmartRF Flash Programmer软件 和仿真器等…

【Android面试八股文】谈谈你对Glide框架的缓存机制设计的理解

文章目录 一、引入缓存的目的二、Glide缓存流程2.1 Glide缓存的读取顺序2.2 Glide加载资源流程2.3 Glide 的缓存分层结构三、内存缓存原理四、存取原理4.1 取数据4.2 存数据4.3 为什么要引入软引用?五、磁盘缓存原理(DiskLruCache)5.1 磁盘缓存概述5.2 Glide磁盘缓存策略5.3…

昆明高校大学智能制造实验室数字孪生可视化系统平台建设项目验收

昆明高校大学智能制造实验室的数字孪生可视化系统平台建设项目&#xff0c;正是在这样的背景下应运而生。项目自启动以来&#xff0c;便受到了校方的高度重视和大力支持。经过数月的紧张筹备和精心实施&#xff0c;项目团队克服了种种技术难题&#xff0c;成功完成了系统的开发…

华为模拟器防火墙配置实验(二)

一.实验拓扑 二.实验要求 1&#xff0c;DMZ区内的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9&#xff1a;00 - 18&#xff1a;00&#xff09;可以访问&#xff0c;生产区的设备全天可以访问. 2&#xff0c;生产区不允许访问互联网&#xff0c;办公区和游客区允许…

AI绘画;盘点用stable diffusion 赚钱的10种方式!

前言 stable diffusion 是一种基于文本生成图像的深度学习模型&#xff0c;它可以根据任何文本输入生成逼真的图像。它利用了 CLIP ViT-L/14 文本编码器的文本嵌入和扩散模型的潜在变量&#xff0c;实现了高质量的图像合成。 stable diffusion 可以用于赚钱的10种方式及思路如…

论文精读(保姆级解析)——DiFaReli: Diffusion Face Relighting

前言 该论文发表在2023年ICCV上&#xff0c;主要针对人像重打光问题提出了一种新的方法&#xff0c;下面给出论文和项目链接&#xff1a; papergithub 摘要 提出了一种针对单张图像的重打光方法&#xff0c;作者提到针对非漫反射光照的处理比较困难&#xff0c;早期的工作主要…

三星AI产品发布会精彩回顾

2024年7月10日&#xff0c;三星在其新品发布会上&#xff0c;重磅发布了一系列围绕AI技术的创新产品。此次发布会不仅展示了三星在AI领域的深耕和探索&#xff0c;还在硬件设计、用户体验和生态系统构建上全面对标苹果。本文将详细回顾此次发布会的内容&#xff0c;解析三星如何…

从“Hello,World”谈起(C++入门)

前言 c的发展史及c能干什么不能干什么不是我们今天的重点&#xff0c;不在这里展开&#xff0c;有兴趣的朋友可以自行查阅相关资料。今天我们主要是围绕c的入门程序&#xff0c;写一个“hello&#xff0c;world”&#xff0c;并且围绕这个入门程序简单介绍一下c和c的一些语法&…

事务的学习

一、什么是事务 事务 是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销请求&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 一组操作集合&#xff0c;不可分割&#xff0c;一起向系统提交/…

7.2 AQS原理

AQS 原理 概述 全称是 AbstractQueuedSynchronizer&#xff0c;是阻塞式锁和相关的同步器工具的框架。 特点&#xff1a; 用 state 属性来表示资源的状态&#xff08;分独占模式和共享模式&#xff09;&#xff0c;子类需要定义如何维护这个状态&#xff0c;控制如何获取锁和…

Clion 使用gdbserver调试FreeSWITCH源码

1.准备环境 window安装clion安装好gdb、ssh、已经编译好的freeswitch可执行文件的docker镜像2.配置clion Settings -> Tools ->SSH Configurations Settings-Build, Execution, Deployment-Toolchains(其实设不设置都行,用默认也行的) Settings-Build, Execution, Depl…

JVM内存配置错误导致的线上服务问题

1.现象 大量用户反映不能正常使用服务&#xff0c;导致用户无法进行下一步工作。 2.检查 因为是休息日&#xff0c;初步听到这个消息的时候怀疑是自己的锅。一是因为项目刚刚进行了重构&#xff1b;二是对MySQL数据进行了迁移&#xff0c;并且对待迁移的旧数据进行了数据分析…

【qt】如何读取文件并拆分信息?

需要用到QTextStream类 还有QFile类 对于文件的读取操作我们可以统一记下如下操作: 就这三板斧 获取到文件名用文件名初始化文件对象用文件对象初始化文本流 接下来就是打开文件了 用open()来打开文件 用readLine()来读取行数据 用atEnd()来判断是否读到结尾 用split()来获取…

LabVIEW中modbusTCP怎样才能和profibusDP通信?

在LabVIEW中&#xff0c;Modbus TCP和Profibus DP是两种不同的工业通信协议&#xff0c;要实现这两者之间的通信&#xff0c;可以采用网关设备进行协议转换&#xff0c;或者通过一个中间设备&#xff08;如PLC&#xff09;进行数据桥接。以下是实现此通信的一些方法&#xff1a…

王牌站士Ⅵ--人工智能集成商的崛起

前言 系统集成商 (SI) 具有独特的优势&#xff0c;可以利用 GenAI 的变革力量。通过综合各种数据并重新定义客户获取见解和采取行动的方式&#xff0c;SI 可以发展成为AI 集成商&#xff0c;彻底改变企业的运营方式和与技术的互动方式&#xff0c;当然&#xff0c;还可以保留长…

[人工智能]对未来建筑行业的影响

作者主页: 知孤云出岫 目录 引言1. 人工智能在建筑行业的应用场景1.1 设计阶段1.2 施工阶段1.3 运营和管理 2. 关键技术2.1 机器学习2.2 计算机视觉2.3 自然语言处理2.4 大数据分析 3. 实际案例分析3.1 案例1&#xff1a;利用GAN生成建筑设计方案3.2 案例2&#xff1a;利用计算…

在ROS中控制机器人运动

一、安装Arbotix 输入指令&#xff1a;sudo apt install ros-noetic-arbotix 找到下载的文件&#xff1a;roscore,roscd arbotix 安装好后&#xff0c;不需要按照教程里面的操作&#xff0c;复制进工作空间。 二、在config中建立配置文件&#xff0c;control.yaml # 该文件…

了解并缓解 IP 欺骗攻击

欺骗是黑客用来未经授权访问计算机或网络的一种网络攻击&#xff0c;IP 欺骗是其他欺骗方法中最常见的欺骗类型。通过 IP 欺骗&#xff0c;攻击者可以隐藏 IP 数据包的真实来源&#xff0c;使攻击来源难以知晓。一旦访问网络或设备/主机&#xff0c;网络犯罪分子通常会挖掘其中…