骚年!用Binder原理彻底征服大厂面试官吧

2024-01-01 18:08

本文主要是介绍骚年!用Binder原理彻底征服大厂面试官吧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者: 刘望舒

原文: http://liuwangshu.cn/framework/binder/3-addservice.html

这是后厂村码农的第 131篇原创分享

前言

在上一篇文章中,我们学习了ServiceManager中的Binder机制,有一个问题由于篇幅问题没有讲完,那就是MediaPlayerService是如何注册的。通过了解MediaPlayerService是如何注册的,可以得知系统服务的注册过程。

1.从调用链角度说明MediaPlayerService是如何注册的

我们先来看MediaServer的入口函数,代码如下所示。
frameworks/av/media/mediaserver/main_mediaserver.cpp

int main(int argc __unused, char **argv __unused)
{signal(SIGPIPE, SIG_IGN);//获取ProcessState实例sp<ProcessState> proc(ProcessState::self());sp<IServiceManager> sm(defaultServiceManager());ALOGI("ServiceManager: %p", sm.get());InitializeIcuOrDie();//注册MediaPlayerServiceMediaPlayerService::instantiate();//1ResourceManagerService::instantiate();registerExtensions();//启动Binder线程池ProcessState::self()->startThreadPool();//当前线程加入到线程池IPCThreadState::self()->joinThreadPool();
}

这段代码中的很多内容都在上一篇文章介绍过了,接着分析注释1处的代码。

frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp

void MediaPlayerService::instantiate() {defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService,());
}

defaultServiceManager返回的是BpServiceManager,不清楚的看Binder这么弱还跑来面腾讯?这篇文章。参数是一个字符串和MediaPlayerService,看起来像是Key/Value的形式来完成注册,接着看addService函数。

frameworks/native/libs/binder/IServiceManager.cpp

 virtual status_t addService(const String16& name, const sp<IBinder>& service,bool allowIsolated, int dumpsysPriority) {Parcel data, reply;//数据包data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());data.writeString16(name); //name值为"media.player"data.writeStrongBinder(service); //service值为MediaPlayerServicedata.writeInt32(allowIsolated ? 1 : 0);data.writeInt32(dumpsysPriority);status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);//1return err == NO_ERROR ? reply.readExceptionCode() : err;}

data是一个数据包,后面会不断的将数据写入到data中, 注释1处的remote()指的是mRemote,也就是BpBinder。addService函数的作用就是将请求数据打包成data,然后传给BpBinder的transact函数,代码如下所示。
frameworks/native/libs/binder/BpBinder.cpp

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{if (mAlive) {status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);if (status == DEAD_OBJECT) mAlive = 0;return status;}return DEAD_OBJECT;
}

BpBinder将逻辑处理交给IPCThreadState,先来看IPCThreadState::self()干了什么?
frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState* IPCThreadState::self()
{   //首次进来gHaveTLS的值为falseif (gHaveTLS) {
restart:const pthread_key_t k = gTLS;//1IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);//2if (st) return st;return new IPCThreadState;//3}...pthread_mutex_unlock(&gTLSMutex);goto restart;
}

注释1处的TLS的全称为Thread local storage,指的是线程本地存储空间,在每个线程中都有TLS,并且线程间不共享。注释2处用于获取TLS中的内容并赋值给IPCThreadState*指针。注释3处会新建一个IPCThreadState,这里可以得知IPCThreadState::self()实际上是为了创建IPCThreadState,它的构造函数如下所示。
frameworks/native/libs/binder/IPCThreadState.cpp                                            

IPCThreadState::IPCThreadState(): mProcess(ProcessState::self()),mStrictModePolicy(0),mLastTransactionBinderFlags(0)
{pthread_setspecific(gTLS, this);//1clearCaller();mIn.setDataCapacity(256);mOut.setDataCapacity(256);
}

注释1处的pthread_setspecific函数用于设置TLS,将IPCThreadState::self()获得的TLS和自身传进去。IPCThreadState中还包含mIn、一个mOut,其中mIn用来接收来自Binder驱动的数据,mOut用来存储发往Binder驱动的数据,它们默认大小都为256字节。
知道了IPCThreadState的构造函数,再回来查看IPCThreadState的transact函数。
frameworks/native/libs/binder/IPCThreadState.cpp  

status_t IPCThreadState::transact(int32_t handle,uint32_t code, const Parcel& data,Parcel* reply, uint32_t flags)
{status_t err;flags |= TF_ACCEPT_FDS;...err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);//1if (err != NO_ERROR) {if (reply) reply->setError(err);return (mLastError = err);}if ((flags & TF_ONE_WAY) == 0) {...if (reply) {err = waitForResponse(reply);//2} else {Parcel fakeReply;err = waitForResponse(&fakeReply);}...} else {//不需要等待reply的分支err = waitForResponse(NULL, NULL);}return err;
}

调用BpBinder的transact函数实际上就是调用IPCThreadState的transact函数。注释1处的writeTransactionData函数用于传输数据,其中第一个参数BC_TRANSACTION代表向Binder驱动发送命令协议,向Binder设备发送的命令协议都以BC_开头,而Binder驱动返回的命令协议以BR_开头。这个命令协议我们先记住,后面会再次提到他。

现在分别来分析注释1的writeTransactionData函数和注释2处的waitForResponse函数。

1.1 writeTransactionData函数分析

frameworks/native/libs/binder/IPCThreadState.cpp  

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{binder_transaction_data tr;//1tr.target.ptr = 0; tr.target.handle = handle;//2 tr.code = code;  //code=ADD_SERVICE_TRANSACTIONtr.flags = binderFlags;tr.cookie = 0;tr.sender_pid = 0;tr.sender_euid = 0;const status_t err = data.errorCheck();//3if (err == NO_ERROR) {tr.data_size = data.ipcDataSize();tr.data.ptr.buffer = data.ipcData();tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);tr.data.ptr.offsets = data.ipcObjects();} else if (statusBuffer) {tr.flags |= TF_STATUS_CODE;*statusBuffer = err;tr.data_size = sizeof(status_t);tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);tr.offsets_size = 0;tr.data.ptr.offsets = 0;} else {return (mLastError = err);}mOut.writeInt32(cmd);  //cmd=BC_TRANSACTIONmOut.write(&tr, sizeof(tr));return NO_ERROR;
}

注释1处的binder_transaction_data结构体(tr结构体)是向Binder驱动通信的数据结构,注释2处将handle传递给target的handle,用于标识目标,这里的handle的值为0,代表了ServiceManager。
注释3处对数据data进行错误检查,如果没有错误就将数据赋值给对应的tr结构体。最后会将BC_TRANSACTION和tr结构体写入到mOut中。
上面代码调用链的时序图如下所示。

1.2 waitForResponse函数分析

接着回过头来查看waitForResponse函数做了什么,waitForResponse函数中的case语句很多,这里截取部分代码。                                           
frameworks/native/libs/binder/IPCThreadState.cpp  

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{uint32_t cmd;int32_t err;while (1) {if ((err=talkWithDriver()) < NO_ERROR) break;//1err = mIn.errorCheck();if (err < NO_ERROR) break;if (mIn.dataAvail() == 0) continue;cmd = (uint32_t)mIn.readInt32();IF_LOG_COMMANDS() {alog << "Processing waitForResponse Command: "<< getReturnString(cmd) << endl;}switch (cmd) {case BR_TRANSACTION_COMPLETE:if (!reply && !acquireResult) goto finish;break;case BR_DEAD_REPLY:err = DEAD_OBJECT;goto finish;...default://处理各种命令协议err = executeCommand(cmd);if (err != NO_ERROR) goto finish;break;}
}
finish:...return err;
}

注释1处的talkWithDriver函数的内部通过ioctl与Binder驱动进行通信,代码如下所示。
frameworks/native/libs/binder/IPCThreadState.cpp  

status_t IPCThreadState::talkWithDriver(bool doReceive)
{if (mProcess->mDriverFD <= 0) {return -EBADF;}//和Binder驱动通信的结构体binder_write_read bwr; //1//mIn是否有可读的数据,接收的数据存储在mInconst bool needRead = mIn.dataPosition() >= mIn.dataSize();const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;bwr.write_size = outAvail;bwr.write_buffer = (uintptr_t)mOut.data();//2//这时doReceive的值为trueif (doReceive && needRead) {bwr.read_size = mIn.dataCapacity();bwr.read_buffer = (uintptr_t)mIn.data();//3} else {bwr.read_size = 0;bwr.read_buffer = 0;}...if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;bwr.write_consumed = 0;bwr.read_consumed = 0;status_t err;do {IF_LOG_COMMANDS() {alog << "About to read/write, write size = " << mOut.dataSize() << endl;}
#if defined(__ANDROID__)if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)//4err = NO_ERROR;elseerr = -errno;
#elseerr = INVALID_OPERATION;
#endif...} while (err == -EINTR);...return err;
}

注释1处的 binder_write_read是和Binder驱动通信的结构体,在注释2和3处将mOut、mIn赋值给binder_write_read的相应字段,最终通过注释4处的ioctl函数和Binder驱动进行通信,这一部分涉及到Driver Binder的内容
了,就不再详细介绍了。

1.3 小节

从调用链的角度来看,MediaPlayerService是如何注册的貌似并不复杂,因为这里只是简单的介绍了一个调用链分支,可以简单的总结为以下几个步骤:

  1. addService函数将数据打包发送给BpBinder来进行处理。

  2. BpBinder新建一个IPCThreadState对象,并将通信的任务交给IPCThreadState。

  3. IPCThreadState的writeTransactionData函数用于将命令协议和数据写入到mOut中。

  4. IPCThreadState的waitForResponse函数主要做了两件事,一件事是通过ioctl函数操作mOut和mIn来与Binder驱动进行数据交互,另一件事是处理各种命令协议。

2.从进程角度说明MediaPlayerService是如何注册的

实际上MediaPlayerService的注册还涉及到了进程,如下图所示。

从图中看出是以C/S架构为基础,addService是在MediaPlayerService进行的,它是Client端,用于请求添加系统服务。而Server端则是指的是ServiceManager,用于完成系统服务的添加。
Client端和Server端分别运行在两个进程中,通过向Binder来进行通信。更详细点描述,就是两端通过向Binder驱动发送命令协议来完成系统服务的添加。这其中命令协议非常多,过程也比较复杂,这里对命令协议进行了简化,只涉及到了四个命令协议,其中
BC_TRANSACTION和BR_TRANSACTION过程是一个完整的事务,BC_REPLY和BR_REPLY是一个完整的事务。
Client端和Server端向Binder驱动发送命令协议以BC开头,而Binder驱动向Client端和Server端返回的命令协议以BR_开头。

步骤如下所示:
1.Client端向Binder驱动发送BC_TRANSACTION命令。
2.Binder驱动接收到请求后生成BR_TRANSACTION命令,唤醒Server端后将BR_TRANSACTION命令发送给ServiceManager。
3.Server端中的服务注册完成后,生成BC_REPLY命令发送给Binder驱动。
4.Binder驱动生成BR_REPLY命令,唤醒Client端后将BR_REPLY命令发送个Client端。

通过这些协议命令来驱动并完成系统服务的注册。

3.总结

本文分别从调用链角度和进程角度来讲解MediaPlayerService是如何注册的,间接的得出了服务是如何注册的。这两个角度都比较复杂,因此这里分别对这两个角度做了简化,作为应用开发,我们不需要注重太多的过程和细节,只需要了解大概的步骤即可。

推荐阅读
6个接私活的网站,你有技术就有钱
Java是世界上最好的语言!
我是如何通过开源项目月入10万的
编程·思维·职场
欢迎扫码关注

这篇关于骚年!用Binder原理彻底征服大厂面试官吧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java线程池核心参数原理及使用指南

《Java线程池核心参数原理及使用指南》本文详细介绍了Java线程池的基本概念、核心类、核心参数、工作原理、常见类型以及最佳实践,通过理解每个参数的含义和工作原理,可以更好地配置线程池,提高系统性能,... 目录一、线程池概述1.1 什么是线程池1.2 线程池的优势二、线程池核心类三、ThreadPoolE

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

一篇文章让你彻底搞懂Java中VO、DTO、BO、DO、PO

《一篇文章让你彻底搞懂Java中VO、DTO、BO、DO、PO》在java编程中我们常常需要做数据交换,那么在数据交换过程中就需要使用到实体对象,这就不可避免的使用到vo、dto、po等实体对象,这篇... 目录深入浅出讲解各层对象区别+实战应用+代码对比,告别概念混淆,设计出更优雅的系统架构!一、 为什么

Java 队列Queue从原理到实战指南

《Java队列Queue从原理到实战指南》本文介绍了Java中队列(Queue)的底层实现、常见方法及其区别,通过LinkedList和ArrayDeque的实现,以及循环队列的概念,展示了如何高效... 目录一、队列的认识队列的底层与集合框架常见的队列方法插入元素方法对比(add和offer)移除元素方法

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

Spring IOC核心原理详解与运用实战教程

《SpringIOC核心原理详解与运用实战教程》本文详细解析了SpringIOC容器的核心原理,包括BeanFactory体系、依赖注入机制、循环依赖解决和三级缓存机制,同时,介绍了SpringBo... 目录1. Spring IOC核心原理深度解析1.1 BeanFactory体系与内部结构1.1.1

MySQL 批量插入的原理和实战方法(快速提升大数据导入效率)

《MySQL批量插入的原理和实战方法(快速提升大数据导入效率)》在日常开发中,我们经常需要将大量数据批量插入到MySQL数据库中,本文将介绍批量插入的原理、实现方法,并结合Python和PyMySQ... 目录一、批量插入的优势二、mysql 表的创建示例三、python 实现批量插入1. 安装 PyMyS

MySQL 5.7彻底卸载与重新安装保姆级教程(附常见问题解决)

《MySQL5.7彻底卸载与重新安装保姆级教程(附常见问题解决)》:本文主要介绍MySQL5.7彻底卸载与重新安装保姆级教程的相关资料,步骤包括停止服务、卸载程序、删除文件和注册表项、清理环境... 目录一、彻底卸载旧版本mysql(核心步骤)二、MySQL 5.7重新安装与配置三、常见问题解决总结废话不多

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS