当前位置:首页 > 英文周记 > 正文内容

binder「binder恒温恒湿箱」

更新时间:2026-07-18 19:35:32 周记网3年前 (2023-04-16)英文周记65

Binder机制的原理

  因为Binder机制是涉及到进程的通信,所以需要对操作系统的进程通信需要有所了解。

  进程的相关知识: Linux进程的学习的笔记

 Binder机制相比于其他的进程通信方法更加高效,是因为使用了内存映射的机制,数据只需要**一次。

binder「binder恒温恒湿箱」

 内存映射的具体内容: 操作系统——内存映射

 在介绍Binder跨进程通信之前,需要去了解一个动态内核可加载模块。

 根据进程空间划分,进程之间的通信需要依赖到内核空间,例如在传统的IPC机制中,管道,Socket都时内核的一部分,因此通过内核支持来实现进程间通信时没有问题的,但是Binder并不是内核的一部分,那么怎么办呢 ?这就得益于Linux的动态内核可载模块的机制。模块时具有独立功能的程序,它可以被单独编译,但是不能独立运行,它在运行时被链接到内核作为内核的一部分运行。这样,Android系统就可以通过动态添加一个模块运行在内核空间,用户进程之间通过这个内核模块做为桥梁来实现通信。在Android系统中,这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就叫Binder驱动。

 有了上面的所说的运行在内核空间的模块,在Android系统中是通过 内存映射 的方式来实现通信,数据的拷贝只需要一次,相比于传统的IPC机制需要两次的数据拷贝,是更加高效的。

Binder IPC是基于内存映射来实现的,但是mmap()通常是用在有物理介质的文件系统上的。比如进程中的用户区域是不能直接核物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘 内核空间 用户空间)。通常在这种场景下mmap()就能发挥作用,通过在物理介质核用户空间之间建立映射,减少数据的拷贝次数,用内存读写代替I/O读写,提高文件读取效率。而Binder并不存在物理介质,因此Binder驱动使mmap()并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

 Binder IPC通信过程:

示意图:

 Binder是一套基于C/S架构的。由一系列的组件组成,主要包括:Client,Server,Service Manager和Binder驱动。其中Client,Service和Service Manager是在用户空间的,Binder驱动是在内核空间的。Client和Service是由用户是用户实现的,Binder驱动和Service Manager是系统实现的。Client,Server和Service Manager都可以通过系统调用open,mmap和ioctl来访问设备文件/dev/binder。从而实现与Binder驱动的交互间接实现进程间的通信。

 其中 Android Bander设计与实现 - 设计篇 对上述的角色中有详细的讲解。

步骤1:使用 BINDERSETCONTEXT_MGR 命令通过Binder驱动将自己注册成为ServiceMannager。

步骤2:注册服务

步骤2:获取服务

关于Binder的原理学习,可参考 Android Binder 原理解析 和 Android跨进程通信:图文详解 Binder机制 原理 ,个人认为这两篇描述的比较详细。

binder进程

Binder设计架构中,只有第一个Binder主线程(也就是Binder_1线程)是由应用程序主动创建,Binder线程池的普通线程都是由Binder驱动根据IPC通信需求创建,Binder线程的创建流程图:

每次由Zygote fork出新进程的过程中,伴随着创建binder线程池,调用spawnPooledThread来创建binder主线程。当线程执行binder_thread_read的过程中,发现当前没有空闲线程,没有请求创建线程,且没有达到上限,则创建新的binder线程。

Binder的transaction有3种类型:

Binder系统中可分为3类binder线程:

Android Binder原理

以前看源码经常会看到Binder的东西,比如AMS,ActivityThread等,之前虽然对Binder有所了解,但也是模模糊糊的,这次终于下定决心好好的弄一弄它的原理,揭开它头上的那块面纱。

首先,Binder主要是Android 跨进程通信的一种方式,它是一个Android 中的一个类,实现了IBinder接口,主要是用在Service中。Android 跨进程通信有几种方式,Bundle、AIDL、Messenger、ContentProvider、socket等。而AIDL跟Messenger内部都是用的是Binder机制。下面就来分析一下Binder的工作机制。

首先我们需要建立三个类,一个是实体类User.kt,  继承Percelable接口

以及User.aidl, 里面定义了

parcelable User

IUserManager.aidl 定义了服务器端可调用的接口

interface IUserManager{

ListUser getUserList();

void addUser(in User user)

}

然后build 工程,系统会在build路径下自动生成IuserManager.java文件,我们具体介绍一下里面的方法。

Binder的唯一标识,一般用类名表示

用于将服务端的Binder对象转换成客户端aidl接口类型的对象,如果客户端跟服务器端在同一个进程里面,返回的是服务器端本身,否则返回的是内部定义的Stub.Proxy()对象

该方法运行在服务器端的binder线程池中,通过code可以确定客户端请求的目标方法,然后从data中取出目标方法所需要的参数,然后再执行目标方法,执行完毕后,把结果写入reply中,如果return false,客户端请求会失败,可以用来做一些权限校验。

该方法运行在客户端,首先用android.os.Parcel.obtain()创建两个Parcel 对象 data(输入), reply(输出),如果该方法有参数的话,把参数写入data,  

data.writeInt(1)

user.writeToParcel(data,0)

然后调用服务端的transact方法来触发RPC请求,mRemote.transact(),同时当前线程挂起,然后服务端的onTransact方**触发,直到RPC过程返回,当前线程继续执行,从reply中获取返回结果。

有两个地方需要注意,首先客户端发起远程请求时,客户端线程会被挂起,所以这个操作可能是一个耗时操作,不能在UI线程中发起请求。其次,服务端的Binder方法是运行在Binder线程池中,所以Binder方法不管是否耗时,都应该用同步的方式去实现。

另外,在进程间通信的时候,我们的binder有可能会断开,这个时候我们需要设置一个死亡代理。我们定义一个DeathRecipient对象,里面有一个回调方法binderDied(), 当Binder死亡的时候该方**回调,然后我们可以调用unlinkToDeath()移除之前的代理,并重新绑定远程service

binder.linkToDead(mDeathRecipient,0)

还有方法isBinderAlive()也可以判断binder是否死亡。

最后,我们用AIDL中,经常需要做一些权限的校验。具体有两种方法,一种是permission校验,我们可以定义一种permission, 并且在onBind()方法中调用checkCallingOrselfPermission()对permission进行校验,客户端想绑定我们的服务必须在AndroidMenifest.xml文件中加上这个权限。

第二种方法,我们可以在onTransact方法中处理。可以用第一种方法的permission校验,也可以采用uid和pid进行校验,验证不通过,直接返回false就可以了。

BINDER是什么意思

binder 英[ˈbaɪndə(r)] 美[ˈbaɪndɚ]

n. 粘合剂; 包扎物,包扎工具; [法] 临时契约; 装订工;

[例句]The final operation in propellant manufacture is the curing of the binder.

制造推进剂的最后操作是粘合剂的固化。

[其他] 复数:binders

Binder机制概述

Android进程间通讯是通过Binder机制来实现的,Android是基于linux系统因此有必要了解Linux系统进程相关知识.

Linux系统中(其他系统也是这样)不同进程之间不允许直接操作或访问另一进程.也就是进程隔离.

为了保证用户进程不能直接访问内核,操作系统从逻辑上将虚拟空间划分为用户空间和内核空间.内核程序运行在内核空间(kernel space),应用程序运行在用户空间(user space).为了安全,他们之间是隔离的,即使用户程序奔溃了,也不会影响内核.内核空间数据是可以共享的,用户空间不可以.

用户空间访问内核空间只能通过系统调用,系统调用是用户空间访问内核空间的唯一方式,保证所有资源访问在内核控制下,避免了用户对系统资源的越权访问,提高了系统安全性和稳定性.

copy_from_user:将用户空间数据拷贝到内核空间

copy_to_user:将内核空间数据拷贝到用户空间

由于用户进程不能直接访问硬件地址,所以系统提供了一种机制:内存映射(Memory Map).在Linux中通过调用函数mmap实现内存映射,将用户空间一块内存地址映射到内核空间.映射关系建立后,对用户空间内存的修改可以反应到内核空间.内存映射可减少拷贝次数.

如果没有内存映射,用户进程需要访问硬盘文件时,需要在内核空间创建一片页缓存,将硬盘文件数据拷贝到页缓存然后用户进程在拷贝页缓存数据,需要两次拷贝.通过内存映射后硬盘文件可以和内核的虚拟内存直接映射,减少一次拷贝.

如图

inter-process-communication进程间通信,指进程间交换数据的过程.

Linux提供了很多进程间通讯的机制,主要有管道(pipe)、消息队列(Message)、信号(sinal)、信号量(semophore)、套接字(socket)等

内核程序在内核空间分配并开辟一块内核缓冲区,发送进程将数据通过copy_from_user拷贝到内核空间的数据缓冲区,内核空间通过copy_to_user将数据拷贝到接收进程,这样就实现了一次进程间通信.如图

Linux的IPC机制有两个问题:

1.数据通过用户空间-内核空间-用户空间,经过两次拷贝,效率不高

2.接收进程无法预先知道数据大小,只能尽可能大创建数据缓冲区,或通过api消息头获取消息体的大小,浪费了空间或时间.

Android进程间通信通过Binder实现,下面介绍Binder机制通信原理,为什么是Binder

内核程序创建一个数据接收缓存区,同时创建一个内核缓冲区.并建立内核缓冲区和数据接收缓冲区内存映射,以及数据内核缓冲区和接收进程用户空间的映射关系.发送进程通过copy_from_user将数据拷贝到内核数据接收缓冲区,因为内核数据接收缓冲区和内核缓冲区以及接收进程用户空间存在映射关系,相当于将数据发送到了接收进程.完成了一次进程间通信.如图

Binder机制是c/s架构的,由Client、server、ServiceManager和Binder组成.client、server和serviceManager都是独立的进程,由于Linux进程隔离的原因,所以需要借助Binder进行通信.

Binder通信主要有三个步骤:注册服务、获取服务、使用服务.如下图

从上一条Binder实现原理示例图中可以看到,Binder可分为Java Binder、Native Binder和Kernal Binder.应用开发需要了解Java Binder和Navive Binder.这里只介绍Binder基本原理.具体可查看文章结尾的链接.

感谢

;request_id=159651217319195188353096biz_id=0utm_medium=distribute.pc_search_result.none-task-blog-2 all first_rank_ecpm_v3~rank_business_v1-1-73560642.ecpm_v3_rank_business_v1utm_term=Binderspm=1018.2118.3001.4187

深入理解Binder

之前一直对 Binder 理解不够透彻,仅仅知道一些皮毛,所以最近抽空深入理解一下,并在这里做个小结。

Binder 是 Android 系统中实现 IPC (进程间通信)的一种机制。Binder 原意是“胶水、粘合剂”,所以可以想象它的用途就是像胶水一样把两个进程紧紧“粘”在一起,从而可以方便地实现 IPC 。

那么为什么会有进程通信呢?这是因为在 Linux 中进程之间是隔离的,也就是说 A 进程不知道有 B 进程的存在,相应的 B 进程也不知道 A 进程的存在。A 、B 两进程的内存是不共享的,所以 A 进程的数据想要传给 B 进程就需要用到 IPC 。

在这里再科普一下进程空间的知识点:进程空间可以分为用户空间和内核空间。简单的说,用户空间是用户程序运行的空间,而内核空间就是内核运行的空间了。因为像内核这么底层、至关重要的东西肯定是不会简单地让用户程序随便调用的,所以需要把内核保护起来,就创造了内核空间,让内核运行在内核空间中,这样就不会被用户空间随便干扰到了。两个进程之间的用户空间是不共享的,但是内核空间是共享的。

所以到这里,有些同学会有个大胆的想法,两个进程间的通信可以利用内核空间来实现啊,因为它们的内核空间是共享的,这样数据不就传过去了嘛。但是接着又来了一个问题:为了保证安全性,用户空间和内核空间也是隔离的。那么如何把数据从发送方的用户空间传到内核空间呢?

针对这个问题提供了 系统调用 来解决,可以让用户程序调用内核资源。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性(这段话来自 《写给 Android 应用工程师的 Binder 原理剖析》 )。我们平时的网络、I/O操作其实都是通过系统调用在内核空间中运行的(也就是 内核态 )。

至此,关于 IPC 我们有了一个大概的实现方案:A 进程的数据通过系统调用把数据传输到内核空间(即copy_from_user),内核空间再利用系统调用把数据传输到 B 进程(即 copy_to_user)。这也正是目前 Linux 中传统 IPC 通信的实现原理,可以看到这其中会有两次数据拷贝。

(图片来自于 《写给 Android 应用工程师的 Binder 原理剖析》 )

Linux 中的一些 IPC 方式:

通过上面的讲解我们可以知道,IPC 是需要内核空间来支持的。Linux 中的管道、socket 等都是在内核中的。但是在 Linux 系统里面是没有 Binder 的。那么 Android 中是如何利用 Binder 来实现 IPC 的呢?

这就要讲到 Linux 中的 动态内核可加载模块 。动态内核可加载模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。(这段话来自 《写给 Android 应用工程师的 Binder 原理剖析》 )在 Android 中,这个内核模块也就是 Binder 驱动。

另外,Binder IPC 原理相比较上面传统的 Linux IPC 而言,只需要一次数据拷贝就可以完成了。那么究竟是怎么做到的呢?

其实 Binder 是借助于 mmap (内存映射)来实现的。mmap 用于文件或者其它对象映射进内存,通常是用在有物理介质的文件系统上的。mmap 简单的来说就是可以把用户空间的内存区域和内核空间的内存区域之间建立映射关系,这样就减少了数据拷贝的次数,任何一方的对内存区域的改动都将被反应给另一方。

所以,Binder 的做法就是建立一个虚拟设备(设备驱动是/dev/binder),然后在内核空间创建一块数据接收的缓存区,这个缓存区会和内存缓存区以及接收数据进程的用户空间建立映射,这样发送数据进程把数据发送到内存缓存区,该数据就会被间接映射到接收进程的用户空间中,减少了一次数据拷贝。具体可以看下图理解

(图片来自于 《写给 Android 应用工程师的 Binder 原理剖析》 )

Binder 的优点

在整个 Binder 通信过程中,可以分为四个部分:

其中 Client 和 Server 是应用层实现的,而 Binder 驱动和 ServiceManager 是 Android 系统底层实现的。

具体流程如下:

(Binder通信过程示意图来自于 《写给 Android 应用工程师的 Binder 原理剖析》 )

标签: binder

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。