Android Binder框架实现之Native层addService详解之请求的反馈

2024-04-13 00:58

本文主要是介绍Android Binder框架实现之Native层addService详解之请求的反馈,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android Binder框架实现之Native层addService详解之请求的反馈


Android Binder框架实现目录:

Android Binder框架实现之Binder的设计思想
Android Binder框架实现之何为匿名/实名Binder
Android Binder框架实现之Binder中的数据结构
Android Binder框架实现之Binder相关的接口和类
Android Binder框架实现之Parcel详解之基本数据的读写
Android Binder框架实现之Parcel read/writeStrongBinder实现
Android Binder框架实现之servicemanager守护进程
Android Binder框架实现之defaultServiceManager()的实现
Android Binder框架实现之Native层addService详解之请求的发送
Android Binder框架实现之Native层addService详解之请求的处理
Android Binder框架实现之Native层addService详解之请求的反馈
Android Binder框架实现之Binder服务的消息循环
Android Binder框架实现之Native层getService详解之请求的发送
Android Binder框架实现之Native层getService详解之请求的处理
Android Binder框架实现之Native层getService详解之请求的反馈
Android Binder框架实现之Binder Native Service的Java调用流程
Android Binder框架实现之Java层Binder整体框架设计
Android Binder框架实现之Framework层Binder服务注册过程源码分析
Android Binder框架实现之Java层Binder服务跨进程调用源码分析
Android Binder框架实现之Java层获取Binder服务源码分析


引言

  在前面的两篇文章Android Binder框架实现之Native层addService详解之请求的发送Android Binder框架实现之Native层addService详解之请求的处理分别介绍了addService中"请求的发送"和"请求的处理"这两部分,本文将介绍addService请求的最后一部分–请求的反馈。ServiceManager在处理完addService请求之后,添加了一个待处理事务到MediaPlayerService的事务列表中,并将MediaPlayerService唤醒。我们从上次MediaPlayerService休眠的地方开始,看看它被唤醒之后干了些什么。

注意:本文是基于Android 7.xx版本进行介绍的,其中涉及的源码路径如下:

frameworks/native/libs/binder/IServiceManager.cpp
frameworks/native/libs/binder/Static.cpp
frameworks/native/libs/binder/ProcessState.cpp
kernel/drivers/staging/android/binder.c
kernel/include/linux/list.h
kernel/drivers/staging/android/uapi/binder.h
external/kernel-headers/original/uapi/linux/android/binder.h
frameworks/native/include/binder/ProcessState.h
frameworks/native/include/binder/BpBinder.h
frameworks/native/libs/binder/BpBinder.cpp
frameworks/native/include/binder/IPCThreadState.h
frameworks/native/libs/binder/IPCThreadState.cpp
frameworks/native/include/binder/IInterface.h
frameworks/native/libs/binder/IInterface.cpp
frameworks/native/include/binder/IBinder.h
frameworks/native/libs/binder/Binder.cpp
frameworks/av/media/mediaserver/main_mediaserver.cpp
frameworks/native/cmds/servicemanager/service_manager.c
  • 在正式开始进行源码分析前,先看下Native Binder服务注册整体示意图:

在这里插入图片描述

  • 接着在正式开始进行源码分析前,先看看Native Binder类图如下:

在这里插入图片描述


1.唤醒MediaPlayerService

   从前面的篇章我们可以知道,MediaPlayerService在发送addService请求之后,会阻塞在Binder驱动wait_event_interruptible_exclusive等待被唤醒,那么我们从此处被唤醒说起,继续往下分析。

static int binder_thread_read(struct binder_proc *proc,struct binder_thread *thread,binder_uintptr_t binder_buffer, size_t size,binder_size_t *consumed, int non_block)
{...if (wait_for_proc_work) {...if (non_block) {...} else//阻塞式读取,阻塞等待事务的发生,此时会被唤醒ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));} else {...}...while (1) {uint32_t cmd;struct binder_transaction_data tr;struct binder_work *w;struct binder_transaction *t = NULL;//如果当前线程的“待完成工作”不为空,则取出待完成工作if (!list_empty(&thread->todo))w = list_first_entry(&thread->todo, struct binder_work, entry);else if (!list_empty(&proc->todo) && wait_for_proc_work)w = list_first_entry(&proc->todo, struct binder_work, entry);else {...}...switch (w->type) {case BINDER_WORK_TRANSACTION: {t = container_of(w, struct binder_transaction, work);} break;...}if (!t)continue;//此时t-buffer-target_node是NULLif (t->buffer->target_node) {...} else {tr.target.ptr = 0;tr.cookie = 0;cmd = BR_REPLY;}//交易码tr.code = t->code;tr.flags = t->flags;tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);if (t->from) {struct task_struct *sender = t->from->proc->tsk;tr.sender_pid = task_tgid_nr_ns(sender,task_active_pid_ns(current));} else {tr.sender_pid = 0;}//数据大小tr.data_size = t->buffer->data_size;//数据中对象的偏移数组的大小(即对象的个数)tr.offsets_size = t->buffer->offsets_size;//数据tr.data.ptr.buffer = (binder_uintptr_t)((uintptr_t)t->buffer->data +proc->user_buffer_offset);//数据中对象的偏移数组tr.data.ptr.offsets = tr.data.ptr.buffer +ALIGN(t->buffer->data_size,sizeof(void *));//将cmd指令写入到ptr,即传递到用户空间if (put_user(cmd, (uint32_t __user *)ptr))return -EFAULT;//将tr数据拷贝到用户空间ptr += sizeof(uint32_t);if (copy_to_user(ptr, &tr, sizeof(tr)))return -EFAULT;ptr += sizeof(tr);...//删除已处理的事务list_del(&t->work.entry);t->buffer->allow_user_free = 1;//设置回复信息if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {...} else {t->buffer->transaction = NULL;kfree(t);binder_stats_deleted(BINDER_STAT_TRANSACTION);}break;}done://更新bwr.read_comsumed的值*consumed = ptr - buffer;...return 0;
}

此时MediaPlayerService进程被Service Manager从睡梦中唤醒,同时它的待处理事务队列中有Service Manager添加的事务,此时,binder_has_thread_work()为true。因此,MediaPlayerService会继续往下执行。下面我们逐步分析MediaPlayerService被唤醒后的处理流程:
(1) 进入while循环,首先取出待处理事务。
(2) 事务的类型是BINDER_WORK_TRANSACTION,得到对应的binder_transaction*类型指针t之后,跳出switch语句。时t不为NULL,因此继续往下执行。下面的工作的目的,是将t中的数据转移到tr中(tr是事务交互数据包结构体binder_transaction_data对应的指针),然后将指令和tr数据都拷贝到用户空间,让MediaPlayerService读取后进行处理。此时的指令是BR_REPLY。
binder_thread_read()执行完毕之后,共反馈了两个指令到用户空间,BR_NOOP和BR_REPLY。此时MediaPlayerService从内核空间返回,进入用户空间,下面让我们重返用户空间分析。



2.MediaPlayerService重返用户空间

   现在回到MediaPlayerService位于用户空间的进程。它会逐个解析Binder驱动反馈的指令。对于BR_NOOP,MediaPlayerService不会做任何实质性的动作。对于BR_REPLY,我们细看代码分析MediaPlayerService的处理流程。让我们来一起分析IPCThreadState::waitForResponse()函数。


2.1 IPCThreadState::waitForResponse

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{uint32_t cmd;int32_t err;while (1) {if ((err=talkWithDriver()) < NO_ERROR) break;...cmd = (uint32_t)mIn.readInt32();...switch (cmd) {...case BR_REPLY:{binder_transaction_data tr;err = mIn.read(&tr, sizeof(tr));ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");if (err != NO_ERROR) goto finish;if (reply) {if ((tr.flags & TF_STATUS_CODE) == 0) {reply->ipcSetDataReference(reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t),freeBuffer, this);} else {...}} else {...}}goto finish;...}}
finish:...return err;
}

此时在在BR_REPLY分支中,先读取出数据,并保存到tr中。由于reply不为null,并且tr.flags & TF_STATUS_CODE为0;因此,会执行reply->ipcSetDataReference()。下面让我们一起分析一下ipcSetDataReference函数。


2.2 Parcel::ipcSetDataReference

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,const binder_size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
{binder_size_t minOffset = 0;freeDataNoInit();mError = NO_ERROR;mData = const_cast<uint8_t*>(data);mDataSize = mDataCapacity = dataSize;//ALOGI("setDataReference Setting data size of %p to %lu (pid=%d)", this, mDataSize, getpid());mDataPos = 0;ALOGV("setDataReference Setting data pos of %p to %zu", this, mDataPos);mObjects = const_cast<binder_size_t*>(objects);mObjectsSize = mObjectsCapacity = objectsCount;mNextObjectHint = 0;mOwner = relFunc;mOwnerCookie = relCookie;for (size_t i = 0; i < mObjectsSize; i++) {binder_size_t offset = mObjects[i];if (offset < minOffset) {ALOGE("%s: bad object offset %" PRIu64 " < %" PRIu64 "\n",__func__, (uint64_t)offset, (uint64_t)minOffset);mObjectsSize = 0;break;}minOffset = offset + sizeof(flat_binder_object);}scanForFds();
}

通过代码可知ipcSetDataReference()是根据参数的值重新初始化Parcel的数据和对象。让我逐步分析一下。

(1) freeDataNoInit()的目的是释放原有的内存。为接下来保存Binder驱动反馈的数据做准备。
(2) 在Android Binder机制(六) addService详解02之 请求的处理中,ServiceManager反馈数据时,我们知道它对应的BR_REPLY的数据实际上是空的!因此,这里的mDataSize和mObjectsSize都是0。

实际上,Binder驱动反馈给MediaPlayerService的指令就是告诉它addService已经成功处理完毕!

在MediaPlayerService解析完Binder驱动反馈的数据之后,它会层层向上返回。这样,MediaPlayerService::instantiate()也就正式执行完了!
MediaPlayerService::instantiate()执行完毕,但是MediaPlayerService进程似乎还没有进入消息循环中等到Client的请求!那么,它是何时进入消息循环的呢?回到MediaPlayerService进程的main()函数入口中,它后面是通过startThreadPool()进入消息循环的。这部分的内容,我们下一章再来介绍。

int main(int argc __unused, char **argv __unused)
{...sp<ProcessState> proc(ProcessState::self());sp<IServiceManager> sm(defaultServiceManager());...MediaPlayerService::instantiate();ResourceManagerService::instantiate();...ProcessState::self()->startThreadPool();IPCThreadState::self()->joinThreadPool();
}


写在最后

   写到这里,addService的整个流程也算告一段落了,读者朋友们你们都get了相关技能了吗,说实话我也木有全部get到。所以还是得理解为主,理解设计者的思路,然后跟着代码解读。多看,多分析一定会有意想不到的效果。最后让我们奉上整个时序图,重温一遍。

在这里插入图片描述

这篇关于Android Binder框架实现之Native层addService详解之请求的反馈的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/898720

相关文章

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon