binder_open(…)都干了什么?
在回答binder_transaction(…)之前,还有一些基础设施要去探究,比如binder_open(…),binder_mmap(…),这些调用是在打开设备文件/dev/binder之后必须完成的程式化操作,而在它们内部需要做一些数据结构的准备。首先来看binder_open(…)
kernel/drivers/staging/android/binder.c:2979
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
......
proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 创建binder_proc结构体
......
get_task_struct(current);
proc->tsk = current;
INIT_LIST_HEAD(&proc->todo); // 初始化链表头
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);
......
// 将proc_node串入全局链表binder_procs中
hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
filp->private_data = proc;
......
return 0;
}
binder_open(…)生成并初始化binder_proc结构体如下:
struct binder_proc
struct binder_proc描述一个“正在使用Binder进程间通信机制”的进程,它的定义参见kernel/goldfish/drivers/staging/android/binder.c:286
struct binder_proc {
// 进程打开设备文件/dev/binder时,Binder驱动会为它创建一个binder_proc结构体,并将它
// 保存在全局hash列表中,proc_node是该hash列表的节点。
struct hlist_node proc_node;
// 每个使用了Binder机制的进程都有一个Binder线程池,用来处理进程间通信请求。threads以
// 线程ID作为key来组织进程的Binder线程池。进程可以调用ioctl将线程注册到Binder驱动中
// 当没有足够的空闲线程处理进程间通信请求时,驱动可以要求进程注册更多的线程到Binder线程
// 池中
struct rb_root threads;
struct rb_root nodes; // 组织Binder实体对象,它以成员ptr作为key
struct rb_root refs_by_desc; // 组织Binder引用对象,它以成员desc作为key
struct rb_root refs_by_node; // 组织Binder引用对象,它以成员node作为key
int pid; // 指向进程组ID
struct vm_area_struct *vma; // 内核缓冲区的用户空间地址,供应用程序使用
struct mm_struct *vma_vm_mm;
struct task_struct *tsk; // 指向进程任务控制块
struct files_struct *files; // 指向进程打开文件结构体数组
// 一个hash表,保存进程可以延迟执行的工作项,这些延迟工作有三种类型
// BINDER_DEFERRED_PUT_FILES、BINDER_DEFERRED_FLUSH、BINDER_DEFERRED_RELEASE
// 驱动为进程分配内核缓冲区时,会为该缓冲区创建一个文件描述符,进程可以通过该描述符将该内
// 核缓冲区映射到自己的地址空间。当进程不再需要使用Binder机制时,就会通知驱动关闭该文件
// 描述符并释放之前所分配的内核缓冲区。由于这不是一个马上就要完成的操作,因此驱动会创建一
// 个BINDER_DEFERRED_PUT_FILES类型的工作来延迟执行;
// Binder线程池中的空闲Binder线程是睡眠在一个等待队列中的,进程可以通过调用函数flush
// 来唤醒这些线程,以便它们可以检查进程是否有新的工作项需要处理。此时驱动会创建一个
// BINDER_DEFERRED_FLUSH类型的工作项,以便延迟执行唤醒空闲Binder线程的操作;
// 当进程不再使用Binder机制时,会调用函数close关闭文件/dev/binder,此时驱动会释放之
// 前为它分配的资源,由于资源释放是个比较耗时的操作,驱动会创建一个
// BINDER_DEFERRED_RELEASE类型的事务来延迟执行
struct hlist_node deferred_work_node;
int deferred_work; // 描述该延迟工作项的具体类型
void *buffer; // 内核缓冲区的内核空间地址,供驱动程序使用
ptrdiff_t user_buffer_offset; // vma和buffer之间的差值
// buffer指向一块大的内核缓冲区,驱动程序为方便管理,将它划分成若干小块,这些小块的内核缓
// 冲区用binder_buffer描述保存在列表中,按地址从小到大排列。buffers指向该列表的头部。
struct list_head buffers;
struct rb_root free_buffers; // buffers中的小块有的正在使用,被保存在此红黑树
struct rb_root allocated_buffers; // buffers中的空闲小块被保存在此红黑树
size_t free_async_space; // 当前可用的保存异步事务数据的内核缓冲区的大小
struct page **pages; // buffer和vma都是虚拟地址,它们对应的物理页面保存在pages
// 中,这是一个数组,每个元素指向一个物理页面
size_t buffer_size; // 进程调用mmap将它映射到进程地址空间,实际上是请求驱动为它
// 分配一块内核缓冲区,缓冲区大小保存在该成员中
uint32_t buffer_free; // 空闲内核缓冲区的大小
struct list_head todo; // 当进程接收到一个进程间通信请求时,Binder驱动就将该请求封
// 装成一个工作项,并且加入到进程的待处理工作向队列中,该队列
// 使用成员变量todo来描述。
wait_queue_head_t wait; // 线程池中空闲Binder线程会睡眠在由该成员所描述的等待队列中
// 当宿主进程的待处理工作项队列增加新工作项后,驱动会唤醒这
// 些线程,以便处理新的工作项
struct binder_stats stats; // 用来统计进程数据
// 当进程所引用的Service组件死亡时,驱动会向该进程发送一个死亡通知。这个正在发出的通知被
// 封装成一个类型为BINDER_WORK_DEAD_BINDER或BINDER_WORK_DEAD_BINDER_AND_CLEAR
// 的工作项,并保存在由该成员描述的队列中删除
struct list_head delivered_death;
int max_threads; // 驱动程序最多可以主动请求进程注册的线程数
int requested_threads;
int requested_threads_started;
int ready_threads; // 进程当前的空闲Binder线程数
long default_priority; // 进程的优先级,当线程处理一个工作项时,线程优先级可能被
// 设置为宿主进程的优先级
struct dentry *debugfs_entry;
};
binder_proc中的链表
在binder_proc内部有若干个list_head类型的字段,用来把binder_proc串到不同的链表中去。一般写链表的做法是在链表节点结构体中追加业务逻辑字段,顺着链表的prev、next指针到达指定节点,然后再访问业务逻辑字段:
在Linux代码中则常常反过来,先定义业务逻辑的结构体,在其内部嵌入链表字段list_head,顺着该字段遍历链表,在每个节点上根据该字段与所在结构体的偏移量找到所在结构体,访问业务逻辑字段:
这样做的好处是让业务逻辑和链表逻辑分离,Linux还定义了宏用于操作链表,以及根据链表字段找到所在结构体。如binder_proc结构体内部盛放多个list_head,表示把该结构体串入了不同的链表。
具体技巧可参见《Linux内核设计与实现》第6章。
INIT_LIST_HEAD(&proc->todo)
回到binder_open(…),除了直接字段赋值,需要解释的是几个链表字段的处理。
INIT_LIST_HEAD(&proc->todo)
用于将todo的next、prev指针指向自己,该宏的定义在kernel/goldfish/include/linux/lish.t:24
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
init_waitqueue_head(&proc->wait)
init_waitqueue_head(&proc->wait)
这个宏定义在kernel/goldfish/include/linux/wait.h:81
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0)
__init_waitqueue_head(...)
定义在kernel/goldfish/kernel/wait.c:13,主要完成了对task_list字段的初始化:
void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
// q=(&proc->todo)
{
spin_lock_init(&q->lock);
lockdep_set_class_and_name(&q->lock, key, name);
INIT_LIST_HEAD(&q->task_list); // 为什么使用符号->来提领task_list呢?
}
说到底还是初始化proc->wait->task_list字段。不过有点奇怪task_list是wait内的结构体,而不是结构体指针,为什么对task_list的提领使用符号->
呢?
struct binder_proc {
......
wait_queue_head_t wait;
......
};
kernel/goldfish/include/linux/wait.h:53
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_lis