Android : 跟我学Binder --- (4) 驱动情景分析
目录:
Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?
Android : 跟我学Binder --- (2) AIDL分析及手动实现
Android : 跟我学Binder --- (3) C程序示例
Android : 跟我学Binder --- (4) 驱动情景分析
Android : 跟我学Binder --- (5) C++实现
Android : 跟我学Binder --- (6) JAVA实现
一、数据结构
首先基于之前的c程序代码再添加一个goodbye服务,引入以下几个概念:
- binder_ref
- binder_node
- binder_proc
- binder_thread
- binder_buffer
1.test_server.c中实现goodbye服务处理函数:
int goodbye_service_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
/* 根据txn->code知道要调用哪一个函数
* 如果需要参数, 可以从msg取出
* 如果要返回结果, 可以把结果放入reply
*/ /* saygoodbye
* saygoodbye_to
*/ uint16_t *s;
char name[];
size_t len;
uint32_t handle;
uint32_t strict_policy;
int i; // Equivalent to Parcel::enforceInterface(), reading the RPC
// header with the strict mode policy mask and the interface name.
// Note that we ignore the strict_policy and don't propagate it
// further (since we do no outbound RPCs anyway).
strict_policy = bio_get_uint32(msg); switch(txn->code) {
case GOODBYE_SVR_CMD_SAYGOODBYE:
saygoodbye();
bio_put_uint32(reply, ); /* no exception */
return ; case GOODBYE_SVR_CMD_SAYGOODBYE_TO:
/* 从msg里取出字符串(16位转8位) */
s = bio_get_string16(msg, &len); //"IGoodbyeService"
s = bio_get_string16(msg, &len); // name
if (s == NULL) {
return -;
}
for (i = ; i < len; i++)
name[i] = s[i];
name[i] = '\0'; /* 处理 */
i = saygoodbye_to(name); /* 把结果放入reply */
bio_put_uint32(reply, ); /* no exception */
bio_put_uint32(reply, i); break; default:
fprintf(stderr, "unknown code %d\n", txn->code);
return -;
} return ;
}
2.main函数中注册服务:
/* add service */
ret = svcmgr_publish(bs, svcmgr, "hello", hello_service_handler);
if (ret) {
fprintf(stderr, "failed to publish hello service\n");
return -;
}
ret = svcmgr_publish(bs, svcmgr, "goodbye", goodbye_service_handler);
if (ret) {
fprintf(stderr, "failed to publish goodbye service\n");
}
......
binder_loop(bs, test_server_handler); //通过函数test_server_handler处理消息
其中 test_server_handler 会根据binder_transaction_data中的descriptor信息调用对应服务的处理函数:
int test_server_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
int (*handler)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply); handler = (int (*)(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply))txn->target.ptr; return handler(bs, txn, msg, reply); // 根据txn->target.ptr转换成对应的处理函数
}
3.test_client.c中获取服务并使用:
/* get service */
handle = svcmgr_lookup(bs, svcmgr, "goodbye");
if (!handle) {
fprintf(stderr, "failed to get goodbye service\n");
return -;
}
g_goodbye_handle = handle;
fprintf(stderr, "Handle for goodbye service = %d\n", g_goodbye_handle);
/* use service */
void saygoodbye(void)
{
unsigned iodata[/];
struct binder_io msg, reply; /* 构造binder_io */
bio_init(&msg, iodata, sizeof(iodata), );
bio_put_uint32(&msg, ); // strict mode header
bio_put_string16_x(&msg, "IGoodbyeService"); /* 放入参数 */ /* 调用binder_call */
if (binder_call(g_bs, &msg, &reply, g_goodbye_handle, GOODBYE_SVR_CMD_SAYGOODBYE))
return ; /* 从reply中解析出返回值 */ binder_done(g_bs, &msg, &reply); }
通过分别对hello和goodbye服务的注册/获取,注册和获取打印的handle值并不对应相同,这就涉及到IPC的概念:①源(自己)、②目的(handle表示)、③数据;
例如调用binder_call:
if (binder_call(g_bs, &msg, &reply, g_goodbye_handle, GOODBYE_SVR_CMD_SAYGOODBYE))
return ;
其原型为:
/*
*@msg:提供的参数
*@reply:返回的数据
*@target:发送数据的目标(服务的引用)
*@code:调用函数
*/
int binder_call(struct binder_state *bs,
struct binder_io *msg, struct binder_io *reply,
uint32_t target, uint32_t code)
binder驱动会根据handle找到目的进程,驱动内部通过 binder_ref 管理对应的引用:
struct binder_ref {
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;
};
binder_ref添加逻辑,如图:
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; //对应server进程
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
void __user *ptr;
void __user *cookie;
unsigned has_strong_ref:;
unsigned pending_strong_ref:;
unsigned has_weak_ref:;
unsigned pending_weak_ref:;
unsigned has_async_transaction:;
unsigned accept_fds:;
unsigned min_priority:;
struct list_head async_todo;
};
struct binder_proc {
struct hlist_node proc_node;// list node for global binder_procs hlist
struct rb_root threads; //红黑树保存每个申请服务对象对应的线程
struct rb_root nodes;
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
...
};
总体流程:
①server在内核态(驱动程序中)为每个服务创建binder_node, binder_node.proc = server进程;
②server_manager创建binder_ref,引用binder_node, binder_ref.desc = 1、2、3...
在用户态创建服务链表对应上面的binder_ref(.name / .handle);
③client通过name向service_manager查询服务;
④service_manager返回handle给驱动程序;
⑤驱动程序在service_manager的binder_ref红黑树中根据handle找到binder_ref,
再根据binder_ref.node找到binder_node,最后给client创建新的binder_ref,驱动返回的desc给client即为handle;
⑥client访问server进程:驱动->handle->binder_ref->binder_node->server进程,把数据放入server进程的binder_proc.todo,
接下来server进程被唤醒,反之server同样类似此流程返回结果给client;
二、数据交互过程
- 一般方法:(需要两次拷贝)
①client:构造数据 copy_from_user ---> 驱动;
②server:驱动 ---> copy_to_user 用户态处理
- binder方法:(数据一次拷贝,数据头要两次)
①server mmp内核的xx内存,可以直接访问;
②client构造数据:驱动 ---> copy_from_user 到xx内存 (涉及binder_buffer)
从前面编写的示例程序可知,与binder驱动交互主要是通过ioctl操作:
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
传入的数据为 struct binder_write_read bwr,该结构体中的 write_buffer 指向数据本身,对应结构体如下:
struct {
uint32_t cmd; //头4个字节表示数据类型
struct binder_transaction_data txn;
} __attribute__((packed)) writebuf;
三、服务注册/获取/使用过程
1.将之前的示例代码仅保留注册hello服务,编译执行log:
./service_manager &
[ 32.566620] service_manager (, ), binder_thread_write : BC_ENTER_LOOPER
[ 32.566712] service_manager (, ), binder_thread_read : BR_NOOP
(开始休眠,等待别的进程注册服务...)
./test_server &
[ 38.320197] test_server (, ), binder_thread_write : BC_TRANSACTION
[ 38.320284] binder: : BC_TRANSACTION -> - node , data beca6a5c-beca6a4c size -
[ 38.320383] test_server (, ), binder_transaction , print data :
[ 38.320454] : . . . . 1a . . . . a . 6e n . d . r .
[ 38.329064] : 6f o . i . d . 2e . . 6f o . s . 2e . . I .
[ 38.337917] : S . e . r . v . i . c . e . 4d M .
[ 38.346771] : a . 6e n . a . g . e . r . . . . .
[ 38.355646] : . . . . h . e . 6c l . 6c l . 6f o . . .
[ 38.364475] : . 2a * b s 7f . . . . e0 . . . . . . . .
[ 38.373350] test_server (, ), binder_thread_read : BR_NOOP
[ 38.379521] service_manager (, ), binder_thread_read : BR_TRANSACTION
[ 38.386633] service_manager (, ), binder_thread_read , print data :
[ 38.393567] : . . . . 1a . . . . a . 6e n . d . r .
[ 38.402410] : 6f o . i . d . 2e . . 6f o . s . 2e . . I .
[ 38.411263] : S . e . r . v . i . c . e . 4d M .
[ 38.420127] : a . 6e n . a . g . e . r . . . . .
[ 38.428971] : . . . . h . e . 6c l . 6c l . 6f o . . .
[ 38.437824] : . 2a * h s 7f . . . . . . . . . . . .
[ 38.447188] test_server (, ), binder_thread_read : BR_INCREFS
[ 38.453114] test_server (, ), binder_thread_read : BR_ACQUIRE
[ 38.459748] test_server (, ), binder_thread_read : BR_TRANSACTION_COMPLETE
[ 38.467270] service_manager (, ), binder_thread_write : BC_ACQUIRE
[ 38.474004] test_server (, ), binder_thread_read : BR_NOOP
[ 38.480122] service_manager (, ), binder_thread_write : BC_REQUEST_DEATH_NOTIFICATION
[ 38.488626] service_manager (, ), binder_thread_write : BC_FREE_BUFFER
[ 38.495828] service_manager (, ), binder_thread_write : BC_REPLY
[ 38.502538] binder: : BC_REPLY -> :, data bed9fa4c-bed9fa3c size -
[ 38.510404] service_manager (, ), binder_transaction , print data :
[ 38.517345] : . . . .
[ 38.520994] test_server (, ), binder_thread_read : BR_REPLY
[ 38.527250] test_server (, ), binder_thread_read , print data :
[ 38.540003] : . . . .
[ 38.543642] service_manager (, ), binder_thread_read : BR_NOOP
[ 38.550253] test_server (, ), binder_thread_write : BC_FREE_BUFFER
[ 38.557063] service_manager (, ), binder_thread_read : BR_TRANSACTION_COMPLETE
[ 38.566481] service_manager (, ), binder_thread_read : BR_NOOP
[ 38.571459] test_server (, ), binder_thread_write : BC_ENTER_LOOPER
[ 38.578368] test_server (, ), binder_thread_read : BR_NOOP
svcmgr: add_service('hello'), handle =
2.根据以上log分析源码:
service_manager.c:(调用顺序如下) |
① bs->fd = open("/dev/binder", O_RDWR); (binder_proc指向当前进程) |
② ioctl(bs->fd, BINDER_VERSION, &vers) |
③ bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); |
④ ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); |
⑤ binder_thread_write : BC_ENTER_LOOPER |
⑥ binder_thread_read : BR_NOOP |
⑦ 开始休眠,等待其他进程来注册服务...... |
⑧ binder_thread_read : BR_TRANSACTION |
⑨ binder_thread_write : BC_ACQUIRE |
⑩ binder_thread_write : BC_REQUEST_DEATH_NOTIFICATION |
⑪ binder_thread_write : BC_FREE_BUFFER |
⑫ inder_thread_write : BC_REPLY |
⑬ binder_thread_read : BR_NOOP |
⑭ binder_thread_read : BR_TRANSACTION_COMPLETE |
⑮ binder_thread_read : BR_NOOP |
test_server.c: |
① open("/dev/binder", O_RDWR); |
② ioctl(bs->fd, BINDER_VERSION, &vers) |
③ mmap |
④ binder_thread_write : BC_TRANSACTION |
⑤ binder_thread_read : BR_NOOP |
⑥ binder_thread_read : BR_INCREFS |
⑦ binder_thread_read : BR_ACQUIRE |
⑧ binder_thread_read : BR_TRANSACTION_COMPLETE |
⑨ binder_thread_read : BR_NOOP |
⑩ binder_thread_read : BR_REPLY |
⑪ binder_thread_write : BC_FREE_BUFFER |
⑫ binder_thread_write : BC_ENTER_LOOPER |
⑬ binder_thread_read : BR_NOOP |
2.1 Binder 协议
Binder协议基本格式是(命令+数据),使用ioctl(fd, cmd, arg)函数实现交互。命令由参数cmd承载,数据由参数arg承载,随cmd不同而不同。
下表列举了所有命令及其所对应的数据:
命令 | 含义 | arg |
BINDER_WRITE_READ |
该命令向Binder写入或读取数据。参数分为两段:写部分和读部分。如果write_size不为0就先将write_buffer里的数据写入Binder;如果read_size不为0再从Binder中读取数据存入read_buffer中。write_consumed和read_consumed表示操作完成时Binder驱动实际写入或读出的数据个数。 |
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_SET_MAX_THREADS |
该命令告知Binder驱动接收方(通常是Server端)线程池中最大的线程数。由于Client是并发向Server端发送请求的,Server端必须开辟线程池为这些并发请求提供服务。告知驱动线程池的最大值是为了让驱动发现线程数达到该值时不要再命令接收端启动新的线程。 |
int max_threads; |
BINDER_SET_CONTEXT_MGR | 将当前进程注册为SMgr。系统中同时只能存在一个SMgr。只要当前的SMgr没有调用close()关闭Binder驱动就不能有别的进程可以成为SMgr。 | -- |
BINDER_THREAD_EXIT |
通知Binder驱动当前线程退出了。Binder会为所有参与Binder通信的线程(包括Server线程池中的线程和Client发出请求的线程)建立相应的数据结构。这些线程在退出时必须通知驱动释放相应的数据结构。 |
-- |
BINDER_VERSION |
获得Binder驱动的版本号。 |
-- |
2.2 BINDER_WRITE_READ 之写操作:
Binder写操作的数据时格式同样也是(命令+数据)。这时候命令和数据都存放在binder_write_read 结构write_buffer域指向的内存空间里,多条命令可以连续存放。数据紧接着存放在命令后面,格式根据命令不同而不同。
下表列举了Binder写操作支持的命令:
cmd | 含义 | arg |
BC_TRANSACTION |
BC_TRANSACTION用于Client向Server发送请求数据;BC_REPLY用于Server向Client发送回复(应答)数据。其后面紧接着一个binder_transaction_data结构体表明要写入的数据。 | struct binder_transaction_data |
BC_FREE_BUFFER |
-- | -- |
BC_FREE_BUFFER | 释放一块映射的内存。Binder接收方通过mmap()映射一块较大的内存空间,Binder驱动基于这片内存采用最佳匹配算法实现接收数据缓存的动态分配和释放,满足并发请求对接收缓存区的需求。应用程序处理完这片数据后必须尽快使用该命令释放缓存区,否则会因为缓存区耗尽而无法接收新数据。 | 指向需要释放的缓存区的指针;该指针位于收到的Binder数据包中 |
BC_INCREFS BC_ACQUIRE BC_RELEASE BC_DECREFS |
这组命令增加或减少Binder的引用计数,用以实现强指针或弱指针的功能。 | 32位Binder引用号 |
BC_INCREFS_DONE BC_ACQUIRE_DONE |
第一次增加Binder实体引用计数时,驱动向Binder实体所在的进程发送BR_INCREFS, BR_ACQUIRE消息;Binder实体所在的进程处理完毕回馈BC_INCREFS_DONE,BC_ACQUIRE_DONE |
void *ptr:Binder实体在用户空间中的指针 void *cookie:与该实体相关的附加数据 |
BC_REGISTER_LOOPER BC_ENTER_LOOPER BC_EXIT_LOOPER |
这组命令同BINDER_SET_MAX_THREADS一道实现Binder驱动对接收方线程池管理。BC_REGISTER_LOOPER通知驱动线程池中一个线程已经创建了;BC_ENTER_LOOPER通知驱动该线程已经进入主循环,可以接收数据;BC_EXIT_LOOPER通知驱动该线程退出主循环,不再接收数据。 |
-- |
BC_REQUEST_DEATH_NOTIFICATION | 获得Binder引用的进程通过该命令要求驱动在Binder实体销毁得到通知。虽说强指针可以确保只要有引用就不会销毁实体,但这毕竟是个跨进程的引用,谁也无法保证实体由于所在的Server关闭Binder驱动或异常退出而消失,引用者能做的是要求Server在此刻给出通知。 |
uint32 *ptr; 需要得到死亡通知的Binder引用 void **cookie: 与死亡通知相关的信息,驱动会在发出死亡通知时返回给发出请求的进程。 |
BC_DEAD_BINDER_DONE | 收到实体死亡通知书的进程在删除引用后用本命令告知驱动。 | void **cookie |
在这些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令对,Binder请求和应答数据就是通过这对命令发送给接收方。这对命令所承载的数据包由结构体struct binder_transaction_data定义。Binder交互有同步和异步之分,利用binder_transaction_data中flag域区分。如果flag域的TF_ONE_WAY位为1则为异步交互,即Client端发送完请求交互即结束, Server端不再返回BC_REPLY数据包;否则Server会返回BC_REPLY数据包,Client端必须等待接收完该数据包方才完成一次交互。
2.3 BINDER_WRITE_READ :从Binder读出数据
从Binder里读出的数据格式和向Binder中写入的数据格式一样,采用(消息ID+数据)形式,并且多条消息可以连续存放。
下表列举了从Binder读出的命令字及其相应的参数:
消息 | 含义 | 参数 |
BR_ERROR | 发生内部错误(如内存分配失败) | -- |
BR_OK BR_NOOP |
操作完成 | -- |
BR_SPAWN_LOOPER | 该消息用于接收方线程池管理。当驱动发现接收方所有线程都处于忙碌状态且线程池里的线程总数没有超过BINDER_SET_MAX_THREADS设置的最大线程数时,向接收方发送该命令要求创建更多线程以备接收数据。 | -- |
BR_TRANSACTION BR_REPLY |
这两条消息分别对应发送方的BC_TRANSACTION和BC_REPLY,表示当前接收的数据是请求还是回复。 | binder_transaction_data |
BR_ACQUIRE_RESULT BR_ATTEMPT_ACQUIRE BR_FINISHED |
-- | -- |
BR_DEAD_REPLY | 交互过程中如果发现对方进程或线程已经死亡则返回该消息 | -- |
BR_TRANSACTION_COMPLETE |
发送方通过BC_TRANSACTION或BC_REPLY发送完一个数据包后,都能收到该消息做为成功发送的反馈。这和BR_REPLY不一样,是驱动告知发送方已经发送成功,而不是Server端返回请求数据。所以不管同步还是异步交互接收方都能获得本消息。 |
-- |
BR_INCREFS BR_ACQUIRE BR_RELEASE BR_DECREFS |
这一组消息用于管理强/弱指针的引用计数。只有提供Binder实体的进程才能收到这组消息。 |
void *ptr:Binder实体在用户空间中的指针 void *cookie:与该实体相关的附加数据 |
BR_DEAD_BINDER BR_CLEAR_DEATH_NOTIFICATION_DONE |
向获得Binder引用的进程发送Binder实体死亡通知书;收到死亡通知书的进程接下来会返回BC_DEAD_BINDER_DONE做确认。 | void **cookie:在使用BC_REQUEST_DEATH_NOTIFICATION注册死亡通知时的附加参数。 |
BR_FAILED_REPLY |
如果发送非法引用号则返回该消息 |
-- |
注:其中只有 BC_TRANSACTION、BC_REPLY、BR_TRANSACTION 、BR_REPLY 涉及两个进程,其它所有cmd只是APP和驱动的交互,用于改变或报告状态。
四、transaction_stack机制
前面大致了解到两个进程A、B通信流程如下:
1.client端发送数据给server进程时handle只表明了对应的进程,而server是多线程的,同时有多个binder_thread,所以分两种情况:
(1)发送的数据一般放在进程的binder_proc.todo列表,唤醒等待于binder_proc.wait的空闲线程。
(2)通过transaction_stack来判断为双向传输,则发送的数据放在binder_thread.todo列表,然后唤醒该线程。
2.server端回复数据时,如果无handle表明目的进程,则通过传输栈transaction_stack保存的“发送者”信息回复,而信息则是在transaction_stack结构体中 .frome 、.to_proc、.to_thread保存,然后通过同一个 binder_transaction 的 .from_parent 放入发送者的栈,通过 .to_parent 放入接收者的栈。
Binder Driver总结:
1. Binder node:
前面说过Service 其实是一个存在于某个进程里的对象,因此,进程PID 和 对象地址可以唯一的标识一个Service 对象,除此之外,因为这个对象可能被很多应用所使用,必须有引用计数来管理他的生命周期。这些工作都必须在内核里完成,Binder node 就是这样一个结构体来管理每个Service 对象。
struct binder_node {
int debug_id; //kernel内部标识node的id
struct binder_work work;
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
struct binder_proc *proc; //Service所在进程的结构体
struct hlist_head refs; //双向链表头,链表里存放一系列指针,指向引用该Service的binder_ref对象,
int internal_strong_refs; //内部强引用计数
int local_weak_refs; //弱引用计数
int local_strong_refs; //强引用计数
binder_ptr __user ptr; //Service对象地址
binder_ptr __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;
};
2. binder_ref
binder_ref 描述了每个对服务对象的引用,对应与Client端。如上图所示,每个Ref通过node指向binder_node. 一个进程所有的binder_ref通过两个红黑树(RbTree)进行管理,通过binder_get_ref() 和 binder_get_ref_for_node快速查找。
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; //如果不为空,则client想获知binder的死亡
};
3. binder_proc
一个进程既包含的Service对象,也可能包含对其他Service对象的引用. 如果作为Service对象进程,它可能会存在多个Binder_Thread,这些信息都在binder_proc结构体进行管理。
struct binder_proc {
struct hlist_node proc_node; //全局链表 binder_procs 的node之一
struct rb_root threads; //binder_thread红黑树,存放指针,指向进程所有的binder_thread, 用于Server端
struct rb_root nodes; //binder_node红黑树,存放指针,指向进程所有的binder 对象
struct rb_root refs_by_desc; //binder_ref 红黑树,根据desc(service No) 查找对应的引用
struct rb_root refs_by_node; //binder_ref 红黑树,根据binder_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; //task_list, binder_work链表,存放指针最终指向某个binder_transaction对象
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;
};
4. binder_transaction
每个transact() 调用在内核里都会生产一个binder_transaction 对象,这个对象会最终送到Service进程或线程的todo队列里,然后唤醒他们来最终完成onTransact()调用。
struct binder_transaction {
int debug_id; //一个全局唯一的ID
struct binder_work work; // 用于存放在todo链表里
struct binder_thread *from; //transaction 发起的线程。如果BC_TRANSACTION, 则为客户端线程,如果是BC_REPLY, 则为服务端线程。
struct binder_transaction *from_parent; //上一个binder_transaction. 用于client端
struct binder_proc *to_proc; //目标进程
struct binder_thread *to_thread; //目标线程
struct binder_transaction *to_parent; //上一个binder_transaction, 用于server端
unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */ struct binder_buffer *buffer;
unsigned int code;
unsigned int flags;
long priority;
long saved_priority;
kuid_t sender_euid;
};
5. binder_thread
binder_proc里的threads 红黑树存放着指向binder_thread对象的指针。这里的binder_thread 不仅仅包括service的binder thread, 也包括访问其他service的调用thread. 也就是说所有与binder相关的线程都会在binder_proc的threads红黑树里留下记录。binder_thread里最重要的两个成员变量是 transaction_stack 和 wait.
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node; //红黑树节点
int pid;
int looper; //
struct binder_transaction *transaction_stack; //transaction栈
struct list_head todo;
uint32_t return_error;
uint32_t return_error2;
wait_queue_head_t wait; //等待队列,用于阻塞等待
struct binder_stats stats;
};
在binder_proc里面我们也能看到一个wait 队列,是不是意味着线程既可以在proc->wait上等待,也可以在thread->wait上等待?binder driver 对此有明确的用法,所有的binder threads (server 端)都等待在proc->wait上。因为对于服务端来说,用哪个thread来响应远程调用请求都是一样的。然而所有的ref thread(client端)的返回等待都发生在调用thread的wait 队列,因为,当某个binder thread 完成服务请求后,他必须唤醒特定的等待返回的线程。但是有一个例外,在双向调用的情况下,某个Server端的thread将会挂在thread->wait上等待,而不是proc->wait. 举个例子,假设两个进程P1 和 P2,各自运行了一个Service, S1,S2, P1 在 thread T1 里调用S2提供的服务,然后在T1->wait里等待返回。S2的服务在P2的binder thread(T2)里执行,执行过程中,S2又调到S1里的某个接口,按理S1 将在P1的binder thread T3里执行, 如果P1接下来又调到了P2,那又会产生新的进程 T4, 如果这个反复调用栈很深,需要耗费大量的线程,显然这是非常不高效的设计。所以,binder driver 里做了特殊的处理。当T2 调用 S1的接口函数时,binder driver 会遍历T2的transaction_stack, 如果发现这是一个双向调用(binder_transaction->from->proc 等于P1), 便会唤醒正在等待reply的T1,T1 完成这个请求后,继续等待S2的回复。这样,只需要最多两个Thread就可以完成多层的双向调用。
binder_thread里的transaction_stack 是用链表实现的堆栈, 调用线程和服务线程的transaction有着不同的堆栈。下图是上面这个例子的堆栈情形:
6. binder_ref_death
binder_ref 记录了从client进程到server进程某个service的引用,binder_ref_death 是binder_ref的一个成员变量,它的不为空说明了client进程想得到这个service的死亡通知(严格意义上讲,是service所在进程的死亡通知,因为一个进程一个/dev/binder的fd, 只有进程死亡了,driver才会知晓,通过 file_operations->release 接口)。
struct binder_ref_death {
struct binder_work work;
binder_ptr __user cookie;
};
一张时序图来了解binder death notifycation 的全过程:
7. binder_work
从应用程序角度来看,所有的binder调用都是同步的。但在binder driver 内部,两个进程间的交互都是异步的,一个进程产生的请求会变成一个binder_work, 并送入目标进程或线程的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;
};
8. binder_buffer
进程间通信除了命令,还有参数和返回值的交换,要将数据从一个进程的地址空间,传到另外一个进程的地址空间,通常需要两次拷贝,进程A -> 内核 -> 进程B。binder_buffer 就是内核里存放交换数据的空间(这些数据是以Parcel的形式存在)。为了提高效率,Android 的 binder 只需要一次拷贝,因为binder 进程通过mmap将内核空间地址映射到用户空间,从而可以直接访问binder_buffer的内容而无需一次额外拷贝。binder_buffer由内核在每次发起的binder调用创建,并赋给binder_transaction->buffer. binder driver 根据binder_transaction 生产 transaction_data(包含buffer的指针而非内容), 并将其复制到用户空间。
9. flat_binder_obj
前面我们说过,<proc, handle> 可以标识一个BpBinder 对象,而<proc, ptr> 可以标识一个BBinder对象。Binder Driver 会收到来自与BpBinder 和 BBinder的系统调用,它是如何判别它们的身份呢?答案就在flat_binder_obj里,先看看它的定义,
struct flat_binder_object {
unsigned long type; //见下面定义
unsigned long flags;
union {
void *binder; //BBinder,通过它driver可以找到对应的node
signed long handle; //BpBinder,根据它driver可以找到对应的ref
};
void *cookie;
}; enum {
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
};
union表明了在Server端和Client端它有着不同的解读。type则表明了它的身份。binder driver 根据它可以找到BpBinder 和 BBinder 在内核中相对应的对象 (ref 或 node). flat_binder_obj 封装在parcel里,详见Parcel.cpp.
流程总结:
1. 当一个service向binder driver 注册时(通过flat_binder_object), driver 会创建一个binder_node, 并挂载到该service所在进程的nodes红黑树。
2. 这个service的binder线程在proc->wait 队列上进入睡眠等待。等待一个binder_work的到来。
3. 客户端的BpBinder 创建的时候,它在driver内部也产生了一个binder_ref对象,并指向某个binder_node, 在driver内部,将client和server关联起来。如果它需要获知Service的死亡状态,则会生成相应的binfer_ref_death。
4. 客户端通过transact() (对应内核命令BC_TRANSACTION)请求远端服务,driver通过ref->node的映射,找到service所在进程,生产一个binder_buffer, binder_transaction 和 binder_work 并插入proc->todo队列,接着唤醒某个睡在proc->wait队列上的Binder_thread. 与此同时,该客户端线程在其线程的wait队列上进入睡眠,等待返回值。
5. 这个binder thread 从proc->todo 队列中读出一个binder_transaction, 封装成transaction_data (命令为 BR_TRANSACTION) 并送到用户空间。Binder用户线程唤醒并最终执行对应的on_transact() 函数。
6. Binder用户线程通过transact() 向内核发送 BC_REPLY命令,driver收到后从其thread->transaction_stack中找到对应的binder_transaction, 从而知道是哪个客户端线程正在等待这个返回。
7. Driver 生产新的binder_transaction (命令 BR_REPLY), binder_buffer, binder_work, 将其插入应用线程的todo对立,并将该线程唤醒。
8. 客户端的用户线程收到回复数据,该Transaction完成。
9. 当service所在进程发生异常退出,driver 的 release函数被调到,在某位内核work_queue 线程里完成该service在内核态的清理工作(thread,buffer,node,work...), 并找到所有引用它的binder_ref, 如果某个binder_ref 有不为空的binder_ref_death, 生成新的binder_work, 送人其线程的todo 对立,唤醒它来执行剩余工作,用户端的DeathRecipient 会最终被调用来完成client端的清理工作。
下面这张时序图描述了上述一个transaction完成的过程。不同的颜色代表不同的线程。注意的是,虽然Kernel和User space 线程的颜色是不一样的,但所有的系统调用都发生在用户进程的上下文里(所谓上下文,就是Kernel能通过某种方式找到关联的进程(通过Kernel的current 宏),并完成进程相关的操作,比如说唤醒某个睡眠的线程,或跟用户空间交换数据,copy_from, copy_to, 与之相对应的是中断上下文,其完全异步触发,因此无法做任何与进程相关的操作,比如说睡眠,锁等)。
-end-
Android : 跟我学Binder --- (4) 驱动情景分析的更多相关文章
- Android : 跟我学Binder --- (6) JAVA实现
目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...
- Android : 跟我学Binder --- (5) C++实现
目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...
- Android : 跟我学Binder --- (3) C程序示例
目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...
- Android : 跟我学Binder --- (2) AIDL分析及手动实现
目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...
- Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?
目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...
- Android系统--Binder系统具体框架分析(二)Binder驱动情景分析
Android系统--Binder系统具体框架分析(二)Binder驱动情景分析 1. Binder驱动情景分析 1.1 进程间通信三要素 源 目的:handle表示"服务",即向 ...
- 9.2 Binder系统_驱动情景分析_服务注册过程
1. 几个重要结构体的引入给test_server添加一个goodbye服务, 由此引入以下概念: 进程间通信其实质也是需要三要素:源.目的.数据,源是自己,目的用handle表示:通讯的过程是源向实 ...
- 9.5 Binder系统_驱动情景分析_transaction_stack机制
参考文章:http://www.cnblogs.com/samchen2009/p/3316001.html test_server服务进程可能有多个线程,而在发送数据的时候handle只表示了那个进 ...
- 9.3 Binder系统_驱动情景分析_服务获取过程
4. 服务获取过程 test_client客户端: (1)在用户态先构造name=“hello”的数据(服务的名字是hello),调用ioctl发送数据给service_manager(handle= ...
随机推荐
- NOIP2009(codevs1173)最优贸易
题目大意:给你一张有n个点m条边的有向图,每个点有一个权值,求一条1到n的路径,使得这条路径上存在两个点且他们的权值差最大. 思路:用dis[i]]记录从1到i的路径中所能得到两点间权值差的最大值,然 ...
- C#中哈希表(HashTable)的用法详解以及和Dictionary比较
1. 哈希表(HashTable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似keyvalue的键值对, ...
- Ubuntu16.04 appstreamcli错误
解决方案:https://askubuntu.com/questions/774986/appstreamcli-hanging-with-100-cpu-usage-during-update 搬运 ...
- C++引用和const引用、常量指针、指针常量
1.引用.常量引用 引用主要被用做函数的形式参数--通常将类对象传递给一个函数. 引用在内部存放的是一个对象的地址,它是该对象的别名.引用不占用内存,因为取地址引用的值和被引用变量的地址相同.但是ob ...
- [转载]要提高SQL查询效率where语句条件的先后次序应如何写
出处:https://www.cnblogs.com/exe19/p/5786806.html 我们要做到不但会写SQL,还要做到写出性能优良的SQL语句. (1)选择最有效率的表名顺序(只在基于规则 ...
- npm -i 与npm install -s与-d的区别
npm i module_name -S = > npm install module_name --save 写入到 dependencies 对象 npm i module_name -D ...
- MySQL插入去重命令_INSERT IGNORE INTO
之前在介绍INSERT INTO命令时,曾经提到,该命令在执行数据插入操作时,会在数据库中对元组的主键进行检测,若没有存在,则执行插入动作,若存在,则会报错. 而INSERT IGNORE INTO命 ...
- linux --- 6. 项目部署
一.负载均衡 .准备三台机器,准备3台虚拟机,或者和俩同桌交流一下 192.168.226.128 是nginx资源服务器,返回页面的 192.168.226.129 用作nginx负载均衡服务器 1 ...
- Always clear download 下载 谷歌浏览器插件
由于该博文不支持上传压缩包,因此,如有需要always clear download插件的可点击此链接在百度网盘上下载https://pan.baidu.com/s/13wWchis3iKqXkIA5 ...
- STS的安装与简单使用
一,STS下载与安装 1.下载地址:http://spring.io/tools3/sts/all 2.选择对应版本安装或者解压 二,STS简单使用 1.快捷方法 (1)main+alt+/+回车 = ...