写点什么

Android 面试必问之 Binder 进程间通信机制,大厂喜欢从哪些角度考你呢?看完这篇你就懂啦

用户头像
Android架构
关注
发布于: 刚刚

上面代码的主要工作是 「创建及初始化 binder_proc」,binder_proc 就是用来存放 binder 相关数据的结构体,每个进程独有一份。


binder_lock(func);binder_stats_created(BINDER_STAT_PROC);hlist_add_head(&proc->proc_node, &binder_procs);proc->pid = current->group_leader->pid;INIT_LIST_HEAD(&proc->delivered_death);filp->private_data = proc;binder_unlock(func);...}


第二个主要工作是 「将 binder_proc 记录起来」,方便后续使用,如上代码所示,通过 hlist_add_head() 方法将 binder_proc 记录到了内核的 binder_procs 表中,另外还将 binder_proc 存放在 filp 的 private_data 域,以便于在后续调用 mmap、ioctl 等方法时获取。

binder_mmap

对于 binder 驱动来说,上层应用调用的 mmap() 最终会执行到 binder_mmap() 方法,binder_mmap() 的主要工作是**「将上层应用的虚拟内存块和 Binder 申请的物理内存块建立映射」**,应用程序和 Binder 就拥有了共享的内存空间,这样不同的应用程序之间可以通过 Binder 实现数据共享。


  • Binder 中有一物理内存块 P;B 进程中有一内存块 b

  • 将 P 分别与 b 建立映射,这样 P、b 就可以看作同一块内存

  • 若 A 进程想要发送数据给 B 进程,只需将数据拷贝到 P 内存,B 进程就能直接读取到了


所以 Binder 只需一次拷贝,binder_mmap() 要做的就是将 P 与 b 建立映射,该方法代码较长,分段看关键部分代码:


static int binder_mmap(struct file *filp, struct vm_area_struct *vma){struct vm_struct *area;struct binder_proc *proc = filp->private_data;const char *failure_string;struct binder_buffer *buffer;//映射空间至多 4Mif ((vma->vm_end - vma->vm_start) > SZ_4M)vma->vm_end = vma->vm_start + SZ_4M;//检查 vma 是否被禁用 if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {ret = -EPERM;failure_string = "bad vm_flags";goto err_bad_arg;}


  • vma(vm_area_struct) 是**「用户态虚拟内存地址空间」**,也就是 b

  • area(vm_struct) 是**「内核态虚拟地址空间」**,指向 P

  • proc(binder_proc) 即 binder_open() 中创建的、存放 binder 相关数据的结构体

  • 另外还做了限制映射空间至多 4M 等映射规则的检查和处理


mutex_lock(&binder_mmap_lock);//检查是否已执行过 binder_mmap 映射过 if (proc->buffer) {ret = -EBUSY;failure_string = "already mapped";goto err_already_mapped;}//申请内核虚拟内存地址空间 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 中 proc->buffer = area->addr;//记录用户态虚拟内存地址和内核态虚拟内存地址的偏移量 proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;mutex_unlock(&binder_mmap_lock);


  • proc->buffer 用于存储最终映射的内核态虚拟地址,并通过此变量控制只能映射一次

  • get_vm_area() 方法申请了与用户态空间大小一致的内核态虚拟地址空间,注意此时还没分配实际的物理内存

  • proc->user_buffer_offset 记录了用户态虚拟内存和内核态虚拟内存地址的偏移量,这样后续方便获取用户态虚拟内存地址


//分配存放物理页地址的数组 proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);proc->buffer_size = vma->vm_end - vma->vm_start;//申请一页物理内存 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;}//最后的收尾工作:将内存记录到相应链表中,设置状态等 INIT_LIST_HEAD(&proc->buffers);list_add(&buffer->entry, &proc->buffers);buffer->free = 1;binder_insert_free_buffer(proc, buffer);proc->free_async_space = proc->buffer_size / 2;proc->files = get_files_struct(current);proc->vma = vma;


  • proc->pages 是一个二维指针,用于存放管理物理页面

  • binder_update_page_range() 方法真正的申请物理页面,并分别映射到内核态和用户态的虚拟内存地址空间


至此 binder_mmap 方法执行结束,我们继续分析**「binder_update_page_range()」** 方法,此方法代码非常有助于我们理解页框以及与虚拟内存地址的映射逻辑。先了解此方法的参数:


  • proc:申请内存的进程所持有的 binder_proc 对象

  • allocate:1 表示申请内存,0 表示释放内存

  • start:虚拟内存地址起点

  • end:虚拟内存地址终点

  • vma:用户态虚拟内存地址空间


static int binder_update_page_range(struct binder_proc *proc, int allocate,void *start, void *end,struct vm_area_struct vma){if (allocate == 0) //区分是申请还是释放 goto free_range;//依据 start、end 循环分配物理页 for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {//每次分配 1 个页框/*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);//将页框映射到内核态虚拟内存地址 ret = map_kernel_range_noflush((unsigned long)page_addr, PAGE_SIZE, PAGE_KERNEL, page);//根据 binder_mmap 方法中记录的偏移量计算出用户态虚拟内存地址 user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;//将页框映射到用户态虚拟内存地址 ret = vm_insert_page(vma, user_page_addr, page[0]);}return 0;


binder_mmap() 的 allocate 参数传入 1 为申请内存,执行上面的代码。若为释放则执行以下代码:


free_range://依据 start、end 从后往前遍历 for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];if (vma)//解除用户态虚拟地址和物理页框的映射 zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);err_vm_insert_page_failed://解除内核态虚拟地址和物理页框的映射 unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);err_map_kernel_failed://释放页框物理内存__free_page(*page);*page = NULL;}

bind

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


er_ioctl


binder 驱动并不提供常规的 read()、write() 等文件操作,全部通过 binder_ioctl() 实现,所以 binder_ioctl() 是 binder 驱动中工作量最大的一个,它承担了 binder 驱动的大部分业务。这里不深入分析源码,只列出 binder_ioctl() 支持的命令列表:



其中 BINDER_WRITE_READ 最为关键,分为若干子命令:



以上均为 binder 驱动作为接收方 binder_ioctl() 方法接收的命令,还有一些与之对应的 BR_ 开头的命令,由 binder 驱动主动发出,比如 BR_TRANSACTION、BR_REPLY,在一次 IPC 调用中是这样应用的:


最后

学习了 binder 源码后,要怎么运用这些知识,应对面试问题呢?贴心的 Android 面试官 已经为你演练过面试问答场景了:



如上图,这里有一份按模块分好的 Binder 源码,并有关键步骤注释。


最后我在这里分享一下这段时间从朋友,大佬那里收集到的一些 2019-2020BAT 面试真题解析,里面内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等,可以很好地帮助我们深刻理解 Android 相关知识点的原理以及面试相关知识


这份资料把大厂面试中常被问到的技术点整理成了 600多页的 PDF 干货,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。


这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~


领取:Android开发者全套学习核心知识笔记


Android 基础知识点



用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android面试必问之Binder进程间通信机制,大厂喜欢从哪些角度考你呢?看完这篇你就懂啦