bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
if (bwr.read_size > 0) {//读数据
ret = binder_thread_read(proc, thread, bwr.read_buffer,
bwr.read_size,
&bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
if (!list_empty(&proc->todo))
//唤醒等待状态的线程
wake_up_interruptible(&proc->wait);
if (ret < 0) { //读失败
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}

可见 binder 驱动内部依赖用户空间的 binder_write_read 决定是要读取还是写入数据:其内部变量 read_size>0 则代表要读取数据,write_size>0 代表要写入数据,若都大于 0 则先写入,后读取。

至此焦点应该集中在 binder_thread_write() 和 binder_thread_read(),下面分析这两个方法。

4.binder_thread_write

在上面的 binder_ioctl_write_read() 方法中调用 binder_thread_write() 时传入了 bwr.write_buffer、bwr.write_size 等,先搞清楚这些参数是什么。

最开始是在用户空间 IPCThreadState 的 transact() 中通过 writeTransactionData() 方法创建数据并写入 mOut 的,writeTransactionData 方法代码如下:

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; //到驱动内部后会取出此结构体进行处理
tr.target.ptr = 0;
tr.target.handle = handle; //目标 server 的 binder 的句柄
//请求码,getService() 服务对应的是 GET_SERVICE_TRANSACTION
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck(); //验证数据合理性
if (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 {…}
mOut.writeInt32(cmd); // transact 传入的 cmd 是 BC_TRANSACTION
mOut.write(&tr, sizeof(tr)); //打包成 binder_transaction_data
return NO_ERROR;
}

然后在 IPCThreadState 的 talkWithDriver() 方法中对 write_buffer 赋值:

bwr.write_buffer = (uintptr_t)mOut.data();

搞清楚了数据的来源,再来看 binder_thread_write() 方法,binder_thread_write() 方法中处理了大量的 BC_XXX 命令,代码很长,这里我们只关注当前正在处理的 BC_TRANSACTION 命令,简化后代码如下:

static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed){
uint32_t cmd;
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) {
if (get_user(cmd, (uint32_t __user *)ptr)) // 读取一个 cmd
return -EFAULT;
//跳过 cmd 所占的空间,指向要处理的数据
ptr += sizeof(uint32_t);
switch (cmd) {
case BC_TRANSACTION:
case BC_REPLY: {
//与 writeTransactionData 中准备的数据结构体对应
struct binder_transaction_data tr;
//拷贝到内核空间 tr 中
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
//跳过数据所占空间,指向下一个 cmd
ptr += sizeof(tr);
//处理数据
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
break;
}
处理其他 BC_XX 命令…
}
//被写入处理消耗的数据量,对应于用户空间的 bwr.write_consumed
*consumed = ptr - buffer;

binder_thread_write() 中从 bwr.write_buffer 中取出了 cmd 和 cmd 对应的数据,进一步交给 binder_transaction() 处理,需要注意的是,BC_TRANSACTION、BC_REPLY 这两个命令都是由 binder_transaction() 处理的。

简单梳理一下,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write ,到目前为止还只是在准备数据,没有看到跟目标进程相关的任何处理,都属于 “准备数据,根据命令分发给具体的方法去处理” 第 1 个工作。

而到此为止,第 1 个工作便结束,下一步的 binder_transaction() 方法终于要开始后面的工作了。

5.binder_transaction

binder_transaction() 方法中代码较长,先总结它干了哪些事:对应开头列出的工作,此方法中做了非常关键的 2-4 步:

  1. 找到目标进程的相关信息
  2. 将数据一次拷贝到目标进程所映射的物理内存块
  3. 记录待处理的任务,唤醒目标线程

以这些工作为线索,将代码分为对应的部分来看,首先是**「找到目标进程的相关信息」**,简化后代码如下:

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
struct binder_transaction *t; //用于描述本次 server 端要进行的 transaction
struct binder_work *tcomplete; //用于描述当前调用线程未完成的 transaction
binder_size_t *offp, *off_end;
struct binder_proc *target_proc; //目标进程
struct binder_thread *target_thread = NULL; //目标线程
struct binder_node *target_node = NULL; //目标 binder 节点
struct list_head *target_list; //目标 TODO 队列
wait_queue_head_t *target_wait; //目标等待队列
if(reply){
in_reply_to = thread->transaction_stack;
…处理 BC_REPLY,暂不关注
}else{
//处理 BC_TRANSACTION
if (tr->target.handle) { //handle 不为 0
struct binder_ref *ref;
//根据 handle 找到目标 binder 实体节点的引用
ref = binder_get_ref(proc, tr->target.handle);
target_node = ref->node; //拿到目标 binder 节点
} else {
// handle 为 0 则代表目标 binder 是 service manager
// 对于本次调用来说目标就是 service manager
target_node = binder_context_mgr_node;
}
}
target_proc = target_node->proc; //拿到目标进程
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
while (tmp) {
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from; //拿到目标线程
tmp = tmp->from_parent;
}
}
target_list = &target_thread->todo; //拿到目标 TODO 队列
target_wait = &target_thread->wait; //拿到目标等待队列

binder_transaction、binder_work 等结构体在上一篇中有介绍,上面代码中也详细注释了它们的含义。比较关键的是 binder_get_ref() 方法,它是如何找到目标 binder 的呢?这里暂不延伸,下文再做分析。

继续看 binder_transaction() 方法的第 2 个工作,「将数据一次拷贝到目标进程所映射的物理内存块」

t = kzalloc(sizeof(*t), GFP_KERNEL); //创建用于描述本次 server 端要进行的 transaction
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //创建用于描述当前调用线程未完成的 transaction
if (!reply && !(tr->flags & TF_ONE_WAY)) //将信息记录到 t 中:
t->from = thread; //记录调用线程
else
t->from = NULL;
t->sender_euid = task_euid(proc->tsk);
t->to_proc = target_proc; //记录目标进程
t->to_thread = target_thread; //记录目标线程
t->code = tr->code; //记录请求码,getService() 对应的是 GET_SERVICE_TRANSACTION
t->flags = tr->flags;
//实际申请目标进程所映射的物理内存,准备接收要传输的数据
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
//申请到 t->buffer 后,从用户空间将数据拷贝进来,这里就是一次拷贝数据的地方!!
if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)) {
return_error = BR_FAILED_REPLY;
goto err_copy_data_failed;
}

为什么在拷贝之前要先申请物理内存呢?之前介绍 binder_mmap() 方法时详细分析过,虽然 binder_mmap() 直接映射了 (1M-8K) 的虚拟内存,但却只申请了 1 页的物理页面,等到实际使用时再动态申请。也就是说,在 binder_ioctl() 实际传输数据的时候,再通过 binder_alloc_buf() 方法去申请物理内存。

至此已经将要传输的数据拷贝到目标进程,目标进程可以直接读取到了,接下来要做的就是将目标进程要处理的任务记录起来,然后唤醒目标进程,这样在目标进程被唤醒后,才能知道要处理什么任务。

最后来看 binder_transaction() 方法的第 3 个工作,「记录待处理的任务,唤醒目标线程」

if (reply) { //如果是处理 BC_REPLY,pop 出来栈顶记录的 transaction(实际上是删除链表头元素)
binder_pop_transaction(target_thread, in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {
//如果不是 oneway,将 server 端要处理的 transaction 记录到当前调用线程
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
} else {
…暂不关注 oneway 的情况
}
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list); //加入目标的处理队列中
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; //设置调用线程待处理的任务类型
list_add_tail(&tcomplete->entry, &thread->todo); //记录调用线程待处理的任务
if (target_wait)
wake_up_interruptible(target_wait); //唤醒目标线程

再次梳理一下,至此已经完成了前四个工作:

  1. 准备数据,根据命令分发给具体的方法去处理
  2. 找到目标进程的相关信息
  3. 将数据一次拷贝到目标进程所映射的物理内存块
  4. 记录待处理的任务,唤醒目标线程

其中第 1 个工作涉及到的方法为 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read()  -> binder_thread_write() ,主要是一些数据的准备和方法转跳,没做什么实质的事情。而 binder_transaction()  方法中做了非常重要的 2-4 工作。

剩下的工作还有:

  1. 调用线程进入休眠
  2. 目标进程直接拿到数据进行处理,处理完后唤醒调用线程
  3. 调用线程返回处理结果

可以想到,5 和 6 其实没有时序上的限制,而是并行处理的。下面先来看第 5 个工作:调用线程是如何进入休眠等待服务端执行结果的。

6.binder_thread_read

在唤醒目标线程后,调用线程就执行完 binder_thread_write() 写完了数据,返回到 binder_ioctl_write_read() 方法中,接着执行 binder_thread_read() 方法。

而调用线程的休眠就是在此方法中触发的,下面将 binder_thread_read() 分为两部分来看,首先是是否阻塞当前线程的判断逻辑:

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){
void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //bwr.read_buffer
void __user *ptr = buffer + *consumed; //数据起始地址
void __user *end = buffer + size; //数据结束地址
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
//是否要准备睡眠当前线程
wait_for_proc_work = thread->transaction_stack == NULL &&
list_empty(&thread->todo);
if (wait_for_proc_work) {
if (non_block) { //non_block 为 false
if (!binder_has_proc_work(proc, thread))
ret = -EAGAIN;
} else
ret = wait_event_freezable_exclusive(proc->wait,
binder_has_proc_work(proc, thread));
} else {
if (non_block) { //non_block 为 false
if (!binder_has_thread_work(thread))
ret = -EAGAIN;
} else
ret = wait_event_freezable(thread->wait,
binder_has_thread_work(thread));
}

consumed 即用户空间的 bwr.read_consumed,这里是 0 ,所以将一个 BR_NOOP 加到了 ptr 中。

怎么理解 wait_for_proc_work 条件呢?在 binder_transaction() 方法中将 server 端要处理的 transaction 记录到了当前调用线程 thread->transaction_stack 中;将当前调用线程待处理的任务记录到了 thread->todo 中。

所以这里的 thread->transaction_stack 和 thread->todo 都不为空,wait_for_proc_work 为 false,代表不准备阻塞当前线程。

但 wait_for_proc_work 并不是决定是否睡眠的最终条件,接着往下看,其中 non_block 恒为 false,那是否要睡眠当前线程就取决于 binder_has_thread_work() 的返回值,binder_has_thread_work() 方法如下:

static int binder_has_thread_work(struct binder_thread *thread){
return !list_empty(&thread->todo) || thread->return_error != BR_OK ||
(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);
}

thread->todo 不为空,所以 binder_has_thread_work() 返回 true,当前调用线程不进入休眠,继续往下执行。你可能会有疑问,不是说调用线程的休眠就是在 binder_thread_read() 方法中触发的吗?确实是,只不过不是本次,先接着分析 binder_thread_read() 继续往下要执行的逻辑:

struct binder_work *w;
w = list_first_entry(&thread->todo, struct binder_work,entry);
switch (w->type) {
case BINDER_WORK_TRANSACTION_COMPLETE: {
cmd = BR_TRANSACTION_COMPLETE;
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
binder_stat_br(proc, thread, cmd);
list_del(&w->entry); //删除 binder_work 在 thread->todo 中的引用
kfree(w);
}
case BINDER_WORK_NODE{…}
case BINDER_WORK_DEAD_BINDER{…}

在上面 binder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。

梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。

inder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。

梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。

干了5Android开发还没掌握-binder-机制、驱动核心源码?我劝你早点改行吧相关推荐

  1. 膜拜大佬!干了5年Android开发还没掌握binder机制?终局之战

    一.架构师专题 想要掌握复杂的技术,必须要理解其原理和架构.本模块结合实际一线互联网大型项目理解架构思维,抽丝剥茧,层层深入,帮助大家成为Android架构师,在思想上对架构认识有一次升华,并知其所以 ...

  2. 牛笔了!干了5年Android开发还没掌握binder机制?完整PDF

    前言 之前我有个学弟,应届毕业生,秋招拿了一些 offer,这几天非常纠结. offer 要考虑的因素太多了,盲目地搜索了几天,网上各种各样的声音都有,收获不多.为此咨询我一下,面对这种情况应该如何选 ...

  3. kotlin和python哪个好?驱动核心源码详解和Binder超系统学习资源,终局之战

    开头 在大厂,写得一手好文档是一个非常吃香的技能.这可不只是一个锦上添花的东西,而是很多工程师晋升,打造自己话语权的武器. 我这两年在组内的深刻体会就是,大部分厉害的高级工程师(不包括那些纯混日子靠资 ...

  4. android什么意思!读完我这份《Android开发核心源码精编解析》面试至少多要3K!面试建议

    开头 经常会有人问:"现在学习Android开发还有前景?"我的看法是现在只是市场趋于平稳了,对开发人员的要求越来越好了,这并不代表没有前景了. 移动开发不等于App开发,所有新的 ...

  5. 程序员进阶!读完我这份《Android开发核心源码精编解析》面试至少多要3K!已拿到offer

    开头 作为一个40的人,能有面试机会是格外的珍惜,也分外的诚恳.没什么豪言壮语,雄心大志.没有狼性,社会把中年人打磨成了听话的舔狗. 感谢马爸爸旗下公司,给了我为数不多机会中一个,而且还是个相当好的位 ...

  6. Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了

    导读: 本文分为三个阶段, 第一阶段,原理概述,力争说人话的基础上,讲明白Binder机制在搞什么,为什么这样搞,以及具体是怎么搞的. 第二阶段,代码层面描述,主要描述了,AIDL.IBinder.B ...

  7. 快付生态开发积分交易核心源码段

    快付生态系统重要单位是积分,积分的产出也是通过系统的任务模型来产生的.会员要赚取更多的积分也是需要向平台的会员购买会员之间积分互转涉及到以下流程: 积分交易购买流程: 1.卖家设积分挂单到系统公海 2 ...

  8. 东莞php开发wfkjnet,thinkphp核心源码注释|App.class.php

    thinkphp核心源码注释|App.class.php 2/10/2017人气:330 // +--------------------------------------------------- ...

  9. Android深入浅出之Binder机制

    Android深入浅出之Binder机制 一 说明 Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的.所以搞明白B ...

最新文章

  1. 【yolov3目标检测】(3) opencv+yolov3 检测交通路况,附python完整代码
  2. QQ互联OAuth2.0 .NET SDK 发布以及网站QQ登陆示例代码(转)
  3. vscode使用教程python-教你正确使用VSCode写Python
  4. 开发奇淫巧技Tips(Android篇)
  5. zzuli 2525: 咕咕的搜索序列
  6. HTML meta refresh 刷新与跳转(重定向)页面
  7. java in thread main_JAVA报错:Exception in thread main……求帮助?
  8. kali linux改中文_【亲测实验】kali linux 2020 设置为中文方法
  9. php的可变函数,php之可变函数的实例详解
  10. 敏捷开发绩效管理之五:敏捷开发生产率(上)(故事点估算)
  11. 梨花带雨html音乐播放器源码,梨花带雨网页悬浮音乐播放器V3开源
  12. 招聘看好的几个果然没来
  13. 新浪微博android apk,新浪微博app(新浪微博手机版) V9.4.2安卓版
  14. 一文吃透strcmp函数
  15. 获取移动光猫(如HS8545M5等设备)pppoe密码明文
  16. 多角度SAR图像匹配
  17. 一些常见的Common类
  18. X86、X64和X86_64区别
  19. 正则表达式在JS中的应用,判断邮箱是否合法
  20. Redmi Note 11Pro+(系统降级)

热门文章

  1. vs2019如何关闭自动更新
  2. 首批最佳骑行路线揭晓
  3. mmdetection3d S3DIS (持续更新)
  4. Ubuntu修改屏幕(尺寸/分辨率)大小
  5. 常用的maven命令
  6. H3c 详细Raid配置过程
  7. CC2640R2F UART
  8. 【opencv】高频低频滤波
  9. Linux密码策略、连接空闲超时时间设置
  10. java sshd实现连接ssh操作