“九层之台,始于垒土;千里之行,始于足下”,不论binder机制在Android源码中多庞大繁杂,总归它得从地基开始–Binder驱动,我们知道Android系统是基于Linux的内核的,因而Binder驱动也必须是一个标准的Linux驱动。具体来说,Binder Driver 会将自己注册为一个misc device,并向上层提供一个/dev/binder节点,当然我们需要牢记,Binder并不真实对应硬件设备。
一、binder驱动
Binder驱动运行于内核态,提供open()、mmap()、ioctl()等常用的文件操作接口。
一般来说Binder驱动源码在Kernel工程的 drivers/staging/android目录中,Linux的字符设备通常要经过alloc_chrdev_region(),cdev_init()等一系列操作才能在内核中注册自己,但misc类型驱动的注册相对简单,只需要调用misc_register()即可,主设备号统一为10,次设备号则为每种设备所独有,也可由系统动态分配次设备号。Binder中与驱动注册相关代码如下:
/*drivers/staging/android/Binder.c*/
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,//动态分配次设备号
.name = "binder",//驱动名称
.fops = &binder_fops//Binder驱动支持的文件操作
};
static int __init binder_init(void)
{
....
ret = misc_register(binder_miscdev);
....
}
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,
};
从这里可以看出,Binder驱动总共为上层应用提供了6个接口–其中使用最多的就是binder_ioctl、binder_mmap、binder_open,常见的read、write在这里没有看到,因为binder_ioctl接口可以代替读写操作,因此一个接口就实现了两种操作,另外Binder所使用的设备驱动入口函数不是module_init(),而是:
device_initcall(binder_init);
这样做的目的可能是Android系统不想支持动态编译的驱动模块,我们可以将其改为动态编译,也就是将设备驱动入口函数改为module_init和module_exit
1、binder_open–打开Binder驱动
上层进程在访问Binder驱动时,首先就是打开/dev/binder节点,这个操作最终的实现是在binder_open()中。
/*drivers/staging/android/Binder.c*/
static int binder_open(truct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
...
proc = kzalloc(sizeof(*proc), GFP_KERNEL); //分配空间
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
proc->tsk = current;
// 初始化binder_proc
INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);
//加入Binder的全局管理中
binder_lock(__func__);
binder_stats_created(BINDER_STAT_PROC);
// 添加到全局列表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;
binder_unlock(__func__);
return 0;
}
在Binder驱动中,通过binder_procs记录了所有使用Binder的进程。每个初次打开Binder设备的进程都会被添加到这个列表中的。binder_open()已经为用户创建了一个Binder驱动的binder_proc实体,之后用户对Binder设备的操作就是以这个对象为基础的。
2、binder_mmap–映射内存空间
binder_mmap函数对应了mmap系统调用的处理,这个函数也是Binder驱动的精华所在,使用Binder机制,数据只需要经历一次拷贝就可以了,其原理就在这个函数中,
binder_mmap这个函数中,进程B会申请一块物理内存,然后在用户空间和内核空间同时对应到这块内存上。在这之后,当进程A要发送数据给进程B的时候,只需一次,将进程A发送过来的数据拷贝到进程B的内核空间指定的内存地址即可,由于这个内存地址在服务端已经同时映射到用户空间,因此无需再做一次复制,进程B即可直接访问,我们来看源码:
/*drivers/staging/android/Binder.c
*vm_area_struct *vma描述了一块供应用程序使用的虚拟内存,
*其中vma->vm_start和vma->end分别是这块连续的虚拟内存起止点
*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
//step1、Binder驱动的虚拟内存
struct vm_struct *area;
/*step2、这里的proc结构体在binder_open()已经看到过,它是Binder驱动为应用进程
分配的一个数据结构,用于存储和该进程有关的所有信息,如内存分配、线程管理
*/
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
...
/*step3、在做实际工作前,Binder驱动会判断应用程序申请的内存大小是否合理
,它最多能申请4MB的mmap操作
*/
if((vma -> vm_end - vma -> vm_start ) > SZ_4M)
{
vma -> vm_end = vma -> vm_start + SZ_4M
}
// 在内核空间获取一块地址范围,也就是说此时还没有分配实际的物理地址
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;
}
proc->buffer = area->addr;//映射后的地址
// 记录内核空间与用户空间的地址偏移
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
mutex_unlock(&binder_mmap_lock);
//这里只是分配了pages数组控件,用于管理物理页面的,也就是用于指示
//Binder申请的物理页面状态
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;
}
//计算虚拟块大小
proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
// 通过下面这个函数真正完成内存的申请和地址的映射
// 初次使用,先申请一个PAGE_SIZE大小的内存
ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma);
...
我们解释下tatic int binder_update_page_range(struct binder_proc *proc, int allocate,
void *start, void *end,
struct vm_area_struct *vma)函数的参数:
1、proc:申请内存的进程所持有的binder_proc对象
2、allocate:是申请还是释放。1 表示申请,0 表示释放
3、start:Binder中虚拟内存起点(页表映射代表从虚拟内存到物理内存)
4、end:Binder中虚拟内存终点
对照上面函数的传参就能看出来,在mmap中Binder驱动实际上只为进程分配了一页物理内存,虽然最多支持4MB的mmap,继续看binder_update_page_range
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;
...
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];
BUG_ON(*page);
// 真正进行内存的分配
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
if (*page == NULL) {
pr_err("%d: binder_alloc_buf failed for page at %p\n",
proc->pid, page_addr);
goto err_alloc_page_failed;
}
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);
if (ret) {
pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n",
proc->pid, page_addr);
goto err_map_kernel_failed;
}
user_page_addr =
(uintptr_t)page_addr + proc->user_buffer_offset;
// 在用户空间进行内存映射
ret = vm_insert_page(vma, user_page_addr, page[0]);
if (ret) {
pr_err("%d: binder_alloc_buf failed to map page at %lx in userspace\n",
proc->pid, user_page_addr);
goto err_vm_insert_page_failed;
}
/* vm_insert_page does not seem to increment the refcount */
}
if (mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
preempt_disable();
return 0;
...
3、binder_ioctl
这是Binder接口中工作量最大的一个函数,承担了Binder驱动的大部分业务,看看接口提供了哪些操作:
如果命令是BINDER_WRITE_READ,并且
如果 bwr.write_size > 0,则调用binder_thread_write
如果 bwr.read_size > 0,则调用binder_thread_read
如果命令是BINDER_SET_MAX_THREADS,则设置进程的max_threads,即进程支持的最大线程数
如果命令是BINDER_SET_CONTEXT_MGR,ServiceManager专用,将自己设置为Binder大管家,系统中只能有一个SM存在
如果命令是BINDER_THREAD_EXIT,则调用binder_free_thread,释放binder_thread
如果命令是BINDER_VERSION,则返回当前的Binder版本号
这其中,最关键的就是binder_thread_write方法。当Client请求Server的时候,便会发送一个BINDER_WRITE_READ命令,同时框架会将将实际的数据包装好。关于这个命令,可以查资料或者翻看源码。
最后来总结一下,Binder驱动依然是Linux的典型驱动模型,提供多个文件操作接口。其中binder_ioctl实现了应用程序与Binder驱动之间的命令交互,承载了Binder驱动的绝大部分工作。
接下来分析“DNS服务器”–ServiceManager[Binder Server](三)