学习资料
1.Android Bander设计与实现 - 设计篇 :强烈推荐此文,对binder整体设计讲得比较清楚,十分容易读懂。
2.红茶一杯话Binder:强烈推荐此文,这是一个系列,对binder驱动的具体实现做了详细分析。全文读下来基本对binder就了解得差不多了。
3.《深入理解Android:卷1》中的binder章节:以MediaPlayerService为例分析利用binder进行ipc的流程。
4.Android源码:看源码什么的最开心了。自问自答
本着带着问题去学习的精神,下文会记录在学习过程中遇到的问题,并尝试用自己的理解作出解答。1.server或者client是使用何种方式与binder驱动跨进程通信的?
答:通过ioctl函数向binder驱动发送指令和数据,ioctl是阻塞式的,当binder驱动处理完后,结果也将由ioctl中的参数返回。2.在binder.c的源码中(如binder_ioctl_write_read),copy_from_user和copy_to_user经常是成对出现的,binder的一次拷贝优势体现在哪?
答:以BINDER_WRITE_READ指令为例,在用户空间和内核空间之间传输的数据结构是:
struct binder_write_read {
signed long write_size;
signed long write_consumed;
unsigned long write_buffer;
signed long read_size;
signed long read_consumed;
unsigned long read_buffer;
};
binder_ioctl_write_read中的copy_from_user和copy_to_user都仅仅拷贝了这个结构体,里面真正保存了数据缓存的write_buffer会在binder_thread_write方法中才做了一次从用户空间到内核空间的拷贝,以及read_buffer只会简单的将已经保存在内核空间的数据指针稍作变换(加上用户空间内存映射地址和内核空间内存映射地址的偏移量)即可得到,没有拷贝其中的数据。简单来说,数据从来源用户空间到内核空间做了一次全拷贝,数据从内核空间到目标用户空间仅仅拷贝了一个外壳。
3.binder驱动中定义的链表节点只有prev和next指针,真正的节点数据保存在哪里?
以插入到todo列表里的binder_work为例:
struct binder_work {
struct list_head entry;
enum {
BINDER_WORK_TRANSACTION = 1,
BINDER_WORK_TRANSACTION_COMPLETE,
BINDER_WORK_NODE,
BINDER_WORK_DEAD_BINDER,
BINDER_WORK_DEAD_BINDER_AND_CLEAR,
BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
} type;
};
只定义了list_head和type,看起来并没有什么用,实际上,binder_work外面还可以有一层binder_node
struct binder_node {
int debug_id;
struct binder_work work;
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
struct binder_proc *proc;
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
binder_uintptr_t ptr;
binder_uintptr_t cookie;
unsigned has_strong_ref:1;
unsigned pending_strong_ref:1;
unsigned has_weak_ref:1;
unsigned pending_weak_ref:1;
unsigned has_async_transaction:1;
unsigned accept_fds:1;
unsigned min_priority:8;
struct list_head async_todo;
};
插入到列表里的是binder_work里的entry,取出来的时候是binder_work,最后通过container_of来获得binder_node。这个对java程序猿来说由于不了解c的list,所以看代码的时候可能会比较困惑。
4.server和client是怎么处理binder驱动一次返回的多条指令的?
其实ServiceManager就比较好理解,从binder_loop通过ioctl取得binder驱动返回的数据后,调用binder_parse进行处理,binder_paser会在一个while循环中逐次解析每个命令。但是如果只看别人的对其他server(如MediaPlayerService)的分析,而不自己看一遍源码,可能会弄不明白。这里也以MediaPlayerService为例,简要回答一下。
MediaPlayerService通过IPCThreadState来跟binder驱动打交道,其中关键的方法是talkWithDriver。网上大部分分析都会省略该方法前面一部分而直接从ioctl开始分析。其实此方法前面部分先判断是否要从binder驱动中读数据。如果之前binder返回的指令还未执行完,则无需再读取,继续使用缓存中现有的数据。不管是否需要写数据,此方法执行完后,下一条还在缓存中的指令都能保证被执行到。
binder_write_read bwr;
// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (long unsigned int)mOut.data();
// This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (long unsigned int)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
...
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
整个read流程走下来大致是这样的: