千里马Android Framework实战开发-binder驱动常见binder_open,binder_mmap介绍

Posted Android高级知识分享官

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了千里马Android Framework实战开发-binder驱动常见binder_open,binder_mmap介绍相关的知识,希望对你有一定的参考价值。

csdn在线学习课程,课程咨询答疑和新课信息:QQ交流群:422901085进行课程讨论

android跨进程通信实战视频课程(加群获取优惠)

背景知识:

大家经常听到的驱动驱动,是不是一般都会有与一个具体的硬件设备相关,比如说wifi驱动,触摸屏驱动等。binder驱动是不是也是会有一个对应的一个硬件设备呢?哈哈,这里要告诉大家是没有的,因为binder本身只是用于操作系统进程间一个通讯而已,只涉及一些内存的拷贝,并不需要额外的硬件来支持,和linux常见的socket,共享内存,等一样哈。所以binder驱动不是实体的硬件驱动。
方法映射关系:
binder驱动代码是在内核层,而普通进程的都是通过一些常见的系统调用才可以到达内核层,所以肯定会有如下的一些应用层的系统调用方法和内核驱动代码的方法映射关系:
open ---->binder_open
mmap -->binder_mmap
ioctl -->binder_ioctl
这个大家记住就行哈,暂时不用深究,就行java到jni一样。。就是一种规则

1.binder_open

static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc;//声明binder_proc指针
	struct binder_device *binder_dev;
	//开始真正内核中开辟binder_proc 空间
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	//初始化todo队列
	INIT_LIST_HEAD(&proc->todo);
	//初始化对应binder_alloc
	binder_alloc_init(&proc->alloc);
	//赋值对应的pid
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	//初始化waiting_threads队列
	INIT_LIST_HEAD(&proc->waiting_threads);
	//proc赋值给private_data 方便后面的使用
	filp->private_data = proc;
	//把proc->proc_node加入binder_procs的list中
	hlist_add_head(&proc->proc_node, &binder_procs);

	return 0;
}

可以看出这里open中主要是做了binder_proc这个结构体的初始化和赋值操作,这里面只是对部分的成员做了赋值,其实binder_proc有很多关键的元素:

struct binder_proc {
	struct hlist_node proc_node;
	//rb_root 其实是红黑树,red black, threads 是binder_proc 进程内用于处理用户请求的线程
	struct rb_root threads;
	// nodes树 代表binder_proc 进程内创建的binder实体对象
	struct rb_root nodes;
	//表示一个binder_ref红黑树,但变了访问获取是通过desc
	struct rb_root refs_by_desc;
	//表示一个binder_ref红黑树,但变了访问获取是通过node
	struct rb_root refs_by_node;
	struct list_head waiting_threads;
	int pid;
	struct task_struct *tsk;
	struct files_struct *files;
	struct mutex files_lock;
	struct hlist_node deferred_work_node;
	int deferred_work;
	bool is_dead;

	struct list_head todo;
	struct binder_stats stats;
	struct list_head delivered_death;
	int max_threads;
	int requested_threads;
	int requested_threads_started;
	int tmp_ref;
	struct binder_priority default_priority;
	struct dentry *debugfs_entry;
	struct binder_alloc alloc;
	struct binder_context *context;
	spinlock_t inner_lock;
	spinlock_t outer_lock;
};

2、binder_mmap

binder.c

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
    //..省略
	//判断申请内存大小是否超过4M如果超过就最多只给4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
	//..省略
	vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
   //..省略
}


这里是不是参数里面有个vm_area_struct和vm_struct 结构体,感觉比较陌生?
vm_area_struct 用于表示0~3G的空间中一段连续的虚拟地址空间,是给user space的process使用.
vm_struct 是kernel space 除low memory中用于表示连续的虚拟地址空间,常用于vmalloc/vfree的操作
大家就可以简单理解为vm_area_struct 就是用户空间的虚拟内存描述结构体,vm_struct 是kernel中的虚拟内存描述结构体。
所以这里大参数是用户空间传递来的虚拟内存结构体。
这里binder_mmap除了判断限制了申请内存最大为4M,其他真正业务都是在binder_alloc_mmap_handler方法中,同时把proc的binder_alloc传递类进去,及用户空间的vm_area_struct 也传递了进去
binder_alloc.c

int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
	int ret;
	struct vm_struct *area;
	const char *failure_string;
	struct binder_buffer *buffer;
	//根据用户空间的vm_area_struct 虚拟地址大小,同样子内核也申请一个虚拟地址结构体
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	//让binder_alloc指向内核申请的虚拟地址
	alloc->buffer = area->addr;
	//通过用户空间虚拟地址起始位与内核虚拟地址起始相减得出与内核与虚拟地址的偏移,这个比较关键哦 
	alloc->user_buffer_offset =
		vma->vm_start - (uintptr_t)alloc->buffer;
	//配置物理页的指针数组,数组大小为 vma 的等效 page 个数
	alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
				   ((vma->vm_end - vma->vm_start) / PAGE_SIZE),
			       GFP_KERNEL);
	alloc->buffer_size = vma->vm_end - vma->vm_start;
	//构造出binder_buffer结构体,而且让buffer->data指向的是alloc->buffer
	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
	buffer->data = alloc->buffer;
	//把bufferbuffer->entry加入到alloc->buffers
	list_add(&buffer->entry, &alloc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(alloc, buffer);
	//异步空闲空间为正常的1/2
	alloc->free_async_space = alloc->buffer_size / 2;
	..省略
	//把应用空间的vma赋值给binder_alloc 的vma
	alloc->vma = vma;
	alloc->vma_vm_mm = vma->vm_mm;
	..省略
}

上面都有详细注释,这里大家是不是会看到好像整个mmap都只是在构造出对应结构体和物理页数据,没有看到真实的内存申请,这个因为改进版本变成你需要内存时候再给你动态分配多少,而不是一开始系统就分配好1M -8K的内存,真正申请内存是按物理页为单位进行申请,具体申请内存方法看如下:
binder_alloc.c

static int binder_update_page_range(struct binder_alloc *alloc, int allocate,
				    void *start, void *end)
{
	void *page_addr;
	unsigned long user_page_addr;
	struct binder_lru_page *page;
	struct vm_area_struct *vma = NULL;
	struct mm_struct *mm = NULL;
	bool need_mm = false;

	。。省略
	//根据start和end区域大小,计算申请对应的内存页
	for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
		page = &alloc->pages[(page_addr - alloc->buffer) / PAGE_SIZE];
		//判断这个物理页是否已经申请,如果没有申请则need_mm = true;就从这一页开始申请分配物理内存
		if (!page->page_ptr) {
			need_mm = true;
			break;
		}
	}

	/* Same as mmget_not_zero() in later kernel versions */
	if (need_mm && atomic_inc_not_zero(&alloc->vma_vm_mm->mm_users))
		mm = alloc->vma_vm_mm;

	if (mm) {
		down_write(&mm->mmap_sem);
		vma = alloc->vma;
	}
//又一次遍历。。其实好像和上面的代码有点重复遍历,可以考虑是否可以优化这里减少遍历次数
	for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
	
		index = (page_addr - alloc->buffer) / PAGE_SIZE;
		page = &alloc->pages[index];
		//page->page_ptr不为null说明已经被使用,只能继续遍历
		if (page->page_ptr) {
		   //lru优化。。。暂时不理会
			on_lru = list_lru_del(&binder_alloc_lru, &page->lru);
			continue;
		}
		//遍历到了没有被申请使用的,那就开始真正page内存页申请
		page->page_ptr = alloc_page(GFP_KERNEL |
					    __GFP_HIGHMEM |
					    __GFP_ZERO);
		。。省略
		page->alloc = alloc;
		//把内核虚拟地址page_addr与真实的页内存进行映射
		ret = map_kernel_range_noflush((unsigned long)page_addr,
					       PAGE_SIZE, PAGE_KERNEL,
					       &page->page_ptr);
		flush_cache_vmap((unsigned long)page_addr,
				(unsigned long)page_addr + PAGE_SIZE);
		//根据原来的内核和用户空间地址偏移,可以直接得出用户空间的虚拟地址
		user_page_addr =
			(uintptr_t)page_addr + alloc->user_buffer_offset;
		把用户空间虚拟地址user_page_addr与真实的页内存进行映射
		ret = vm_insert_page(vma, user_page_addr, page[0].page_ptr);
	

		if (index + 1 > alloc->pages_high)
			alloc->pages_high = index + 1;
	}
	
	。。省略
}

具体map_kernel_range_noflush和flush_cache_vmap源码可以参考https://www.cnblogs.com/arnoldlu/p/8251333.html

以上是关于千里马Android Framework实战开发-binder驱动常见binder_open,binder_mmap介绍的主要内容,如果未能解决你的问题,请参考以下文章

千里马Android Framework实战开发-native程序之间binder通信实战案例分析

千里马Android Framework实战开发-native程序之间binder通信实战案例分析

千里马Android Framework实战开发-跨进程通信专题博客总结

千里马Android Framework实战开发-跨进程通信专题课表介绍

千里马android framework实战开发-binder驱动之oneway导致的transaction failed

千里马android framework实战开发-binder驱动之oneway导致的transaction failed