Binder概述,快速了解Binder体系,写给正在求职的Android开发

本文介绍了Android Binder的注册驱动、通讯过程及一次拷贝机制。通过Linux的设备驱动模型,Binder将进程间通信(IPC)优化,使得数据在用户空间和内核空间间仅需一次拷贝。文章详细解析了Binder驱动的注册,包括misc_register和系统调用映射,以及接收和发送端的通讯流程。此外,还阐述了Binder如何利用内存映射实现一次拷贝,提高效率。
摘要由CSDN通过智能技术生成

Linux最大的特点就是一切皆文件,驱动也不例外,所有驱动都会被挂载在文件系统dev目录下,Binder对应的目录是/dev/binder,注册驱动时将open release mmap等系统调用注册到Binder自己的函数,这样的话在用户空间就可以通过系统调用以访问文件的方式使用Binder。下面来粗略看一下相关代码。

device_initcall函数用于注册驱动,由系统调用

binder_init中调用misc_register注册一个名为binder的misc驱动,同时指定函数映射,将binder_open映射到系统调用open,这样就可以通过open("/dev/binder")来调用binder_open函数了

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,

};

// 注册驱动参数结构体

static struct miscdevice binder_miscdev = {

.minor = MISC_DYNAMIC_MINOR,

// 驱动名称

.name = “binder”,

.fops = &binder_fops

};

static int binder_open(struct inode *nodp, struct file *filp){…}

static int binder_mmap(struct file *filp, struct vm_area_struct *vma){…}

static int __init binder_init(void)

{

int ret;

// 创建名为binder的单线程的工作队列

binder_deferred_workqueue = create_singlethread_workqueue(“binder”);

if (!binder_deferred_workqueue)

return -ENOMEM;

// 注册驱动,misc设备其实也就是特殊的字符设备

ret = misc_register(&binder_miscdev);

return ret;

}

// 驱动注册函数

device_initcall(binder_init);

Binder的简略通讯过程


一个进程如何通过binder和另一个进程通讯?最简单的流程如下

  1. 接收端进程开启一个专门的线程,通过系统调用在binder驱动(内核)中先注册此进程(创建保存一个bidner_proc),驱动为接收端进程创建一个任务队列(biner_proc.todo)

  2. 接收端线程开始无限循环,通过系统调用不停访问binder驱动,如果该进程对应的任务队列有任务则返回处理,否则阻塞该线程直到有新任务入队

  3. 发送端也通过系统调用访问,找到目标进程,将任务丢到目标进程的队列中,然后唤醒目标进程中休眠的线程处理该任务,即完成通讯

在Binder驱动中以binder_proc结构体代表一个进程,binder_thread代表一个线程,binder_proc.todo即为进程需要处理的来自其他进程的任务队列

struct binder_proc {

// 存储所有binder_proc的链表

struct hlist_node proc_node;

// binder_thread红黑树

struct rb_root threads;

// binder_proc进程内的binder实体组成的红黑树

struct rb_root nodes;

}

Binder的一次拷贝


众所周知Binder的优势在于一次拷贝效率高,众多博客已经说烂了,那么什么是一次拷贝,如何实现,发生在哪里,这里尽量简单地解释一下。

上面已经说过,不同进程通过在内核中的Binder驱动来进行通讯,但是用户空间和内核空间是隔离开的,无法互相访问,他们之间传递数据需要借助copy_from_user和copy_to_user两个系统调用,把用户/内核空间内存中的数据拷贝到内核/用户空间的内存中,这样的话,如果两个进程需要进行一次单向通信则需要进行两次拷贝,如下图。

2copy

Binder单次通信只需要进行一次拷贝,因为它使用了内存映射,将一块物理内存(若干个物理页)分别映射到接收端用户空间和内核空间,达到用户空间和内核空间共享数据的目的。

发送端要向接收端发送数据时,内核直接通过copy_from_user将数据拷贝到内核空间映射区,此时由于共享物理内存,接收进程的内存映射区也就能拿到该数据了,如下图。

binder_mmap

代码实现部分

用户空间通过mmap系统调用,调用到Binder驱动中binder_mmap函数进行内存映射,这部分代码比较难读,感兴趣的可以看一下。

drivers/android/binder

binder_mmap创建binder_buffer,记录进程内存映射相关信息(用户空间映射地址,内核空间映射地址等)

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)

{

int ret;

//内核虚拟空间

struct vm_struct *area;

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;

// 采用IOREMAP方式,分配一个连续的内核虚拟空间,与用户进程虚拟空间大小一致

// vma是从用户空间传过来的虚拟空间结构体

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;

}

// 指向内核虚拟空间的地址

proc->buffer = area->addr;

// 用户虚拟空间起始地址 - 内核虚拟空间起始地址

proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;


// 分配物理页的指针数组,数组大小为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;

// buffer插入链表

INIT_LIST_HEAD(&proc->buffers);

list_add(&buffer->entry, &proc->buffers);

buffer->free = 1;

binder_insert_free_buffer(proc, buffer);

// oneway异步可用大小为总空间的一半

MEM;

failure_string = “alloc small buf”;

goto err_alloc_small_buf_failed;

}

buffer = proc->buffer;

// buffer插入链表

INIT_LIST_HEAD(&proc->buffers);

list_add(&buffer->entry, &proc->buffers);

buffer->free = 1;

binder_insert_free_buffer(proc, buffer);

// oneway异步可用大小为总空间的一半

千里马8年Android系统及应用开发经验,曾担任过美国unokiwi公司移动端技术总监兼架构师,对系统开发,性能优化,应用高级开发有深入的研究,Android开源定制ROM Lineage的贡献者之一,国内首家线下开辟培训Android Framework课程,拥有2年的Android系统培训经验。成为腾讯课堂专业负责android framework课程分享第一人,致力于提高国内android Framework水平Android Framework领域内是国内各大手机终端科技公司需要的人才,应用开发者都对Android系统充满着好奇,其中的binder是重中之重,都说无binderAndroidbinde是Android系统的任督二脉。课程水平循序渐进,由中级再到高级,满足各个层次水平的android开发者。1、灵活使用binder进程通信,在app端对它的任何api方法等使用自如2、可以单独分析android系统源码中任何binder部分,分析再也没有难度3、掌握binder驱动本质原理,及对应binder驱动怎么进行跨进程通信,及内存等拷贝方式数据等4、对binder从上层的java app端一直到最底层的内核binder驱动,都可以顺利理通5、针对系统开发过程中遇到的binder报错等分析方法,及binder bug案例学习6、针对面试官任何的binder问题都可以对答自如7、socket这种跨进程通信实战使用8、针对android源码中使用的socket源码轻松掌握9、android系统源码中最常见的socketpair中双向跨进程通信10、使用socket实现一个可以让app执行shell命令的程序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值