1

Android10.0 Binder通信原理(三)-ServiceManager篇

 1 year ago
source link: https://blog.csdn.net/yiranfeng/article/details/105210069
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通信原理(三)-ServiceManager篇

摘要:本节主要来讲解Android10.0 Binder中守护进程ServiceManager是如何启动、注册、获取服务

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

文章首发微信公众号:IngresGe

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

欢迎关注我的公众号!

20200113052246361.jpg

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

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

《系统启动篇》

《日志系统篇》

《Binder通信原理》

  《HwBinder通信原理》

《编译原理》

在Android中,系统提供的服务被包装成一个个系统级service,这些service往往会在设备启动之时添加进Android系统。当我们某个应用想要调用系统某个服务的功能时,往往是向系统发出请求,调用该服务的外部接口。在上一节我们也了解到,这种外部接口,我们通常称为代理接口,也就是我们要拿到目标服务对应的代理对象。

但是Android中有很多个服务,如果都是直接向目标服务拿对象也不太现实,开销会很大。因此在Android中,引入了一个ServiceManager的系统级服务--也可以称之为Binder的守护进程来管理这些服务对象。

上一节我们大体的了解了Binder的设计思路,了解了Binder机制的四要素:Client、Server、ServiceManager、Binder驱动。其中ServiceManager主要用来管理Server的名称和对象。

ServiceManager本身的工作很简单:注册服务、查询服务、列出所有服务,启动一个死循环来解析Binder驱动读写动作,进行事务处理。

2.Binder架构

Binder通信流程如下:

1)首先服务端需要向ServiceManager进行服务注册,ServiceManager有一个全局的service列表svcinfo,用来缓存所有服务的handler和name。

2)客户端与服务端通信,需要拿到服务端的对象,由于进程隔离,客户端拿到的其实是服务端的代理,也可以理解为引用。客户端通过ServiceManager从svcinfo中查找服务,ServiceManager返回服务的代理。

3)拿到服务对象后,我们需要向服务发送请求,实现我们需要的功能。通过 BinderProxy 将我们的请求参数发送给 内核,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态。然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。

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

3.servicemanager的启动

init进程启动时,加载servicemanager.rc,启动serviecmanager:

20200330220536767.jpg

servicemanager.rc 文件如下:

从上面的rc文件可知道,servicemanager编译后,位于手机的/system/bin中,这是一个Native C/C++的进程,C/C++的进程都会有一个main()的入口,顺藤摸瓜,我们找到了servicemanager的入口:service_manager.c 的main()。

4.service_manager调用栈:

我们在看源码或者调试死机的时候,最喜欢看的就是上下文的调用关系,在此,我列出了service_manager.c 的调用栈信息,暂时先忽略细节部分

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

5. 源码分析

   servicemanager的整体流程和 service管理的结构我们都有了一个整体认识,那么细节部分就得靠看源码来深入理解了。

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

5.1 主程序启动main()

前面我们知道,servicemanager 是一个Native C/C++的程序,那么会存在一个main()的入口,代码如下:

在Android8.0后,谷歌引入Treble机制,binder机制增加了hwbinder和vndbinder,其中vndbinder的守护进程为vndservicemanager。

vndservicemanager和service共用同一份代码,只是传入的参数和宏控制的流程有部分差异。

vndservicemanager会传入参数"/dev/vndbinder", servicemanager使用默认的"/dev/binder".

servicemanager主要做了以下几件事:

1.打开binder驱动,申请了128K的内存空间

2.然后调用binder_become_context_manager()让自己成为整个系统中唯一的上下文管理器,其实也就是service管理器

3.调用binder_loop()进入无限循环,不断监听并解析binder驱动发来的命令。

4.binder_loop()通过binder_parse()进行命令解析,然后调用回调函数svcmgr_handler()进行处理,例如:注册服务、获取服务、列出服务信息

下面我们按步来看看各个回合工作的细节部分。

5.2 binder_open()

servicemanager启动后,先通过binder_open()来打开"/dev/binder", 代码如下:

binder_open()的工作也比较简单,分为以下几步

1.通过系统调用open()来打开"/dev/binder",获得一个句柄信息,在Binder驱动中对应的是函数binder_open()

2.通过ioctl 获取binder的版本信息,比较binder协议版本是否相同,不同则跳出,在Binder驱动中对应的是函数binder_ioctl()

3.通过mmap内存映射128K的内存空间,即把binder驱动文件的128K字节映射到了内存空间,这128K内存空间为servicemanager使用。在Binder驱动中对应的是函数binder_mmap()。

其他的binder服务进程会映射 BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2) 的内存空间,_SC_PAGE_SIZE表示一个page页的大小,通常情况下为4K,即(1M - 4K*2)=(1M-7K)

这个page的大小,不同厂家有时候也会调整大小,一般有1M,64K,4K,1KB,通常为4K。

ServiceManager 进程mmap的内存大小可以通过adb shell命令得出

其中 0x7457d61000 - 0x7457d41000 = 0x20000, 转成10进制,即为128K

20200330220937375.jpg

ARM32内存映射:

虚拟空间的低3GB部分从0-0XBFFFFFFF的虚拟线性地址,用户态和内核态都可以寻址,这部分也是每个进程的独立空间。

虚拟空间的高1G部分从0XC0000000到0XFFFFFFFF的虚拟地址,只有内核态的进程才能访问,这种限制由页目录和页表描述符的权限标志位决定,通过MMU自动控制。

虚拟地址空间为4G,0-3G是用户地址空间(用户态和内核态都可以寻址,这也是每个进行的独立空间),3G-4G内核地址空间,每个进程都是拥有4G地址空间,只是用户态下面无法访问高1G空间;内核空间是被所有进程共享的

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

ARM64内存映射:

默认情况下,32位系统默认只能支持4G的内存,在打开PAE后,最大可以扩展到64G的内存。随着物理硬件的不断升级,现在的内存越来越大,因此基本上都切换到了64位系统。

理论上讲,64位的地址总线可以支持高达16EB(2^64)的内存。

2^64 次方太大了,Linux 内核只采用了 64 bits 的一部分(开启 CONFIG_ARM64_64K_PAGES 时使用 42 bits,页大小是 4K 时使用 39 bits),该文假设使用的页大小是 4K(VA_BITS = 39)

ARM64 有足够的虚拟地址,用户空间和内核空间可以有各自的 2^39 = 512GB 的虚拟地址。

需要注意到,32 位应用仍然拥有 512GB 的内核虚拟地址空间,并且不与内核共享自己的 4GB 空间。但在 ARM32 上,32 位应用只有 3GB 的地址空间。

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

ARM32和ARM64内存地址比较:

ARM32

ARM64

32位应用虚拟地址空间

64位应用虚拟地址空间

内核虚拟空间

5.3 binder_become_context_manager()

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

binder_become_context_manager()的作用是让ServiceManager成为整个系统中唯一的上下文管理器,其实也就是service管理器,这样我们就可以把ServiceManager称之为守护进程。

对应的binder驱动中操作如下:

从用户空间拷贝ioctl的参数,调用binder_ioctl_set_ctx_mgr()进行设置

BINDER_SET_CONTEXT_MGR_EXT带参数,BINDER_SET_CONTEXT_MGR不带参数

binder_ioctl_set_ctx_mgr()的流程也比较简单

1.先检查当前进程是否具有注册Context Manager的SEAndroid安全权限

2.如果具有SELinux权限,会为整个系统的上下文管理器专门生成一个binder_node节点,使该节点的强弱应用加1

3.新创建的binder_node 节点,记入context->binder_context_mgr_node,即ServiceManager 进程的context binder节点,使之成为serviceManager的binder管理实体

5.4 binder_loop()

下一步进行守护进程的循环处理,binder_loop()会先向binder驱动发出了BC_ENTER_LOOPER命令,告诉binder驱动"本线程要进入循环状态了",接着进入一个for循环不断调用ioctl()读取发来的数据,接着解析这些数据

其中最重要的一个结构体是binder_write_read,它用来记录Binder buffer中读和写的数据信息结构体如下:

5.5 binder_parse()

在binder_loop()进入for循环之后,核心处理流程就是ioctl和binder_parse(), 即不停的从Binder驱动接收读写数据,进行binder解析后,进行处理.

在binder_loop()中声明了一个128字节的栈内存-readbuf, 用BINDER_WRITE_READ命令从驱动读取一些内容,并传入binder_parse(),binder_parse()根据binder驱动传来的 "BR_XXX"协议码,进行相关的逻辑处理,最主要的有三个"BR_XXX"协议

BR_TRANSACTION  : 事务处理,解析binder_transaction_data的数据,调用回调函数svcmgr_handler() 进行服务的注册、获取等操作

BR_REPLY       : 消息回复

BR_DEAD_BINDER  : 死亡通知

只要binder_parse()解析正常,binder_loop()就会一直执行下去,ServiceManager进程不退出。

binder_parse()解析binder驱动传来的readbuf的内存,readbuf拥有128字节的栈内存,每次可以只处理一个cmd,也可以有多个cmd,所以存在一个while循环,可以同时解析多个cmd,多个cmd的结构如下图所示:

20200330221444413.jpg

5.5.1 BR_XXX 协议码分析

BR_XXX码,也成为Binder响应码,这里介绍了ServiceManager处理的一些响应码的作用:

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

5.5.2 BR_TRANSACTION 解析

我们这里单独分析下 BR_TRANSACTION 的流程,这也是我们常用的一个流程,这是Binder驱动向Server端发送请求数据。

从readbuf中解析出binder_transaction_data的数据,最后对接收和发送数据进行了封装,传递给svcmgr_handler()做详细处理

从上面的逻辑看,我们重点关注的是 binder_transaction_data 这个结构,binder_transaction_data说明了transaction到底在传输什么语义,而语义码就记录在其code成员中。不同语义码需要携带的数据也是不同的,这些数据由data指定.

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

结构体说明如下:

从上面binder_transaction_data的结构可以看出,data可存入的数据很少,主要采用了数据其实地址和数据偏移量,根据代码上下文可知,调用了bio_init_from_txn(),从txn.transaction_data 解析出binder_io的信息,存入msg

5.5.2.1 bio_init_from_txn()

   bio_init_from_txn()的作用就是把 binder_transaction_data 的“数据起始地址”、“偏移量”、“data数据的总大小”和“偏移数组中可用的条目”:

binder_transaction_data 和  binder_io的关联

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

初始化完 binder_io 的replay,并把transaction_data 转换成了binder_io 的msg后,调用回调函数svcmgr_handler()进行最终逻辑处理

5.6 svcmgr_handler()

在BR_TRANSACTION的命令解析后,就把binder_transaction_data_secctx的数据传给回调函数svcmgr_handler()进行处理。

根据不同的传输语义码(txn->code) 来进行相应的操作:查询服务,注册服务,以及列举所服务

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

源码如下:

其中有几个binder_io 数据获取的方法要注意:

1) uint32_t bio_get_uint32(struct binder_io *bio)

  作用:获取bio->data开始后的4个字节。

  说明:bio->data 往后偏移4个字节,bio-->data_avail 缓冲区的剩余可用字节数减去4个字节。bio-->data0一直不变,指向数据缓冲区的起始地址

2) uint16_t *bio_get_string16(struct binder_io *bio, size_t *sz)

  作用:在svcmgr_handler()主要是用来获取服务名和length

  说明:先读取一个32bits的整数,这个整数值就是字符串的长度。然后再读取((len + 1) * sizeof(uint16_t))字节。

  这里len+1的目的:字符串都要包含最后一个’\0’,需要把这个偏移量加入

3) static void *bio_get(struct binder_io *bio, size_t size)

  作用:根据传入的size,获取对应的ptr内容

  说明:bio->data 往后偏移4个字节,bio-->data_avail 缓冲区的剩余可用字节数减去4个字节。bio-->data0一直不变,指向数据缓冲区的起始地址

4) uint32_t bio_get_ref(struct binder_io *bio)

  作用:获取一个服务端的handle

  说明:从bio->data 中读出一个flat_binder_object的结构数据,返回 flat_binder_object->handle

5) void bio_put_ref(struct binder_io *bio, uint32_t handle)

  作用:在svcmgr_handler中主要是把handler存入reply

  说明:bio->data 偏移size(flat_binder_object), 其中存入 flat_binder_object obj的数据,并把hanlder存入obj

6) void bio_put_string16(struct binder_io *bio, const uint16_t *str)

  作用:把service的名称 存入reply

svcmgr_handler() 对binder驱动传来的binder_transaction_data进行解析读取后,根据不同的code,执行不同的操作,下面一起看看“注册服务”和“查找服务”是如何操作的。

5.7 ServiceManager 是如何管理service信息的?

通过前面的一些信息,我们了解到,ServiceManager用来管理服务信息,那么它是如何进行管理的呢?

在ServiceMnager的进程里有一个全局性的svclist变量,服务信息都存在于这里

struct svcinfo *svclist = NULL;

20200330221858552.jpg

5.8 注册服务

根据传入的code:SVC_MGR_ADD_SERVICE得知,本次binder流程想要进行服务注册。

  1. 从binder_io  msg中获取服务名称和长度
  2. 从binder_io msg 中获取handle
  3. 检查该服务是否有注册的selinx权限
  4. 查询服务列表svclist 是否存在该handle,如果有handle,就更新该服务的handle信息,通过这个handle我们最终就能找到远端的service实体
  5. 如果svclist不存在该服务,申请一个svcinfo的空间,把服务名、长度、handle等信息存入其中
  6. 把svcinfo 加入svclist的链表中
  7. 再以BC_ACQUIRE命令,handle为目标的信息,通过ioctl发送给binder驱动
  8. 最后以BC_REQUEST_DEATH_NOTIFICATION命令的信息,通过ioctl发送给binder驱动,主要用于清理内存等收尾工作

5.9 查找服务

根据传入的code:SVC_MGR_GET_SERVICE、SVC_MGR_CHECK_SERVICE得知,本次binder流程想要进行服务获取。

  1. 获取服务的名字s和长度len
  2. 从svclist中根据名字查到一个svcinfo的结构
  3. 进行selinx检查
  4. 检查通过,返回该服务的handle
  5. 把handle放入binder_io 的reply的data中
  6. 最后在binder_parse()中调用binder_send_reply(),以BC_REPLY命令, 通过ioctl发送给binder驱动,最终转到client进程

查到服务的handle后,存入binder_io reply的data中,其中存入的type为HANDLE: BINDER_TYPE_HANDLE

服务的handle被放入到binder_io的reply中后,我们回到binder_parse(),当svcmgr_handler()处理完成后,执行binder_send_reply()的过程。

binder_send_reply()主要是将BC_FREE_BUFFER和BC_REPLY命令协议发送给Binder驱动,向client端发送reply. 其中data的数据区中保存的是TYPE为HANDLE

ServiceManager 注册成为上下文管理者-守护进程,负责管理系统中的所有服务。ServiceManger进程跟所有向其注册服务的死亡通知建立联系, 那么当服务所在进程死亡后, 会只需告知ServiceManager.每个Client通过查询ServiceManager可获取Server进程的情况,降低所有Client进程直接检测会导致负载过重。

ServiceManager的整体流程如下:

  1. 打开设备,mmap一个128K的内存空间,进行用户空间和内核空间的内存绑定
  2. 注册成为上下文的管理者--守护进程
  3. 陷入死循环,标注线程的looper状态为enter
  4. 不停的操作binder读写过程
  5. 解析binder数据,进行查询服务、注册服务、列出服务的核心操作
  6. 由于在进行服务注册的时候,把svcinfo_death()注册到了binder_death的中,当收到BR_DEAD_BINDER时,表明应用层向Binder驱动发送Binder调用时,Binder应用层的另一个端已经死亡,进行死亡通知操作,调用svcinfo_death(),最终调用binder_release()发送BC_RELEASE 到内核进行处理。
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw==,size_16,color_FFFFFF,t_70

7.代码路径:

frameworks/native/cmds/servicemanager/binder.c

frameworks/native/cmds/servicemanager/service_manager.c

frameworks/native/cmds/servicemanager/servicemanager.rc

我的微信公众号:IngresGe

20200113052246361.jpg

参考:

《启动ServiceManager》

《Binder(ServiceManager篇)》

《浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路》

《SEAndroid安全机制对Binder IPC的保护分析》 


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK