2.binder驱动层原理

本文深入探讨了Android系统中Binder驱动的工作原理,重点关注IPC通信机制、transaction_stack机制以及服务注册、获取和使用的流程。详细阐述了binder_ref、binder_node、binder_proc和binder_thread等关键结构体,揭示了数据传输过程中的拷贝机制,并分析了服务器端如何处理多线程请求。
摘要由CSDN通过智能技术生成

 

本章关键点总结 & 说明:

思维导图在系统核心机制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服务会让自己的线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值