理论教育 Binder机制的上下文管理者——ServiceManager

Binder机制的上下文管理者——ServiceManager

时间:2023-06-19 理论教育 版权反馈
【摘要】:在Android系统中,Service Manager负责告知Binder驱动程序它是Binder机制的上下文管理者。Service Manager是整个Binder机制的保护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能。Service Manager在用户空间的源码位于“frameworks/base/cmds/servicemanager”目录下,主要是由文件binder.h、binder.c和service_manager.c组成。图3-2 Service Manager在Binder机制中的基本执行流程在Service Manager的入口在文件service_manager.c中,主函数main的代码如下。结构体binder_state定义在文件frameworks/base/cmds/servicema-nager/binder.c中,代码如下。这表示Service Manager的句柄为0,Binder通信机制使用句柄来代表远程接口。

Binder机制的上下文管理者——ServiceManager

在Android系统中,Service Manager负责告知Binder驱动程序它是Binder机制的上下文管理者。Service Manager是整个Binder机制的保护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能。

因为Service Manager组件是用来管理Server并且向Client提供查询Server远程接口的功能,所以Service Manager必然要和Server以及Client进行通信。Service Manger、Client和Server三者分别是运行在独立的进程当中的,这样它们之间的通信也属于进程间的通信,而且也是采用Binder机制进行进程间通信。因此,Service Manager在充当Binder机制的保护进程的角色同时也在充当Server的角色,也是一种特殊的Server。

Service Manager在用户空间的源码位于“frameworks/base/cmds/servicemanager”目录下,主要是由文件binder.h、binder.c和service_manager.c组成。Service Manager在Binder机制中的基本执行流程如图3-2所示。

978-7-111-51616-3-Part02-2.jpg

图3-2 Service Manager在Binder机制中的基本执行流程

在Service Manager的入口在文件service_manager.c中,主函数main的代码如下。

978-7-111-51616-3-Part02-3.jpg

上述函数main()主要有如下三个功能:

打开Binder设备文件。

告诉Binder驱动程序自己是Binder上下文管理者,即前面所说的保护进程。

进入一个无穷循环,充当Server的角色,等待Client的请求。

在分析上述三个功能之前,先看一下这里用到的结构体binder_state、宏BINDER_SERVICE_MANAGER的定义。结构体binder_state定义在文件frameworks/base/cmds/servicema-nager/binder.c中,代码如下。

978-7-111-51616-3-Part02-4.jpg

其中fd表示文件描述符,即表示打开的“/dev/binder”设备文件描述符;mapped表示把设备文件“/dev/binder”映射到进程空间的起始地址;mapsize表示上述内存映射空间的大小。

宏BINDER_SERVICE_MANAGER定义在文件frameworks/base/cmds/servicemanager/binder.h中,代码如下。

978-7-111-51616-3-Part02-5.jpg

这表示Service Manager的句柄为0,Binder通信机制使用句柄来代表远程接口。

函数首先打开Binder设备文件的操作函数binder_open(),此函数的定义位于文件frameworks/base/cmds/servicemanager/binder.c中,具体代码如下。

978-7-111-51616-3-Part02-6.jpg

978-7-111-51616-3-Part02-7.jpg

通过文件操作函数open()打开设备文件“/dev/binder”,此设备文件是在Binder驱动程序模块初始化的时候创建的。接下来先看一下这个设备文件的创建过程,进入kernel/common/drivers/staging/android目录,打开文件binder.c,可以看到如下模块初始化入口binder_init。

978-7-111-51616-3-Part02-8.jpg

978-7-111-51616-3-Part02-9.jpg

在函数misc_register()中实现了创建设备文件的功能,并实现了misc设备的注册,在“/proc”目录中创建了各种Binder相关的文件供用户访问。通过函数binder_open()如下的执行语句即可进入到Binder驱动程序的binder_open()函数。

978-7-111-51616-3-Part02-10.jpg

函数binder_open()的实现代码如下。

978-7-111-51616-3-Part02-11.jpg

函数binder_open()的主要功能是创建一个名为binder_proc的数据结构,使用此数据结构可以保存打开设备文件“/dev/binder”的进程的上下文信息,并且将这个进程上下文信息保存在打开文件数据结构file的私有数据成员变量private_data中。

而结构体struct binder_proc也被定义在文件kernel/common/drivers/staging/android/binder.c中,具体代码如下。

978-7-111-51616-3-Part02-12.jpg

上述结构体中的成员比较多,其中最为重要的是如下四个成员变量。

threads

nodes

refs_by_desc

refs_by_node

上述四个成员变量都是表示红黑树的节点,即binder_proc分别挂在四个红黑树下,具体说明如下。

threads树:用来保存binder_proc进程内用于处理用户请求的线程,它的最大数量由max_threads来决定。

node树:用来保存binder_proc进程内的Binder实体。

refs_by_desc树和refs_by_node树:用来保存binder_proc进程内的Binder引用,即引用的其他进程的Binder实体,它分别用两种方式来组织红黑树,一种是以句柄作来key值来组织,一种是以引用的实体节点的地址值作来key值来组织,它们都是表示同一样东西,只不过是为了内部查找方便而用两个红黑树来表示。

这样就完成了打开设备文件/dev/binder的工作,接下来需要对打开的设备文件进行内存映射操作,即mmap()函数。

978-7-111-51616-3-Part02-13.jpg

对应Binder驱动程序的是函数binder_mmap(),实现代码如下。

978-7-111-51616-3-Part02-14.jpg

978-7-111-51616-3-Part02-15.jpg

978-7-111-51616-3-Part02-16.jpg

在上述函数binder_mmap()中,首先通过filp->private_data得到在打开设备文件“/dev/binder”时创建的结构binder_proc,在vma参数中保存内存映射信息。此处的vma的数据类型是结构vm_area_struct,它表示的是一块连续的虚拟地址空间区域。另外,结构体vm_struct表示一块连续的虚拟地址空间区域。

接下来分析结构体binder_proc中的如下成员变量。

buffer:是一个void*指针,它表示要映射的物理内存在内核空间中的起始位置。

buffer_size:是一个size_t类型的变量,表示要映射的内存的大小。

pages:是一个struct page*类型的数组,struct page是用来描述物理页面的数据结构。

user_buffer_offset:是一个ptrdiff_t类型的变量,它表示的是内核使用的虚拟地址与进程使用的虚拟地址之间的差值,即如果某个物理页面在内核空间中对应的虚拟地址是addr的话,那么这个物理页面在进程空间对应的虚拟地址就为如下格式。

978-7-111-51616-3-Part02-17.jpg

接下来还需要看一下Binder驱动程序管理内存映射地址空间的方法,即如何管理“buffer~(buffer+buffer_size)”这段地址空间,这个地址空间被划分为一段一段来管理,每一段是用结构体binder_buffer来描述的,具体代码如下。

978-7-111-51616-3-Part02-18.jpg

每一个binder_buffer通过其成员entry按从低址到高地址连入到结构体binder_proc中的buffers表示的链表中去,并且每一个binder_buffer又分为正在使用的和空闲的,通过free成员变量来区分,空闲的binder_buffer借助变量rb_node来到结构体binder_proc中的free_buffers表示的红黑树中去。而那些正在使用的binder_buffer,通过成员变量rb_node连入到结构体binder_proc中的allocated_buffers表示的红黑树中去。这样做的目的是,方便查询和维护这块地址空间。

继续分析函数binder_update_page_range(),查看Binder驱动程序把一个物理页面同时映射到内核空间和进程空间的方法。具体实现代码如下。

978-7-111-51616-3-Part02-19.jpg

978-7-111-51616-3-Part02-20.jpg

通过上述代码不但可以分配物理页面,而且还可以用来释放物理页面,这可以通过参数allocate来区别,在此只需关注分配物理页面的情况。要分配物理页面的虚拟地址空间范围为(start~end),函数前面的一些检查逻辑就不看了,只需直接查看中间的for循环代码,具体代码如下。

978-7-111-51616-3-Part02-21.jpg

上述代码的具体实现流程如下。(www.daowen.com)

(1)调用alloc_page()分配一个物理页面,此函数返回一个结构体page物理页面描述符,根据这个描述的内容初始化结构体vm_struct tmp_area。

(2)通过函数map_vm_area()将这个物理页面插入到tmp_area描述的内核空间中。

(3)通过page_addr+proc->user_buffer_offset获得进程虚拟空间地址。

(4)通过函数vm_insert_page()将这个物理页面插入到进程地址空间去,参数vma表示要插入的进程的地址空间。

再次回到文件frameworks/base/cmds/servicemanager/service_manager.c中的main()函数,接下来需要调用函数binder_become_context_manager()来通知Binder驱动程序自己是Binder机制的上下文管理者,即保护进程。函数binder_become_context_manager()在文件frameworks/base/cmds/servicemanager/binder.c中定义,具体代码如下:

978-7-111-51616-3-Part02-22.jpg

在此通过调用ioctl文件操作函数通知Binder驱动程序自己是保护进程,命令号是BINDER_SET_CONTEXT_MGR,并没有任何参数。BINDER_SET_CONTEXT_MGR定义为:

978-7-111-51616-3-Part02-23.jpg

这样就进入到Binder驱动程序的函数binder_ioctl(),在此只关注如下BINDER_SET CONTEXT_MGR命令即可,具体代码如下。

978-7-111-51616-3-Part02-24.jpg

978-7-111-51616-3-Part02-25.jpg

在分析函数binder_ioctl()之前,需要先弄明白如下两个数据结构的含义。

结构体binder_thread:表示一个线程,这里就是执行binder_become_context_manager()

函数的线程。具体代码如下。

978-7-111-51616-3-Part02-26.jpg

978-7-111-51616-3-Part02-27.jpg

在上述结构体中,proc表示是这个线程所属的进程。结构体binder_proc中成员变量thread的类型是rb_root,它表示一查红黑树,把属于这个进程的所有线程都组织起来,结构体binder_thread的成员变量rb_node就是用来链入这棵红黑树的节点了。looper成员变量表示线程的状态,可以取如下的值。

978-7-111-51616-3-Part02-28.jpg

另外,transaction_stack表示线程正在处理的事务,todo表示发往该线程的数据列表,return_error和return_error2表示操作结果返回码,wait用来阻塞线程等待某个事件的发生,stats用来保存一些统计信息。这些成员变量遇到的时候再分析它们的作用。

数据结构binder_node:表示一个binder实体,具体代码如下。

978-7-111-51616-3-Part02-29.jpg

由此可见,rb_node和dead_node组成了一个联合体,具体来说分为如下两种情形。

如果这个Binder实体还在正常使用,则使用rb_node来连入“proc->nodes”所表示的红黑树的节点,这棵红黑树用来组织属于这个进程的所有Binder实体。

如果这个Binder实体所属的进程已经销毁,而这个Binder实体又被其他进程所引用,则这个Binder实体通过dead_node进入到一个哈希表中去存放。proc成员变量就是表示这个Binder实例属于进程操作范畴

在上述数据结构binder_node中,主要成员的具体说明如下。

refs:把所有引用了该Binder实体的Binder引用连接起来构成一个链表。

internal_strong_refs、local_weak_refs和local_strong_refs:表示这个Binder实体的引用计数。

ptr和cookie:分别表示这个Binder实体在用户空间的地址以及附加数据。

接下来回到函数binder_ioctl()中,首先是通过filp->private_data获得proc变量,此处的函数binder_mmap()是一样的,然后通过函数binder_get_thread()获得线程信息,此函数的代码如下。

978-7-111-51616-3-Part02-30.jpg

978-7-111-51616-3-Part02-31.jpg

在上述代码中,把当前线程current的pid作为键值,在进程proc->threads表示的红黑树中进行查找,看是否已经为当前线程创建过了binder_thread信息。在这个场景下,由于当前线程是第一次进行到这里,所以肯定找不到binder_thread信息,即“*p==NULL”成立,于是,就为当前线程创建一个线程上下文信息结构体binder_thread,并初始化相应的成员变量,插入到proc->threads所表示的红黑树中,下次要使用时就可以从proc中获取。注意,这里的“thread->looper=BINDER_LOOPER_STATE_NEED_RETURN”。

再回到函数binder_ioctl()中,接下来会有两个全局变量binder_context_mgr_node和binder_context_mgr_uid,定义如下。

978-7-111-51616-3-Part02-32.jpg

其中binder_context_mgr_node用来表示Service Manager实体,binder_context_mgr_uid表示Service Manager保护进程的uid。在这个场景下,由于当前线程是第一次进行到这里,所以binder_context_mgr_node为NULL,binder_context_mgr_uid为-1,于是初始化binder_context_mgr_uid为current->cred->euid,这样当前线程就成为Binder机制的保护进程了,并且通过binder_new_node为Service Manager创建Binder实体:

978-7-111-51616-3-Part02-33.jpg

978-7-111-51616-3-Part02-34.jpg

在这里传进来的ptr和cookie都为NULL。上述函数会首先检查proc->nodes红黑树中是否已经存在以ptr为键值的node,如果已经存在则返回NULL。在这个场景下,由于当前线程是第一次进入到这里,所以肯定不存在,于是就新建了一个ptr为NULL的binder_node,并且初始化其他成员变量,插入到proc->nodes红黑树中。

当binder_new_node返回到函数binder_ioctl()之后,会把新建的binder_node指针保存在binder_context_mgr_node中,然后又初始化binder_context_mgr_node中的引用计数值。这样执行BINDER_SET_CONTEXT_MGR命令完毕,在函数binder_ioctl()返回之前执行下面的语句。

978-7-111-51616-3-Part02-35.jpg

再次回到文件frameworks/base/cmds/servicemanager/service_manager.c中的main()函数,接下来需要调用函数binder_loop()进入循环,等待Client发送请求。函数binder_loop()定义在文件frameworks/base/cmds/servicemanager/binder.c中:

978-7-111-51616-3-Part02-36.jpg

978-7-111-51616-3-Part02-37.jpg

在上述代码中,首先通过函数binder_write()执行BC_ENTER_LOOPER命令以告诉Binder驱动程序,Service Manager马上要进入循环。在此还需要理解设备文件“/dev/binder”操作函数ioctl的操作码BINDER_WRITE_READ,首先看其定义:

978-7-111-51616-3-Part02-38.jpg

此io操作码有一个形式为struct binder_write_read的参数,具体代码如下。

978-7-111-51616-3-Part02-39.jpg

用户空间程序和Binder驱动程序交互时,大多数是通过BINDER_WRITE_READ命令实现的,write_bufffer和read_buffer所指向的数据结构还指定了具体要执行的操作,write_bufffer和read_buffer所指向的结构体是binder_transaction_data,定义此结构体的具体代码如下。

978-7-111-51616-3-Part02-40.jpg

978-7-111-51616-3-Part02-41.jpg

到此为止,我们从源代码一步一步地分析完Service Manager是如何成为Android进程间通信机制Binder的保护进程。在接下来的内容中,简要总结Service Manager成为Android进程间通信机制Binder保护进程的过程。

(1)打开/dev/binder文件:

978-7-111-51616-3-Part02-42.jpg

(2)建立128KB内存映射:

978-7-111-51616-3-Part02-43.jpg

(3)通知Binder驱动程序它是保护进程:

978-7-111-51616-3-Part02-44.jpg

(4)进入循环等待请求的到来:

978-7-111-51616-3-Part02-45.jpg

在这个过程中,在Binder驱动程序中建立了一个binder_proc结构体、一个binder_thread结构体和一个binder_node结构体,这样,Service Manager就在Android系统的进程间通信机制Binder中担负起保护进程的职责了。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈