Binder 驱动篇
向内核中注册Binder设备驱动
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
if (!binder_device)
return -ENOMEM;
binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
binder_device->miscdev.name = name;
refcount_set(&binder_device->ref, 1);
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
mutex_init(&binder_device->context.context_mgr_node_lock);
// 该行代码真正向内核中注册了Binder设备
ret = misc_register(&binder_device->miscdev);
if (ret < 0) {
kfree(binder_device);
return ret;
}
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
binder_miscdev的定义如下:
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "binder",
.fops = &binder_fops
};
这里指定了Binder设备的名称是“binder”。这样,在用户空间便可以通过对/dev/binder文件进行操作来使用Binder。
binder_miscdev同时也指定了该设备的fops。fops是另外一个结构体,这个结构中包含了一系列的函数指针,其定义如下:
const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
这其中,有三个函数尤为重要,它们是:binder_open,binder_mmap和binder_ioctl。 这是因为,需要使用Binder的进程,几乎总是先通过binder_open打开Binder设备,然后通过binder_mmap进行内存映射。
在这之后,通过binder_ioctl来进行实际的操作。Client对于Server端的请求,以及Server对于Client请求结果的返回,都是通过ioctl完成的。
这里提到的流程如下图所示:
5. 主要结构
Binder驱动中包含了很多的结构体。为了便于下文讲解,这里我们先对这些结构体做一些介绍。
驱动中的结构体可以分为两类:
(1) 一类是与用户空间共用的,这些结构体在Binder通信协议过程中会用到。因此,这些结构体定义在binder.h中,包括:
结构体名称 说明
--------------------------------------------------------------
binder_write_read 存储一次读写操作的数据
binder_transaction_data 存储一次事务的数据
flat_binder_object 描述在Binder IPC中传递的对象,见下文
transaction_flags 描述事务的flag,例如是否是异步请求,是否支持fd
binder_version 存储Binder的版本号
binder_ptr_cookie 包含了一个指针和一个cookie
binder_handle_cookie 包含了一个句柄和一个cookie
binder_pri_desc 暂未用到
binder_pri_ptr_cookie 暂未用到
这其中,binder_write_read和binder_transaction_data这两个结构体最为重要,它们存储了IPC调用过程中的数据。关于这一点,我们在下文中会讲解。
(2) Binder驱动中,还有一类结构体是仅仅Binder驱动内部实现过程中需要的,它们定义在binder.c中,包括:
结构体名称 说明
--------------------------------------------------------------
binder_proc 描述使用Binder的进程
binder_thread 描述使用Binder的线程
binder_node 描述Binder实体节点,即:对应了一个Server
binder_ref 描述对于Binder实体的引用
binder_buffer 描述Binder通信过程中存储数据的Buffer
binder_work 描述通信过程中的一项任务
binder_transaction 描述一次事务的相关信息
binder_deferred_state 描述延迟任务
binder_ref_death 描述Binder实体死亡的信息
binder_transaction_log debugfs日志
binder_transaction_log_entry debugfs日志条目
6. Binder协议
Binder协议可以分为控制协议和驱动协议两类。
控制协议是进程通过ioctl(“/dev/binder”) 与Binder设备进行通讯的协议,是 ioctl(cmd) -> binder_ioctl(cmd) 的 cmd 参数,该协议包含以下几种命令:
命令 说明
--------------------------------------------------------------
BINDER_WRITE_READ 读写操作,最常用的命令。IPC过程就是通过这个命令进行数据传递
BINDER_VERSION 获取Binder驱动的版本号
BINDER_SET_MAX_THREADS 设置进程支持的最大线程数量
BINDER_SET_CONTEXT_MGR 设置自身为ServiceManager
BINDER_THREAD_EXIT 通知驱动Binder线程退出
BINDER_SET_IDLE_PRIORITY 暂未用到
BINDER_SET_IDLE_TIMEOUT 暂未用到
7. Binder的驱动协议描述了对于Binder驱动的具体使用过程。驱动协议又可以分为两类:
(1) 一类是 binder_driver_command_protocol,描述了进程发送给Binder驱动的命令。应用发给驱动的命令集合, 这些是从 binder_buffer 中解析出来的命令!在 binder_thread_write 中使用。共包含17个命令,分别是:
命令 说明 参数类型
-----------------------------------------------------------------------------------------------
BC_TRANSACTION Binder事务,即:Client对于Server的请求 binder_transaction_data
BC_REPLY 事务的应答,即:Server对于Client的回复 binder_transaction_data
BC_FREE_BUFFER 通知驱动释放Buffer binder_uintptr_t
BC_ACQUIRE 强引用计数+1 __u32
BC_RELEASE 强引用计数-1 __u32
BC_INCREFS 弱引用计数+1 __u32
BC_DECREFS 弱引用计数-1 __u32
BC_ACQUIRE_DONE BR_ACQUIRE的回复 binder_ptr_cookie
BC_INCREFS_DONE BR_INCREFS的回复 binder_ptr_cookie
BC_ENTER_LOOPER 通知驱动主线程ready void
BC_REGISTER_LOOPER 通知驱动子线程ready void
BC_EXIT_LOOPER 通知驱动线程已经退出 void
BC_REQUEST_DEATH_NOTIFICATION 请求接收死亡通知 binder_handle_cookie
BC_CLEAR_DEATH_NOTIFICATION 去除接收死亡通知 binder_handle_cookie
BC_DEAD_BINDER_DONE 已经处理完死亡通知 binder_uintptr_t
BC_ATTEMPT_ACQUIRE 暂未实现 -
BC_ACQUIRE_RESULT 暂未实现 -
(2) 一类是 binder_driver_return_protocol,描述了Binder驱动发送给进程的命令,是要放到 binder_buffer 中的命令,在 binder_thread_read 函数中使用,binder_driver_return_protocol共包含18个命令,分别是:
返回类型 说明 参数类型
-----------------------------------------------------------------------------------------------
BR_OK 操作完成 void
BR_NOOP 操作完成 void
BR_ERROR 发生错误 __s32
BR_TRANSACTION 通知进程收到一次Binder请求(Server端) binder_transaction_data
BR_REPLY 通知进程收到Binder请求的回复(Client) binder_transaction_data
BR_TRANSACTION_COMPLETE 驱动对于接受请求的确认回复 void
BR_FAILED_REPLY 告知发送方通信目标不存在 void
BR_SPAWN_LOOPER 通知Binder进程创建一个新的线程 void
BR_ACQUIRE 强引用计数+1请求 binder_ptr_cookie
BR_RELEASE 强引用计数-1请求 binder_ptr_cookie
BR_INCREFS 弱引用计数+1请求 binder_ptr_cookie
BR_DECREFS 若引用计数-1请求 binder_ptr_cookie
BR_DEAD_BINDER 发送死亡通知 binder_uintptr_t
BR_CLEAR_DEATH_NOTIFICATION_DONE 清理死亡通知完成 binder_uintptr_t
BR_DEAD_REPLY 告知发送方对方已经死亡 void
BR_ACQUIRE_RESULT 暂未实现 -
BR_ATTEMPT_ACQUIRE 暂未实现 -
BR_FINISHED 暂未实现 -
单独看上面的协议可能很难理解,这里我们以一次Binder请求过程来详细看一下Binder协议是如何通信的,就比较好理解了。
这幅图的说明如下:
a. Binder是C/S架构的,通信过程牵涉到:Client,Server以及Binder驱动三个角色
b. Client对于Server的请求以及Server对于Client回复都需要通过Binder驱动来中转数据
c. BC_XXX命令是进程发送给驱动的命令
d. BR_XXX命令是驱动发送给进程的命令
e. 整个通信过程由Binder驱动控制
这里再补充说明一下,通过上面的Binder协议的说明中我们看到,Binder协议的通信过程中,不仅仅是发送请求和接受数据这些命令。同时包括了对于引用计数的管理和对于死亡通知的管理(告知一方,通讯的另外一方已经死亡)等功能。
这些功能的通信过程和上面这幅图是类似的:一方发送BC_XXX,然后由驱动控制通信过程,接着发送对应的BR_XXX命令给通信的另外一方。因为这种相似性,对于这些内容就不再赘述了。
在有了上面这些背景知识介绍之后,我们就可以进入到Binder驱动的内部实现中来一探究竟了。
8. 打开Binder设备
任何进程在使用Binder之前,都需要先通过open("/dev/binder")打开Binder设备。上文已经提到,用户空间的open系统调用对应了驱动中的binder_open函数。在这个函数,Binder驱动会为调用的进程做一些初始化工作。binder_open函数代码如下所示:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc, *itr;
struct binder_device *binder_dev;
struct binderfs_info *info;
struct dentry *binder_binderfs_dir_entry_proc = NULL;
bool existing_pid = false;
binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
current->group_leader->pid, current->pid);
// 创建进程对应的binder_proc对象
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
// 初始化binder_proc
spin_lock_init(&proc->inner_lock);
spin_lock_init(&proc->outer_lock);
get_task_struct(current->group_leader);
proc->tsk = current->group_leader;
proc->cred = get_cred(filp->f_cred);
INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->freeze_wait);
if (binder_supported_policy(current->policy)) {
proc->default_priority.sched_policy = current->policy;
proc->default_priority.prio = current->normal_prio;
} else {
proc->default_priority.sched_policy = SCHED_NORMAL;
proc->default_priority.prio = NICE_TO_PRIO(0);
}
/* binderfs stashes devices in i_private */
if (is_binderfs_device(nodp)) {
binder_dev = nodp->i_private;
info = nodp->i_sb->s_fs_info;
binder_binderfs_dir_entry_proc = info->proc_log_dir;
} else {
binder_dev = container_of(filp->private_data,
struct binder_device, miscdev);
}
refcount_inc(&binder_dev->ref);
proc->context = &binder_dev->context;
binder_alloc_init(&proc->alloc);
binder_stats_created(BINDER_STAT_PROC);
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
INIT_LIST_HEAD(&proc->waiting_threads);
filp->private_data = proc;
mutex_lock(&binder_procs_lock);
hlist_for_each_entry(itr, &binder_procs, proc_node) {
if (itr->pid == proc->pid) {
existing_pid = true;
break;
}
}
// 添加到全局列表binder_procs中
hlist_add_head(&proc->proc_node, &binder_procs);
mutex_unlock(&binder_procs_lock);
if (binder_debugfs_dir_entry_proc && !existing_pid) {
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/*
* proc debug entries are shared between contexts.
* Only create for the first PID to avoid debugfs log spamming
* The printing code will anyway print all contexts for a given
* PID so this is not a problem.
*/
proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
binder_debugfs_dir_entry_proc,
(void *)(unsigned long)proc->pid,
&proc_fops);
}
if (binder_binderfs_dir_entry_proc && !existing_pid) {
char strbuf[11];
struct dentry *binderfs_entry;
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/*
* Similar to debugfs, the process specific log file is shared
* between contexts. Only create for the first PID.
* This is ok since same as debugfs, the log file will contain
* information on all contexts of a given PID.
*/
binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc,
strbuf, &proc_fops, (void *)(unsigned long)proc->pid);
if (!IS_ERR(binderfs_entry)) {
proc->binderfs_entry = binderfs_entry;
} else {
int error;
error = PTR_ERR(binderfs_entry);
pr_warn("Unable to create file %s in binderfs (error %d)\n",
strbuf, error);
}
}
return 0;
}
9. 内存映射(mmap)
在打开Binder设备之后,进程还会通过mmap进行内存映射。mmap的作用有如下两个:
(1) 申请一块内存空间,用来接收Binder通信过程中的数据
(2) 对这块内存进行地址映射,以便将来访问
binder_mmap函数对应了mmap系统调用的处理,这个函数也是Binder驱动的精华所在(这里说的binder_mmap函数也包括其内部调用的binder_update_page_range函数,见下文)。
前文我们说到,使用Binder机制,数据只需要经历一次拷贝就可以了,其原理就在这个函数中。
binder_mmap这个函数中,会申请一块物理内存,然后在用户空间和内核空间同时对应到这块内存上。在这之后,当有Client要发送数据给Server的时候,只需一次,将Client发送过来的数据拷贝到Server端的内核空间指定的内存地址即可,由于这个内存地址在服务端已经同时映射到用户空间,因此无需再做一次复制,Server即可直接访问,整个过程如下图所示:
a. Server在启动之后,调用对/dev/binder设备调用mmap
b. 内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在用户空间和内核空间同时进行映射
c. Client通过BINDER_WRITE_READ命令发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝到内核空间
d. 驱动通过BR_TRANSACTION通知Server有人发出请求,Server进行处理。由于这块内存也在用户空间进行了映射,因此Server进程的代码可以直接访问
了解原理之后,我们再来看一下Binder驱动的相关源码。这段代码有两个函数:
a. binder_mmap 函数对应了mmap的系统调用的处理
b. binder_update_page_range函数真正实现了内存分配和地址映射
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct binder_proc *proc = filp->private_data;
if (proc->tsk != current->group_leader)
return -EINVAL;
binder_debug(BINDER_DEBUG_OPEN_CLOSE,
"%s: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
__func__, 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));
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
proc->pid, vma->vm_start, vma->vm_end, "bad vm_flags", -EPERM);
return -EPERM;
}
vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
return binder_alloc_mmap_handler(&proc->alloc, vma);
}
=============================================================
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
struct vm_area_struct *vma)
{
struct binder_buffer *buffer;
const char *failure_string;
int ret, i;
if (unlikely(vma->vm_mm != alloc->mm)) {
ret = -EINVAL;
failure_string = "invalid vma->vm_mm";
goto err_invalid_mm;
}
mutex_lock(&binder_alloc_mmap_lock);
if (alloc->buffer_size) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start,
SZ_4M);
mutex_unlock(&binder_alloc_mmap_lock);
alloc->buffer = vma->vm_start;
alloc->pages = kcalloc(alloc->buffer_size / PAGE_SIZE,
sizeof(alloc->pages[0]),
GFP_KERNEL);
if (alloc->pages == NULL) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) {
alloc->pages[i].alloc = alloc;
INIT_LIST_HEAD(&alloc->pages[i].lru);
}
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer) {
ret = -ENOMEM;
failure_string = "alloc buffer struct";
goto err_alloc_buf_struct_failed;
}
buffer->user_data = alloc->buffer;
list_add(&buffer->entry, &alloc->buffers);
buffer->free = 1;
binder_insert_free_buffer(alloc, buffer);
alloc->free_async_space = alloc->buffer_size / 2;
/* Signal binder_alloc is fully initialized */
binder_alloc_set_vma(alloc, vma);
return 0;
err_alloc_buf_struct_failed:
kfree(alloc->pages);
alloc->pages = NULL;
err_alloc_pages_failed:
alloc->buffer = 0;
mutex_lock(&binder_alloc_mmap_lock);
alloc->buffer_size = 0;
err_already_mapped:
mutex_unlock(&binder_alloc_mmap_lock);
err_invalid_mm:
binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
"%s: %d %lx-%lx %s failed %d\n", __func__,
alloc->pid, vma->vm_start, vma->vm_end,
failure_string, ret);
return ret;
}
---参考 ## 理解Android Binder机制(1/3):驱动篇