本章主要介绍binder驱动的三个重要函数,binder_open()、binder_mmap()以及binder_ioctl()。在介绍这三个函数的过程中,简单提binder中的数据结构。
在上一篇《ServiceManager启动过程》中,在servicemanager.c的main方法中,servicemanager进程调用了自己的binder_open()方法,申请了128K大小的内存空间,
我们从这里开始说起。
在servicemanager自己实现的binder_open()中,它分别调用了open(),mmap()以及ioctl()三个系统调用。这三个方法最终会调用到binder驱动层的binder_open(),binder_mmap()以及binder_ioctl()方法。
之所以会这样,是因为在binder驱动的调用binder_init()->init_binder_device()初始化过程中,在binder_device结构体中,已经对这些驱动层的方法做了映射。
kernel/msm-5.4/drivers/android/binder.c以及binder_internal.h
/**
* struct binder_device - information about a binder device node(binder设备的节点信息)
* @hlist: list of binder devices (only used for devices requested via
* CONFIG_ANDROID_BINDER_DEVICES)
* @miscdev: information about a binder character device node
* @context: binder context information
* @binderfs_inode: This is the inode of the root dentry of the super block
* belonging to a binderfs mount.
*/
struct binder_device {
struct hlist_node hlist;
struct miscdevice miscdev;
struct binder_context context;
struct inode *binderfs_inode;
refcount_t ref;
};
//映射的结构体
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,
};
//init_binder_device方法
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
//在内核空间为binder_device申请内存
binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
//在这里做的映射
binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
binder_device->miscdev.name = name;
refcount_set(&binder_device->ref, 1);
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
mutex_init(&binder_device->context.context_mgr_node_lock);
//挂载binder驱动
ret = misc_register(&binder_device->miscdev);
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
1: binder_open()
任何一个进程想要使用binder驱动进行通信时,都需要执行binder_open方法,打开binder驱动。
为的是,创建初始化binder_proc,并将当前进程的binder_proc保存在binder驱动管理的全局链表binder_procs中。
在binder驱动中,binder_procs链表管理着所有进程执行完binder_open()创建的binder_proc。
binder_open()
static int binder_open(struct inode *nodp, struct file *filp)
{
//binder_proc结构体,每一个进程在binder驱动中都对应着一个此结构体。binder_proc是进程在使用binder通信时在内核中的数据结构。
struct binder_proc *proc;
//为binder_proc结构体申请内存,并初始化为0,并返回一个指向分配的内存块起始地址的地址指针。
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
//获取一个当前线程的task_struct(PCB进程控制块),在linux中,无论是进程还是线程都是有PCB进程控制块来表示的。
get_task_struct(current);
//将当前线程的task保存到binder进程的tsk
proc->tsk = current;
//初始化todo队列(也叫做工作队列),用于存放待处理的请求(server端),由于服务进程不会是一个一个处理请求的,在todo队列中的请求会多线程处理。
INIT_LIST_HEAD(&proc->todo);
//初始化wait队列,当某个服务进程被请多的次数过多时,多余的任务会放在wait队列中。
init_waitqueue_head(&proc->wait);
//将当前线程线程优先级保存在binder进程的default_priority中
proc->default_priority = task_nice(current); /*获取当前进程的优先级*/
//持有锁
binder_lock(__func__);
//将当前的binder_proc对象加入到binder驱动管理的全局binder_procs链表中
hlist_add_head(&proc->proc_node, &binder_procs);
//将当前线程所处进程的pid记录在binder进程的pid中
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
//将proc记录到文件对象(传到用户空间的desc描述符就是描述此文件对象的)的私有数据当中,为的是在其它函数中也可以访问到此binder_proc对象
filp->private_data = proc;
//释放锁
binder_unlock(__func__);
return 0;
}
2: binder_mmap()
以下为binder驱动一次拷贝原理图,binder_mmap主要是做了以下的事情。
binder_mmap
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
//这里先申请一个内核虚拟内存的指针地址
struct vm_struct *area;
//从filp中拿出私有数据,在binder_open的时候就把本进程的binder_proc保存进去了。
struct binder_proc *proc = filp->private_data;
const char *failure_string;
//申请一个binder_buffer的地址,此地址是用来进程间传递数据的内核缓冲区
struct binder_buffer *buffer;
//从这里可以看出,用户空间一次申请内存的大小不能大于4M,如果大于4M就再加4M。在用户空间的时候可以看到serviemanager申请了128*1024(128K)的大小。
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
mutex_lock(&binder_mmap_lock);
//一个进程只能映射一次
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
//使用IOREMAP的方式,在内核虚拟内存申请大小为vma->vm_end - vma->vm_start的空间。
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
//把申请到的area的首地址,保存到pro->buffer。这样,*buffer就指向刚刚申请到的内核空间的首地址了。
//其中proc->buffer为当前进程buffer的首地址,后面还会新增其他buffer
proc->buffer = area->addr;
//这里是在计算用户空间vm_area_struct vam的首地址与内核空间vm_struct area首地址的差值(偏移量),并保存至proc->user_buffer_offset
//由于虚拟内存是连续的(32位应用为0~4G),通过计算偏移量,就可以在用户空间拿到内核空间的地址。
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
mutex_unlock(&binder_mmap_lock);
// 在物理内存上申请对应页数总大小的物理内存。这里这样算一下是为了取整,因为待会分配物理内存时会按照page进行分配。“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);
//设置内核空间的buffer_size。
proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
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;
}
//让binder_buffer 对象指向binder_proc的buffer地址,也就是binder_buffer指向了area的首地址。
buffer = proc->buffer;
// 初始化binder_proc的buffers链表头
INIT_LIST_HEAD(&proc->buffers);
//将当前binder_proc拥有的binder_buffer链接到binder_proc当中,注意这个时候还只有一个binder_buffer
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1; /*表明当前buffer是空闲buffer*/
binder_insert_free_buffer(proc, buffer); /*将当前的binder_buffer插入到binder_proc空闲链表中*/
proc->free_async_space = proc->buffer_size / 2;
barrier();
proc->files = get_files_struct(current);
proc->vma = vma; /*保留这128K在用户空间地址空间*/
proc->vma_vm_mm = vma->vm_mm; /*保留这128在kernel的地址空间*/
return 0;
}
然后是虚拟地址与物理地址的映射,包括用户空间与内核空间的两次映射。(这里主要是linux内核的知识,暂不做深究)
binder_update_page_range()
static int binder_update_page_range(struct binder_proc *proc, int allocate,
void *start, void *end,
struct vm_area_struct *vma)
{
void *page_addr;
unsigned long user_page_addr;
struct page **page;
// mm_struct 是 task_struct 结构体中虚拟地址管理的结构体
struct mm_struct *mm;
if (vma)
// binder_mmap 过程 vma 不为空,其他情况都为空
mm = NULL;
else
// 获取 mm 结构体,从 tsk 中获取
mm = get_task_mm(proc->tsk);
if (mm) {
// 获取 mm_struct 的写信号量
down_write(&mm->mmap_sem);
vma = proc->vma;
}
// 此处 allocate 为 1,代表分配过程。如果为 0 则代表释放过程
if (allocate == 0)
goto free_range;
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;binder_update_page_range()
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
// 分配一个 page 的物理内存
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
// 物理空间映射到虚拟内核空间
ret = map_kernel_range_noflush((unsigned long)page_addr,
PAGE_SIZE, PAGE_KERNEL, page);
// 用户空间地址 = 内核空间地址 + 偏移量
user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
//物理空间映射到虚拟进程空间
ret = vm_insert_page(vma, user_page_addr, page[0]);
}
if (mm) {
// 释放内存的写信号量
up_write(&mm->mmap_sem);
// 减少 mm->mm_users 计数
mmput(mm);
}
return 0;
//释放内存的流程
free_range:
...
return -ENOMEM;
}
3、linux内核分配内存函数
此部分为赋值了他们的文档,为的是看内核内存分配函数时可以查询。
1.kmalloc()
文件包含:#include
功能描述:kmalloc()用于分配在物理上连续的内存,虚拟地址也是连续的,是基于slab分配实际上存在的内存。
但是kmalloc最多只能开辟大小为32XPAGE_SIZE的内存,一般的PAGE_SIZE=4kB,也就是128kB的大小的内存。
函数定义:static __always_inline void * kmalloc (size_t size, gfp_t flags);
输入参数:size:分配内存的字节数
flags:分配标志,要分配内存的类型
返回参数:返回一个指向分配的内存块起始地址的地址指针
释放函数:kfree()
2.kcalloc()
文件包含:#include
功能描述:kcalloc()是基于slab分配实际上存在的内存,并且在分配后将内存中的内容都初始化为0,但kcalloc()主要为一个数组分配
内存空间,数组中的一个元素对应一个内存对象
函数定义:static inline void * kcalloc (size_t n, size_t size, gfp_t flags);
输入参数:n:数组中的元素个数
size:分配数组中每个元素所对应的内存对象的字节数
flags:分配标志,要分配内存的类型
返回参数:返回一个对所分配的内存对象数组的引用
释放函数:kfree()
3.kzalloc()
文件包含:#include
功能描述:kzalloc()是基于slab分配实际上存在的内存,在分配内存后将内存中的内容都初始化为0,和kcalloc()有些相似,
但不针对数组。
函数定义:static inline void * kzalloc (size_t size, gfp_t flags);
输入参数:size:分配内存的字节数
flags:分配标志,要分配内存的类型
返回参数:返回一个指向分配的内存块起始地址的地址指针
释放函数:kfree()
4.krealloc()
文件包含:#include
功能描述:重新分配内存,但不改变原地址空间中的内容
函数定义:void * __must_check krealloc (const void *p, size_t new_size, gfp_t flags);
输入参数:p:重新分配的原内存空间的起始地址
new_size:重新分配内存的字节数
flags:分配标志,要分配内存的类型
返回参数:重新分配的内存空间的起始地址
释放函数:遵从原分配内存函数的分配函数
5.vmalloc()
文件包含:#include
功能描述:用于分配一块非连续的地址空间,物理地址一般是非连续的,但是虚拟地址是连续的,分配的内存空间
被映射进入内核数据段中,从用户空间是不可见的。vmalloc比kmalloc要慢。
函数定义:void *vmalloc(unsigned long size);
输入参数:size:分配内存的字节数
返回参数:返回创建的地址区间的虚拟地址,如果分配失败返回NULL。
释放函数:vfree()
6.vmalloc_to_page()
文件包含:#include
功能描述:找到vmalloc()所分配内存的虚拟地址所映射的物理页,并返回该页的指针描述符
函数定义:struct page *vmalloc_to_page(const void *addr);
输入参数:addr:一般是vmalloc()的返回地址,也是一个虚拟地址
返回参数:addr映射的物理页的指针描述符
7.vmalloc_user()
文件包含:#include
功能描述:功能类似于vmalloc(),在vmalloc()的基础上将地址空间清零,这样该地址空间被映射到用户空间不会发生数据泄露
函数定义:void *vmalloc_user(unsigned long size);
输入参数:size:分配内存的字节数
返回参数:返回创建的地址区间的虚拟地址,如果分配失败返回NULL。
释放函数:vfree()