struct binder_buffer *buffer;
//映射空间至多 4M
if ((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;
}
binder_ioctl
binder 驱动并不提供常规的 read()、write() 等文件操作,全部通过 binder_ioctl() 实现,所以 binder_ioctl() 是 binder 驱动中工作量最大的一个,它承担了 binder 驱动的大部分业务。这里不深入分析源码,只列出 binder_ioctl() 支持的命令列表:
命令 | 说明 |
---|---|
BINDER_WRITE_READ | 向 binder 驱动写入或读取数据 |
BINDER_SET_MAX_THREADS | 设置支持的最大线程数 |
BINDER_SET_CONTEXT_MGR | Service Manager 专用的注册命令 |
BINDER_THREAD_EXIT | 通知 binder 驱动某线程退出,释放相应资源 |
BINDER_VERSION | 获取 Binder 版本号 |
其中 BINDER_WRITE_READ 最为关键,分为若干子命令:
命令 | 说明 |
---|---|
BC_INCREFS、BC_ACQUIRE、BC_RELEASE、BC_DECREFS | 管理 binder_ref 的引用计数 |
BC_INCREFS_DONE、BC_ACQUIRE_NODE | 管理 binder_node 的引用计数 |
BC_FREE_BUFFER | 释放 Binder 内存缓冲区 |
BC_TRANSACTION | 向 binder 驱动发送通信数据(主动调用) |
BC_REPLY | 向 binder 驱动发送通信数据(返回结果) |
BC_REGISTER_LOOPER、BC_ENTER_LOOPER、BC_EXIT_LOOPER | 设置 Binder looper 状态 |
BC_REQUEST_DEATH_NOTIFICATION | 注册 Binder 死亡通知 |
BC_CLEAR_DEATH_NOTIFICATION | 清除 Binder 死亡通知 |
BC_DEAD_BINDER_DONE | 告知 Binder 已处理完 Binder 死亡通知 |
以上均为 binder 驱动作为接收方 binder_ioctl() 方法接收的命令,还有一些与之对应的 BR_ 开头的命令,由 binder 驱动主动发出,比如 BR_TRANSACTION、BR_REPLY,在一次 IPC 调用中是这样应用的:
最后
学习了 binder 源码后,要怎么运用这些知识,应对面试问题呢?贴心的Android 面试官 已经为你演练过面试问答场景了:
如上图,这里有一份按模块分好的 Binder 源码,并有关键步骤注释。
最后我在这里分享一下这段时间从朋友,大佬那里收集到的一些2019-2020BAT 面试真题解析,里面内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题
等等,可以很好地帮助我们深刻理解Android相关知识点的原理以及面试相关知识。
这份资料把大厂面试中常被问到的技术点整理成了 600多页
的PDF 干货,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~
Android 基础知识点
Java 基础知识点
架构师筑基包括哪些内容
我花了将近半个月时间将:深入 Java 泛型.、注解深入浅出、并发编程.、数据传输与序列化、Java 虚拟机原理、反射与类加载、高效 IO、Kotlin项目实战等等Android架构师筑基必备技能整合成了一套系统知识笔记PDF,相信看完这份文档,你将会对这些Android架构师筑基必备技能有着更深入、更系统的理解。
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的【架构师筑基必备技能】文档领取方式:点赞+关注,然后私信关键词 【666】即可获得免费领取方式!或者 可以查看我的【Github】
注:资料与上面思维导图一起看会更容易学习哦!每个点每个细节分支,都有对应的目录内容与知识点!
这份资料就包含了所有Android初级架构师所需的所有知识!需要的可以在我的GIthub里面去查看!
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
)]
[外链图片转存中…(img-2CeG8PSb-1710964166710)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-gPxboGu3-1710964166711)]