Binder驱动学习笔记

299 阅读9分钟

Linux驱动背景知识

Linux文件系统由4层组成:用户层、内核层、驱动层、硬件层。

  1. 用户层:为用户提供操作接口
  2. 内核层:实现各种文件系统
  3. 驱动层:各种设备的驱动程序
  4. 硬件层:存储器和各种硬件外设

设备驱动程序的作用: 驱动程序是提供硬件到操作系统的一个接口,并且协调二者之间的关系。是硬件和系统之间的桥梁。

设备驱动的分类: 字符设备、块设备、网络设备

字符设备: 能一个字节一个字节读取数据的设备,一般需要在驱动层实现open()、close()、read()、write()、ioctl()等函数。这些函数最终被文件系统中的相关函数调用。内核为字符设备对应一个文件,/dev/xxx。对字符设备的操作可以用字符设备/dev/xxx来进行。字符设备一般不支持寻址,但特殊情况下,有很多字符设备也是支持寻址的。寻址的意思是,对硬件中一块寄存器进行随机地访问。不支持寻址就是只能对硬件中的寄存器进行顺序的读取,读取数据后,由驱动程序自己分析需要哪一部分数据。

模块机制: 模块是可以在运行时加入内核的代码。模块在内核启动时装载称为静态装载,在内核已经运行时装载称为动态装载。 可以使用insmod(insert module的缩写)命令将模块加入正在运行的内核,也可以使用rmmod(remove module的缩写)命令将一个未使用的模块从内核中删除。试图删除一个正在使用的模块,将是不允许的。

一个模块的最基本框架代码如下:

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/init.h> 

int __init xxx_init(void) { 
    /*这里是模块加载时的初始化工作*/
    return 0; 
} 

void __exit xxx_exit(void) { 
    /*这里是模块卸载时的销毁工作*/ 
} 

module_init(xxx_init); /*指定模块的初始化函数的宏*/ 
module_exit(xxx_exit); /*指定模块的卸载函数的宏*/

Linux中的file_operations结构体

  1. file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。通过file_operations中相应的函数指针,把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
  2. 在你通读 file_operations 方法的列表时, 你会注意到不少参数包含字串 __user. 这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的用户空间地址. 对于正常的编译, __user 没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用。
  3. 大部分的基础性的驱动操作包括3个重要的内核数据结构,称为file_operations、file、inode:
    • struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。
    • struct file代表一个打开的文件,在执行file_operations中的open操作时被创建,这里需要注意的是与用户空间inode指针的区别,一个在内核,而file指针在用户空间,由c库来定义。
    • struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件
    • struct inode结构是用来在内核内部表示文件的.同一个文件可以被打开好多次,所以可以对应很多struct file,但是只对应一个struct inode
  4. 几个Binder涉及到的函数说明:
    • open 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
    • flush 操作在进程关闭它的设备文件描述符的拷贝时调用;
    • release 当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数
    • mmap 用来请求将设备内存映射到进程的地址空间。
    • ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写)
    • poll 返回设备资源的可获取状态即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
    • owner 一个指向拥有这个结构的模块的指针,这个成员用来在它的操作还在被使用时阻止模块被卸载.

Binder驱动

Binder驱动属于字符设备,而且是静态装载,只有模块初始化函数,没有模块卸载函数

/* module_init宏解开就是device_initcall,所以这里等价于module_init(binder_init) */ 
device_initcall(binder_init); /* binder驱动的初始化函数 */  

Binder驱动层实现的函数

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,
};

static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops
};

关键函数说明

binder_init

创建一个binder的内核线程队列,并创建binder驱动设备文件节点

/* binder驱动模块初始化函数 */
static int __init binder_init(void)
{
	int ret;
        
        /* 创建一个专用的内核线程来执行提交到工作队列中的函数 */
	binder_deferred_workqueue = create_singlethread_workqueue("binder");
	if (!binder_deferred_workqueue)
		return -ENOMEM;

        /* 注册binder驱动程序 */
        /* misc_device是特殊字符设备,采用misc_register函数注册 
         * 此函数中会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。 
         */
	ret = misc_register(&binder_miscdev);

	return ret;
}

binder_open

创建并初始化binder_proc对象(保存当前进程信息),并将binder_proc加入全局哈希表binder_procs队列中,同时把binder_proc对象保存到文件指针filp->private_data

/* 用户空间程序打开"/dev/binder"时,内核空间binder驱动程序将执行此函数进行open操作 */
static int binder_open(struct inode *nodp, struct file *filp)
{
	/**
	  * binder_proc是内核中描述Binder进程上下文信息的结构体,其在用户空间相对应的是ProcessState。
          * Binder驱动的文件节点是"/dev/binder",每当一个程序打开该文件节点时;
          * Binder驱动中都会新建一个binder_proc对象来保存该进程的上下文信息。
          * /
	struct binder_proc *proc;

	/* 为proc分配内存空间并初始化为0 */
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL)
		return -ENOMEM;
                
        /* 增加线程引用计数,防止current被释放 */
	get_task_struct(current);
	proc->tsk = current;
        /* 初始化进程的待处理事件队列 */
	INIT_LIST_HEAD(&proc->todo);
        /* 初始化进程的等待队列 */
	init_waitqueue_head(&proc->wait);
        /* 获取默认优先级(获取进程的nice值,nice值其实代表进程的优先级)*/
	proc->default_priority = task_nice(current);

	binder_lock(__func__);
        /* BINDER_PROC 对象创建数加1 */
	binder_stats_created(BINDER_STAT_PROC);
        /* 将 proc_node 节点添加到全局哈希表binder_procs队列头,(binder_procs统计了所有的binder proc进程)*/
	hlist_add_head(&proc->proc_node, &binder_procs);
	proc->pid = current->group_leader->pid;
        /* 初始化已分发的死亡通知列表 */
	INIT_LIST_HEAD(&proc->delivered_death);
        /* file 文件指针的 private_data 变量指向 binder_proc 数据 */
	filp->private_data = proc;

	binder_unlock(__func__);

	return 0;
}

binder_mmap

binder_mmap 的主要作用就是开辟一块连续的内核虚拟地址空间,并且开辟一个物理页的地址空间,同时映射到用户空间和内核空间。 总结binder_mmap顺序:

  1.     检测内存映射条件,即映射内存大小(4MB),flags,是否已经映射过;
  2.     获取内核虚拟地址空间,并把此空间的地址记录到进程信息buffer中;
  3.     分配一个物理页面并记录下来;
  4.     将buffer插入到进程信息的buffer列表中;
  5.     调用binder_update_page_range函数将分配的物理页面和vm空间对应起来;
  6.     通过binder_insert_free_buffer函数把此进程的buffer插入到进程信息中。
/* vm_area_struct 用户态虚拟地址空间,vm_struct 内核虚拟地址空间*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma) 
{
	int ret;
        /* vm_struct 内核虚拟地址空间, vm_area_struct 用户态虚拟地址空间 */
	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	if (proc->tsk != current)
		return -EINVAL;
	/* 保证映射内存大小不超过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_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

	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);
        
	/** 
         * kzalloc申请内存, 并被初始化为0
         * 分配描述所有物理页面的数组,一个物理页面用page来表示,
         * 缓存区所需物理页面个数为((vma->vm_end - vma->vm_start) / PAGE_SIZE),
         * 所以描述所有物理页面信息需要
         * sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE)
         * 大小的存储空间 
         */
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
	if (proc->pages == NULL) {
		ret = -ENOMEM;
		failure_string = "alloc page array";
		goto err_alloc_pages_failed;
	}
	proc->buffer_size = vma->vm_end - vma->vm_start;

	/* 注册进程虚拟地址空间的操作函数 */
	vma->vm_ops = &binder_vm_ops;
        /* 将当前进程的binder_proc注册到虚拟地址空间描述符的vm_private_data中 */
	vma->vm_private_data = proc;

        /* 分配物理页面,同时映射到内核空间和进程空间,先分配1个物理页 */
	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;
	}
        
	buffer = proc->buffer;
        /* 初始化进程的 buffers 链表头 */
	INIT_LIST_HEAD(&proc->buffers);
        /* 将 binder_buffer 地址加入到所属进程的 buffers 队列 */
	list_add(&buffer->entry, &proc->buffers);
	buffer->free = 1;
        /* 将空闲 buffer 放入 proc->free_buffers 中 */
	binder_insert_free_buffer(proc, buffer);
        /* 异步可用空间大小为 buffer 总大小的一半。 */
	proc->free_async_space = proc->buffer_size / 2;
	barrier();
	proc->files = get_files_struct(current);
	proc->vma = vma;
	proc->vma_vm_mm = vma->vm_mm;

	return 0;
}

binder_ioctl

binder ioctl中的命令有以下5个,定义在binder.h中,如下所示:

  1.         #define BINDER_WRITE_READ         _IOWR('b',1, struct binder_write_read)
  2.         #define BINDER_SET_MAX_THREADS    _IOW('b', 5, size_t)
  3.         #define BINDER_SET_CONTEXT_MGR    _IOW('b', 7, int)
  4.         #define BINDER_THREAD_EXIT        _IOW('b', 8, int)
  5.         #define BINDER_VERSION            _IOWR('b',9, struct binder_version)
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;

        /* 进入休眠状态,直到中断唤醒 */
	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
        
        /* 从binder_proc中取出binder_thread线程描述符 */
	thread = binder_get_thread(proc);
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

        /* 处理不同的binder命令 */
	switch (cmd) {
        /* 进行 binder 的读写操作 */
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
        /* 设置 binder 最大支持的线程数 */
	case BINDER_SET_MAX_THREADS:
		if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
			ret = -EINVAL;
			goto err;
		}
		break;
        /* 成为 binder 的上下文管理者,也就是 ServiceManager 成为守护进程 */
        /**
         * 若一个进程/线程能被成功设置成binder_context_mgr_node对象,称该进程/线程为Context Manager.
         * 即设置驱动中的全局变量binder_context_mgr_uid为当前进程的uid.
         * 并初始化一个Binder_node并赋值给全局变量binder_context_mgr_node
         * 只有创建binder_context_mgr_node对象的Binder上下文管理进程/线程才有权限重新设置这个对象
	case BINDER_SET_CONTEXT_MGR:
		ret = binder_ioctl_set_ctx_mgr(filp);
		if (ret)
			goto err;
		ret = security_binder_set_context_mgr(proc->tsk);
		if (ret < 0)
			goto err;
		break;
        /* 当 binder 线程退出,释放 binder 线程 */
	case BINDER_THREAD_EXIT:
		binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
			     proc->pid, thread->pid);
		binder_free_thread(proc, thread);
		thread = NULL;
		break;
        /* 获取 binder 的版本号 */
	case BINDER_VERSION: {
		struct binder_version __user *ver = ubuf;

		if (size != sizeof(struct binder_version)) {
			ret = -EINVAL;
			goto err;
		}
		if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
			     &ver->protocol_version)) {
			ret = -EINVAL;
			goto err;
		}
		break;
	}
	default:
		ret = -EINVAL;
		goto err;
	}
	ret = 0;
err:
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	binder_unlock(__func__);
	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret && ret != -ERESTARTSYS)
		pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
	trace_binder_ioctl_done(ret);
	return ret;
}