Android Binder 驱动分析1
Binder驱动的属于Binder 框架的最核心,无论是应用层的Binder Client 还是Binder Server,或者是Service Manager都是它的Client.
Binder驱动的源码位于:
- kernel/drivers/android/Binder.c
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
和其他字符驱动一样,binder驱动也提供了实现了一套file_operations, 接下来逐项分析这些函数
binder_open
首先分析binder_open函数,每一个需要完成进程间通信的进程都需要完成这个接口调用。实际上就是打开/dev/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);
//current的是当前调用open的应用层进程
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
get_task_struct(current); //增加task_struct的引用计数
proc->tsk = current;
INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->wait);
proc->default_priority = task_nice(current);
binder_lock(__func__);
binder_stats_created(BINDER_STAT_PROC);
hlist_add_head(&proc->proc_node, &binder_procs);//保存至binder_procs
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
filp->private_data = proc;
binder_unlock(__func__);
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);
}
return 0;
}
这里面使用了个类似于全局变量的current的结构体,它的类型是
task_struct
.The current pointer refers to the user process currently executing. During the execution of a system call,
such as open or read, the current process is the one that invoked the call.这个过程主要是创建初始化 binder_proc, binder_proc的信息主要来源于
current
.即应用层对应进程的相关信息。这里穿插一句:在Linux中,内核对于线程的表示并不是process,而是task. 有时候可以理解为task即使应用层进程和线程在内核的
代表。最后把该proc 加入到
binder_procs
列表中, 同时把该proc放在file的private_data中方便获取
binder_mmap
在frameworks/native/libs/binder/ProcessState.cpp,应用层将调用mmap来把binder映射到用户空间来方便数据交换。其调用的方式大致如此:
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) //1016K
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
上述代码为拼凑代码,可以知道需要映射的空间接近1M(1016K)
接下去贴上binder_mmap的代码
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)// struct vm_area_struct是描述用户空间地址
{
int ret;
struct vm_struct *area;//struct vm_struct描述内核空间地址
//在binder_open中将表示当前应用层的proc信息存储在file的private_data中
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
//限制映射的代码在4M 以内(实际上应用层申请的地址空间是不到1M)
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
mutex_lock(&binder_mmap_lock);
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
//申请分配内核地址空间,注意其大小是struct vm_area_struct中的大小,
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
proc->buffer = area->addr;//把分配的内核空间地址首地址保存在proc的buffer中
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;//计算出进程分配地址空间首地址和内核空间首地址的偏差
mutex_unlock(&binder_mmap_lock);
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
//分配物理页帧,并把它映射到进程地址空间和内核空间。这样
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;
}
//这个是binder_buffer buffer, 而proc->buffer是void* , 这里有个有趣的地方就是转换,利用binder_buffer来管理申请分配的内存(零数组技巧)
buffer = proc->buffer;
INIT_LIST_HEAD(&proc->buffers);
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1;
binder_insert_free_buffer(proc, buffer);
proc->free_async_space = proc->buffer_size / 2;
barrier();
proc->files = get_files_struct(current);
proc->vma = vma;
proc->vma_vm_mm = vma->vm_mm;
/*pr_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:
mutex_lock(&binder_mmap_lock);
vfree(proc->buffer);
proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
mutex_unlock(&binder_mmap_lock);
err_bad_arg:
pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",
proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
return ret;
- 代码中有两个很像的结构体,理解他们对于理解代码有很大的帮助:
struct vm_area_struct
和struct vm_struct
这两者的区别如下:
struct vm_area_struct表示的虚拟地址是给进程使用的,而struct vm_struct表示的虚拟地址是给内核使用的,
它们对应的物理页面都可以是不连续的。
struct vm_area_struct表示的地址空间范围是0~3G,而struct vm_struct表示的地址空间范围是(3G + 896M + 8M) ~ 4G。
struct vm_struct表示的地址空间 范围为什么不是3G~4G呢?原来,3G ~ (3G + 896M)范围的地址是用来映射连续的物理页面的,
这个范围的虚拟地址和对应的实际物理地址有着简单的对应关系,即对应0~896M的物理地址空间,而(3G + 896M) ~ (3G + 896M + 8M)
是安全保护区域(例如,所有指向这8M地址空间的指针都是非法的),因此 struct vm_struct使用(3G + 896M + 8M) ~ 4G地址空间来
映射非连续的物理页面。
代码中申请内核地址空间和用户地址空间通过物理叶帧建立关联,即访问内核和用户层访问虚拟地址对应的是同一个物理地址。
将这个内核地址空间用binder_buffer管理起来,添加进proc的buffers中链表中
binder_update_page_range
在binder_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;
trace_binder_update_page_range(proc, allocate, start, end);
//PAGE_SIZE的大小是4K
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结构中
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
//tmp_area 是struct vm_struct,代表内核线性地址空间段
tmp_area.addr = page_addr;//申请的内核地址空间的首地址
tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
page_array_ptr = page;
//与内核地址空间与对应的页帧建立映射,实际上该函数会修改对应的页表项 PTE
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
//通过内核空间地址 + 内核地址与进程空间地址的偏移 = 进程地址空间地址,所以user_page_addr是page_addr对应的
//进程空间地址,其中page_addr已经完成了物理页帧关联
user_page_addr =(uintptr_t)page_addr + proc->user_buffer_offset;
//同样样该物理页帧与进程地址空间建立映射关系
ret = vm_insert_page(vma, user_page_addr, page[0]);
/* vm_insert_page does not seem to increment the refcount */
}
return 0;
}