if (mDriverFD >= 0) {
// 将应用进程虚拟内存空间与 binder 驱动映射,binder_mmap()
mVMStart = mmap(0, binder_VM_SIZE, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
close(mDriverFD);
mDriverFD = -1;
}
}
}
IPCThreadState 为线程单例,负责与 binder 驱动进行具体的命令通信。在其 talkWithDriver() 方法中,调用了 binder 驱动的 binder_ioctl() 函数:
// binder_ioctl
ioctl(mProcess->mDriverFD, binder_WRITE_READ, &bwr)
binder 驱动注册
binder 驱动运行在内核态,向上层提供 /dev/binder 设备节点,并不对应真实的硬件设备。binder 驱动的注册逻辑在 Binder.c 中:
//drivers/staging/android/Binder.c
static init __init binder_init(void){
…
ret = misc_register(&binder_miscdev); //注册为 misc 驱动
}
binder_miscdev 即 Binder 设备描述如下:
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR, //自动分配次设备号
.name = “binder”, //驱动名称
.fops = &binder_fops //binder 驱动支持的文件操作
}
binder_fops 为 Binder 设备支持的操作函数,如下:
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
binder_open
用户应用程序通过 Binder 通信时,需先调用 binder_open() 方法打开 binder 驱动,binder_open() 中主要做了两个工作,对应的分为两部分来看:
//binder.c
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
…
proc = kzalloc(sizeof(*proc), GFP_KERNEL); //创建 binder_proc
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
proc->tsk = current;
INIT_LIST_HEAD(&proc->todo); //初始化 todo 队列
init_waitqueue_head(&proc->wait); //初始化 todo 队列
proc->default_priority = task_nice(current);
上面代码的主要工作是 「创建及初始化 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;
//映射空间至多 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;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
学习福利
【Android 详细知识点思维脑图(技能树)】
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
详细整理在GitHub点击可见;
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
701352974)]
详细整理在GitHub点击可见;
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。