[RK3399][Android7.1]通信机制 Binder

本博客首先介绍了进程间的通信机制 Binder,其次介绍了 JNI 机制以及传感器模 块实现中是如何使用的,最后以数据传递为主线分析了各模块的设计与实现。

平台 内核版本 安卓版本
RK3399 Linux4.4 Android7.1

1、进程间通信机制

Binder作为 Android 中另外一个庞大的体系,虽然代码量多、跨度广,但也 同样需要自己的地基——Binder 驱动。它以服务端客/服端的模式运行,其中,提供 服务的一方为 server 端,与 server 端进行交互的一端为客户端。

Android 系统是建立在 Linux 的基础上,所以 Binder 驱动也应该是一个标准的 驱动。Binder 驱动是简单的字符设备驱动,并创建/dev/binder 的节点供上层使用。 值得一提的是 Binder 驱动不对应具体的硬件设备。Binder 驱动为上层提供 openmmap 等操作。

Binder的函数操作的结构体如下:

static const file_operations binder_fops={ 
	.own = THIS_MODULE, 
	.poll = binder_poll, 
	.unlocked_ioctl = binder_ioctl, 
	.compat_ioctl=binder_ioctl, 
	.mmap = binder_mmap, 
	.open = binder_open, 
	.flush = binder_flush, 
	.release = binder_release; 
};

当打开 binder 设备时,由 binder_fops 结构体可知将会调用 binder_open 函数。 由于每个进程都有一个 binder_proc 的结构体,该结构记录了内存信息和 binder 节点的相关信息。接下来对新生成的 proc 对象进行各种初始化操作,有一个全局的结构 体记录所有的 binder_proc 变量,binder_proc 也会被记录在 open 函数的 file 结构体 的 private_data 变量中,下次调用 mmap 或者 ioctl 函数时可以直接在通过private_data 变量得到。

当打开设备后,还需要进行 binder_mmap 操作。Binder 驱动中使用 mmap 函数 可以减少数据copy 的次数。当两个进程 AB,其中 B 进程通过 openmmap 后 与 binder 驱动建立联系。对于应用程序,调用 mmap 就会返回一个虚拟内存地址, 这个地址通过虚拟内存转换后就可以转换成物理地址。同时 binder 驱动中也申请一 段虚拟地址,将这段地址映射为与应用程序指向同一块物理地址。此时,如果进程 A 复制一段数据到 Binder 驱动指针(binder_proc->buffer)指向的虚拟地址。该数据 可以被 B 直接访问,从而减少了一次数据复制。

当用户空间调用 mmap 函数时,需要制 mmap 空间的大小,系统会将这个地址 封装在结构体 struct vm_area_struct *vma 变量中,该结构体记录了系统分配给应用程序的虚拟内存,其中 vmavm_startvm_end 分别是这块连续的虚拟内存的起 止点。mmap 函数中会判断应用程序申请的内存大小是否大于 4M 空间。当申请的 内存超过 4M 时,mmap 函数没有直接退出,而是只分配 4M 的内存。procbuffer 变量用于记录内核中获取的虚拟内存空间,内核通过 get_vm_area 获取。但是此时 不会分配真正的内存,为了便于数据的复制,将会记录该地址与用户空间地址的偏 移量。最后在 binder_update_page_range 函数中,将用户空间地址,内核地址和物理 内存关联起来。函数接口如下所示:

int binder_update_page_range(struct binder_proc *proc,int allocate, void* start,void *end, struct vm_area_struct *vma)

其中,proc 代表申请内存的进程所持有的 binder proc 对象,start代表 get_vma_area 返回内存地址的起点,end 代表 get_vma_area 返回地址内存的终点, vma 代表应用程序中虚拟内存段的描述。从 mmap 函数中可以看出,最多可以分 配 4M 的虚拟内存。但是在本函数中只分配了一页物理内存,待物理页不够时,再向系统申请一页,这是出于节省空间的考虑。最后将用户空间地址,内核地址和 物理内存关联起来。在 binder_proc 中管理着三条链表,list_head buffers所有内存块 需 要 在 这 里 备 案 , rb_root free_buffers 所 有 可 用 的 空 闲 内 存 , rb_root allocated_buffers 所有已经分配了的内存。在 binder_mmap 函数中对分配到的内存进 行管理,主要工作是将其加入相应的链表。

命令 说明
BINDER_WRITE_READ 读写操作,可以用此命令向 Binder 读写或者 写入数据
BINDER_SET_MAX_THREADS 设置最大的线程数。因为客户端可以并发向 服务器端发送请求,如果当前线程的数量超 过阈值将会停止创建线程
BINDER_SET_CONTEXT_MGR Service Manager 通过传递该命令将自己注册 为服务的管理者
BINDER_THREAD_EXIT 通知线程退出。每个线程在退出时都应该告知 Binder 驱动。这样才能释放相关的资源
BINDER_VERSION 获取 Binder 的版本号

进程通过设备节点提供的 ioctl 接口传递命令,Binder驱动的大部分的工作都是 在这个函数中完成的。binder_ioctl提供的命令如上表

Android 系统中客户端和服务器端是通过 Binder 驱动进行命令传递和数据交互。客户端如果需要和服务端进行交互,需要向 Service Manager 查询,获得服务代理,通过代理进行通信。如果没有 Service Manager,服务就不能注册,而客户端也不能获得服务代理。Service Manager 是系统中服务的管理者,它是进程间通信的关键。

ServiceManager 是一个特殊的服务进程。它负责管理系统的服务,例如管理 Activity 的服务、管理 Package 的服务等,所有的服务都是 ServiceManager服务的客户端。 ServiceManager 是在 Linux 守护进程 init 进程启动时,解析配置文件 init.rc 时启动 的,配置文件如下所示:

/*init.rc*/ 
service servicemanager /system/bin/servicemanager 
	class core 
	user system 
	group system 
	critical 
	onrestart restart zygote 
	onrestart restart media 
	onrestart restart surfaceflinger 
	onrestart restart drm

在这里插入图片描述
ServiceManager 启动过程主要分为三步:
第一步,打开/dev/binder 驱动节点,然后调用 mmap 设置 128k 内存缓冲区;
第二步,将 ServiceManager注册成为 Binder 机制的服务的管理者,通过向 Binder Driver发送 BINDER_SET_CONTEXT_MGRioctl 命令。因为 ServiceManager 是在 init 进程中被启动的,所以可以保证它是第 一个想 binder driver 注册成为管理者的程序;
第三步,通过 binder_loop 循环等待来 自客服端的请求。它的消息循环和典型的基于事件驱动的程序循环框架类似流程如上图。

通过以上三步,ServiceManager 被注册为 Binder的上下文管理者。并在相关变 量中记录服务管理者的相关信息,例如进程号等。当其他的服务也通过 ioctl进行注 册时上下文管理者时,此时 binder_context_mgr_node 已经不为空,拒绝注册,从而 保证了服务管理者的唯一性。

IServiceManager 提供函数 功能
getService(const String16& name) 客户端传入服务名称,接收到服务名称后, 调用 checkService 成员函数。当 Binder IPC 失败时,重新尝试。该函数返回值是一个指 向 BpBinder实例的指针
checkService(const String16& name) 接收服务名称,然后从 ContextManager 查看 服 务 信 息 , 最 后 返 回 服 务 代 理 , 和 getService功能类似
status_t addService(const String16& name,const sp<IBinder>& service) 注册服务,以服务名和服务实例指针为参 数,返回注册是否成功
vector<String16> listService 该参数是无参函数,系统中注册的所有服 务,并以数组的形式返回。

客户端和 ServiceManager 都是继承 IServiceManagerIServiceManager定义了服务端的功能。IServiceManager 定义成员函数如上表所示:

ServiceManager提 供 了 的 如上 表几 种 功 能 , 客 服 端 通 过 代 理 BpServiceseManager来完成这些功能。客服端通过 defaultServiceManager 来获代 理,函数实现如下:

defaultServiceManager() 
{ 
	if(gDefaultServiceManager 不为空){ 
		返回 gDefaultServiceManager 
	}
	if(gDefaultServiceManager 为空) { 
		向 ContextManager 查找 ServiceMnager 服务 
	}返回 gDefaultServiceManager; 
}

通过以上代码可以看出,BpServiceManager 是通过单例模式实现的,这样可以 减少对象生成的个数。生成 gDefaultServiceManager 主要需要三步:

  1. ProcessState::self() 通 过 单 利 模 式 创 建ProcessState对 象 , 用 全 局 变 量 ProcessState来保存该变量。ProcessState的构造函数打开 BinderDriver,将返回的文 件描述符保存在 mDriverFD 变量中,然后通过 mmap函数获得一块 mmap区域。当 进程间通信需要传递数据到 Binder 驱动中时,该块区域被用作保存区域,并将区域 的首地址保存在成员变量 mVmStart 变量中。上述的创建过程如下图所示。

  2. 通过 getStrongProxyObject 函数并传递 0,获得 ServiceManagerBpBinderProcess中通过全局列表来记录 Binder 对象的信息,每个表项是一个 handle_entry。 当表项中不存在时,创建 BpBinder 对象,并保存在表项中。ServiceManagerBinder Driver 中的 handler 为 0。

  3. interface_cast<IServiceManage>是一个宏定义的模板函数,调用该函数时最终调用的 IServiceManagerasInterface 成员函数。通过转换生成BpServiceManager 实例对象。
    在这里插入图片描述

发布了270 篇原创文章 · 获赞 95 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_33487044/article/details/104116174