欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Binder进程间通信系统第二篇-----Binder驱动程序

程序员文章站 2024-03-24 09:11:34
...

本文拜读参考自罗升杨老师的 《Android系统源代码情景分析》

文中加黑斜体文字为关键步骤,可作为思维主线


一 概述
二 Binder驱动之Binder设备初始化
三 Binder驱动之Binder设备打开过程
四 Binder驱动之Binder设备映射过程
五 Binder驱动关键数据结构简单列举
六 小结

一 概述

Binder驱动 不属于内核,它是Android专用的底层驱动,但底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟字符设备,没有直接操作硬件,只是对设备内存的处理。主要是驱动设备的初始
化(binder_init),打开 (binder_open),映射(binder_mmap),数据操作(binder_ioctl)。

kernel/drivers/android/binder.c
kernel/include/uapi/linux/android/binder.h

二 Binder驱动之Binder设备初始化

Binder驱动初始化步骤:

  • 1 创建名为 “binder” 的工作队列

  • 2
    2.1 在目标设备创建一个 “/proc/binder/proc” 目录,每一个使用了Binder进程间通信机制的进程都会在改目录下对应有一个文件,这些文件以进程ID来命名,通过它们就可以读取到各个进程的Binder线程池,Binder实体对象Binder引用对象以及内核缓冲等信息。

    2.2 在 “/proc/binder/proc” 目录下创建5个文件,分别是 state、stats、transactions、transaction_log、failed_transaction_log。通过这五个文件可以读取到 Binder驱动程序的运行状况。例如,各个命令协议和返回协议的请求次数,日志记录等信息,以及正在执行进程间通信过程的进程信息等。

  • 3 创建 misc类型 Binder 字符设备并初始化

    • 3.1 分配 binder_device 结构体
    • 3.2 初始化 binder_device>miscdev 类字符设备结构替,绑定 设备文件的操作方法 binder_fops
    • 3.3 注册 binder_device-> miscdevice,使用 misc_register 方法向系统注册设备节点
    • 3.4 添加 该binder_device 到 总binder_device链表

代码如下:
kernel\drivers\android\binder.c

static int __init init_binder_device(const char *name)
{
	int ret;
	struct binder_device *binder_device;

	binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
	if (!binder_device)
		return -ENOMEM;
		//初始化binder_device结构
	binder_device->miscdev.fops = &binder_fops;
	binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
	binder_device->miscdev.name = name;

	binder_device->context.binder_context_mgr_uid = INVALID_UID; //??
	binder_device->context.name = name;
	mutex_init(&binder_device->context.context_mgr_node_lock);
		//注册 binder 字符设备, /device/binder (/device/vndbinder,/device/hwbinder)下生成设备节点
	ret = misc_register(&binder_device->miscdev);
	if (ret < 0) {
		kfree(binder_device);
		return ret;
	}
		//binder_device* 加入到binder_devices链表中
	hlist_add_head(&binder_device->hlist, &binder_devices);

	return ret;
}


static int __init binder_init(void)
{
	int ret;
	char *device_name, *device_names, *device_tmp;
	struct binder_device *device;
	struct hlist_node *tmp;

	ret = binder_alloc_shrinker_init();
	if (ret)
		return ret;

	atomic_set(&binder_transaction_log.cur, ~0U);
	atomic_set(&binder_transaction_log_failed.cur, ~0U);
	
	// 1. 创建名为binder的工作队列
	binder_deferred_workqueue = create_singlethread_workqueue("binder");
	if (!binder_deferred_workqueue)
		return -ENOMEM;

	// 2. 创建binder相关目录
	//2.1 创建binder/proc的目录
	binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
	if (binder_debugfs_dir_entry_root)
		binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
						 binder_debugfs_dir_entry_root);

	
	//2.2 binder 在 /proc/binder目录下创建了5个文件
	if (binder_debugfs_dir_entry_root) {
		debugfs_create_file("state",
				    0444,
				    binder_debugfs_dir_entry_root,
				    NULL,
				    &binder_state_fops);
		debugfs_create_file("stats",
				    0444,
				    binder_debugfs_dir_entry_root,
				    NULL,
				    &binder_stats_fops);
		debugfs_create_file("transactions",
				    0444,
				    binder_debugfs_dir_entry_root,
				    NULL,
				    &binder_transactions_fops);
		debugfs_create_file("transaction_log",
				    0444,
				    binder_debugfs_dir_entry_root,
				    &binder_transaction_log,
				    &binder_transaction_log_fops);
		debugfs_create_file("failed_transaction_log",
				    0444,
				    binder_debugfs_dir_entry_root,
				    &binder_transaction_log_failed,
				    &binder_transaction_log_fops);
	}

	 //3. 创建binder设备名称
	device_names = kzalloc(strlen(binder_devices_param) + 1, GFP_KERNEL);
	if (!device_names) {
		ret = -ENOMEM;
		goto err_alloc_device_names_failed;
	}
		//"binder,hwbinder,vndbinder"
	strcpy(device_names, binder_devices_param);

	device_tmp = device_names;
	while ((device_name = strsep(&device_tmp, ","))) {
		// //binder设备初始化
		ret = init_binder_device(device_name);
		if (ret)
			goto err_init_binder_device_failed;
	}

	return ret;

err_init_binder_device_failed:
	hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
		misc_deregister(&device->miscdev);
		hlist_del(&device->hlist);
		kfree(device);
	}

	kfree(device_names);

err_alloc_device_names_failed:
	debugfs_remove_recursive(binder_debugfs_dir_entry_root);

	destroy_workqueue(binder_deferred_workqueue);

	return ret;
}


static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};

三 Binder驱动之Binder设备打开过程

一个进程在使用Binder进程间通信机制之前,首先需要调用open函数打开设备文件 /dev/binder 来获取一个文件描述符,然后才能通过这个文件描述符来和Binder驱动程序交互,继而和其他进程执行Binder进程间通信,当进程调用 open() 打开binder设备文件 dev/binder时,Binder驱动程序中的函数 binder_open()就会被调用。

Binder驱动 open 步骤:

  • 1 为 binder_proc 结构体分配kernel内存空间

  • 2 初始化 struct binder_proc 结构体

    • 2.1 初始化 struct Binder_proc->tsk 为 current->group_leader

    • 2.2 初始化binder进程todo列表

    • 2.3 初始化 struct Binder_proc->default_priority(优先级相关) :default_priority.sched_policy = current->policy; default_priority.prio = current->normal_prio;

    • 2.4 初始化 struct Binder_proc->context 为 binder_device->context (上下文信息相关)

    • 2.5 初始化 struct Binder_proc->struct binder_alloc->pid(相关联的BindRePro(在初始化后不变)的PID) 以及初始化 struct Binder_proc->struct binder_alloc->list_head链表(通过MMAP映射的每个PROC地址空间的基础 )

    • 2.6 将Binder_proc->pid设置为当前进程组的PID

    • 2.7 初始化 delivered_death和waiting_threads链表

  • 3 在 struct Binder_proc 初始化完成后,将其保存在参数 filp->private_data 中。
    参数file指向一个打开文件结构体,当进程调用 open() 打开 /dev/binder之后,内核会返回一个文件描述符给进程,而这个文件描述符与参数 filp 所指向的打开文件结构体是相关联的,因此,当进程后面以这个文件描述符为参数调用函数 mmap()或者ioctl()来与Binder驱动交互时,内核就会将与该文件描述符相关联的的打开文件结构体传递给 Binder驱动,这时候Binder驱动就可以通过 filp->private_data 来获得前面在函数binder_open()为进程创建的 binder_proc结构体了。

  • 4 将 binder_proc 结构体 加入binder_procs全局哈希表中,binder_proc->proc_node 是在全局哈希表中代表binder_proc结构体的结点

  • 5 在 “/proc/binder/proc” 目录下创建一个以进程ID为名的文件。

代码如下:

	static int binder_open(struct inode *nodp, struct file *filp)
	{
		struct binder_proc *proc;
		struct binder_device *binder_dev;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
		     current->group_leader->pid, current->pid);

	//1. 为 binder_proc 结构体分配kernel内存空间
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL)
		return -ENOMEM;
	//2. 初始化 struct binder_proc 结构体

	...
	
	//2.1 初始化 struct Binder_proc->tsk 为 current->group_leader
	proc->tsk = current->group_leader;
	mutex_init(&proc->files_lock);
	
	//2.2 初始化binder进程todo列表
	INIT_LIST_HEAD(&proc->todo);
	
	//2.3 初始化 struct Binder_proc->default_priority(优先级相关) :default_priority.sched_policy = current->policy; default_priority.prio = current->normal_prio;
	if (binder_supported_policy(current->policy)) {
		proc->default_priority.sched_policy = current->policy;
		proc->default_priority.prio = current->normal_prio;
	} else {
		proc->default_priority.sched_policy = SCHED_NORMAL;
		proc->default_priority.prio = NICE_TO_PRIO(0);
	}
	// 通过 miscdev 获取 binder_device 地址,暂时没搞懂 filp->private_data 是是啥时候指向 miscdev结构的
	binder_dev = container_of(filp->private_data, struct binder_device, miscdev);
	//2.4 初始化 struct Binder_proc->context 为 binder_device->context (上下文信息相关)
	proc->context = &binder_dev->context;
	
	//2.5 初始化 struct Binder_proc->struct binder_alloc->pid(相关联的BindRePro(在初始化后不变)的PID ) 以及初始化 struct Binder_proc->struct binder_alloc->list_head链表(通过MMAP映射的每个PROC地址空间的基础 )
	binder_alloc_init(&proc->alloc);
	//BINDER_STAT_PROC 对象创建+1
	binder_stats_created(BINDER_STAT_PROC);

	//2.6 将Binder_proc->pid设置为当前进程组的PID
	proc->pid = current->group_leader->pid;
	
	//2.7  初始化 delivered_death和waiting_threads链表
	INIT_LIST_HEAD(&proc->delivered_death);
	INIT_LIST_HEAD(&proc->waiting_threads);
	
	//3.  在 struct Binder_proc 初始化完成后,将其保存在参数 filp->private_data 中。
		//参数file指向一个打开文件结构体,当进程调用 open() 打开 /dev/binder之后,内核会返回一个文件描述符给进程,而这个文件描述符与参数 filp 所指向的打开文件结构体是相关联的,因此,当进程后面以这个文件
		//描述符为参数调用函数 mmap()或者ioctl()来与Binder驱动交互时,内核就会将与该文件描述符相关联的的打开文件结构体传递给 Binder驱动,这时候Binder驱动就可以通过 filp->private_data 来获得前面在函数
		//binder_open()为进程创建的 binder_proc结构体了。
	filp->private_data = proc;

	mutex_lock(&binder_procs_lock);
	//4. 将 binder_proc结构体 加入binder_procs全局哈希表中,binder_proc->proc_node 是在全局哈希表中代表binder_proc结构体的结点
	hlist_add_head(&proc->proc_node, &binder_procs);
	
	mutex_unlock(&binder_procs_lock);

	//5. 在 "/proc/binder/proc" 目录下创建一个以进程ID为名的文件。
	if (binder_debugfs_dir_entry_proc) {
		char strbuf[11];

		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
		/*
		 * proc debug entries are shared between contexts, so
		 * this will fail if the process tries to open the driver
		 * again with a different context. The priting code will
		 * anyway print all contexts that a given PID has, so this
		 * is not a problem.
		 */
		proc->debugfs_entry = debugfs_create_file(strbuf, 0444,binder_debugfs_dir_entry_proc,(void *)(unsigned long)proc->pid,&binder_proc_fops);
	}

	return 0;
	}

四 Binder驱动之Binder设备映射过程

说明:
mmap是一种内存映射文件的方法,即讲一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中指定的一段虚拟地址空间的一一对应关系。在用户空间使用mmap系统调用,内核会通过系统调用,调用对应的mmap函数。而Binder驱动中mmap(binder_mmap)的实现,实际上是将用户空间的一段虚拟地址和Kernel空间的一段虚拟地址映射到同一块物理内存上,以减少IPC过程中数据从用户空间向内和空间的拷贝次数。

进程打开了设备文件 /dev/binder 之后,还必须要调用函数 mmap() 把这个设备文件映射到进程的地址空间,然后才可以使用Binder进程间通信机制。设备文件 /dev/Binder 对应的是一个虚拟的设备,将他映射到进程的地址空间的目的是为了为进程分配内存缓冲区,以便他可以用来传输进程间通信数据。当进程调用 mmap() 将设备文件 /dev/binder 映射到自己的地址空间时,Binder驱动的中的 binder_mmap() 就会被调用。

大致工作:
首先在进程的内核地址空间申请一块与进程的用户空间内存大小相同的内存,然后申请一个page大小的物理内存,再将同一块物理内存分别映射到 内核虚拟内存空间 和 用户虚拟内存空间,从而实现了用户空间的buffer与内核空间的buffer同步操作的功能。

步骤:

  • 1 提取 在binder_open()中创建好了的binder_proc 结构体,(binder_open中创建、初始化并且关联到filp->private_data 的binder_proc 初始化之)

  • 2 检查该进程分配的用户虚拟空间大小,不能超过 4M

  • 3 检查进程指定要映射的用户地址空间是否可写、是否可以复制、是否有写操作标志位。 binder驱动为进程分配的内核缓冲区在用户空间是只读的,也不可以复制。

  • 4 get_vm_area() : 在进程的内核地址空间分配一段大小同用户虚拟空间大小一致 (vma->vm_end - vma->vm_start)的空间

  • 5 初始化 binder_proc->binder_alloc

    • 5.1 alloc->buffer = area->addr; 用 binder_proc->binder_buffer->buffer 保存 上面申请的内核虚拟空间首地址

    • 5.2 alloc->user_buffer_offset = vma->vm_start - (uintptr_t)alloc->buffer;
      计算要映射的用户空间起始地址与前面获得的内和空间的起始地址的差值,并且保存在 binder_proc->binder_buffer->user_buffer_offset 中。Binder驱动为进程分配的内核缓冲区有两个地址,其中一个是用户空间地址(vm_area_struct)。另一个是内核空间地址(vm_struct)。进程通过用户空间地址来访问这块内核缓冲区的内容,而Binder驱动程序通过内核空间地址来访问这块内核缓冲区的内容。由于它们是连续的,并且起始地址相差固定值,因此,只要知道其中一个地址,就可以计算出另一个地址。

    • 5.3 将 用户虚拟空间大小存储在 binder_proc->binder_alloc->buffer_size (名字起得更操蛋,很容易记混)

  • 6 接下来为进程要映射的 虚拟地址空间 vma和area 分配物理页面,即分配内核缓冲区。 并将地址保存在binder_proc->binder_buffer->pages

  • 7 创建一个 binder_buffer 内核缓存区描述 结构体 ,并分配内存

  • 8 binder_buffer->data = binder_proc->binder_buffer->buffer 初始化 binder_buffer->data 为 申请的内核虚拟空间首地址

  • 9 将 刚刚申请的空闲的,还没有使用的 内核缓冲区 binder_buffer 添加到 proc binder_proc->binder_alloc->list_head 进程的空闲内核缓冲区红黑树free_buffers中。

  • 10 将进程最大可用于异步事务的内核缓冲区大小设置为总的内核缓冲区大小的一半,这样就可以防止异步事务消耗过多的内核缓冲区。

Binder进程间通信系统第二篇-----Binder驱动程序

代码如下:

static void binder_insert_free_buffer(struct binder_alloc *alloc,struct binder_buffer *new_buffer)
{
	struct rb_node **p = &alloc->free_buffers.rb_node;
	struct rb_node *parent = NULL;
	struct binder_buffer *buffer;
	size_t buffer_size;
	size_t new_buffer_size;

	BUG_ON(!new_buffer->free);

/*
* binder_proc->binder_alloc.free_buffers 用来描述一个红黑树,他按照大小来组织进程中的空闲内核缓冲区。因此,在将 内
* 核缓冲区new_buffer加入到目标进proc的空闲内核缓冲区红黑树之前,先调用 binder_alloc_buffer_size()计算他的大小
*
*/
	new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer);

......

/*
* while循环中,在目标进程 proc 的空闲内核缓冲区红黑树 free_buffer 中找到一个合适的位置
*/
	while (*p) {
		parent = *p;
		buffer = rb_entry(parent, struct binder_buffer, rb_node);
		BUG_ON(!buffer->free);

		buffer_size = binder_alloc_buffer_size(alloc, buffer);

		if (new_buffer_size < buffer_size)
			p = &parent->rb_left;
		else
			p = &parent->rb_right;
	}

/* 调用 rb_link_node()/rb_insert_color() 将内核缓冲区 new_buffer 保存在这个位置上,这样就相当于
*  将内核缓冲区 new_buffer 插入到目标进程 proc 的空闲内核缓冲区红黑树 free_buffer中了。
*
*/
	rb_link_node(&new_buffer->rb_node, parent, p);
	rb_insert_color(&new_buffer->rb_node, &alloc->free_buffers);
}



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;

......

	//4. 在进程的内核地址空间分配一段大小为 (vma->vm_end - vma->vm_start)(同用户虚拟空间大小一致)的空间,返回值为vm_struct指针
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_ALLOC);

	//5.. 将这段 内核空间 虚拟地址的首地址复制给 binder_alloc->buffer(名字起得很操蛋),binder_alloc 结构是在binder_open中初始化的
	alloc->buffer = area->addr;
	
	
	/* 3
	* 计算要映射的用户虚拟空间起始地址与前面获得的内核虚拟空间地址其实地址之间的差值,并且保存在 alloc->user_buffer_offset中。
	* binder驱动为进程分配的内核缓冲区有两个地址,其中一个是用户虚拟空间地址,由参数 vma所指向的 vm_area_struct 结构体来描述。 
	* 另外一个是内核虚拟空间地址,由变量 area所指向的 vm_struct描述,进程通过用户虚拟空间地址来访问这块内核缓冲区的内容,而Binder
	* 驱动通过内核空间地址来访问这款内核缓冲区的内容。由于他们是连续的,并且起始地址相差固定值,因此,只要知道其中一个地址,
	* 就可以很方便的计算出另外一 个地址。
	*/
	alloc->user_buffer_offset = vma->vm_start - (uintptr_t)alloc->buffer;


	/*4  分配page数组,并计算 用户空间虚拟地址 buffer_size
	* 为进程要映射的 虚拟地址空间 vma和area 分配物理页面,即分配内核缓冲区。首先创建一个物理页面结构体指针数组,
	* 大小为 (vma->vm_end - vma->vm_start) / PAGE_SIZE ,并保存在 alloc->pages 中。Linux内核中,一个物理页面的
	* 大小PAGE_SIZE,PAGE_SIZE是一个宏,一般大小为4K。
	*/
	alloc->pages = kzalloc(sizeof(alloc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE),GFP_KERNEL);
	
	//将 用户虚拟空间大小存储在  alloc->buffer_size(名字起得更操蛋,很容易记混)
	alloc->buffer_size = vma->vm_end - vma->vm_start;

	//6. 为一个 binder_buffer 结构体分配内存。 binder_buffer :buffer used for binder transactions
	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
	
	//7. 初始化 binder_buffer, binder_buffer->data = 已申请 的 Kernel 虚拟空间首地址
	buffer->data = alloc->buffer;
	
	//8. 将 binder_buffer 添加到 binder_proc->binder_alloc->list_head 链表上
	list_add(&buffer->entry, &alloc->buffers);
	// 标注 binder_buffer 缓冲区是空闲的
	buffer->free = 1;
	
	//将空闲的binder_buffer空间 放入binder_alloc->free_buffer中 ?????
	//9. 将 binder_buffer 加入到进程结构体 binder_proc->binder_alloc 空闲的内核缓冲区红黑树 free_buffers中,
	binder_insert_free_buffer(alloc, buffer);
	
	//10. 将进程最大可用于异步事务的内核缓冲区大小设置为总的内核缓冲区大小的一半,这样就可以防止异步事务消耗过多的内核缓冲区。
	alloc->free_async_space = alloc->buffer_size / 2;
}


static const struct vm_operations_struct binder_vm_ops = {
	.open = binder_vma_open,
	.close = binder_vma_close,
	.fault = binder_vm_fault,
};



//函数参数中的  struct vm_area_struct *vma 用来描述一段/内核虚拟空间
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
		
	int ret;
	
	//1. 参数file指向一个打开文件结构体,它的成员 filp->private_data 指向的是一个进程结构体 binder_proc,binder_proc是在 binder_open()中创建并关联到 filp->private_data的,因此此处取出之前创建并初始化好了的
		//binder_proc结构体,
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;

	if (proc->tsk != current->group_leader)
		return -EINVAL;

	//2. Binder驱动最多为进程分配4M内核缓冲区来传输进程间通信数据,(vma->vm_end - vma->vm_start)是指定了要映射的用户虚拟空间地址范围。
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE,
		     "%s: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
		     __func__, proc->pid, vma->vm_start, vma->vm_end,
		     (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
		     (unsigned long)pgprot_val(vma->vm_page_prot));

	//3.检查进程要映射的用户地址空间是否可写 FORBIDDEN_MMAP_FLAGS宏 代表是否可写,binder驱动为进程分配的内核缓冲区在用户空间是可以读,而不可以写,因此如果进程指定要映射的用户地址空间可写,就会返回错误。
	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
		ret = -EPERM;
		failure_string = "bad vm_flags";
		goto err_bad_arg;
	}
	//进程指定要映射的用户地址空间除了不可写之外,也是不可以复制的,以及禁止设置任何可能会执行写操作标志位的,故设置如下:
	vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
	vma->vm_flags &= ~VM_MAYWRITE;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
	if (ret)
		return ret;
	mutex_lock(&proc->files_lock);
	proc->files = get_files_struct(current);
	mutex_unlock(&proc->files_lock);
	return 0;

err_bad_arg:
	pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
	       proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
	return ret;
}

五 Binder驱动关键数据结构简单列举

简单画了一下几个重要结构体之间在binder驱动设备 初始化 打开 映射过程中的关系
Binder进程间通信系统第二篇-----Binder驱动程序

//代表了一个Binder设备

struct binder_device {
	struct hlist_node hlist; // 指向Bidner设备在 binder_devices链表的节点
	struct miscdevice miscdev; //misc类型字符设备
	struct binder_context context; //当前Binder设备的上下文
};

// 指向Bidner设备在binder_devices链表的节点

struct hlist_node {
	struct hlist_node *next, **pprev;
};

//代表了内核中的一个misc设备

struct miscdevice  {
	int minor;
	const char *name;
	const struct file_operations *fops;
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const struct attribute_group **groups;
	const char *nodename;
	umode_t mode;
};

//当前Binder设备的上下文,这个binder设备的上下文即是我们熟悉的ServiceManager在内核这中的表示

struct binder_context {
	struct binder_node *binder_context_mgr_node;
	struct mutex context_mgr_node_lock;

	kuid_t binder_context_mgr_uid; // ???
	const char *name;
};

//描述一个正在使用Binder进程间通信机制的进程,binder_proc代表了一个打开了Binder驱动的进程

struct binder_proc {
	struct hlist_node proc_node;//进程打开设备文件/dev/binder时,Binder驱动会为它创建一个binder_proc结构体,并将它保存在全局hash列表中,proc_node是该hash列表的节点。
	struct list_head waiting_threads;//当前等待进程的线程 
	int pid;
	struct task_struct *tsk; // 指向进程任务控制块
	struct files_struct *files;
	struct list_head delivered_death; //死亡通知清单
	......
};

//记录 打开binder设备的进程的内存缓存状态

struct binder_alloc {
	struct list_head buffers; //通过MMAP映射的每个PROC地址空间的基础 
	...
	int pid; // 相关联的BindRePro(在初始化后不变)的PID 
	void *data;
};

结构体 vm_area_struct 和 vm_struct 所描述的虚拟地址空间时连续的,但是他们对应物理页面可以是不连续的。在Linux内核中,一个进程可以占用的虚拟地址空间是4G,其中 0~3G 是用户空间地址,3G~4G是内核空间地址为了区分进程的用户地址空间和内核地址空间,Linux内核就分别使用结构体 vm_area_struct和vm_struct来描述他们。实际上,结构体vm_struct所描述的内核地址空间范围只有 (3G+896M+8M)~4G ,中间空出来的3G~3G+896M 的 896M空间是用来映射物理内存的前 896M的,他们之间是简单的线性关系。而 3G+896M ~ 3G+896M+8M 中间的8M空间时一个安全区域,用来检测非法指针,即所有只想这8M的指针都是非法的。

//表示用户空间的一段虚拟内存区域

struct vm_area_struct
{
     struct mm_struct *vm_mm; // 指向了进程的mm_struct结构体          

     unsigned  long vm_start;       /*虚拟内存区域起始地址   vm_start就是mmap系统调用所返回的地址 */ 
     unsigned  long vm_end;         /*虚拟内存区域结束地址*/

     //....
} ;

//表示的是内核空间的一段连续的虚拟内存区域,用于将内核空间后120M地址映射到物理地址

struct vm_struct {  
    struct vm_struct    *next;       /*指向下一个vm区域*/  
    void                *addr;       /*指向第一个内存单元(线性地址), 表示这段虚拟内存区域的起始地址*/  
    unsigned long        size;       /*该块内存区的大小*/  
    //... 
};

//内核缓存区描述符

struct binder_buffer {
	struct list_head entry; /* 用于挂载到 binder_alloc 的 struct list_head buffers */
	struct rb_node rb_node; 

	unsigned free:1;
	unsigned allow_user_free:1;
	unsigned async_transaction:1;
	unsigned free_in_progress:1;
	unsigned debug_id:28;

	struct binder_transaction *transaction;

	struct binder_node *target_node;
	size_t data_size;
	size_t offsets_size;
	size_t extra_buffers_size;
	void *data; //用于存储
};

六 小结

简单画了一下几个重要结构体之间在binder驱动设备 初始化 打开 映射过程中的关系
Binder进程间通信系统第二篇-----Binder驱动程序

总结:一个使用Binder间进程通信的进程只有将Binder设备文件映射到自己的地址空间,Binder驱动才能够为他分配内核缓存区,以便可以用来传输进程间通信数据。Binder驱动为进程分配的内核缓冲区有两个地址,其中一个是用户空间地址,另一个是内和空间地址,他们有简单的线性对应关系Binder驱动为进程分配的内核缓冲区即为一系列物理页面,他们分别被映射到进程的用户地址空间和内核地址空间。当Binder驱动需要将一块数据传输给一个进程时,它就可以先把这块数据保存在为该进程分配的一块内核缓冲区中,然后再把这块内核缓冲区的用户空间地址告诉进程,最后进程就可以访问到里面的数据了。