本章关键点总结 & 说明:
思维导图在系统核心机制binder这一部分中也是 持续不断迭代的,随着对binder的不断分析和讲解,导图内容也不断增多。这里主要关注➕ Binder驱动 部分即可。
本章节我们主要从驱动的角度来解读binder,首先要搞清楚的就是IPC通信机制,这就少不了基础知识,因此第一部分对基础做一个讲解,接下来对驱动的关键3大流程进行讲解,内容拷贝机制,最后谈谈transaction stack机制(数据传输时从源 发给 目标,目标怎么知道是 源发的呢)和server端的多线程(Binder的多线程到底是如何实现的,如何创建呢?这个是系统底层实现的)。
1 Binder,进程间的IPC通信机制
1.1 handle的含义
客户端表示为A,服务端表示为B
@1 handle表示客户端A对服务端B的引用(服务的引用),是binder IPC机制中对应驱动层的binder_ref
@2 handle对于每个不同的客户端进程A0、A1、A2,均指向服务端B的服务,但handle值不同。这类似于多个进程访问同一个文件,都会得到自己的文件描述符,但因为每个进程访问文件描述符的顺序不同而导致不同(虽然不同,但指向的是同一个文件)。如下图所示:
说明:图左边每个进程中的小球对应进程中的一个个handle(后面会讲到对应驱动中的binder_ref,而很多binder_ref可以对应一个binder_node),右边是不同进程文件描述符与文件之间的关系。
1.2 驱动层关键的结构体
这里主要对 驱动层的几个关键结构体进行说明,binder_ref表示的引用, binder_node表示实体,binder_proc表示所在进程, binder_thread表示处理线程;这些结构体也是binder驱动层 分析的重点。
@1 binder_ref代码如下:
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
int debug_id;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
struct binder_proc *proc;
struct binder_node *node;//对应服务端的节点
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
如上所示,handle对应驱动层的binder_ref,表示binder_node(服务端的实体)的引用(对服务的引用),可以多对一。
@2 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;//多个binder_ref对应一个binder_node
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
void __user *ptr;
void __user *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_node表示服务的实体,通过binder_node,可以找到binder_proc(表示服务所在进程)。
@3 binder_proc代码如下:
struct binder_proc {
struct hlist_node proc_node;
struct rb_root threads;//多个线程
struct rb_root nodes;
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
int pid;
struct vm_area_struct *vma;
struct mm_struct *vma_vm_mm;
struct task_struct *tsk;
struct files_struct *files;
struct hlist_node deferred_work_node;
int deferred_work;
void *buffer;
ptrdiff_t user_buffer_offset;
struct list_head buffers;
struct rb_root free_buffers;
struct rb_root allocated_buffers;
size_t free_async_space;
struct page **pages;
size_t buffer_size;
uint32_t buffer_free;
struct list_head todo;
wait_queue_head_t wait;
struct binder_stats stats;
struct list_head delivered_death;
int max_threads;
int requested_threads;
int requested_threads_started;
int ready_threads;
long default_priority;
struct dentry *debugfs_entry;
};
binder_proc表示服务所在进程,一个进程可以有多个服务。同时包含红黑树结构的thread(每个thread对应一个binder_thread),一般会有多个客户端同时来获取服务,服务端会开启多个线程对这些请求进行处理。
@4 binder_thread代码如下:
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
int pid;
int looper;
struct binder_transaction *transaction_stack;
struct list_head todo;
uint32_t return_error; /* Write failed, return error code in read buf */
uint32_t return_error2; /* Write failed, return error code in read */
/* buffer. Used when sending a reply to a dead process that */
/* we are also waiting on */
wait_queue_head_t wait;
struct binder_stats stats;
};
服务端开启的多个线程,每个线程用一个 binder_thread 来表示。
1.3 驱动层binder逻辑流程
这里对binder3个关键流程:注册服务、获取服务、使用服务来说明
图的说明:绿色对应服务端,紫色部分对应servicemanager,蓝色对应客户端(后面的图都是这个特点)
@1 注册服务
@2 获取服务
@3 使用服务
1.4 数据传输过程(客户端和服务端)
binder的客户端和服务端进行数据传输过程如下所示:
1.5 数据复制过程
@1 一般的IPC (比如socket)数据拷贝过程如下:
@2 binder关键数据拷贝过程如下:
@3 binder 数据拷贝的全貌(结构变量+数据)
在bctest.c中使用ioctl读或写时一定会传入一个结构体binder_write_read类型的变量,而binder中关键内容的内存拷贝则会采用mmap的方式来实现。
@4 数据的跨进程传递,只需要一次拷贝就可以完成的原理:当把同一块物理页面同时映射到进程空间和内核空间,这时在两者之间传递数据,只需要其中任意一方把数据拷贝到物理页面,另一方直接读取即可。
@5 总结:结构变量拷贝两次,内存buf拷贝与映射各一次。
2 binder驱动层的3个关键流程
这里首先对binder传递的数据进行说明,数据传递本身涉及的binder_io、binder_transaction_data、binder_write_read等结构体,但本质上传递的是binder_io结构体中的data数据,在bctest.c的svrmgr_publish中的代码中这里对binder_io数据进行初始化,代码如下所示:
int svcmgr_publish(struct binder_state *bs, void *target, const char *name, void *ptr)
{
unsigned status;
unsigned iodata[512/4];
struct binder_io msg, reply;
bio_init(&msg, iodata, sizeof(iodata), 4);//初始化binder_io结构体变量msg,偏移4个字节
/*
从这里开始向binder_io中写入数据,实际上是对binder_io中的data变量进行写入操作
这里先向binder_io结构体中写入4个字节的0
写入字符串SVC_MGR_NAME与name(均先写入长度,再写入字符串)
最后写入flat_binder_object,底层需要据此创建binder_node节点
*/
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, SVC_MGR_NAME);
bio_put_string16_x(&msg, name);
bio_put_obj(&msg, ptr);
if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE))
return -1;
status = bio_get_uint32(&reply);
binder_done(bs, &msg, &reply);
return status;
}
这里涉及的三个函数bio_put_uint32(),bio_put_string16_x(),bio_put_obj()中实际都是在向binder_io结构体中的data变量写入数据,在驱动中也是通过data变量获取信息的,即binder_io中的data就是所谓的buffer,这里将代码
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, SVC_MGR_NAME);
bio_put_string16_x(&msg, name);
bio_put_obj(&msg, ptr);
转换为图像的模式,让大家看的更明白一些,如下所示:
其中offs1 offs2 off3 off4分别表示一个地址值,存储的是flat_binder_object的地址值,最多可以存储四个,代码是通过内部逻辑确定具体有几个flat_binder_object的,这里不多描述。有了这个基础,接下来主要就是看binder的ioctl流程来对binder驱动有更深的了解了。
2.1 服务注册流程
@1 服务注册流程中ioctl命令交互流程
该部分是驱动中打印的ioctl 命令的交互流程,在驱动中打印,(下面交互图均如此),关键的命令流程如下所示:
@2 进程间通信的关键命令
从上面的图可知,在Binder的命令集中,只有BC_TRANSACTION、BC_REPLY、BR_TRANSACTION、BR_REPLY涉及两个进程之间的通信,其他所有的CMD都是APP与驱动之间的交互,用于改变/报告状态。抽象出来,进程间通信的关键信息如下:
@3 传递数据说明
注册服务时binder传递的关键数据是 android.os.IServiceManager(前缀)、hello(服务名称)、flat_binder_object(驱动据此创建一个binder_node节点),获得的回复数据为0时表示正常。
2.2 服务获取流程
@1 服务获取的ioctl 流程如下图所示:
@2 传递数据说明
获取服务时binder传递的关键数据是android.os.IServiceManager(前缀)与hello(服务名称),获得的回复数据是flat_binder_object (驱动据此创建一个binder_ref节点)
2.3 服务使用流程
@1 使用服务的ioctl 流程如下图所示:
@2 传递数据说明
使用服务时binder传递的关键数据是参数值(即传递的函数参数,对于一般的方法int say_hello_to(char* reference)来讲是一个字符串值),获得的回复数据是服务端函数执行后的返回值
2.4 总结及归纳
无论是注册服务过程还是获取服务,本质上都是一次进程间通信,关注黄色方块部分,都要有一次BC_TRANSACTION--->BR_TRANSACTION--->BC_REPLY--->BR_REPLY的过程,这个过程是最关键的,所谓注册服务,获取服务,使用服务就是三次进程间通信的过程。
第一次是服务端与ServiceManager之间通信,注册服务。
第二次是客户端与ServiceManager之间通信,获取服务。
第三次是客户端与服务端之间通信,使用服务。
3 transaction_stack机制解读
@1 具体的发送者和接收者是谁?
针对数据传输时,有多个线程的情况,这里关注两个问题:
发给谁(handle可以表示一个进程,那么那具体是该进程的哪个线程呢?)
说明:binder_proc下面有自己的todo链表,binder_thread下面也有自己的todo链表。
一般情况下是放在binder_proc中,之后唤醒等待于binder_proc.wait的空闲线程。
对于双向传输而言,则是则放在binder_thread.todo里面,唤醒该线程(为何是双向传输,通过transaction_stack来判断)
回复给谁(对于回复来讲,没有handle,那具体回复给具体哪一个进程或者进程下面的线程呢?)
回复过程中是没有handle表示进程的,那么必定有某个地方记录之前的发送者,这就是transaction_stack,对应的结构体是binder_transaction。
@2 binder_transaction一般情况下的传输流程
传输流程的详细过程如下:
忽略其中详细繁琐的细节,最核心传输的框架如下:
至此,针对一般情况,给出答案:
发送给谁:从客户端test_client发送到服务端test_server,有handle作为进程的标识。
回复给谁:从服务端test_server回复到客户端test_client有binder_transaction存储作为标识。
@3 binder_transaction双向传输的说明
考虑这样一种情景,具体如下:
进程P1提供S1服务,同时创建有T1-1,T1-2,T1-3几个线程,同理,进程P2,P3...。
此时,P1提供S1服务,需要P2提供的S2服务,而P2提供的S2服务需要P3提供的S3服务,而P3提供的S3服务需要P1提供的S1服务。那么,P1提供的S1服务会让自己的线程