//设置用户进程最大线程数
caseBINDER_SET_MAX_THREADS:
//使用copy_from_user()函数,将用户空间的数据拷贝到内核空间//这里就是把线程数拷贝给进程结构体的max_threadsif(copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) { ... }
break;
注意,上面使用了copy_from_user()函数,把用户空间的值,写到了驱动层的进程信息体的成员max_threads。
好了,上次open_driver()这个坑算是补上了。
接下来看看ProcessState::getStrongProxyForHandle()函数留下的坑吧。
接着getStrongProxyForHandle()说
注意啦,从这里开始是山路十八弯,抓好扶好了啊!
先来看一张流程图。
不够高清?点这个链接下载吧!http://ogemdlrap.bkt.clouddn.com/Binder%E8%BF%9B%E9%98%B6%E5%AE%8C%E6%95%B4.png。So Sweet!
图中相同颜色的流程线表示同一个流程,上面标有数字,你需要按照数字顺序来看,因为这真的是一个复杂无比的流程!
另外,同一种颜色的双向箭头线指向的是同一个变量或者值相同的变量。同理,相同颜色的带字空心箭头指向的也是同一个变量或者相同的值。
每个函数框上部的框表示在我们这个流程中,传入函数的参数。
温习一下getStrongProxyForHandle()中的坑sp ProcessState::getStrongProxyForHandle(int32_t handle){ sp result; ...
//尝试获取handle对应的handle_entry对象,没有的话会创建一个handle_entry* e = lookupHandleLocked(handle); if(e != NULL) { IBinder* b = e->binder;
if(b == NULL|| !e->refs->attemptIncWeak(this)) {
// 上面的判断确保了同一个handle不会重复创建新的BpBinderif(handle == 0) { Parcel data;
//在handle对应的BpBinder第一次创建时//会执行一次虚拟的事务请求,以确保ServiceManager已经注册status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0);
if(status == DEAD_OBJECT)
//如果ServiceManager没有注册,直接返回returnNULL; } //创建一个BpBinder//handle为0时创建的是ServiceManager对应的BpBinderb = newBpBinder(handle); e->binder = b;
if(b) e->refs = b->getWeakRefs(); result = b;//待会儿返回b} ... } returnresult;}
上次CoorChice在getStrongProxyForHandle()函数中是把下面这段代码省略了的,为了方便大家关注流程。
if(handle == 0) { Parcel data;
//在handle对应的BpBinder第一次创建时//会执行一次虚拟的事务请求,以确保ServiceManager已经注册status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0); if(status == DEAD_OBJECT) //如果ServiceManager没有注册,直接返回returnNULL;}
由于我们发起了获取ServiceManager的Binder的请求,所以handle是0的。还记得吗?应用进程在首次获取(或者说创建)ServiceManager的Binder前,会先和ServiceManager进行一次无意义的通讯(可以看到这次通讯的code为PING_TRANSACTION),以确保系统的ServiceManager已经注册。既然是在这第一次见到Binder通讯,那么我们就索性从这开始来探索Binder通讯机制的核心流程吧。
IPCThreadState的创建IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)
这句代码首先会获取IPCThreadState单例。这是我在图中省略了的。
IPCThreadState* IPCThreadState::self(){
if(gHaveTLS) { restart:
constpthread_key_tk = gTLS;
//先检查有没有,以确保一个线程只有一个IPCThreadStateIPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if(st) returnst;
returnnewIPCThreadState; //没有就new一个IPCThreadState} ...}
很明显,这段代码确保了进程中每一线程都只会有一个对应IPCThreadState。
接下来看看IPCThreadState的构造函数。
IPCThreadState::IPCThreadState()
//保存所在进程
: mProcess(ProcessState::self()),mMyThreadId(androidGetTid()),mStrictModePolicy(0),mLastTransactionBinderFlags(0){ pthread_setspecific(gTLS, this); clearCaller();
//用于接收Binder驱动的数据,设置其大小为256mIn.setDataCapacity(256);
//用于向Binder驱动发送数据,同样设置其大小为256mOut.setDataCapacity(256);}
CoorChice注释的地方比较重要哦,想要看懂后面的流程,上面3个注释的记住哦!
好了,我们的IPCThreadState算是创建出来了。事实上IPCThreadState主要就是封装了和Binder通讯的逻辑,当我们需要进行通讯时,就需要通过它来完成。
下面就来看看通讯是怎么开始的。
第一步 IPCThreadState::transact()发起通讯
你可以先在图中找到对应的流程线。transact()完整代码的话你可以看图中的,或者在/frameworks/native/libs/binder/IPCThreadState.cpp看源码。由于流程复杂,CoorChice就以小片段来说明。
status_tIPCThreadState::transact(int32_thandle,
uint32_tcode, constParcel& data, Parcel* reply, uint32_tflags){ flags |= TF_ACCEPT_FDS; //添加TF_ACCEPT_FDS...
if(err == NO_ERROR) { ...
//将需要发送的数据写入mOut中err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); } ...}
首先,在传入的flags参数中添加一个TF_ACCEPT_FDS标志,表示返回数据中可以包含文件描述符。以下是几个标志位的意义:
enumtransaction_flags { TF_ONE_WAY = 0x01, /*异步的单向调用,没有返回值*/TF_ROOT_OBJECT = 0x04, /*里面的数据是一个组件的根对象*/TF_STATUS_CODE = 0x08, /*数据包含的是一个32bit的状态码*/TF_ACCEPT_FDS = 0x10, /*允许返回对象中,包含文件描述符*/}
接着,会调用writeTransactionData()函数,把需要发送的数据准备好。注意这里的命令是BC_TRANSACTION哦。如果你随时对照着图查看参数的话,这个流程将会变的容易理解一些。
第二步 writeTransactionData()准备发送数据status_tIPCThreadState::writeTransactionData(int32_tcmd, uint32_tbinderFlags,
int32_thandle, uint32_tcode, constParcel& data, status_t* statusBuffer){ //储存通讯事务数据的结构binder_transaction_data tr; tr.target.ptr = 0; //binder_node的地址tr.target.handle = handle; //用于查找目标进程Binder的handle,对应binder_reftr.code = code; //表示事务类型tr.flags = binderFlags; tr.cookie= 0; ... //Parcel mOut,与之相反的有Parcel mIn//写入本次通讯的cmd指令mOut.writeInt32(cmd); //把本次通讯事务数据写入mOut中mOut.write(&tr, sizeof(tr));
returnNO_ERROR;}
如你所见,这个函数主要创建了一个用于储存通讯事务数据的binder_transaction_data结构t,并把需要发送的事务数据放到其中,然后再把这个tr写入IPCThreadState的mOut中。这样一来,后面就可以从mOut中取出这个通讯事务数据结构了。它非常重要,你一定要记住它是什么?以及从那来的?
此外,还需要把本次通讯的命令也写入mOut中,这样后面才能获取到发送方的命令,然后执行相应的操作。
第三步 waitForResponse()等待响应
第二步完成后,我们再次回到IPCThreadState::transact()函数中。
status_tIPCThreadState::transact(int32_thandle,
uint32_tcode, constParcel& data, Parcel* reply, uint32_tflags){ ... flags |= TF_ACCEPT_FDS; //添加TF_ACCEPT_FDS... //等待响应if((flags & TF_ONE_WAY) == 0) { //检查本次通讯是否有TF_ONE_WAY标志,即没有响应//reply是否为空if(reply) { err = waitForResponse(reply); } else{ Parcel fakeReply; err = waitForResponse(&fakeReply); } ... } ...
returnerr;}
一般通讯都需要响应,所以我们就只看有响应的情况了,即flags中不包含TF_ONE_WAY标记。调用waitForResponse()函数时,如果没有reply,会创建一个fakeReplay。我们回顾一下:
transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)
看,我们上面传入的replay是一个NULL,所以这里是会创建一个fakeReplay的。
紧接着,我们就进入到IPCThreadState::waitForResponse()中了。可以看下图中的流程线哦,对应红色编号3的线。
status_tIPCThreadState::waitForResponse(Parcel *reply, status_t*acquireResult){ ...
while(1) {
//真正和Binder驱动交互的是talkWithDriver()函数if((err=talkWithDriver()) < NO_ERROR) break; ... } ...}
这个方法中,一开始就有些隐蔽的调用了一个十分重要的方法IPCThreadState::talkWithDriver(),从名字也能看出来,真正和Binder驱动talk的逻辑是在这个函数中的。这个地方给差评!
顺着代码,我们进入talkWithDriver()看看用户空间是如何和Binder驱动talk的。
第四步 talkWithDriver()和Binder talk!
注意,需要说明一下,IPCThreadState::talkWithDriver()这个函数的参数默认为true!一定要记住,不然后面就看不懂了!
status_tIPCThreadState::talkWithDriver(booldoReceive){ ... //读写结构体,它是用户空间和内核空间的信使binder_write_read bwr; ... //配置发送信息bwr.write_size = outAvail; bwr.write_buffer = (uintptr_t)mOut.data(); ... //获取接收信息if(doReceive && needRead){ bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (uintptr_t)mIn.data(); } else{ bwr.read_size = 0; bwr.read_buffer = 0; } ... //设置消耗为0bwr.write_consumed = 0; bwr.read_consumed = 0;
status_terr; do{ ...
//通过ioctl操作与内核进行读写if(ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; ... } while(err == -EINTR); ...}
这个函数中,有一个重要结构被定义,就是binder_write_read。它能够储存一些必要的发送和接收的通讯信息,它就像用户空间和内核空间之间的一个信使一样,在两端传递信息。
在这个函数中,首先会把用户空间要传递/读取信息放到bwr中,然后通过一句关键的代码ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)与Binder驱动talk。ioctl()函数CoorChice已经在上一篇中说了,它最终会调用到Binder内核的binder_ioctl()函数,至于为什么?你可以再看看上一篇文章回顾下。
注意这里我们给ioctl()函数传递的参数。
第一个参数,是从本进程中取出上面篇中打开并保存Binder设备文件描述符,通过它可以获取到之前生成的file文件结构,然后传给binder_ioctl()函数。没印象的同学先看看上篇回顾下这里。
第二个参数,是命令,它决定了待会到内核空间中要执行那段逻辑。
第三个参数,我们把刚刚定义的信使bwr的内存地址传到内核空间去。
这些参数是理解后面步骤的关键,不要忘了哦!现在,进入到老盆友binder_ioctl()函数中,看看收到用户空间的消息后,它干了什么?
第五步 在binder_ioctl()中处理消息staticlongbinder_ioctl(struct file *filp, unsignedintcmd, unsignedlongarg)
{
intret; // 从file结构体中取出进程信息structbinder_proc*proc= filp->private_data;structbinder_thread*thread;unsignedintsize = _IOC_SIZE(cmd);
//表明arg是一个用户空间地址//__user标记该指针为用户空间指针,在当前空间内无意义void__user *ubuf = (void__user *)arg; ...
//锁定同步binder_lock(__func__);
//取出线程信息thread = binder_get_thread(proc); ...
switch(cmd) {
//读写数据caseBINDER_WRITE_READ: ret = binder_ioctl_write_read(filp, cmd, arg, thread); ... } ... } ...
//解锁binder_unlock(__func__); ...}
首先会根据文件描述符获得的file结构,获取到调用ioctl()函数的进程的进程信息,从而再获得进程的线程。然后将arg参数地址转换成有用户空间标记的指针。接着,在switch中根据cmd参数判断需要执行什么操作。这些步骤和上篇文章中是一样的。不同的是,我们这次的cmd命令是BINDER_WRITE_READ,表示要进行读写操作。可以看到,接下来的读写逻辑是在binder_ioctl_write_read()函数中的。
嗯,接下来,我们即将进入第6步,看看Binder驱动中的这段读写通讯逻辑是怎样的?
第六步 binder_ioctl_write_read() talkingstaticintbinder_ioctl_write_read(struct file *filp,
unsignedintcmd, unsignedlongarg, struct binder_thread *thread)
{
intret = 0; //获取发送进程信息structbinder_proc*proc= filp->private_data;unsignedintsize = _IOC_SIZE(cmd);
//来自用户空间的参数地址void__user *ubuf = (void__user *)arg;
//读写信息结构体structbinder_write_readbwr;... //拷贝用户空间的通讯信息bwr到内核的bwrif(copy_from_user(&bwr, ubuf, sizeof(bwr))) ...
if(bwr.write_size > 0) {
//写数据ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed); ...}
咱们先看上面这个片段。
首先自然是取出用户空间的进程信息,然后转换获得用户空间的参数地址(对应本次通讯中为bwr的地址),这些都跟在binder_ioctl()中做的差不多。
接下来,你可以看到一个binder_write_read结构的申明struct binder_write_read bwr,紧跟着通过copy_from_user(&bwr, ubuf, sizeof(bwr))把用户空间的bwr拷贝到了当前内核空间的bwr。现在,Binder内核空间的bwr就获取到了来自用户空间的通讯信息了。
获取到来自用户空间的信息后,先调用binder_thread_write()函数来处理,我看看是如何进行处理的。
第七步binder_thread_write()处理写入staticintbinder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
binder_uintptr_tbinder_buffer, size_tsize,
binder_size_t*consumed)
{
uint32_tcmd;
void__user *buffer = (void__user *)(uintptr_t)binder_buffer;
void__user *ptr = buffer + *consumed; //起始地址void__user *end = buffer + size; //结束地址while(ptr < end && thread->return_error == BR_OK) {
//从用户空间获取cmd命令if(get_user(cmd, (uint32_t__user *)ptr)) -EFAULT; ptr += sizeof(uint32_t);
switch(cmd) {
caseBC_TRANSACTION:
caseBC_REPLY: { //用来储存通讯信息的结构体structbinder_transaction_datatr;//拷贝用户空间的binder_transaction_dataif(copy_from_user(&tr, ptr, sizeof(tr))) return-EFAULT; ptr += sizeof(tr);
//处理通讯binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
break; } ... } *consumed = ptr - buffer; }
return0;}
一开始就是对一些变量进行赋值。
首先,binder_buffer是啥?哪来的?快到到传参的地方方看看bwr.write_buffer,它是写的buffer。那么它里面装了啥?这就得回到第4步中找了,因为bwr是在那个地方定义和初始化的。bwr.write_buffer = (uintptr_t)mOut.data(),嗯,它指向了mOut中的数据。那么问题又来了?mOut中的数据是啥?...
看,这就是为什么CoorChice一直在强调,前面的一些参数和变量一定要记住!不然到后面就会云里雾里的!不过还好,有了CoorChcie上面那张图,你随时可以快速的找到答案。我们回到第二步writeTransactionData(),就是通讯事务结构定义的那个地方。看到没,mOut中储存的就是一个通讯事务结构。
现在答案就明了了,buffer指向了用户空间的通讯事务数据。
另外两个参数,ptr此刻和buffer的值是一样的,因为consumed为0,所以它现在也相当于是用户空间的通讯事务数据tr的指针;而end可以明显的看出,它指向了tr的末尾。
通过get_user(cmd, (uint32_t __user *)ptr)函数,我们可以将用户空间的tr的cmd拷贝到内核空间。get_user()和put_user()这对函数就是干这个的,拷贝一些简单的变量。回到第2步writeTransactionData()中,看看参数。没错,cmd为BC_TRANSACTION。所以,进到switch中,对应执行的就是case BC_TRANSACTION。
可以看到BC_TRANSACTION事务命令和BC_REPLAY响应命令,执行的是相同的逻辑。
//用来储存通讯信息的结构体
structbinder_transaction_datatr;
//拷贝用户空间的binder_transaction_data
if(copy_from_user(&tr, ptr, sizeof(tr))) return-EFAUptr += sizeof(tr);
//处理通讯
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
先定义了一个内核空间的通讯事务数据tr,然后把用户空间的通讯事务数据拷贝到内核中tr。此时,ptr指针移动sizeof(tr)个单位,现在ptr应该和end的值是一样的了。然后,调用binder_transaction()来处理事务。
后记
篇幅所限,binder_transaction()来处理事务、binder_thread_read()读取数据等有部分删减,可点击左下角“阅读原文”查看全部。抽出空余时间写文章分享需要动力,还请各位看官动动小手点个赞,给我点鼓励。
原文链接:http://www.apkbus.com/blog-911356-68743.html
程序员大咖整理发布,转载请联系作者获得授权返回搜狐,查看更多