android binder 传递函数指针,深入剖析Android系统Binder通讯机制

//设置用户进程最大线程数

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()这个坑算是补上了。

d6c11c645c4c90e72e73445bc4d644a3.png

接下来看看ProcessState::getStrongProxyForHandle()函数留下的坑吧。

接着getStrongProxyForHandle()说

注意啦,从这里开始是山路十八弯,抓好扶好了啊!

先来看一张流程图。

2105b1f0af9a2cdf2d556aac157bea61.png

不够高清?点这个链接下载吧!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通讯的逻辑,当我们需要进行通讯时,就需要通过它来完成。

2f2f541fc2612c19bbb217939d6b2c1a.png

下面就来看看通讯是怎么开始的。

第一步 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的逻辑是在这个函数中的。这个地方给差评!

a50d070ed21d6ff1307f3cfa5370b21e.png

顺着代码,我们进入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()函数,至于为什么?你可以再看看上一篇文章回顾下。

f8103176b2305293bd671d3adb91684e.png

注意这里我们给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中的数据是啥?...

5af97279b20327de7b3be0270b9e94fb.png

看,这就是为什么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

程序员大咖整理发布,转载请联系作者获得授权返回搜狐,查看更多

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值