binder-服务获取及使用过程

获取服务的过程

获取服务的过程比注册过程简单很多,从之前写的测试代码入手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{
uint32_t handle;
unsigned iodata[512/4];
struct binder_io msg, reply;

bio_init(&msg, iodata, sizeof(iodata), 4);
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, SVC_MGR_NAME);
bio_put_string16_x(&msg, name);

if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE))
return 0;

handle = bio_get_ref(&reply);

if (handle)
binder_acquire(bs, handle);

binder_done(bs, &msg, &reply);

return handle;
}

有没有发现这个方法很眼熟,它和注册服务是一样的,只是传的 code不一样,同样调用到 linux 层的 ioctl,进入 binder_ioctl_write_read 函数,其实可以直接走流程走到 binder_transaction 函数

1
2
3
4
5
6
7
8
9
if (tr->target.handle) {
......
} else {
target_node = binder_context_mgr_node;
if (target_node == NULL) {
return_error = BR_DEAD_REPLY;
goto err_no_context_mgr_node;
}
}

因为我们传的 handle 是 0 ,所以可以看到我们之前遇到过的代码,binder_context_mgr_node ,这个东西在服务初始化的时候就设置了,代表service_manager,因此,我们获得了一个 代表service_manager的服务结构体 node,然后就可以把数据放入 service_manager 的 todo 链表中

刚才讲的那些操作都是从client端发起的,client发送数据后,驱动程序就是把数据放到目的进程的todo链表,之后唤醒目的进程,驱动程序再把数据返回给目的进程,server端也就是我们的service_manager中的binder_loop循环就会被唤醒,取出数据会执行它的处理函数 svcmgr_handler

找到 SVC_MGR_CHECK_SERVICE

1
2
3
4
5
6
7
8
9
10
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
if (!handle)
break;
bio_put_ref(reply, handle);
return 0;
1
2
3
4
5
6
7
8
9
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
//从svclist链表中查找服务
struct svcinfo *si = find_svc(s, len);

......

return si->handle;
}

这个handle是存在 service_manager中的hanlde,它的值是按照注册服务的顺序来决定的,1、2、3、4、5……

然后调用函数 bio_put_ref ,它会构造 flat_binder_object 放入 binder_io

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void bio_put_ref(struct binder_io *bio, uint32_t handle)
{
struct flat_binder_object *obj;

if (handle)
obj = bio_alloc_obj(bio);
else
obj = bio_alloc(bio, sizeof(*obj));

if (!obj)
return;

obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj->hdr.type = BINDER_TYPE_HANDLE;
obj->handle = handle;
obj->cookie = 0;
}

函数处理完后再回到parse函数,在 BR_TRANSACTION 节点中

1
2
3
4
5
6
7
8
9
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
//这个是刚才分析的svcmgr_handler
res = func(bs, txn, &msg, &reply);
if (txn->flags & TF_ONE_WAY) {
binder_free_buffer(bs, txn->data.ptr.buffer);
} else {
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}

binder_send_reply在注册服务文章分析过,现在再来看一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void binder_send_reply(struct binder_state *bs,
struct binder_io *reply,
binder_uintptr_t buffer_to_free,
int status)
{
struct {
uint32_t cmd_free;
binder_uintptr_t buffer;
uint32_t cmd_reply;
struct binder_transaction_data txn;
} __attribute__((packed)) data;

data.cmd_free = BC_FREE_BUFFER;
data.buffer = buffer_to_free;
data.cmd_reply = BC_REPLY;
data.txn.target.ptr = 0;
data.txn.cookie = 0;
data.txn.code = 0;
if (status) {
data.txn.flags = TF_STATUS_CODE;
data.txn.data_size = sizeof(int);
data.txn.offsets_size = 0;
data.txn.data.ptr.buffer = (uintptr_t)&status;
data.txn.data.ptr.offsets = 0;
} else {
data.txn.flags = 0;
data.txn.data_size = reply->data - reply->data0;
data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0);
data.txn.data.ptr.buffer = (uintptr_t)reply->data0;
data.txn.data.ptr.offsets = (uintptr_t)reply->offs0;
}
binder_write(bs, &data, sizeof(data));
}

可以看出,server处理完数据后,会封装好数据,调用binder_write,最终调用ioctl,cmd为 BC_REPLY,然后调用 驱动程序中的 binder_thread_write,binder_thread_write中根据cmd为 BC_REPLY 调用 binder_transaction,在 binder_transaction 中找到目的进程,然后拷贝数据,接着处理 flat_binder_object,这个流程应该很熟悉了,在注册服务的时候就分析过了

这里传来的是一个 handle ,而之前我们注册服务的时候,它是一个binder实体,所以需要找 BINDER_TYPE_WEAK_HANDLE节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case BINDER_TYPE_WEAK_HANDLE: {
//根据 当前proc(service_manager)的handle 找到 ref
struct binder_ref *ref = binder_get_ref(proc, fp->handle);
......
// target_proc 是我们的 client ,当前proc 是 service_manager
if (ref->node->proc == target_proc) {
......
} else {
struct binder_ref *new_ref;
//在target_proc 创建 ref
new_ref = binder_get_ref_for_node(target_proc, ref->node);
if (new_ref == NULL) {
return_error = BR_FAILED_REPLY;
goto err_binder_get_ref_for_node_failed;
}
//修改handle值
fp->handle = new_ref->desc;
binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
trace_binder_transaction_ref_to_ref(t, ref,
new_ref);
}
} break;

然后就是数据放入目的进程的todo链表,唤醒目的进程,这里我们的目的进程是 client,client被唤醒后,可以获取到handle的值,获取到这个值,就可以为所欲为了

上面就是服务的获取过程,比较简单,因为很多逻辑在前一篇的注册过程已经分析过了,因此就不做结构图了

使用服务的过程

首先,对于每一个proc,里面都有红黑树,可以用来查找ref,node等等信息

  • 构造数据 binder_io
  • 转为 binder_transaction_data
  • 调用ioctl
  • 进入内核态
  • 调用bidner_ioctl
  • 数据放入目的进程的todo链表
  • 唤醒server
  • 取出数据
  • 构造返回数据(reply)
  • 找到要回复的进程
  • 数据放入进程的todo链表
  • 唤醒client

代码不做分析了,因为流程和之前的两个情况非常类似,因此给出一个大致流程图