前面一篇文章介绍了binder通讯的实现,这篇讨论下这个在binder通讯中比较重要的通知机制:binder实体死亡通知。虽然说binder实体只要存在强引用就不会被销毁,但是这毕竟是跨进程的引用,谁也无法保证binder实体所在server关闭或者binder驱动一次退出而消失,这个时候就尽量要求在server退出时给出通知。而这个死亡通知也不是见谁就发的,只有通过命令BC_REQUEST_NOTIFICATION告诉过binder驱动本进程需要知道binder实体已经销毁的进程才会收到这个通知。
一、 相关命令和数据结构
@ kernel/drivers/staging/android/binder.h
struct binder_write_read结构体的write_buffer中记录的是BC命令和数据,BC命令定义如下:
enum BinderDriverCommandProtocol {
BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
BC_REPLY = _IOW('c', 1, struct binder_transaction_data),
…
BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_ptr_cookie),
// 获得Binder引用的进程通过该命令要求驱动在Binder实体销毁时得到通知。
BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_ptr_cookie),
// 取消订阅binder实体死亡通知。
BC_DEAD_BINDER_DONE = _IOW('c', 16, void *),
// 收到实体死亡通知书的进程在删除引用后用本命令告知驱动。
}
struct binder_ptr_cookie {
void *ptr; // 指向binder实体对应的引用号的指针
void *cookie; // 使用binder引用号的进程写进binder驱动中去的额外数据,已在取消订阅时进行校验,已明确身份。
};
struct binder_write_read结构体的read_buffer中记录的是BR命令和数据,BR命令定义如下:
enum BinderDriverReturnProtocol {
…
BR_DEAD_BINDER = _IOR('r', 15, void *),
// 当前task接收到的binder实体死亡通知
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, void *),
// 当前task取消订阅binder死亡通知成功之后得到的回复命令
…
}
只要某进程对某binder引用订阅了其实体的死亡通知,那么binder驱动将会为该binder引用建立一个通知结构体:binder_ref_death,将其保存在当前进程的对应binder引用结构体的death域中。
struct binder_ref_death {
struct binder_work work;
void __user *cookie;
};
struct binder_ref {
…
struct binder_ref_death *death;
};
二、 代码分析
订阅、取消通知或者返回确认命令给binder驱动,都将是通过ioctl的BINDER_WRITE_READ带上binder_write_read结构体进入内核空间的。只不过需要在write_buffer指向的空间中加上BC_REQUEST_DEATH_NOTIFICATION、BC_CLEAR_DEATH_NOTIFICATION、BC_DEAD_BINDER_DONE,当然还需要参数数据binder_ptr_cookie,这个结构体中包含用户空间中binder引用号变量的指针和私有cookie数据的指针。
这部分的代码位于函数binder_thread_write()和binder_thread_read()之中,下面只列出代码片段,完整的代码请参考源码。
2.1 请求和取消订阅死亡通知
case BC_REQUEST_DEATH_NOTIFICATION: // 订阅binder死亡通知
case BC_CLEAR_DEATH_NOTIFICATION: { // 取消订阅binder死亡通知
uint32_t target;
void __user *cookie;
struct binder_ref *ref;
struct binder_ref_death *death;
/* 获取用户空间传下来的参数 */
if (get_user(target, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
if (get_user(cookie, (void __user * __user *)ptr))
return -EFAULT;
ptr += sizeof(void *);
ref = binder_get_ref(proc, target);
// 根据binder引用号得到位于当前进程中的binder_ref结构体
if (ref == NULL) {
…// 错误处理,如果为NULL,表明当前binder引用号无效
}
…
if (cmd == BC_REQUEST_DEATH_NOTIFICATION) { // 请求订阅
if (ref->death) { // 当前进程已经订阅过该binder实体的死亡通知了
…
break;
}
death = kzalloc(sizeof(*death), GFP_KERNEL);
…
binder_stats_created(BINDER_STAT_DEATH); // 全局统计计数
INIT_LIST_HEAD(&death->work.entry);// binder_ref_death中的binder_work初始化
death->cookie = cookie;
ref->death = death;
/* 实际上到这里,订阅binder死亡通知的工作基本完成,不过为了接下来还要检查对应的binder实体是否已经不存在了。 */
if (ref->node->proc == NULL) {
ref->death->work.type = BINDER_WORK_DEAD_BINDER;
// 如果实体不存在了就将binder_work的类设置成该值,表示binder实体死亡
if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
list_add_tail(&ref->death->work.entry, &thread->todo);
} else {/* 如果binder_thread表示的task以就绪,那么将该work加入当前task的私有todo队列中,否则加入进程的全局todo中,然后唤醒进程的空闲线程等待队列。*/
list_add_tail(&ref->death->work.entry, &proc->todo);
wake_up_interruptible(&proc->wait);
}
}
} else { // 取消订阅
if (ref->death == NULL) {
… // 错误处理,如果当前进程没有为该binder实体订阅死亡通知
break;
}
death = ref->death;
if (death->cookie != cookie) { // cookie数据验证,之前订阅是保存在驱动中的和当前ioctl传下来的cookie比较。
…
break;
}
ref->death = NULL; // 该域置为NULL
if (list_empty(&death->work.entry)){ // 无当前binder实体的死亡通知需要处理。
death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
// 那么就直接将binder_work的类型定义成该值,表示只取消订阅通知。
if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
list_add_tail(&death->work.entry, &thread->todo);
/* 这里为什么不用wake_up一下thread->wait呢?这个wait中经常是没有task在等待的,即使有,也是在同步通信的时候将自己挂进这个wait中等待,没有其他情况了,所以这里首先判断这个task是不是已经在进入循环开始工作了,如果是的话,可以将当前work加入到task的私有todo中,这样在后面的通讯过程中,迟早会处理这个work任务的。 */
} else {
list_add_tail(&death->work.entry, &proc->todo);
wake_up_interruptible(&proc->wait);/* 个人觉得,这里也可以不用 */
}
} else {
BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER);
death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR;
/* 如果在取消订阅之前已经有收到当前对应binder实体的死亡通知了,也就是&death->work.entry已经存在于todo队列中。这个时候将binder_work类型修改为BINDER_WORK_DEAD_BINDER_AND_CLEAR,表示需要先处理死亡通知,再处理取消取消订阅的请求。*/
}
} // else 取消订阅
} break;
2.2 驱动发送binder死亡通知
虽然2.1中已经有发送死亡通知的情况出现,但是那些情况都是在刚刚请求了或者取消死亡通知时做的临时检查,这个检查是必要的,不过binder驱动中真正发送死亡通知的地方在哪里呢?
在函数binder_deferred_release()中,会做如下工作:
while ((n = rb_first(&proc->nodes))) {
…
struct binder_node *node = rb_entry(n, struct binder_node, rb_node);
nodes++;
rb_erase(&node->rb_node, &proc->nodes);
list_del_init(&node->work.entry);
if (hlist_empty(&node->refs)) {// 如果当前node不存在refs队列,直接释放node的空间
kfree(node);
binder_stats_deleted(BINDER_STAT_NODE);
} else { // 存在多个binder_ref结构体和当前的binder_node有关。
struct binder_ref *ref;
int death = 0;
node->proc = NULL; // binder实体死亡标志,前面请求通知时就是通过这个判断的
node->local_strong_refs = 0;
node->local_weak_refs = 0;
…
hlist_for_each_entry(ref, pos, &node->refs, node_entry) {
incoming_refs++;
if (ref->death) {
death++;
if (list_empty(&ref->death->work.entry)) {
ref->death->work.type = BINDER_WORK_DEAD_BINDER;
list_add_tail(&ref->death->work.entry, &ref->proc->todo);
wake_up_interruptible(&ref->proc->wait);
/* 这里是将binder_work加入到进程的全家todo队列中的 */
} else
BUG();
}
} // hlist_for_each_entry(ref, pos, &node->refs, node_entry)
} // else
…
} // while
2.3 binder_thread_read函数中对这些binder_work的处理
其实上面2.1中,请求和取消死亡通知是比较简单的,binder驱动就可以单方向完成。不过在请求和取消时临时检查了下binder实体是否死亡和是否有binder死亡通知存在,如果有这两种情况出现,那么就需要请求或取消的task在binder_thread_read的时候对特定类型的binder_work进行处理。
switch (w->type) {
…
case BINDER_WORK_DEAD_BINDER: // 表示binder实体已经死亡
case BINDER_WORK_DEAD_BINDER_AND_CLEAR:// 取消订阅之前有收到某binder实体死亡通知
case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {// 取消binder实体死亡通知
struct binder_ref_death *death;
uint32_t cmd;
death = container_of(w, struct binder_ref_death, work); // 得到保护这个binder_work的binder_ref_death结构体指针。
if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE; // binder驱动返回的取消binder死亡通知订阅的确认命令
else
cmd = BR_DEAD_BINDER; // binder驱动返回的binder实体死亡的命令
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
if (put_user(death->cookie, (void * __user *)ptr))
return -EFAULT;
ptr += sizeof(void *);
…
if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) {
list_del(&w->entry);
kfree(death); // 取消订阅,释放binder_ref_death内存空间
binder_stats_deleted(BINDER_STAT_DEATH);
} else
/* 将binder_work移动到delivered_death队列, 等待task上层空间处理完之后,发送BC_ DEAD_BINDER_DONE命令给驱动之后,驱动再从这里面取出binder_work作处理。*/
list_move(&w->entry, &proc->delivered_death);
if (cmd == BR_DEAD_BINDER)
goto done; /* DEAD_BINDER notifications can cause transactions */
} break;
} // switch (w->type)
2.4 task上层收到通知后给驱动发送确认命令
// 收到实体死亡通知书的进程在删除引用后用本命令告知驱动。
case BC_DEAD_BINDER_DONE: {
struct binder_work *w;
void __user *cookie;
struct binder_ref_death *death = NULL;
if (get_user(cookie, (void __user * __user *)ptr))
return -EFAULT;
ptr += sizeof(void *);
list_for_each_entry(w, &proc->delivered_death, entry) {
struct binder_ref_death *tmp_death = container_of(w, struct binder_ref_death, work);
if (tmp_death->cookie == cookie) {
death = tmp_death;
break;
}
} // 找到分离出去的binder_work,以cookie为依据。
…
if (death == NULL) { // 没有找到对应的binder_ref_death结构体
binder_user_error("binder: %d:%d BC_DEAD"
"_BINDER_DONE %p not found\n",
proc->pid, thread->pid, cookie);
break;
}
list_del_init(&death->work.entry); // 从列表上分离出来
if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR) {
/* 如果在取消订阅的时候有未处理的死亡通知,原则上是按照先把死亡通知处理完之后,再来处理取消订阅通知。这里接下来就是添加一个取消订阅通知的 binder_work。 */
death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
list_add_tail(&death->work.entry, &thread->todo);
} else {
list_add_tail(&death->work.entry, &proc->todo);
wake_up_interruptible(&proc->wait);
}
}
} break;
完!