掌握 binder 机制?别想绕开 binder 驱动源码分析!

binder 驱动在系统内核中,从用户空间应用程序到内核空间 binder 驱动,会经过一些 ProcessState、IPCThreadState 等中间的操作封装类,然后通过系统调用陷入系统内核。

掌握 binder 机制?先搞懂这几个关键类! 中详细分析过 ProcessState、IPCThreadState、BpBinder 以及 BinderProxy 等 binder 操作封装类源码,不了解的建议前往收藏阅读。

ProcessState 是进程单例,主要负责打开 binder 驱动设备及 mmap。binder 驱动的 binder_open()、binder_mmap() 函数在 ProcessState 的构造函数中被调用,如下:

ProcessState::ProcessState()
mDriverFD(open_driver()) //打开 binder 设备,binder_open()

, mVMStart(MAP_FAILED)

, mMaxThreads(DEFAULT_MAX_binder_THREADS)

, mThreadPoolSeq(1){

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;

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 个页框*/
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

[外链图片转存中…(img-x87aFhOu-1713624353956)]

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值