学习binder机制的过程中,了解binder驱动和binder核心api是关键。linux系统采用两级保护机制,其中0级用于系统内核,3级用于用户程序。传统的linux ipc通信原理与binder的通信过程有显著的区别。servicemanager进程的启动和mmap的使用也是binder机制中的重要部分。
主要的驱动设备操作包括初始化(binder_init)、打开(binder_open)、映射(binder_mmap)和数据操作(binder_ioctl)。
用户态的程序调用Kernel层驱动时,需要通过系统调用(syscall)进入内核态。例如,打开Binder驱动的调用链为:open-> __open() -> binder_open()。open()是用户空间的方法,__open()是系统调用中的处理方法,通过查找,最终调用到内核Binder驱动的binder_open()方法。其他从用户态进入内核态的流程也基本类似。
用户空间调用open()方法,最终会调用到binder驱动的binder_open()方法;mmap()/ioctl()方法的调用过程也是类似的。在Binder系列的后续文章中,从用户态进入内核态都依赖于系统调用过程。
Binder核心API的主要功能是注册misc设备。debugfs_create_dir用于在debugfs文件系统中创建目录,返回值是指向dentry的指针。
注册misc设备时,miscdevice结构体作为参数传递。
file_operations结构体指定相应的文件操作方法,用户空间的方法最终调用到对应的方法,这取决于file_operations结构体的定义。
创建binder_proc对象,并将当前进程信息保存到binder_proc对象中。该对象管理IPC所需的各种信息,并作为其他结构体的根结构体;然后将binder_proc对象保存到文件指针filp,并将binder_proc加入到全局链表binder_procs中。
Binder驱动通过static HLIST_HEAD(binder_procs);创建全局哈希链表binder_procs,用于保存所有binder_proc队列。每次新创建的binder_proc对象都会加入binder_procs链表中。
主要功能是:首先在内核虚拟地址空间中申请一块与用户虚拟内存相同大小的内存;然后再申请1个page大小的物理内存,并将同一块物理内存分别映射到内核虚拟地址空间和用户虚拟内存空间,从而实现用户空间的Buffer和内核空间的Buffer同步操作的功能。
总结:
binder_init:初始化字符设备; binder_open:打开驱动设备,过程需要持有binder_main_lock同步锁; binder_mmap:申请内存空间,该过程需要持有binder_mmap_lock同步锁; binder_ioctl:执行相应的ioctl操作,该过程需要持有binder_main_lock同步锁; 当处于binder_thread_read过程,read_buffer无数据时释放同步锁,并处于wait_event_freezable过程,等有数据到来时唤醒并尝试持有同步锁。
Linux使用两级保护机制:0级供系统内核使用,3级供用户程序使用。当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
当进程在执行用户自己的代码时,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。
系统调用主要通过如下两个函数来实现:
copy_from_user() //将数据从用户空间拷贝到内核空间 copy_to_user() //将数据从内核空间拷贝到用户空间
Linux下的传统IPC通信原理传统的IPC通信方式存在两个问题:
跨进程通信需要内核空间支持。传统的IPC机制如管道、Socket是内核的一部分,因此通过内核支持实现进程间通信是没问题的。但是Binder并不是Linux系统内核的一部分,而是通过Linux的动态内核可加载模块(Loadable Kernel Module,LKM)机制实现的; LKM:模块是具有独立功能的程序,可以单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android系统可以通过动态添加一个内核模块在内核空间运行,用户进程之间通过这个内核模块作为桥梁实现通信。这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就是Binder驱动(Binder Driver)。
Binder IPC机制中涉及到的内存映射通过mmap()来实现,mmap()是操作系统中一种内存映射的方法。内存映射就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反映到内核空间;反之内核空间对这段区域的修改也能直接反映到用户空间。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。
Linux下的传统IPC通信原理Binder IPC正是基于内存映射(mmap)来实现的,但mmap()通常用于有物理介质的文件系统上。例如,进程中的用户区域不能直接与物理设备打交道,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间→用户空间);在这种场景下,mmap()就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。而Binder并不存在物理介质,因此Binder驱动使用mmap()并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
Binder IPC通信过程通常是这样: 1.首先Binder驱动在内核空间创建一个数据接收缓存区; 2.接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系; 3.发送方进程通过系统调用copy_from_user()将数据copy到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder基于C/S架构,由Client、Server、ServiceManager、Binder驱动等组件组成。其中Client、Server、Service Manager运行在用户空间,Binder驱动运行在内核空间。Service Manager和Binder驱动由系统提供,而Client、Server由应用程序实现。Client、Server和ServiceManager均通过系统调用open、mmap和ioctl访问设备文件/dev/binder,从而实现与Binder驱动的交互,间接实现跨进程通信。
Binder IPC通信至少是两个进程的交互:
client进程执行binder_thread_write,根据BC_XXX命令生成相应的binder_work; server进程执行binder_thread_read,根据binder_work.type类型生成BR_XXX,发送到用户空间处理。
Binder通信过程1.首先,一个进程使用BINDER_SET_CONTEXT_MGR命令通过Binder驱动将自己注册成为ServiceManager。2.Server通过驱动向ServiceManager中注册Binder(Server中的Binder实体),表明可以对外提供服务。驱动为这个Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字以及新建的引用打包传给ServiceManager,ServiceManger将其填入查找表。3.Client通过名字,在Binder驱动的帮助下从ServiceManager中获取到对Binder实体的引用,通过这个引用就能实现和Server进程的通信。
Binder通信的代理模式当A进程想要获取B进程中的object时,驱动并不会真的把object返回给A,而是返回了一个跟object看起来一模一样的代理对象objectProxy,这个objectProxy具有和object一摸一样的方法,但是这些方法并没有B进程中object对象那些方法的能力,这些方法只需要把请求参数交给驱动即可。对于A进程来说和直接调用object中的方法是一样的。当Binder驱动接收到A进程的消息后,发现这是个objectProxy就去查询自己维护的表单,一查发现这是B进程object的代理对象。于是就会去通知B进程调用object的方法,并要求B进程把返回结果发给自己。当驱动拿到B进程的返回结果后就会转发给A进程,一次通信就完成了。
ServiceManager进程启动1.ServiceManager成为Binder管理者首先在android系统开始启动时通过init.rc启动了servicemanager(Native进程)可执行文件进程,系统真正的ServieManager管理最终通过Native进程servicemanager来完成。接下来分析在main函数中做了那些事情:
1.binder_open(128*1024)通过打开/dev/binder设备节点返回fd文件描述符,通过mmap最终实现对binder驱动128K大小的内存映射2.binder_become_context_manager(bs)通过ioctl往设备节点发送BINDER_SET_CONTEXT_MGR命令通知Binder驱动为servicemanager创建特殊不变的0句柄实体binder_node:binder_context_mgr_node
3.Binder_loop(bs,svcmgr_handler)通过iotcl发送BINDER_WRITE_READ命令并携带BC_ENTER_LOOPER请求命令告知Binder驱动创建了binder主线程并使binder驱动创建与之对应的binder_thread结构体存储到binder_proc中,并通过for循环进入循环读取binder驱动返回信息的循环流程
ServiceManager(native进程)最终通过获取binder驱动设备节点fd地址,与binder驱动内存映射(虚拟内存与物理内存映射),开启binder主体线程循环读取binder驱动返回的消息而成为服务端;成为服务端后能够不断接收来自客户端(服务端与客户端在此统称为客户端)的binder请求通信,ServiceManager(native进程)完成其他服务的注册并保存binder驱动创建的binder句柄值与服务名称字符串,保证客户端通过字符串获取其他服务引用句柄;因而ServiceManager(native进程)为binder管理者(服务注册与获取的桥梁)。2.客户端通过ServiceManager获取服务用户进程需要和ServiceManager(native进程)进程通信,ServiceManager进程接收到请求后去响应1.用户进程第一步先实例化ServiceManager Binder Proxy代理
通过BinderInternal.getContextObject()调用获取到句柄值为0的BpBinder native对象,最终通过javaObjectForIbinder()函数的jni转换为BinderProxy类对象其实就是填充了这个类的mObject对象也就是ServiceManager Binder对象的引用封装对象,所以SMN才能通过asInterface完成代理Proxy的实例化
MMAP虚拟进程地址空间(vm_area_struct)和虚拟内核地址空间(vm_struct)都映射到同一块物理内存空间。当Client端与Server端发送数据时,Client(作为数据发送端)先从自己的进程空间把IPC通信数据copy_from_user拷贝到内核空间,而Server端(作为数据接收端)与内核共享数据,不再需要拷贝数据,而是通过内存地址空间的偏移量,即可获悉内存地址,整个过程只发生一次内存拷贝。一般地做法,需要Client端进程空间拷贝到内核空间,再由内核空间拷贝到Server进程空间,会发生两次拷贝。
对于进程和内核虚拟地址映射到同一个物理内存的操作是发生在数据接收端,而数据发送端还是需要将用户态的数据复制到内核态。到此,为何不直接让发送端和接收端直接映射到同一个物理空间,那样就连一次复制的操作都不需要了,0次复制操作那就与Linux标准内核的共享内存的IPC机制没有区别了,对于共享内存虽然效率高,但是对于多进程的同步问题比较复杂,而管道/消息队列等IPC需要复制2两次,效率较低。 如下是大致流程图:
借鉴文章:
https://www.php.cn/link/76c2f1b4a1f0bfbbff4a4789d9d82630
关于mmap:https://www.php.cn/link/b39e0454a95c993bd9388d2605e035a2
以上就是Android进程间通信之一:Binder机制学习的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号