Android源码之Binder(一)

在Android系统中,进程间通信使用的是Binder机制。Binder通信在Android里占据着很重要的地位,Android系统的方方面面都会涉及到Binder.
因此,接下来我们就分析一下Binder的源码。
我们先来看一下Binder设备的初始化过程,Binder是作为Android的一个设备存活的。
在binder.c文件的最后有下面这么一句代码
device_initcall(binder_init)
[// device_initcall(binder_init)
device_initcall函数到底是怎么工作的呢?这是内核知识,我不知道啊!那怎么办呢?baidu一下吧!
从baidu得知,device_initcall是用于驱动的注册的。因此,binder_init函数是真正的初始化函数,binder_init函数的定义如下:
static int __init binder_init(void)
{
int ret;

binder_deferred_workqueue = create_singlethread_workqueue("binder");
[// binder_deferred_workqueue = create_singlethread_workqueue("binder")
create_singlethread_workqueue函数的作用是注册一个binder的工作队列。
]// binder_deferred_workqueue = create_singlethread_workqueue("binder")
if (!binder_deferred_workqueue)
return -ENOMEM;

binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
if (binder_debugfs_dir_entry_root)
binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", binder_debugfs_dir_entry_root);
[// binder_debugfs_dir_entry_proc = debugfs_create_dir
上面这3行代码创建了一个/proc/binder/proc目录,每一个使用了Binder进程间通信机制的进程在该目录下都对应有一个文件,这些文件是以进程ID来命名的,通过它们就可以读取到各个进程的Binder线程池,Binder实体对象,Binder引用对象以及内核缓冲区等信息。
]// binder_debugfs_dir_entry_proc = debugfs_create_dir
ret = misc_register(&binder_miscdev);
[// ret = misc_register(&binder_miscdev)
misc_register函数的作用是创建一个Binder设备。
binder_miscdev的定义如下:
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "binder",
.fops = &binder_fops
[// .fops = &binder_fops
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
]// .fops = &binder_fops
};
Binder驱动程序在目标设备上创建了一个Binder设备文件/dev/binder,这个设备文件的操作方法列表是由全局变量binder_fops指定的。
]// ret = misc_register(&binder_miscdev)
if (binder_debugfs_dir_entry_root) {
debugfs_create_file("state",
S_IRUGO,
binder_debugfs_dir_entry_root,
NULL,
&binder_state_fops);
debugfs_create_file("stats",
S_IRUGO,
binder_debugfs_dir_entry_root,
NULL,
&binder_stats_fops);
debugfs_create_file("transactions",
S_IRUGO,
binder_debugfs_dir_entry_root,
NULL,
&binder_transactions_fops);
debugfs_create_file("transaction_log",
S_IRUGO,
binder_debugfs_dir_entry_root,
&binder_transaction_log,
&binder_transaction_log_fops);
debugfs_create_file("failed_transaction_log",
S_IRUGO,
binder_debugfs_dir_entry_root,
&binder_transaction_log_failed,
&binder_transaction_log_fops);
}
[// if (binder_debugfs_dir_entry_root)
上面这个if语句里创建了5个文件state, stats, transactions, transaction_log和failed_transaction_log, 通过这五个文件就可以读取到Binder驱动程序的运行状况。例如,各个命令协议和返回协议的请求次数,日志记录信息,以及正在执行进程间通信过程的进程信息等。
]// if (binder_debugfs_dir_entry_root)
return ret;
}
]// device_initcall(binder_init)
device_initcall函数的左右就是为了注册或者说初始化Binder设备。
分析完Binder设备的创建和初始化过程,我们来分析一下Binder设备文件的打开过程.
一个进程在使用Binder进程间通信机制之前,首先要调用函数open打开设备文件/dev/binder来获得一个文件描述符,然后才能通过这个文件描述符来和Binder驱动程序交互,继而和其他进程执行Binder进程间通信。
从前面的分析可知,当进程调用函数open打开设备文件/dev/binder时,Binder驱动程序中的函数binder_open就会被调用。我们就从binder_open函数入手开始分析Binder设备的打开过程:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;

binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
current->group_leader->pid, current->pid);
[// binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n", current->group_leader->pid, current->pid)
binder_debug函数是用于打印日志的。我们也来看一下binder_debug函数的实现:
哦,错了!binder_debug是一个宏!
#define binder_debug(mask, x...) \
do { \
if (binder_debug_mask & mask) \
printk(KERN_INFO x); \
} while (0)
[// #define binder_debug(mask, x...)
binder_debug_mask也是一个宏,定义如下:
static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR | BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION;
binder_debug_mask只定义了3个flag。居然没有BINDER_DEBUG_OPEN_CLOSE。
]// #define binder_debug(mask, x...)
]// binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n", current->group_leader->pid, current->pid)
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
[// proc = kzalloc(sizeof(*proc), GFP_KERNEL)
这里调用kzalloc来创建一个binder_proc结构体proc.
binder_proc用来描述一个正在使用Binder进程间通信机制的进程。
我们在这里分析一下结构体binder_proc, binder_proc的定义如下:
struct binder_proc {
struct hlist_node proc_node; //当一个进程调用函数open来打开设备文件/dev/binder时,Binder驱动程序就会为它创建一个binder_proc结构体,并且将它保存在一个全局的hash列表中,proc_node就是该hash列表中的一个节点。
struct rb_root threads; //threads是一个红黑树的根节点。它以线程ID作为关键字来组织一个进程Binder线程池。
struct rb_root nodes; //nodes所描述的红黑树是用来组织Binder实体对象的,它以Binder实体对象的成员变量ptr作为关键字
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
[// struct rb_root refs_by_node
refs_by_desc和refs_by_node所描述的红黑树是用来组织Binder引用对象的,前者以Binder引用对象的成员变量desc作为关键字,而后者以Binder引用对象的成员变量node作为关键字。
]// struct rb_root refs_by_node
int pid; //描述进程的进程组ID
struct vm_area_struct *vma; //Binder驱动程序为进程分配的内核缓冲区在用户空间的地址
struct task_struct *tsk; //描述进程的任务控制块
struct files_struct *files; //描述进程打开文件结构体数组
struct hlist_node deferred_work_node; //如果一个进程有延迟执行的工作项,那么成员变量deferred_work_node就刚好是该hash列表中的一个节点。
int deferred_work; //描述延迟工作项的具体类型
void *buffer; //Binder驱动程序为进程分配的内核缓冲区在内核中空间的地址
ptrdiff_t user_buffer_offset; //Binder驱动程序为进程分配的内核缓冲区在内核空间中的地址和在用户空间中的地址的差值,这是一个固定的值

struct list_head buffers; 
struct rb_root free_buffers;
struct rb_root allocated_buffers;
[// struct rb_root allocated_buffers
成员变量buffer指向的是一块大的内核缓冲区,Binder驱动程序为了方便对它进行管理,会将它划分成若干个小块。它们保存在一个列表中,按照地址值从小到大的顺序来排列。成员变量buffers指向的便是该列表的头部。列表中的小块内核缓冲区有的是正在使用的,即已经分配了屋里页面;有的是空闲的,即还没有分配物理页面,它们分别组织在两个红黑树中,其中,前者保存在成员变量allocated_buffers所描述的红黑树中,而后者保存在成员变量 free_buffers所描述的红黑树。
]// struct rb_root allocated_buffers
size_t free_async_space; //保存了当前可以用来保存异步事务数据的内核缓冲区的大小

struct page **pages; //Binder驱动程序为进程分配的内核缓冲区的物理页面
size_t buffer_size; //Binder驱动程序为进程分配的内核缓冲区的大小
uint32_t buffer_free; //保存空闲内核缓冲区的大小
struct list_head todo; //进程的待处理工作队列中
wait_queue_head_t wait; //Binder线程池中的空闲Binder线程会睡眠在由成员变量wait所描述的一个等待队列中
struct binder_stats stats; //用于统计
struct list_head delivered_death; //表示Binder驱动程序正在进程发送的死亡通知
int max_threads; //Binder驱动程序最多可以主动请求进程注册的线程的数量
int requested_threads;
int requested_threads_started;
int ready_threads; //表示进程当前的空闲Binder线程数目。
long default_priority; //进程的优先级
struct dentry *debugfs_entry; //用于调试
};

]// proc = kzalloc(sizeof(*proc), GFP_KERNEL)
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
proc->tsk = current;
[// proc->tsk = current
上面这两句代码是初始化binder_proc的tsk成员,使之指向当前进程的任务控制块
]// proc->tsk = current
INIT_LIST_HEAD(&proc->todo);
[// INIT_LIST_HEAD(&proc->todo)
这句代码初始化binder_proc的待处理工作队列
]// INIT_LIST_HEAD(&proc->todo)
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);
mutex_lock(&binder_lock);
binder_stats_created(BINDER_STAT_PROC);
[// binder_stats_created(BINDER_STAT_PROC)
binder_stats_created函数是用于统计
static inline void binder_stats_created(enum binder_stat_types type)
{
binder_stats.obj_created[type]++;
[// binder_stats.obj_created[type]++
binder_stats是个全局的static变量,定义如下:
static struct binder_stats binder_stats;
struct binder_stats {
int br[_IOC_NR(BR_FAILED_REPLY) + 1];
int bc[_IOC_NR(BC_DEAD_BINDER_DONE) + 1];
int obj_created[BINDER_STAT_COUNT];
int obj_deleted[BINDER_STAT_COUNT];
};
enum binder_stat_types {
BINDER_STAT_PROC,
BINDER_STAT_THREAD,
BINDER_STAT_NODE,
BINDER_STAT_REF,
BINDER_STAT_DEATH,
BINDER_STAT_TRANSACTION,
BINDER_STAT_TRANSACTION_COMPLETE,
BINDER_STAT_COUNT
};
]// binder_stats.obj_created[type]++
}
]// binder_stats_created(BINDER_STAT_PROC)
hlist_add_head(&proc->proc_node, &binder_procs);
[// hlist_add_head(&proc->proc_node, &binder_procs);
这句代码会讲该进程binder_proc加入到全局的hash列表中。其中binder_procs的定义如下:
static HLIST_HEAD(binder_procs);
]// hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;
[// proc->pid = current->group_leader->pid
初始化进程组ID
]// proc->pid = current->group_leader->pid
INIT_LIST_HEAD(&proc->delivered_death);
[// INIT_LIST_HEAD(&proc->delivered_death)
初始化死亡通知列表
]// INIT_LIST_HEAD(&proc->delivered_death)
filp->private_data = proc;
[// filp->private_data = proc
这句代码将初始化之后的binder_proc结构体保存到参数flip的成员变量private_data中,参数flip指向一个打开文件结构体,当进程调用open打开设备文件/dev/binder之后,内核就会返回一个文件描述符给进程,而这个文件描述符与参数flip所指向的打开文件结构体是关联在一起的。因此。当进程后面以这个文件描述符为参数调用函数mmap或者ioctl来与Binder驱动程序交互时,内核就会将与该文件描述符相关联的打开文件描述符传递给Binder驱动程序,这时候Binder驱动程序就可以通过它的成员变量private_data来获得前面在函数binder_open中为进程创建的binder_proc结构体proc.
]// filp->private_data = proc
mutex_unlock(&binder_lock);

if (binder_debugfs_dir_entry_proc) {
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
}
[// if (binder_debugfs_dir_entry_proc)
这个if语句会在/proc/binder/proc以进程id创建一个文件用于统计Binder相关的信息
]// if (binder_debugfs_dir_entry_proc)

return 0;
}
进程打开设备文件/dev/binder之后,还必须要调用mmap把这个设备文件映射到进程的地址空间,然后才可以使用Binder进行进程间通信。
当进程调用mmap将设备文件/dev/binder映射到自己的地址空间时,Binder驱动程序中的binder_mmap就会被调用。因此,我们分析一下binder_mmap函数的实现:
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
struct vm_struct *area;
[// struct vm_struct *area
参数vma指向一个结构体vm_area_struct,用来描述一段虚拟地址空间。
area指向的是一个结构体vm_struct,这也是用来描述一段虚拟地址空间的。
这两个结构体所描述的虚拟地址空间都是连续的,但它们对应的物理页面是可以不连续的。在Linux内核中,一个进程可以占用的虚拟地址空间是4G,其中,0~3G是用户地址空间,3G~4G是内核地址空间。为了区分进程的用户空间地址和内核地址空间,Linux内核就分别使用结构体vm_area_struct和vm_struct来描述它们。
实际上,结构体vm_struct所描述的内核地址空间范围只有(3G+896M+8M)~4G.中间的3G~(3G+896M+8M)是有特殊用途的,其中3G~(3G+896M)的896M空间是用来映射物理内存的前896M的,它们之间有简单的线性对应关系,而(3G+896M)~(3G+896M+8M)的8M空间是一个安全保护区,是用来检测非法指针的,即所有指向这8M空间的指针都是非法的。
]// struct vm_struct *area
struct binder_proc *proc = filp->private_data;
[// struct binder_proc *proc = filp->private_data
参数flip指向一个打开文件结构体,它的成员变量private_data指向的是一个进程结构体binder_proc,这是在binder_open函数中创建的。
]// struct binder_proc *proc = filp->private_data
const char *failure_string;
struct binder_buffer *buffer;
[// struct binder_buffer *buffer;
buffer是一个binder_buffer类型的结构体变量。
结构体binder_buffer用来描述一个内核缓冲区,它是用来来进程间传输数据的。
struct binder_buffer {
struct list_head entry; /* free and allocated entries by addesss */
[// struct list_head entry;
每一个使用Binder进程间通信机制的进程在Binder驱动程序中都有一个内核缓冲区列表,用来保存Binder驱动程序为它所分配的内核缓冲区,而成员变量entry正好是这个内核缓冲区列表的一个节点。
]// struct list_head entry;
struct rb_node rb_node; /* free entry by size or allocated entry */
/* by address */
[// struct rb_node rb_node
进程使用了两个红黑树来分别保存那些正在使用的内核缓冲区,以及空闲的内核缓冲区。如果一个内核缓冲区是空闲的,那么成员变量rb_node就是空闲内核缓冲区红黑树中的一个节点,否则,成员变量rb_node就是正在使用内核缓冲区红黑树中的一个节点。
]// struct rb_node rb_node
unsigned free:1;
[// unsigned free:1
free表示是这块内核缓冲区是空闲还是正在使用
]// unsigned free:1
unsigned allow_user_free:1;
[// unsigned allow_user_free:1
allow_user_free表示Service组件是否请求Binder驱动程序释放该内核缓冲区。
]// unsigned allow_user_free:1
unsigned async_transaction:1;
[// unsigned async_transaction:1
如果async_transaction设置为1,那么与该内核缓冲区关联的是一个异步事务。
]// unsigned async_transaction:1
unsigned debug_id:29;
[// unsigned debug_id:29
用来标志一个内核缓冲区的身份,它是用来帮助调试Binder驱动程序的。
]// unsigned debug_id:29

struct binder_transaction *transaction;
[// struct binder_transaction *transaction
transaction用来描述一个内核缓冲区正在交给哪一个事务使用. 结构体binder_transaction的定义如下所示:
struct binder_transaction {
int debug_id;
[// int debug_id
debug_id用来标志一个事务结构体的身份,它是用来帮助调试Binder驱动程序的。
]// int debug_id
struct binder_work work;
[// struct binder_work work
work描述待处理的工作项。binder_work结构的定义如下所示:
struct binder_work {
struct list_head entry;
[// struct list_head entry
entry用来将该结构体嵌入到一个宿主结构中。
]// 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;
[// type
type表示工作项的类型。
]// type
};
]// struct binder_work work
struct binder_thread *from;
[// struct binder_thread *from;
from指向发起事务的线程,称为源线程。
binder_thread用来描述Binder线程池中的一个线程。binder_thread的定义如下:
struct binder_thread {
struct binder_proc *proc;
[// struct binder_proc *proc
proc指向气宿主进程
]// struct binder_proc *proc
struct rb_node rb_node;
[// struct rb_node rb_node
进程结构体binder_proc使用一个红黑树来组织其Binder线程池中的线程,rb_node就是该红黑树中的一个节点
]// struct rb_node rb_node
int pid;
[// int pid
线程的ID
]// int pid
int looper;
[// int looper
线程的状态。线程状态的取值如下所列:
enum {
BINDER_LOOPER_STATE_REGISTERED  = 0x01,
BINDER_LOOPER_STATE_ENTERED     = 0x02,
BINDER_LOOPER_STATE_EXITED      = 0x04,
BINDER_LOOPER_STATE_INVALID     = 0x08,
BINDER_LOOPER_STATE_WAITING     = 0x10,
BINDER_LOOPER_STATE_NEED_RETURN = 0x20
};
一个线程注册到Binder驱动程序时,Binder驱动程序就会为它创建一个Binder_thread结构体,并且将它的状态初始化为BINDER_LOOPER_STATE_NEED_RETURN,表示该线程需要马上返回到用户空间。由于一个线程在注册为Binder线程时可能还没有准备好去处理进程间通信请求,因此,最好返回到用户空间去做准备工作。此外,当进程调用函数flush来刷新它的Binder线程池时,Binder线程池中的线程的状态也会被重置为BINDER_LOOPER_STATE_NEED_RETURN。
一个线程注册到Binder驱动程序之后,它接着就会通过BC_REGISTER_LOOPER或者BC_ENTER_LOOPER协议来通知Binder驱动程序,它可以处理进程间通信请求了,这时候Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_REGISTERED或者BINDER_LOOPER_STATE_ENTERED。如果一个线程是应用程序主动注册的,那么它就通过BC_ENTER_LOOPER协议来通知Binder驱动程序,它已经准备就绪处理进程间通信请求了;如果一个线程是Binder驱动程序请求创建的,那么它就通过BC_REGISTER_LOOPER协议来通知Binder驱动程序,这时候Binder驱动程序就会增加它所请求进程创建的Binder线程的数目。
当一个Binder线程处于空闲状态时,Binder驱动程序就会把它的状态设置为BINDER_LOOPER_STATE_WAITING;而当一个Binder线程退出时,它就会通过BC_EXIT_LOOPER协议来通知Binder驱动程序,这时候Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_EXITED。
在异常情况下,一个Binder线程的状态会被设置为BINDER_LOOPER_STATE_INVALID,例如,当该线程已经处于BINDER_LOOPER_STATE_REGISTERED状态时,如果它又再次通过BC_ENTER_LOOPER协议来通知Binder驱动程序它已经准备就绪了,那么Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_INVALID。
]// int looper
struct binder_transaction *transaction_stack;
[// struct binder_transaction *transaction_stack
当Binder驱动程序决定将一个事务交给一个Binder线程处理时,它就会将该事务封装成一个binder_transaction结构体,并且将它添加到transaction_stack所描述的一个事务堆栈中。
]// struct binder_transaction *transaction_stack
struct list_head todo;
[// struct list_head todo
当一个来自Client进程的请求指定要由某一个Binder线程来处理时,这个请求就会加入到相应的binder_thread结构体的成员变量todo所表示的队列中,并且会唤醒这个线程来处理,因为这时候这个线程可能处于睡眠状态。
]// 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;
[// wait_queue_head_t wait
当一个Binder线程在处理一个事务T1并需要依赖于其他的Binder线程来处理另外一个事务T2时,它就会睡眠在由成员变量wait所描述的一个等待队列中,知道事务T2处理完成为止。
]// wait_queue_head_t wait
struct binder_stats stats;
[// struct binder_stats stats
stats用来统计Binder线程数据
]// struct binder_stats stats
};
]// struct binder_thread *from;
struct binder_transaction *from_parent;
[// struct binder_transaction *from_parent
from_parent描述一个事务所依赖的另一个事务
]// struct binder_transaction *from_parent
struct binder_proc *to_proc;
[// struct binder_proc *to_proc
to_proc指向负责处理该事务的进程,称为目标进程
]// struct binder_proc *to_proc
struct binder_thread *to_thread;
[// struct binder_thread *to_thread
to_thread指向负责处理该事务的线程,称为目标线程
]// struct binder_thread *to_thread
struct binder_transaction *to_parent;
[// struct binder_transaction *to_parent
to_parent描述目标线程下一个需要处理的事务。
]// struct binder_transaction *to_parent
unsigned need_reply:1;
[// unsigned need_reply:1;
need_reply用来区分一个事务是同步还是异步的。设置为1表示同步事务,需要等待对方回复。否则,就设置为0, 表示这是一个异步事务,不需要等待回复。
]// unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */
struct binder_buffer *buffer;
[// struct binder_buffer *buffer
buffer指向Binder驱动程序为该事务分配的一块内核缓冲区,它里面保存了进程间通信数据。
]// struct binder_buffer *buffer
unsigned int code;
unsigned int flags;
long priority;
[// long priority
priority描述源线程的优先级
]// long priority
long saved_priority;
[// long saved_priority
saved_priority保存原来的线程优先级,以便线程处理完成该事务后可以恢复原来的优先级
]// long saved_priority
uid_t sender_euid;
[// uid_t sender_euid
sender_euid描述源线程的用户ID.
]// uid_t sender_euid
};
]// struct binder_transaction *transaction
struct binder_node *target_node;
[// struct binder_node *target_node
target_node用来描述一个内核缓冲区正在交给哪一个Binder实体对象使用。
结构体binder_node用来描述一个Binder实体对象。每一个Service组件在Binder驱动程序中都对应一个Binder实体对象,用来描述它在内核中的状态。
结构体binder_node的定义如下:
struct binder_node {
int debug_id;
struct binder_work work;
[// struct binder_work work
当一个Binder实体对象的引用计数由0变成1,或者由1变成0时,Binder驱动程序就会请求相应的Service组件增加或者减少其引用计数。
这时候Binder驱动程序就会将该引用计数修改操作封装成一个类型为binder_node的工作项,即将一个Binder实体对象的成员变量work的值设置为BINDER_WORK_NODE, 并且将它添加到响应进程的todo队列中去等待处理。
]// struct binder_work work
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
[// union
在Binder驱动程序中,宿主进程通过一个红黑树来维护它内部所有的Binder实体对象,而rb_node就正好是这个红黑树中的一个节点。
如果一个Binder实体对象的宿主进程已经死亡了,那么dead_node就指向保存在一个全局的hash列表中的一个节点。
]// union
struct binder_proc *proc;
[// struct binder_proc *proc
成员变量proc指向一个Binder实体对象的宿主进程。
]// struct binder_proc *proc
struct hlist_head refs;
[// struct hlist_head refs
refs描述了引用了同一个Binder实体对象的所有引用。Binder驱动程序通过refs就可以知道有哪些Client组件引用了同一个Binder实体对象。
]// struct hlist_head refs
int internal_strong_refs;
[// int internal_strong_refs
internal_strong_refs描述一个Binder实体对象的强引用计数。
]// int internal_strong_refs
int local_weak_refs;
[// local_weak_refs
local_weak_refs用来描述一个Binder实体对象的弱引用计数
]// local_weak_refs
int local_strong_refs;
[// int local_strong_refs
local_strong_refs用来描述一个Binder实体对象的强引用计数
]// int local_strong_refs
void __user *ptr;
void __user *cookie;
[// void __user *cookie
成员变量ptr和cookie分别指向一个用户空间地址,它们用来描述用户空间中的一个Sercive组件。
其中,成员变量cookie指向该Service组件的地址,而成员变量ptr指向该Service组件内部的一个引用计数对象的地址。
]// 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 has_async_transaction:1
has_async_transaction用来描述一个Binder实体对象是否正在处理一个异步事务。
]// unsigned has_async_transaction:1
unsigned accept_fds:1;
[// unsigned accept_fds:1
accept_fds用来描述一个Binder实体对象是否可以接收包含有文件描述符的进程间通信数据。
]// unsigned accept_fds:1
unsigned min_priority:8;
struct list_head async_todo;
[// struct list_head async_todo
async_todo保存异步事务队列
]// struct list_head async_todo
};
]// struct binder_node *target_node
size_t data_size;
size_t offsets_size;
uint8_t data[0];
[// uint8_t data[0]
data指向一块大小可变的数据缓冲区,它是真正用来保存通信数据的。数据缓冲区保存的数据划分为两种类型,其中一种是普通数据,另一种是Binder对象。
Binder驱动程序不关心数据缓冲区的普通数据,但是必须要知道里面的Binder对象,因为它需要根据它们来维护内核中的Binder实体对象和Binder引用对象的生命周期。
由于数据缓冲区中的普通数据和Binder对象是混合在一起保存的,它们之间并没有固定的顺序。因此,Binder驱动程序就需要额外的数据来找到里面的Binder对象。在数据缓冲区的后面,有一个偏移数组,它记录了数据缓冲区中每一个Binder对象在数据缓冲区的位置。偏移数组的大小保存在成员变量offsets_size中,而数据缓冲区的大小保存在成员变量data_size中。
]// uint8_t data[0]
};
]// struct binder_buffer *buffer;

if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
[// if ((vma->vm_end - vma->vm_start) > SZ_4M)
参数vma的成员变量vm_start和vm_end指定了要映射的用户地址空间范围。先判断它是否超过4M,如果是的话,那么就讲它截断为4M.从这里可以看出,Binder驱动程序最多可以为进程分配4M内核缓冲区来传输进程间通信数据。
]// if ((vma->vm_end - vma->vm_start) > SZ_4M)

binder_debug(BINDER_DEBUG_OPEN_CLOSE,
"binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
proc->pid, vma->vm_start, vma->vm_end,
(vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
(unsigned long)pgprot_val(vma->vm_page_prot));
[// binder_debug(BINDER_DEBUG_OPEN_CLOSE
binder_debug用于调试,这在前面的分析中已经分析过
]// binder_debug(BINDER_DEBUG_OPEN_CLOSE

if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
ret = -EPERM;
failure_string = "bad vm_flags";
goto err_bad_arg;
}
[// if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS)
这里会检查进程要映射的用户空间地址是否可写,其中FORBIDDEN_MMAP_FLAGS的定义如下:
#define FORBIDDEN_MMAP_FLAGS                (VM_WRITE)
Binder驱动程序为进程分配的内核缓冲区在用户空间只可以读,而不可以写,因此,如果进程指定要映射的用户地址空间可写,那就出错返回了。
]// if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS)
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
[// vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
进程指定要映射的用户地址空间除了不可以写之外,也是不可以拷贝,已经禁止设置可能会执行写操作标志位的。
]// vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
[// if (proc->buffer)
判断进程是否重复调用函数mmap来映射设备文件/dev/binder
]// if (proc->buffer)

area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
[// area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP)
调用函数get_vm_area在进程的内核地址空间中分配一段大小为vma->vm_end - vma->vm_start的空间,并赋值给vm_struct类型的变量area
]// area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP)
if (area == NULL) {
ret = -ENOMEM;
failure_string = "get_vm_area";
goto err_get_vm_area_failed;
}
[// if (area == NULL)
分配失败
]// if (area == NULL)
proc->buffer = area->addr;
[// proc->buffer = area->addr
将为进程分配的内核空间地址保存在proc的buffer字段
]// proc->buffer = area->addr
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
[// proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
这句代码将要映射的用户空间地址与前面获得的内核空间起始地址的差值,并保存在proc的user_buffer_offset中。Binder驱动程序为进程分配的内核缓冲区有两个地址,其中一个是用户空间地址,由参数vma所指向的一个vm_area_struct结构体来描述;另一个是内核空间地址,由变量area所指向的一个vm_struct结构体来描述。进程通过用户空间地址来访问这块内核缓冲区的内容,而Binder驱动程序通过内核空间地址来访问这块内核缓冲区的内容。由于它们是连续的,并且起始地址相差一个固定值,因此,只要知道其中的一个地址,就可以方便地计算出另外一个地址。
]// proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;

#ifdef CONFIG_CPU_CACHE_VIPT
if (cache_is_vipt_aliasing()) {
while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {
printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);
vma->vm_start += PAGE_SIZE;
}
}
#endif
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
[// proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
这句代码创建一个物理页面结构体指针数组,大小为(vma->vm_end - vma->vm_start) / PAGE_SIZE。即每一页虚拟地址空间都对应有一个物理页面,并且将该数组的地址保存在proc->pages中。在Linux内核中,一个物理页面的大小为PAGE_SIZE。PAGE_SIZE是一个宏,一般定义为4K.
]// proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);

if (proc->pages == NULL) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
[// if (proc->pages == NULL)
申请物理页面结构体数组失败
]// if (proc->pages == NULL)
proc->buffer_size = vma->vm_end - vma->vm_start;
[// proc->buffer_size = vma->vm_end - vma->vm_start;
buffer_size保存的为进程分配的内核空间的大小
]// proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
[// vma->vm_ops = &binder_vm_ops
这里将操作进程内核空间的函数设置为binder_vm_ops。其中binder_vm_ops的定义如下:
static struct vm_operations_struct binder_vm_ops = {
.open = binder_vma_open,
.close = binder_vma_close,
};
]// vma->vm_ops = &binder_vm_ops
vma->vm_private_data = proc;
[// vma->vm_private_data = proc;

]// vma->vm_private_data = proc;

if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
[// if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma))
binder_update_page_range函数来为虚拟空间地址分配一个物理页面,对应的内核地址空间为proc->buffer~proc->buffer + PAGE_SIZE.
binder_update_page_range的作用是为一段指定的虚拟地址空间分配或者释放物理页面.
binder_update_page_range函数的第一个参数proc指向要操作的目标进程
第二个参数allocate用于区分是分配还是释放物理页面。值为1表示要分配物理页面,值为0表示要释放物理页面.
第三个参数start和第四个参数end指定了要操作的内核空间地址的开始地址和结束地址
第五个参数vma指向要映射的用户地址空间
static int binder_update_page_range(struct binder_proc *proc, int allocate, void *start, void *end, struct vm_area_struct *vma)
                {
                void *page_addr;
                unsigned long user_page_addr;
                struct vm_struct tmp_area;
                struct page **page;
                struct mm_struct *mm;

                binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                     "binder: %d: %s pages %p-%p\n", proc->pid,
                     allocate ? "allocate" : "free", start, end);

                if (end <= start)
                return 0;
[// if (end <= start)
判断参数是否、合法
]// if (end <= start)
                if (vma)
                mm = NULL;
                else
                mm = get_task_mm(proc->tsk);

                if (mm) {
                down_write(&mm->mmap_sem);
                vma = proc->vma;
                }
                [// if (mm)
                上面这段代码判断参数vma是否指向一个空的用户地址空间,如果是,就从目标进程proc的成员变量vma来获得要映射的用户地址空间。
                ]// if (mm)

                if (allocate == 0)
                goto free_range;
[// if (allocate == 0)
这里是判断分配物理内存还是释放物理内存
]// if (allocate == 0)
                if (vma == NULL) {
                printk(KERN_ERR "binder: %d: binder_alloc_buf failed to "
                       "map pages in userspace, no vma\n", proc->pid);
                goto err_no_vma;
                }

                for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
                [// for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE)
                在这个for循环里依次为每一个虚拟地址空间页面分配一个物理页面
                ]// for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE)
                int ret;
                struct page **page_array_ptr;
                page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
                [// page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]
                这句代码是从目标进程proc的物理页面结构体指针数组pages中获得一个与内核地址空间page_addr~(page_addr+PAGE_SIZE)对应的物理页面指针
                ]// page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]

                BUG_ON(*page);
                *page = alloc_page(GFP_KERNEL | __GFP_ZERO);
                [// *page = alloc_page(GFP_KERNEL | __GFP_ZERO)
                调用函数alloc_page为该内核地址空间分配一个物理页面。
                ]// *page = alloc_page(GFP_KERNEL | __GFP_ZERO)
                if (*page == NULL) {
                printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
                       "for page at %p\n", proc->pid, page_addr);
                goto err_alloc_page_failed;
                }
                [// if (*page == NULL)
                物理页面分配失败
                ]// if (*page == NULL)
                tmp_area.addr = page_addr;
                tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
                page_array_ptr = page;
                ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
                [// ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr)
                上面这几句代码是用来将物理页面映射到内核地址空间。
                上面tmp_area.size = PAGE_SIZE + PAGE_SIZE这句代码将要映射的内核地址空间大小设置为两个页面,即用一个物理页面来映射两个内核地址空间页面。
                为什么要这样做呢?原来,Linux内核规定,在(3G+896M+8M)~4G范围内的任意一块内核地址空间都必须姚在后面保留一块空的地址空间来作为安全保护区,用来检测非法指针。
                因此这里就在内核地址空间page_addr~(page_addr+PAGE_SIZE)的后面保留了大小为一页的安全保护区。
                ]// ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr)
                if (ret) {
                printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
                       "to map page at %p in kernel\n",
                       proc->pid, page_addr);
                goto err_map_kernel_failed;
                }
                [// if (ret)
                映射内核地址空间失败
                ]// if (ret)
                user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
                ret = vm_insert_page(vma, user_page_addr, page[0]);
                [// ret = vm_insert_page(vma, user_page_addr, page[0])
                上面这两句代码是用来将物理内存映射到用户地址空间。
这里先得到与内核地址相对应的用户空间地址,方法是用内核地址page_addr加上一个偏移量,而这个偏移量是固定的,保存在目标进程结构体proc->user_buffer_offset的成员变量中。
接着调用vm_insert_page函数来映射用户空间地址。
]// ret = vm_insert_page(vma, user_page_addr, page[0])
                if (ret) {
                printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
                       "to map page at %lx in userspace\n",
                       proc->pid, user_page_addr);
                goto err_vm_insert_page_failed;
                }
                [// if (ret)
用户空间映射失败
                ]// if (ret)
                /* vm_insert_page does not seem to increment the refcount */
                }
                if (mm) {
                up_write(&mm->mmap_sem);
                mmput(mm);
                }
                return 0;

                free_range:
                for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
[// for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE)
这里是释放物理页面的部分。同样由于内核地址空间start~end可能包含了多页页面。所以这里通过一个for循环来依次为每一个虚拟地址空间页面释放物理页面。
]// for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE)
                page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
[// page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
这句代码从进程的proc的物理页面结构体指针数组pages中获得一个与内核地址空间page_addr~(page_addr+PAGE_SIZE)对应的物理页面指针
]// page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
                if (vma)
                zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);
[// zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL)
调用zap_page_range函数得到物理页面在用户空间的映射
]// zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL)
err_vm_insert_page_failed:
unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
[// unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE)
unmap_kernel_range函数得到物理页面在内核空间的映射
]// unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE)
err_map_kernel_failed:
__free_page(*page);
[// __free_page(*page)
__free_page函数来释放该物理页面
]// __free_page(*page)
*page = NULL;
err_alloc_page_failed:
;
}
err_no_vma:
if (mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
return -ENOMEM;
                }
]// if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma))
ret = -ENOMEM;
failure_string = "alloc small buf";
goto err_alloc_small_buf_failed;
}
buffer = proc->buffer;
INIT_LIST_HEAD(&proc->buffers);
list_add(&buffer->entry, &proc->buffers);
[// list_add(&buffer->entry, &proc->buffers)
上面这两句代码是初始化进程结构体proc的内核缓冲区列表buffers,并且将刚刚分配的物理页面加入到进程结构体proc的内核缓冲区列表buffers中。
]// list_add(&buffer->entry, &proc->buffers)
buffer->free = 1;
[// buffer->free = 1
将free字段设置为1表示这个物理页面是空闲的
]// buffer->free = 1
binder_insert_free_buffer(proc, buffer);
[// binder_insert_free_buffer(proc, buffer)
这里会调用binder_insert_free_buffer函数来将一个空闲的内核缓冲区加入到进程的空闲内核缓冲区红黑树中。
static void binder_insert_free_buffer(struct binder_proc *proc, struct binder_buffer *new_buffer)
{
struct rb_node **p = &proc->free_buffers.rb_node;
struct rb_node *parent = NULL;
struct binder_buffer *buffer;
size_t buffer_size;
size_t new_buffer_size;

BUG_ON(!new_buffer->free);

new_buffer_size = binder_buffer_size(proc, new_buffer);
[// new_buffer_size = binder_buffer_size(proc, new_buffer)
在结构体binder_proc的成员变量free_buffers用来描述一个红黑树,它是按照大小来组织进程中的空闲内核缓冲区的。
因此,在将内核缓冲区new_buffer加入到目标进程proc的空闲内核缓冲区红黑树中之前,首先调用binder_buffer_size来计算它的大小。
我们看一下binder_buffer_size函数的实现:
static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer)
{
[// static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer)
binder_buffer_size函数用于计算内核缓冲区的大小。
使用结构体binder_buffer描述的内核缓冲区由两块数据组成,其中一个是元数据块,用来描述内核缓冲区本身;另外一个是有效数据块,用来保存真正的事务数据。一个内核缓冲区的大小指的是其有效数据块的大小。
]// static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer)
if (list_is_last(&buffer->entry, &proc->buffers))
return proc->buffer + proc->buffer_size - (void *)buffer->data;
[// if (list_is_last(&buffer->entry, &proc->buffers))
计算一个内核缓冲区binder_buffer的大小时,需要考虑它在进程内核缓冲区列表buffers中的位置。
如果它是列表中的最后一个元素,那么它所描述的内核缓冲区的有效数据块就从它的成员变量data开始,一直到Binder驱动程序为进程所分配的一块连续内核地址空间的末尾。
]// if (list_is_last(&buffer->entry, &proc->buffers))
else
return (size_t)list_entry(buffer->entry.next, struct binder_buffer, entry) - (size_t)buffer->data;
[// else
如果内核缓冲区buffer不是进程内核缓冲区列表的最后一个元素,那么它的大小等于下一个内核缓冲区的起始地址,再减去它的成员变量data的地址。
]// else
}
]// new_buffer_size = binder_buffer_size(proc, new_buffer)

binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
"binder: %d: add free buffer, size %zd, "
"at %p\n", proc->pid, new_buffer_size, new_buffer);

while (*p) {
parent = *p;
buffer = rb_entry(parent, struct binder_buffer, rb_node);
BUG_ON(!buffer->free);

buffer_size = binder_buffer_size(proc, buffer);

if (new_buffer_size < buffer_size)
p = &parent->rb_left;
else
p = &parent->rb_right;
}
[// while (*p)
这个for循环里找到要插入的位置
]// while (*p)
rb_link_node(&new_buffer->rb_node, parent, p);
rb_insert_color(&new_buffer->rb_node, &proc->free_buffers);
[// rb_insert_color(&new_buffer->rb_node, &proc->free_buffers)
上面这两句代码将内核缓冲区new_buffer保存在红黑树的这个位置上。
]// rb_insert_color(&new_buffer->rb_node, &proc->free_buffers)
}
]// binder_insert_free_buffer(proc, buffer)
proc->free_async_space = proc->buffer_size / 2;
[// proc->free_async_space = proc->buffer_size / 2
这句话将进程最大可用于异步事务的内核缓冲区大小设置为总的内核缓冲区大小的一半。这样做是为了防止异步事务消耗过多的内核缓冲区,从而影响同步事务的执行。
]// proc->free_async_space = proc->buffer_size / 2
barrier();
proc->files = get_files_struct(current);
proc->vma = vma;

/*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n",
proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
return 0;

err_alloc_small_buf_failed:
kfree(proc->pages);
proc->pages = NULL;
err_alloc_pages_failed:
vfree(proc->buffer);
proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
err_bad_arg:
printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n",
   proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
return ret;
}

猜你喜欢

转载自zzu-007.iteye.com/blog/2261594