Android中进程间通信Binder机制之Binder驱动 【二】
Binder驱动是Android专用的一个驱动程序(前身是OpneBinder,不过它已经停止更新了),保持了和一般Linux驱动一样框架。Binder驱动不涉及任何外设,本质上只操作内存,负责将数据从一个进程传递到另外一个进程。
我们知道Android系统是基于Linux内核,所以Binder驱动也是一个标准的Linux驱动,所以它的注册和使用等操作也跟标准驱动一样。
Binder驱动会将自己注册成一个misc device,Linux中的misc device意思是杂项设备,也就是大杂烩的意思,它的注册和使用比较简单;然后创建/dev/binder 设备节点,Binder节点并不会对应真实的硬件设备,主要是对内存的管理。
一个完整的Binder驱动使用步骤如下:
- 通过init(),将Binder注册成miscdevice,创建/dev/binder 设备节点
- 通过open(),打开Binder驱动,获取其文件描述符
- 通过mmap(),进行内存申请,实现内存映射
- 通过ioctl(),将IPC中的数据传递给Binder驱动
注意:
上面提到的几个方法都是用户空间的方法,如果要调用到内核空间的方法,需要涉及到系统调用,比如在用户空间调用open,就会通过系统调用走到 _open() ,最后对应调用到内核空间binder驱动的binder_open()方法,其它调用类似
Linux内核代码路径如下:
/kernel/drivers/staging/android/binder.c
/kernel/drivers/staging/android/uapi/binder.h
Binder驱动注册:
static int __init binder_init(void)
{
........
// 最主要的一行代码,注册misc设备
//其它大部分代码是在通过debugfs_create_dir和debugfs_create_file函数创建debugfs对应的文件
//可以通过# ls -l /sys/kernel/debug/binder/查看文件,
//当中的proc目录记录哪些进程正在使用Binder
//transaction_log和transactions文件记录Binder通信的数据
ret = misc_register(&binder_miscdev);
}
这样就把自己注册成了miscdevice,看看参数binder_miscdev ,也就是miscdevice结构体
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR, //动态分配次设备号,Binder驱动主设备号是10
.name = "binder", //驱动名称或者说设备名称,在用户空间就可以通过对/dev/binder文件进行操作来使用Binder
.fops = &binder_fops //Binder驱动的文件操作结构,这是file_operations结构体
};
再看看file_operations结构体
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设备时的操作;比如binder_open对应了_open的系统调用,对应了用户空间的open方法,binder_mmap对应了_mmap系统调用等。
在这里面有三个函数最为重要也是用的最多的,binder_open,binder_mmap,binder_ioctl;只要是要用到Binder的进程,先要通过binder_open打开Binder驱动,然后binder_mmap进行内存映射,最后通过binder_ioctl进行实际操作;Client请求Server,Server响应Client都是通过binder_ioctl来完成
打开Binder驱动
任何进程在使用Binder驱动时,都需要打开/dev/binder节点,即打开Binder设备,这个操作最终会通过内核空间的binder_open()方法实现
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
//创建进程对应的binder_proc对象,并为这个结构体分配空间
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
//将当前线程的task保存到binder进程的tsk
proc->tsk = current;
//初始化todo链表
INIT_LIST_HEAD(&proc->todo);
//初始化wait链表
init_waitqueue_head(&proc->wait);
//将当前进程的nice值转换为进程优先级
proc->default_priority = task_nice(current);
// 锁保护 因为binder支持多线程访问
binder_lock(__func__);
///binder_proc对象创建数+1
binder_stats_created(BINDER_STAT_PROC);
// 全局哈希链表binder_procs, 把binder_proc加入到binder_procs链表头部
hlist_add_head(&proc->proc_node, &binder_procs);
//进程pid
proc->pid = current->group_leader->pid;
//初始化已分发的死亡通知列表
INIT_LIST_HEAD(&proc->delivered_death);
//file文件指针的private_data变量指向binder_proc
filp->private_data = proc;
//释放同步锁
binder_unlock(__func__);
return 0;
}
这个方法实例化了binder_proc 对象,当前进程的信息被保存在这个对象里,后续IPC需要的信息从这里取;还有binder_proc对象保存到文件指针filp,这样以后就能通过filp找到binder_proc ;所有使用Binder的进程保存在了binder_procs链表;到这里Binder驱动已经为进程创建了一个属于它自己的binder_proc实体,之后进程对binder设备操作将以这个对象为基础
内存映射
内存映射的概念在上篇博客已经讲述过了
进程在打开Binder驱动之后,并不是直接就进行数据操作了,如果这样就跟别的IPC机制没有本质区别了;而Binder机制在这里有一步非常关键的操作就是内存映射,也是提高效率所在(只进行了一次数据拷贝)
这一步操作在内核空间对应的是binder_mmap()函数,对应的用户空间mmap()方法;这个函数可以把设备指定的内存映射到使用其进程的虚拟内存空间中,但是Binder并不是一个真的硬件设备,而是基于内存的伪硬件,那Binder把什么内存映射到了进程中呢?
mmap函数
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
//内核虚拟内存
struct vm_struct *area;
//从filp取出进程对应的binder_proc,在open发方法中保存在了filp
struct binder_proc *proc = filp->private_data;
const char *failure_string;
//每一次Binder传输数据时,都会先从Binder内存缓存区中分配一个binder_buffer来存储传输数据
struct binder_buffer *buffer;
if (proc->tsk != current)
return -EINVAL;
//保证映射内存大小不超过4M
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
mutex_lock(&binder_mmap_lock); //同步锁
//采用IOREMAP方式,分配一个连续的内核虚拟空间,与进程用户虚拟空间大小一致
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;
}
//将buffer指针指向内核虚拟空间地址
proc->buffer = area->addr;
//记录内核空间与用户虚拟空间的地址偏移 地址偏移量 = 用户虚拟地址空间 - 内核虚拟地址空间
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
mutex_unlock(&binder_mmap_lock); //释放锁
...
//分配物理页的指针数组,数组大小为vma的等效page个数;
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;
//通过下面这个函数真正完成物理内存的申请和地址的映射
//分配物理页面,同时映射到内核空间和用户空间,先分配1个物理页
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; //binder_buffer对象 指向proc的buffer地址
INIT_LIST_HEAD(&proc->buffers); //创建进程的buffers链表头
list_add(&buffer->entry, &proc->buffers); //将binder_buffer地址 加入到所属进程的buffers队列
buffer->free = 1;
//将空闲buffer放入proc->free_buffers中
binder_insert_free_buffer(proc, buffer);
//异步可用空间大小为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;
return 0;
...
// 错误flags跳转处,free释放内存之类的操作
return ret;
}
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 page **page;
struct mm_struct *mm; // 内存结构体
if (vma)
mm = NULL; //binder_mmap过程vma不为空,其他情况都为空
else
mm = get_task_mm(proc->tsk); //获取mm结构体
if (mm) {
down_write(&mm->mmap_sem); //获取mm_struct的写信号量
vma = proc->vma;
}
//此处allocate为1,代表分配过程。如果为0则代表释放过程
if (allocate == 0)
goto free_range;
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
//分配一个page的物理内存
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
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 (mm) {
up_write(&mm->mmap_sem); //释放内存的写信号量
mmput(mm); //减少mm->mm_users计数
}
return 0;
free_range:
... //释放内存的流程
return -ENOMEM;
}
- 参数一是文件描述符,里面保存了binder_proc结构体;参数二是用户虚拟内存空间
- 通过加锁,保证多进程的并发访问
- 确定映射内存大小,通过get_vm_area函数分配一个连续的内核虚拟空间,与进程用户虚拟空间大小一致;将进程的内核空间的起始地址指向该空间地址,这时候还没有分配实际的物理内存
- 在binder_update_page_range函数中通过alloc_page函数申请一页物理内存,并同时映射到用户虚拟空间和内核虚拟空间;参数一是申请内存的进程的binder_proc对象;第二个参数1表示申请,0表示释放;第三个参数是Binder中虚拟内存起点;第四个参数是Binder中虚拟内存的终点;第五个参数是用户空间虚拟内存
内存映射完成后,Binder机制中通信过程如下
数据处理
上面几步做完,进程通信的基础已经有了,接下来就是通过binder_ioctl()函数进行操作了,对应ioctl()系统调用;这个方法主要实现了应用进程与Binder驱动之间的命令交互,可以说承载了Binder驱动的大部分业务,我们之前提到过Binder没有提供read()和write()等文件操作,因为binder_ioctl()可以完全替代它们
函数定义如下
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
- 第一个参数是文件描述符,与上述方法一样
- 第二个参数是命令,第三个参数是数据类型,这两个参数是一体的
命令与数据类型如下:
- BINDER_WRITE_READ:对应数据类型struct binder_write_read;主要作用是读写操作,可以用此命令向Binder读取获写入数据
- BINDER_SET_MAX_THREADS:对应数据类型__u32;设置Binder线程最大数
- BINDER_SET_CONTEXT_MGR:对应数据类型__s32;Service Manager专用,将自己设置为Binder大管家,系统中只能有一个SM存在
- BINDER_THREAD_EXIT:对应数据类型__s32;通知Binder有线程退出,每个线程在退出时都应该通知Binder,这样Binder驱动就能释放资源,避免内存泄漏
- BINDER_VERSION:对应数据类型struct binder_version;获取Binder版本号
在这里面BINDER_WRITE_READ是使用最多的,也是ioctl最核心的命令
ioctl函数
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
// binder线程
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
//进入休眠状态,直到中断唤醒
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
binder_lock(__func__);
//获取binder_thread
//从binder_proc中查找binder_thread,如果当前线程已经加入到binder_proc的线程队列则直接返回
//如果不存在则创建binder_thread,并将当前线程添加到当前的proc
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
case BINDER_WRITE_READ:
//进行binder的读写操作
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
case BINDER_SET_MAX_THREADS:
//设置binder支持的最大线程数
if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
ret = -EINVAL;
goto err;
}
break;
case BINDER_SET_CONTEXT_MGR:
//成为binder的上下文管理者,也就是ServiceManager成为守护进程
ret = binder_ioctl_set_ctx_mgr(filp);
if (ret)
goto err;
break;
case BINDER_THREAD_EXIT:
//当binder线程退出,释放binder线程
binder_free_thread(proc, thread);
thread = NULL;
break;
case BINDER_VERSION: {
//获取binder的版本号
struct binder_version __user *ver = ubuf;
if (size != sizeof(struct binder_version)) {
ret = -EINVAL;
goto err;
}
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL;
goto err;
}
break;
}
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
binder_unlock(__func__);
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
err_unlocked:
trace_binder_ioctl_done(ret);
return ret;
}
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
int ret = 0;
struct binder_proc *proc = filp->private_data;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto out;
}
//把数据ubuf拷贝到bwr,即从用户空间到内核空间的传递
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
if (bwr.write_size > 0) {
//当写缓存中有数据,则执行binder写操作
ret = binder_thread_write(proc, thread,
bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
trace_binder_write_done(ret);
if (ret < 0) { //当写失败,再将bwr数据写回用户空间,并返回
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
if (bwr.read_size > 0) {
//当读缓存中有数据,则执行binder读操作
ret = binder_thread_read(proc, thread,
bwr.read_buffer, bwr.read_size, &bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait); //唤醒等待状态的线程
if (ret < 0) { //当读失败,再将bwr数据写回用户空间,并返回
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
//将数据bwr拷贝到ubuf,即内核空间到用户空间的传递
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
out:
return ret;
}
主要逻辑:
- 如果传递进来的命令是 BINDER_WRITE_READ时执行binder_ioctl_write_read方法
- 在binder_ioctl_write_read方法中,通过copy_from_user函数将用户空间数据ubuf拷贝到内核空间bwr
- 当bwr结构体中的写缓存(write_buffer)有数据即(write_size > 0),则执行binder_thread_write;当写失败则将bwr数据写回用户空间并退出;
- 当bwr结构体中的读缓存(read_buffer)有数据即(read_size > 0),则执行binder_thread_read;当读失败则再将bwr数据写回用户空间并退出;
- 最后,通过copy_to_user函数把内核数据bwr拷贝到用户空间ubuf。
在这里有一个非常重要的结构体:binder_write_read,结构如下
注意:
- 在Binder操作中,缓存的分配是通过binder_alloc_buf函数完成的,使用binder_buffer结构体描述缓存,一次Binder事务就会对应一个binder_buffer
- 内存释放通过binder_free_buf函数实现,做了三件事:
– 1.重新计算进程的空闲缓存大小
– 2.通过binder_update_page_range释放内存
– 3.更新binder_proc的buffers,free_buffers,allocated_buffers字段
Binder通信协议
Binder协议可以分为控制协议和驱动协议两类:
控制协议是进程通过ioctl(“/dev/binder”) 与Binder设备进行通讯的协议,该协议包含的命令有BINDER_WRITE_READ,BINDER_SET_MAX_THREADS,BINDER_SET_CONTEXT_MGR,BINDER_THREAD_EXIT,BINDER_VERSION等,这在上面数据处理那一步中已经描述过
驱动协议描述了对于Binder驱动的具体使用过程。驱动协议又可以分为两类:
一类是BINDER_COMMAND_PROTOCOL:binder请求码,以”BC_“开头,简称BC码,描述了进程发送给Binder驱动的命令
一类是BINDER_RETURN_PROTOCOL :binder响应码,以”BR_“开头,简称BR码,描述了Binder驱动发送给进程的命令
Binder请求码有如下:
- BC_TRANSACTION:Binder事务,即Client对于Server的请求,参数类型是binder_transaction_data
- BC_REPLY:事务的应答,即:Server对于Client的回复,参数类型是binder_transaction_data
- BC_FREE_BUFFER:通知驱动释放内存,参数类型是binder_uintptr_t(指针)
- BC_ACQUIRE:binder_ref强引用计数+1,参数类型是__u32
- BC_RELEASE:binder_ref强引用计数-1,参数类型是__u32
- BC_INCREFS:binder_ref弱引用计数+1,参数类型是__u32
- BC_DECREFS:binder_ref弱引用计数-1,参数类型是__u32
- BC_ACQUIRE_DONEBR:binder_node强引用减1操作,参数类型是binder_ptr_cookie
- BC_INCREFS_DONEBR:binder_node弱引用减1操作,参数类型是binder_ptr_cookie
- BC_ENTER_LOOPER:通知驱动应用线程进入looper,无参数
- BC_REGISTER_LOOPER:通知驱动创建新的looper线程,无参数
- BC_EXIT_LOOPER:通知驱动应用线程退出looper,无参数
- BC_REQUEST_DEATH_NOTIFICATION:注册死亡通知,参数类型是binder_handle_cookie
- BC_CLEAR_DEATH_NOTIFICATION:取消注册的死亡通知,参数类型是binder_handle_cookie
- BC_DEAD_BINDER_DONE:已完成binder的死亡通知,参数类型是binder_uintptr_t
请求处理过程是通过binder_thread_write()方法,该方法用于处理Binder协议中的请求码。当binder_buffer存在数据,binder线程的写操作循环执行。
对于请求码为BC_TRANSACTION或BC_REPLY时,会执行binder_transaction()方法,这是最为频繁的操作
Binder响应码如下:
- BR_ERROR:操作发生错误,参数类型__s32
- BR_OK:操作完成,无参数
- BR_NOOP:不做处理
- BR_SPAWN_LOOPER:binder驱动已经检测到进程中没有线程等待即将到来的事务。那么当一个进程接收到这条命令时,该进程必须创建一条新的服务线程并注册该线程,无参数
- BR_TRANSACTION:Binder驱动向Server端发送请求数据,参数类型binder_transaction_data
- BR_REPLY:Binder驱动向Client端发送回复数据
- BR_TRANSACTION_COMPLETE:对请求发送的成功反馈,当Client端向Binder驱动发送BC_TRANSACTION命令后,Client会收到BR_TRANSACTION_COMPLETE命令,告知Client端请求命令发送成功;对于Server向Binder驱动发送BC_REPLY命令后,Server端会收到BR_TRANSACTION_COMPLETE命令,告知Server端请求回应命令发送成功,无参
- BR_DEAD_REPLY:当应用层向Binder驱动发送Binder调用时,若Binder应用层的另一个端已经死亡,则驱动回应BR_DEAD_BINDER命令,往往是线程或节点为空,无参
- BR_FAILED_REPLY:当应用层向Binder驱动发送Binder调用时,若transaction出错,比如调用的函数号不存在,则驱动回应BR_FAILED_REPLY,无参
- BR_INCREFS:binder_ref弱引用加1操作(Server端),参数类型binder_ptr_cookie
- BR_DECREFS:binder_ref弱引用减1操作(Server端),参数类型binder_ptr_cookie
- BR_ACQUIRE:binder_ref强引用加1操作(Server端),参数类型binder_ptr_cookie
- BR_RELEASE:binder_ref强引用减1操作(Server端),参数类型binder_ptr_cookie
- BR_DEAD_BINDER:Binder驱动向client端发送死亡通知,参数类型 binder_uintptr_t(指针)
- BR_CLEAR_DEATH_NOTIFICATION_DONE:BC_CLEAR_DEATH_NOTIFICATION命令对应的响应码,参数类型binder_uintptr_t(指针)
响应处理过程是通过binder_thread_read()方法,该方法根据不同的binder_work->type以及不同状态,生成相应的响应码。
Client对于Server的请求以及Server对于Client回复都需要通过Binder驱动来中转数据,在这里面Binder协议有很重要的作用,看一个完整的Binder通信过程
主要就是一方发送BC_XXX,然后由驱动控制通信过程,接着发送对应的BR_XXX命令给通信的另外一方