一,Binder的作用:
1),Binder是一种IPC,是一种Client/Server模式的进程间通信机制;
优点:使用方便,性能稳定,开销很小,访问RPC和访问本地API的形式一样,让上层API使用者感觉不到中间与内核Binder驱动通信的过程,如下图:
2),缺点:在双向通信方面有些不足,如果要实现双向通信,每一个进程是Client的同时也必须是Server,这样就稍显复杂;
二,linux内核Binder驱动为用户空间导出的API接口:
主要是通过open,ioctl,mmap三个系统调用来访问内核Binder驱动模块;
用户空间用到的主要数据结构如下(下述过程以服务进程访问servicemanager进程为例,基本包括了所有的数据结构):
unsigned iodata[512/4]; //用户空间传递给内核Binder驱动的数据缓存;
//struct binder_io 主要用来对上述iodata数组缓存区进行有效管理和操作;
struct binder_io
{
char *data; /* 从该指针指向的内存空间处开始读或写; */
binder_size_t *offs; /* 数组偏移地址 */
size_t data_avail; /* 在数据缓存中可以可以有效利用的数据长度*/
size_t offs_avail; /* 在偏移数组中可以存放的数组元素个数 */
char *data0; /* 有效数据区的起始地址 */
binder_size_t *offs0; /* 偏移数组的起始地址 */
uint32_t flags;
uint32_t unused;
};
struct binder_io的成员属性与iodata缓存数组之间的关系如下图所示:
在frameworks\native\cmds\servicemanager\binder.h头文件中提供了通过binder_io操作iodata的api操作函数,方法名字语义很明确:
void bio_init(struct binder_io *bio, void *data, size_t maxdata, size_t maxobjects);
void bio_put_obj(struct binder_io *bio, void *ptr);
void bio_put_ref(struct binder_io *bio, uint32_t handle);
void bio_put_uint32(struct binder_io *bio, uint32_t n);
void bio_put_string16(struct binder_io *bio, const uint16_t *str);
void bio_put_string16_x(struct binder_io *bio, const char *_str);
uint32_t bio_get_uint32(struct binder_io *bio);
uint16_t *bio_get_string16(struct binder_io *bio, size_t *sz);
uint32_t bio_get_ref(struct binder_io *bio);
经过上述操作之后,然后再将struct binder_io 转换为:
struct {
uint32_t cmd;
struct binder_transaction_data txn;
} __attribute__((packed)) writebuf;
转换过程如下:
writebuf.cmd = BC_TRANSACTION;//这个是Binder驱动规定的命令,这个命令会处理将数据发送到目标进程的逻辑;
writebuf.txn.target.handle = target;//这个是
writebuf.txn.code = code;
writebuf.txn.flags = 0;
writebuf.txn.data_size = msg->data - msg->data0;
writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);
writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;
writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;
上述中struct binder_transaction_data 结构体为:
struct binder_transaction_data {
union {
__u32 handle;//目标进程的句柄handle通过这个handle在Binder驱动中可以找到binder_ref;
binder_uintptr_t ptr;//服务进程读取数据的时候,会将内核Binder驱动为该进程的某个服务实例创建的struct binder_node的成员属性ptr的值读取到该ptr属性内存中;
} target;
binder_uintptr_t cookie;//将struct binder_node的成员属性cookie放入该属性内存中;
__u32 code;//服务进程中某个服务中的业务方法编码;
__u32 flags;
pid_t sender_pid;
uid_t sender_euid;
binder_size_t data_size;//iodata的有效数据区的需要发送的数据大小,上图中的data_avail;
binder_size_t offsets_size;//iodata起始偏移数组中要发送的所有元素占用内存大小(占用内存字节数不是元素个数),
union {
struct {
binder_uintptr_t buffer;//iodata有效数据区的起始地址,对应binder_io中的data0属性;
binder_uintptr_t offsets;//iodata起始偏移数组的起始地址,对应binder_io中的offs0属性;
} ptr;
__u8 buf[8];
} data;
};
最后将上述 writebuf 结构体对象放入下面这个结构体实例中,然后通过ioctl发送给内核Binder驱动:
struct binder_write_read {
binder_size_t write_size;//writebuf实例内存占用大小;
binder_size_t write_consumed;//writebuf实例占用内存中有效数据开始之前未存放有效数据部分,此处为0;
binder_uintptr_t write_buffer;//writebuf实例地址;
binder_size_t read_size;//存放读取数据的缓存总大小;
binder_size_t read_consumed;//在读取缓存中存放有效数据的起始地址之前的未存放数据的空间大小;
binder_uintptr_t read_buffer;//存放读取数据的缓存基地址;
};
//将writebuf放入bwr;
struct binder_write_read bwr;
bwr.write_size = sizeof(writebuf);
bwr.write_consumed = 0;
bwr.write_buffer = (uintptr_t) &writebuf;
unsigned readbuf[32];
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
向内核Binder驱动发送数据:
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//BINDER_WRITE_READ 这个命令专门处理内核Binder驱动中向目标进程发送数据和接受返回数据的业务逻辑;
总结一下:
首先构造iodata数据 ——> 在用这个数据构造binder_io ——> 然后在用binder_io构造writebuf ——> 在将writebuf放入bwr ——> 最后将bwr发送给内核Binder驱动;
三,Linux内核Binder驱动分析:
内核中的Binder驱动是整个Binder IPC机制的核心所在;
Binder驱动是一个虚拟misc设备驱动;
驱动源码位于:drivers/android/binder.c
入口函数:__init binder_init(void)
static int __init binder_init(void)
{
... ...
while ((device_name = strsep(&device_names, ","))) {
ret = init_binder_device(device_name);//初始化misc设备;
if (ret)
goto err_init_binder_device_failed;
}
... ...
}
init_binder_device方法:
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;//初始化设备操作struct file_operations
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;//动态获取此设备号;
binder_device->miscdev.name = name;//设备名字:"binder"
binder_device->context.binder_context_mgr_uid = INVALID_UID;//selinux权限相关;
binder_device->context.name = name;
ret = misc_register(&binder_device->miscdev);//注册misc设备到内核;
if (ret < 0) {
kfree(binder_device);
return ret;
}
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
设备操作实例struct file_operations binder_ops:
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设备open操作binder_open:驱动IPC操作之前的各种必须的数据结构的初始化;
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
struct binder_device *binder_dev;
proc = kzalloc(sizeof(*proc), GFP_KERNEL);//分配当前进程结构体struct binder_proc指针指向的内存空间;
if (proc == NULL)
return -ENOMEM;
get_task_struct(current->group_leader);
proc->tsk = current->group_leader;//current 是Linux内核的当前进程struct task_struct结构体指针,proc的tsk成员属性即为struct task_struct类型指针;
proc->vma_vm_mm = current->group_leader->mm;//mm为进程的内存映射管理,类型为struct mm_struct,vma_vm_mm成员属性即为sruct mm_struct结构体类型指针;struct mm_struct 保存了struct vm_area_struct类型表示的当前进程的虚拟内存区域的红黑树;
INIT_LIST_HEAD(&proc->todo);//初始化当前进程的todo链表,该链表存放了当前进程需要完成的任务;
init_waitqueue_head(&proc->wait);//初始化当前进程的等待队列头,当进程等待数据的时候就会等待在该等待队列上;
proc->default_priority = task_nice(current);//初始化进程优先级;
binder_dev = container_of(filp->private_data, struct binder_device,
miscdev);//由Linux miscdev设备找到binder_dev设备私有数据struct binder_device结构体指针实例,filp->private_data的值在misc子系统核心的公共open方法中赋值为struct miscdevice结构体类型的指针;
proc->context = &binder_dev->context;
binder_lock(__func__);
binder_stats_created(BINDER_STAT_PROC);
hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;//初始化进程pid;
INIT_LIST_HEAD(&proc->delivered_death);//初始化链表,该链表存放了当进程被杀死的时候执行的一些操作;
filp->private_data = proc;//在此处更新filp->private_data的值为Binder驱动设备私有数据结构体类型struct binder_proc的指针变量proc;
binder_unlock(__func__);
misc设备子系统核心:drivers/char/misc.c
static int misc_open(struct inode * inode, struct file * file)
{
int minor = iminor(inode);
struct miscdevice *c;
... ...
/*
* Place the miscdevice in the
* private_data of file so it can be used by the
* file operations, including f_op->open below
*/
file->private_data = c;//上述的filp->private_data第一次赋值在此处;
err = 0;
replace_fops(file, new_fops);
if (file->f_op->open)
err = file->f_op->open(inode,file);//此处进入binder_open;
return err;
}
Binder驱动核心操作函数binder_ioctl:通过各种命令来实现各种情况下的IPC操作;
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);
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__);
thread = binder_get_thread(proc);//创建当前进程的线程实例,即使一个进程也要创建;
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
case BINDER_WRITE_READ://IPC交换数据的逻辑;
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
case BINDER_SET_MAX_THREADS://当前进程能够创建的最大IPC线程的数量;
case BINDER_SET_CONTEXT_MGR:
case BINDER_THREAD_EXIT://结束IPC线程;
case BINDER_VERSION:
default:
}
ret = 0;
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;//标志线程已经进入循环开始进行IPC数据通信;
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;
}
分析上述方法中的BINDER_WRITE_READ命令逻辑:
binder_ioctl_write_read方法:
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
... ...
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
//第一步:先把上述第二部分中介绍的用户空间struct binder_write_read 结构体对象bwr复制到内核空间;
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
if (bwr.write_size > 0) {//首先判断有没有写入数据,如果写入数据大小大于0则先写入数据;
ret = binder_thread_write(proc, thread,
bwr.write_buffer,
bwr.write_size,
&bwr.write_consumed);//写入操作逻辑;
trace_binder_write_done(ret);
if (ret < 0) {
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
if (bwr.read_size > 0) {//判断有没有读取的数据,如果读取数据大小大于0则开始读取数据;
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);//如果todo链表不为空则休眠等待被唤醒;
if (ret < 0) {
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
out:
return ret;
}
分析数据写入目标进程的操作,binder_thread_write:
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed)
{
uint32_t cmd;
struct binder_context *context = proc->context;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
... ...
//第二步:用第二部分介绍的bwr中存放的writebuf对象地址将writebuf对象中前面四个字节的命令读取到内核中,该命令为:cmd=BC_TRANSACTION,因此就执行到了下面switch的case BC_TRANSACTION分析;
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
... ...
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
//第三步:用上述ptr指针将第二部分中介绍的writebuf对象中的struct binder_transaction_data对象的数据复制到内核空间;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
//处理向目标进程发送数据的业务逻辑;
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
... ...
*consumed = ptr - buffer;
通过分析binder_transaction方法的源码,可以看到在这个方法中会将第二部介绍的需要发送的数据中的最后一部分iodata缓存数据复制到目标进程mmap系统调用映射的内存空间;这样就不用把这部分数据复制到内核然后再复制到目标进程的用户空间,所以少了一次内存复制,iodata存放的数据量相对比较大,而且Binder驱动作为Android上层应用的主要IPC机制,IPC的数量巨大,几乎无时无刻不在通过Binder驱动进行IPC通信,所以通过减少一次内存复制极大的提高了Binder IPC通信机制的性能及减少了内存占用;