Android开发,车载通讯应用——binder通讯原理解析

Binder简单理解

简单来说,Binder 就是用来Client 端和 Server 端通信的。并且 Client 端和 Server 端 可以在一个进程也可以不在同一个进程,Client 可以向 Server 端发起远程调用,也可以向Server传输数据(当作函数参数来传),并且不用关心对方在哪个进程。

Binder的基本原理

Binder借助了内存映射(mmap)的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射。从发送方用户空间拷贝到内核空间缓存区的数据,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。

IPC通信原理

理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。

通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copyfromuser() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copytouser() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信

我们来看下原理图:

这种传统的 IPC 通信方式有两个问题:

1、性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;

2、接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

Binder跨进程通信原理

理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。

动态内核可加载模块

正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的 动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。

在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。

那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。这就涉及到 内存映射 的概念了。

内存映射

Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

Binder IPC 实现原理

Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

一次完整的 Binder IPC 通信过程通常是这样:

1、首先 Binder 驱动在内核空间创建一个 数据接收缓存区 ;

2、接着在内核空间开辟一块内核缓存区,建立 内核缓存区 和 内核中数据接收缓存区 之间的映射关系,以及 内核中数据接收缓存区 和 接收进程用户空间地址 的映射关系;

3、发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

我们来看下原理图:

Binder通信流程

我们上面以及聊过,Binder的通信流程是先由Client发出,经过Binder Framework到达Kernel,再经过Kernel转发,最终通过Binder Framework到达Server。整体的通信流程有点像网络通信。

我们以常见的启动四大组件为例来描述Binder的通信流程,如下所示:

1、当我们在Activity里启动一个Service,这个调用会经过层层传递到ActivityManagerProxy,然后ActivityManagerProxy会通过Binder发起跨进程调用。

2、接着Client就会向Binder Driver发起binder ioctl请求,在IPCTreadState::waitForResponse里执行While循环,在While循环中调用IPCThreadState::talkWithDriver()与驱动交互,然后Client线程等待驱动回复

binder驱动收到BC_TRANSACTION事件后的应答消息:

BR_TRANSACTION_COMPLETE:对于oneway transaction(非阻塞通信、单向),当收到该消息,则完成了本次Binder通信

BR_DEAD_REPLY:回复失败,往往是线程或节点为空,则结束本次通信Binder

BR_FAlLED_REPLY:回复失败,往往是transaction出错导致,则结束本次通信Binder

BR_REPLY:对于非oneway transaction时,当收到该消息,则完整地完成本次Binder通信

3、上述命令除了BR_TRANSACTION_COMPLETE:其他回复Client端的进程都会接着调用IPCThreadState::executeCommand()处理驱动返回的命令。

4、对于Server端而言,会调用IPCThreadState::joinThreadPool循环执行IPCThreadState::getAndExecuteCommand,最终调用ActivityManagerService.startService方法。如果需要回复Client会调用talkWithDriver与驱动通信

Client在与Service的通信过程按照是否需要Server返回数据可以分为两种方式:

单向模式:不需要Server返回数据

双向模式:需要Server返回数据

对以上的代码做简单分析:

binder_ioctl -> binder_ioctl_write_read -> binder_thread_write

```java
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{...switch (cmd) {case BINDER_WRITE_READ:ret = binder_ioctl_write_read(filp, cmd, arg, thread)...
}——————————————————static int binder_ioctl_write_read(struct file *filp,unsigned int cmd, unsigned long arg,struct binder_thread *thread)
{...if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { //把用户空间数据ubuf拷贝到bwrret = -EFAULT;goto out;...if (bwr.write_size > 0) {  //写缓冲区有数据时,binder_thread_writeret = binder_thread_write(proc, thread,bwr.write_buffer,bwr.write_size,&bwr.write_consumed);trace_binder_write_done(ret);...}if (bwr.read_size > 0) {  //读缓冲区有数据时,binder_thread_readret = binder_thread_read(proc, thread, bwr.read_buffer,bwr.read_size,&bwr.read_consumed,filp->f_flags & O_NONBLOCK);trace_binder_read_done(ret);binder_inner_proc_lock(proc);...}...//将内核数据bwr拷贝到用户空间ubufif (copy_to_user(ubuf, &bwr, sizeof(bwr))) { ret = -EFAULT;goto out;}
}

Client端

status_t IPCThreadState::transact(int32_t handle,uint32_t code, const Parcel& data,Parcel* reply, uint32_t flags)
{if (reply) {//等待响应err = waitForResponse(reply);} else {Parcel fakeReply;err = waitForResponse(&fakeReply);}...
} else {//oneway,则不需要等待 reply 的场景err = waitForResponse(nullptr, nullptr);
}
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{int32_t cmd;int32_t err;//在while循环中做下面的事情:while (1) { //循环中通过talkWithDriver与驱动通信if ((err=talkWithDriver()) < NO_ERROR) break; ...if (mIn.dataAvail() == 0) continue;cmd = mIn.readInt32();switch (cmd) {...  //循环中等待Binder驱动回复,执行executeCommanderr = executeCommand(cmd); ...}}...return err;
}—————————————————status_t IPCThreadState::executeCommand(int32_t cmd)
{
...
switch ((uint32_t)cmd) {  //switch判断case BR_ERROR:result = mIn.readInt32();break;case BR_OK:break;case BR_ACQUIRE:refs = (RefBase::weakref_type*)mIn.readPointer();obj = (BBinder*)mIn.readPointer();ALOG_ASSERT(refs->refBase() == obj,"BR_ACQUIRE: object %p does not match cookie %p (expected %p)",refs, obj, refs->refBase());obj->incStrong(mProcess.get());IF_LOG_REMOTEREFS() {LOG_REMOTEREFS("BR_ACQUIRE from driver on %p", obj);obj->printRefs();}mOut.writeInt32(BC_ACQUIRE_DONE);mOut.writePointer((uintptr_t)refs);mOut.writePointer((uintptr_t)obj);break;case BR_RELEASE:refs = (RefBase::weakref_type*)mIn.readPointer();obj = (BBinder*)mIn.readPointer();...

Server端

void IPCThreadState::joinThreadPool(bool isMain) 
{
...do {   //循环里面不断的读取命令数据,然后解析命令数据,并给出相应的响应。processPendingDerefs();// now get the next command to be processed, waiting if necessaryresult = getAndExecuteCommand();...}—————————————————————status_t IPCThreadState::getAndExecuteCommand()
{status_t result;int32_t cmd;result = talkWithDriver();if (result >= NO_ERROR) {size_t IN = mIn.dataAvail();if (IN < sizeof(int32_t)) return result;cmd = mIn.readInt32();IF_LOG_COMMANDS() {alog << "Processing top-level Command: "<< getReturnString(cmd) << endl;}pthread_mutex_lock(&mProcess->mThreadCountLock);mProcess->mExecutingThreadsCount++;if (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads &&mProcess->mStarvationStartTimeMs == 0) {mProcess->mStarvationStartTimeMs = uptimeMillis();}pthread_mutex_unlock(&mProcess->mThreadCountLock);//解析来自驱动的命令result = executeCommand(cmd);pthread_mutex_lock(&mProcess->mThreadCountLock);mProcess->mExecutingThreadsCount--;if (mProcess->mExecutingThreadsCount < mProcess->mMaxThreads &&mProcess->mStarvationStartTimeMs != 0) {int64_t starvationTimeMs = uptimeMillis() - mProcess->mStarvationStartTimeMs;if (starvationTimeMs > 100) {ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms",mProcess->mMaxThreads, starvationTimeMs);}mProcess->mStarvationStartTimeMs = 0;}pthread_cond_broadcast(&mProcess->mThreadCountDecrement);pthread_mutex_unlock(&mProcess->mThreadCountLock);}return result;
}

本文主要对在Android开发中的binder通信机制原理的理解,更多有关通讯以及Android开发的技术进阶可以前往《Android高级开发进阶手册》点击可以查看详细内容板块。

Binder 的完整定义

1.从进程间通信的角度看,Binder 是一种进程间通信的机制;

2.从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;

3.从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理

4.从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/176420.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【element-ui】表格

效果展示 组件代码 <el-table class"compTableClass" ref"tableOOOOO":class"(className in tableConfig)?tableConfig.className:":data"tableConfig.data" :height"tableConfig.height" style"width: 100%"…

阿里发布AI编码助手:通义灵码,兼容 VS Code、IDEA等主流编程工具

今天是阿里云栖大会的第一天&#xff0c;相信场外的瓜&#xff0c;大家都吃过了。这里就不说了&#xff0c;有兴趣可以看看这里&#xff1a;云栖大会变成相亲现场&#xff0c;最新招婿鄙视链来了... 。 这里主要说说阿里还发布了一款AI编码助手&#xff0c;对于我们开发者来说…

selenium+python web自动化测试框架项目实战实例教程

自动化测试对程序的回归测试更方便。 由于回归测试的动作和用例是完全设计好的,测试期望的结果也是完全可以预料的,将回归测试自动运行... 可以运行更加繁琐的测试 自动化测试的一个明显好处就是可以在很短的时间内运行更多的测试。学习自动化测试最终目的是应用到实际项目中&…

通过shiro框架记录用户登录,登出及浏览器关闭日志

背景&#xff1a; 公司项目之前使用websocket记录用户登录登出日志及浏览器关闭记录用户登出日志&#xff0c;测试发现仍然存在问题&#xff0c; 问题一&#xff1a;当浏览器每次刷新时websocket其实是会断开重新连接的&#xff0c;因此刷新一下就触发记录登出的日志&#xff0…

C语言KR圣经笔记 2.11条件表达式 2.12优先级和求值顺序

2.11条件表达式 if (a > b) z a; else z b; 上面的语句计算 a 和 b 中的最大值并存入 z。而使用三元操作符 ? : 的条件表达式&#xff0c;为这个结构及类似结构提供了另一种写法。在如下表达式 expr1 ? expr2 : expr3 中&#xff0c;首先对 expr1 求值。如果值非0 …

验证链(CoVe)降低LLM中的幻觉10.31

验证链&#xff08;CoVE&#xff09;降低LLM中的幻觉 摘要1 引言2 相关工作3 验证链&#xff08;Chain-of-Verification&#xff09;3.1 生成基准回答3.2 计划验证3.3 执行验证3.4 最终验证的回答 4 实验&#xff08;直译&#xff09;4.1 任务4.1.1 WIKIDATA4.1.2 WIKI-CATEGOR…

ByteBuffer的原理和使用详解

ByteBuffer是字节缓冲区&#xff0c;主要用户读取和缓存字节数据&#xff0c;多用于网络编程&#xff0c;原生的类&#xff0c;存在不好用&#xff0c;Netty采用自己的ByteBuff&#xff0c;对其进行了改进 1.ByteBuffer的2种创建方式 1.ByteBuffer buf ByteBuffer.allocate(i…

mediapipe 训练自有图像数据分类

参考&#xff1a; https://developers.google.com/mediapipe/solutions/customization/image_classifier https://colab.research.google.com/github/googlesamples/mediapipe/blob/main/examples/customization/image_classifier.ipynb#scrollToplvO-YmcQn5g 安装&#xff1a…

【漏洞复现】酒店宽带运营系统RCE

漏洞描述 安美数字 酒店宽带运营系统 server_ping.php 远程命令执行漏洞 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;尊重社会公德&#xff0c;不得利用网络从事危害国家安全、荣誉和利益&#xff…

生成瑞利信道(Python and Matlab)

channel h k h_k hk​ is modeled as independent Rayleigh fading with average power loss set as 10^−3 Python import numpy as np# Set the parameters average_power_loss 1e-3 # Average power loss (10^(-3)) num_samples 1000 # Number of fading samples to …

1深度学习李宏毅

目录 机器学习三件事&#xff1a;分类&#xff0c;预测和结构化生成 2、一般会有经常提到什么是标签label&#xff0c;label就是预测值&#xff0c;在机器学习领域的残差就是e和loss​编辑3、一些计算loss的方法&#xff1a;​编辑​编辑 4、可以设置不同的b和w从而控制loss的…

Android Button修改背景颜色及实现科技感效果

目录 效果展示 实现科技感效果 修改Button背景 结语 效果展示 Android Button修改背景颜色及实现科技感效果效果如下&#xff1a; 实现科技感效果 操作方法如下&#xff1a; 想要创建一个富有科技感的按钮样式时&#xff0c;可以使用 Android 的 Shape Drawable 和 Sele…

CSS3表格和表单样式

在传统网页中&#xff0c;表格主要用于网页布局&#xff0c;因此也成为网页编辑的主要工具&#xff1b;在标准化网页设计中&#xff0c;表格的主要功能是显示数据&#xff0c;也可适当辅助结构设计。本章主要介绍如何使用CSS控制表格和表单的显示效果&#xff0c;如表格和表单的…

cortex-A7核UART总线

1.总线 各个部件之间传输一种媒介 2.串行总线 3.并行总线 4.同步和异步 同步&#xff1a; 异步&#xff1a; 5.ST-LINK仿真器连接方式 6.串口通信信息---异步串行全双工总线 串口配置信息8N1代表什么? 7.串口通讯协议

小米电视播放win10视频 win10共享问题

解决的方法就是安装SMB1.0协议 重启就OK了

PyCharm下载和安装教程(包含配置Python解释器)

PyCharm 是 JetBrains 公司&#xff08;www.jetbrains.com&#xff09;研发&#xff0c;用于开发 Python 的 IDE 开发工具。图 1 所示为 JetBrains 公司开发的多款开发工具&#xff0c;其中很多工具都好评如潮&#xff0c;这些工具可以编写 Python、C/C、C#、DSL、Go、Groovy、…

免费的PPT模版--九五小庞

PPT模板&#xff1a; www.1ppt.com/moban/    行业PPT模板&#xff1a;www.1ppt.com/hangye/ 节日PPT模板&#xff1a;www.1ppt.com/jieri/    PPT素材&#xff1a; www.1ppt.com/sucai/PPT背景图片&#xff1a;www.1ppt.com/beijing/   PPT图表&#xff…

C++基础算法④——排序算法(快速、归并附完整代码)

快速排序 快速排序是对冒泡排序的一种改进。 它的基本思想是:通过一趟排序将待排记录分割成独立的两部分&#xff0c;其中一部分记录的关键字均比另一部分记录的关键字小&#xff0c;则可分别对这两部分记录继续进行快速排序&#xff0c;以达到整个序列有序。 假设我们现在对 …

Linux————内置命令大全

&#xff08;一&#xff09;内置命令 Shell 内置命令&#xff0c;就是由 Bash Shell 自身提供的命令&#xff0c;而不是文件系统中的可执行脚本文件。内置命令的执行速度通常优于外部命令&#xff0c;因为执行外部命令不仅会导致磁盘I/O操作&#xff0c;而且还需要为其fork一个…

Android Studio中配置Git

安装Git 在安装Android Studio之前&#xff0c;需要先安装Git。可以从Git官网下载并安装Git&#xff1a;https://git-scm.com/downloads 在Android Studio中配置Git 在Android Studio中&#xff0c;依次点击“File” -> “Settings”&#xff0c;在弹出的窗口中选择“Ver…