ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Binder概述,快速了解Binder体系,面试

2021-09-08 09:01:49  阅读:192  来源: 互联网

标签:struct buffer binder vma 面试 Binder 概述 vm proc


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](https://www.icode9.com/i/ll/?i=img_convert/60c83d034b86d625d1b532594a8d59e8.png)



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



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



![binder_mmap](https://www.icode9.com/i/ll/?i=img_convert/ad3d6493280fbdf48647d7b0083a6716.png)



### []( )代码实现部分



用户空间通过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异步可用大小为总空间的一半

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;



/*pr_info("binder_mmap: %d %lx-%lx maps %p\n",

         proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/

return 0;

}




`binder_update_page_range` 函数为映射地址分配物理页,这里先分配一个物理页(4KB),然后将这个物理页同时映射到用户空间地址和内存空间地址



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 (end <= start)

    return 0;

标签:struct,buffer,binder,vma,面试,Binder,概述,vm,proc
来源: https://blog.csdn.net/m0_61040530/article/details/120171683

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有