Binder系列1-Binder Driver
1. Android采用Binder
Linux Kernel是开源系统,所开放源代码许可协议GPL保护,
Binder Driver
运行在Linux Kernel是GPL协议。用户空间采用Apache-2.0协议,内核空间
与用户空间
(即在GPL协议与Apache-2.0协议)之间的Lib库中采用BSD证授权方法,GPL协议限定在Linux Kernel,有效隔断了GPL的传染性,仍有较大争议。开源与商业化共存的一个成功典范。【Android,在争议中逃离 Linux 内核的 GPL 约束】Binder 是基于开源的OpenBinder实现的,而OpenBinder的作者在Google工作,直接采用 Binder 作为核心的IPC机制。
传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;但是 Binder 并不是 Linux 内核的一部分,它是怎么做到访问内核空间的呢? Linux 的动态可加载内核模块(Loadable Kernel Module, LKM )机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在
内核空间
运行。这样,Android系统可以通过添加一个内核模块运行在内核空间
,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。
所以,在 Android 系统中,这个运行在内核空间
的,负责各个用户进程通过 Binder 通信的内核模块叫做Binder 驱动
2. Binder Driver实现
Binder驱动
是一个misc device,是虚拟字符设备,主设备号是10,其设备节点是/dev/binder
(该节点并不对应真实的硬件设备)。misc类型驱动只需要调用misc_deregister
。
在Linux系统中,系统调用(System Call
)是用户空间访问内核的唯一手段,也是惟一的合法入口。用户进程与内核之间使用系统调用进行沟通。
主要是驱动设备的初始化(binder_init
),打开 (binder_open
),映射(binder_mmap
),数据操作(binder_ioctl
)。
linux/drivers/android/binder.c
linux/include/uapi/linux/android/binder.h
方法 | 描述 | 定义位置 |
---|---|---|
binder_init | 初始化虚拟字符设备,init_binder_device()\init_binderfs() | kernel/drivers/android/binder.c |
binder_open | 打开Binder驱动设备, binder_procs_lock同步锁 | kernel/drivers/android/binder.c |
binder_mmap | 申请内存空间, binder_alloc_mmap_lock同步锁 | kernel/drivers/android/binder.c |
binder_ioctl | 对设备文件实现读、写等一些扩展功能 ioctl 操作, binder_procs_lock同步锁;当处于binder_thread_read过程,read_buffer无数据则释放同步锁 | kernel/drivers/android/binder.c |
2.1 binder_init 注册misc设备节点/dev/binder
binder_init() -> init_binder_device(device_name) -> misc_register(&binder_device->miscdev)
注册misc设备节点CONFIG_ANDROID_BINDERFS
、binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES
在kernel/configs/xxx/android-base.config
配置
binder_fops
Binder驱动支持的文件操作,其中使用做多的binder_ioctl \ binder_mmap \ binder_open
init_binderfs()
注册register_filesystem(&binder_fs_type)
(linux/drivers/android/binderfs.c)
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,
};
static int __init init_binder_device(const char *name)
{
// ... ...
binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
binder_device->miscdev.name = name;
// ... ...
ret = misc_register(&binder_device->miscdev);
// ... ...
}
static int __init binder_init(void)
{
// ... ...
if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
strcmp(binder_devices_param, "") != 0) {
/*
* Copy the module_parameter string, because we don't want to
* tokenize it in-place.
*/
device_names = kstrdup(binder_devices_param, GFP_KERNEL);
if (!device_names) {
ret = -ENOMEM;
goto err_alloc_device_names_failed;
}
device_tmp = device_names;
while ((device_name = strsep(&device_tmp, ","))) {
ret = init_binder_device(device_name);
if (ret)
goto err_init_binder_device_failed;
}
}
ret = init_binderfs();
if (ret)
goto err_init_binder_device_failed;
return ret;
err_init_binder_device_failed:
// ... ...
err_alloc_device_names_failed:
// ... ...
}
device_initcall(binder_init);
2.2 binder_open 打开Binder驱动设备
binder_proc
binder进程,每个进程都有独立的记录;proc->todo
todo链表binder_stats_created(BINDER_STAT_PROC)
BINDER_PROC对象创建数加1,binder_stats
是binder中统计数据载体filp->private_data = proc
把binder_proc对象保存到文件指针filphlist_add_head(&proc->proc_node, &binder_procs)
把binder_proc加入到全局链表binder_procs
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;
// ... ...
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
// ... ...
INIT_LIST_HEAD(&proc->todo);
init_waitqueue_head(&proc->freeze_wait);
proc->default_priority = task_nice(current);
// ... ...
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;
}
}
hlist_add_head(&proc->proc_node, &binder_procs);
mutex_unlock(&binder_procs_lock);
// ... ...
}
2.3 binder_mmap 申请内存空间
binder_alloc_mmap_handler
申请内存空间,linux/drivers/android/binder_alloc.c
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;
}
vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
vma->vm_flags &= ~VM_MAYWRITE;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
return binder_alloc_mmap_handler(&proc->alloc, vma);
}
2.4 binder_ioctl 执行相应的ioctl操作
binder_ioctl
函数负责在两个进程间收发IPC数据和IPC reply数据处理- 核心方法
binder_ioctl_write_read() -> binder_thread_write()\binder_thread_read()
- binder_thread_write() :
get_user(cmd, (uint32_t __user *)ptr)
获取IPC数据BC请求码 - binder_thread_read():根据
w->type
(即binder_work->type
)响应相应处理。
- binder_thread_write() :
ioctl命令cmd | 说明 |
---|---|
BINDER_WRITE_READ | 收发Binder IPC数据 |
BINDER_SET_MAX_THREADS | 设置Binder线程最大个数 |
BINDER_SET_CONTEXT_MGR | 设置Service Manager节点 |
BINDER_SET_CONTEXT_MGR_EXT | |
BINDER_THREAD_EXIT | 释放Binder线程 |
BINDER_VERSION | 获取Binder版本信息 |
… | … |
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
// ... ...
trace_binder_ioctl(cmd, arg);
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
// ... ...
thread = binder_get_thread(proc);
// ... ...
switch (cmd) {
case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
case BINDER_SET_MAX_THREADS://......
case BINDER_SET_CONTEXT_MGR_EXT://......
case BINDER_SET_CONTEXT_MGR://......
case BINDER_THREAD_EXIT://......
case BINDER_VERSION://......
case BINDER_GET_NODE_INFO_FOR_REF://......
case BINDER_GET_NODE_DEBUG_INFO://......
case BINDER_FREEZE://......
case BINDER_GET_FROZEN_INFO://......
case BINDER_ENABLE_ONEWAY_SPAM_DETECTION://......
case BINDER_GET_EXTENDED_ERROR://......
default:
ret = -EINVAL;
goto err;
}
// ... ...
if (thread)
thread->looper_need_return = false;
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
// ... ...
}
3. Binder通信模型
Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成。Android系统中对外只暴露Client端,Client端将任务发送给Server端。
4、mmap和一次拷贝
binder作为Android系统中最常用的IPC技术,其中比较重要的一个优势在于进程间通信时数据的
一次拷贝
。
由于系统中进程隔离的原因,用户进程之间不能拷贝数据,所以只能依赖内核空间做为辅助,间接传递数据,如下步骤:
- 数据从
client端
拷贝到内核
。- 数据从
内核
拷贝到server端
。
mmap
是操作系统中成熟的技术,Android binder使用了内存映射(memory map)
技术,只拷贝一次就可以达到用户进程之间传递数据的目的:
client
进程通过copy_from_user()
将数据从用户空间拷贝到了内核空间。server
进程通过mmap
将内核空间的内存地址映射到了用户空间。- 只要
client
将数据拷贝到内核空间,那么server
进程就可以直接读取到内核空间的数据,无需再次拷贝。