博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux 4.16 Binder驱动学习笔记--------接口简析
阅读量:7193 次
发布时间:2019-06-29

本文共 12632 字,大约阅读时间需要 42 分钟。

1. binde设备初始化

1.1 binder_init()

static int __init binder_init(void)

binder设备初始化过程可以简化为如下步骤:

1.初始化binder缓冲区分配

ret = binder_alloc_shrinker_init();

2.创建binder相关目录

debugfs_create_dir第一个参数为创建的目录,第二个参数为父目录,因此在前面binder就创建了/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);

binder在/proc/binder目录下创建了5个文件,包括state,stats,transactions,transaction_log,failed_transaction_log

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_tmp = device_names;    while ((device_name = strsep(&device_tmp, ","))) {        ret = init_binder_device(device_name);        if (ret)            goto err_init_binder_device_failed;    }

init_binder_device()中,实现了创建binder设备的操作.

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->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);    ret = misc_register(&binder_device->miscdev);    if (ret < 0) {        kfree(binder_device);        return ret;    }    hlist_add_head(&binder_device->hlist, &binder_devices);    return ret;}

该方法通过misc_register创建设备,设备的参数通过binder_device进行设置。最终该设备还加入到全局哈希表中binder_devices

2. Binde设备文件的打开过程

2.1 binder_open()

static int binder_open(struct inode *nodp, struct file *filp)

binder_open的流程如下:

1.创建binder进程

struct binder_proc *proc;    proc = kzalloc(sizeof(*proc), GFP_KERNEL);

2.初始化binder_proc

spin_lock_init(&proc->inner_lock);    spin_lock_init(&proc->outer_lock);    get_task_struct(current->group_leader);    proc->tsk = current->group_leader;    mutex_init(&proc->files_lock);    INIT_LIST_HEAD(&proc->todo);//初始化binder进程todo列表    proc->default_priority = task_nice(current);    binder_dev = container_of(filp->private_data, struct binder_device,                  miscdev);    proc->context = &binder_dev->context;    binder_alloc_init(&proc->alloc);//初始化binder进程的内核缓冲区        binder_stats_created(BINDER_STAT_PROC);    proc->pid = current->group_leader->pid;    INIT_LIST_HEAD(&proc->delivered_death);    INIT_LIST_HEAD(&proc->waiting_threads);    filp->private_data = proc;//filp的private_data中保存binder进程结构体

这里注意而的是filp的private_data保存了binder进程结构体,当进程打开/dev/binder后,内核返回一个文件描述符,该文件描述符与filp所指向的文件结构是一致的,当进程在之后的操作用该文件描述符作为参数调用方法mmap,ioctl等方法与binder驱动程序交互时,内核就会通过该文件描述符相关联的打开文件结构提传递给binder驱动,并通过其private_data字段去获取binder_open为进程创建的binder_proc结构体。

3.将binder进程加入binder_procs全局哈希表中

由于是全局的哈希表,因此需要使用互斥量。

mutex_lock(&binder_procs_lock);    hlist_add_head(&proc->proc_node, &binder_procs);    mutex_unlock(&binder_procs_lock);

4.创建以进程ID为名的文件

if (binder_debugfs_dir_entry_proc) {        char strbuf[11];        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);        proc->debugfs_entry = debugfs_create_file(strbuf, 0444,            binder_debugfs_dir_entry_proc,            (void *)(unsigned long)proc->pid,            &binder_proc_fops);    }

3. binder设备内存映射过程

binder在打开了设备文件/dev/binder后,还需要通过mmap将该设备文件映射到进程地址空间,才可以进行进程间通信。

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)

1.获取binder进程

通过参数的文件描述符的private_data字段获取binder进程

struct binder_proc *proc = filp->private_data;

2.限定用户空间范围

if ((vma->vm_end - vma->vm_start) > SZ_4M)        vma->vm_end = vma->vm_start + SZ_4M;

方法传参中,vma表示的是用空间虚拟地址,类型为vm_area_struct,由此可见用户空间地址范围在4M以内。

3.检查用户空间是否可写

if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {        ret = -EPERM;        failure_string = "bad vm_flags";        goto err_bad_arg;}

其中FORBIDDEN_MMAP_FLAGS的值为:

#define FORBIDDEN_MMAP_FLAGS                (VM_WRITE)

4.设置缓冲区参数

vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;    vma->vm_ops = &binder_vm_ops;    vma->vm_private_data = proc;

Binder驱动为进程分配的内核缓冲区在用户空间设置为只可以读,不可以写。并且不可以进行复制。也将VM_MAYWRITE位设置为非。

5.为进程分配内核缓冲区

ret = binder_alloc_mmap_handler(&proc->alloc, vma);

3.1 binder_alloc_mmap_handler

binder_alloc_mmap_handler是实际为进程映射虚拟空间的函数,其定义如下:

int binder_alloc_mmap_handler(struct binder_alloc *alloc,                  struct vm_area_struct *vma)

alloc为内存分配结构体,vma为用户虚拟空间。其逻辑如下:

1.在内核地址空间分配空间

mutex_lock(&binder_alloc_mmap_lock);struct vm_struct *area;area = get_vm_area(vma->vm_end - vma->vm_start, VM_ALLOC);if (area == NULL) {        ret = -ENOMEM;        failure_string = "get_vm_area";        goto err_get_vm_area_failed;    }    alloc->buffer = area->addr; alloc->user_buffer_offset =        vma->vm_start - (uintptr_t)alloc->buffer;    mutex_unlock(&binder_alloc_mmap_lock);

vm_struct描述的是内核虚拟地址,vm_area_structure描述的是用户虚拟地址。get_vm_area就会在进程的内核地址空间分配一段大小为vma->vm_end - vma->vm_start的空间。申请成功后,将地址area->addr赋值给allloc->buffer。其中还会计算地址的差值:

alloc->user_buffer_offset =        vma->vm_start - (uintptr_t)alloc->buffer;

vma->vm_start为用户空间起始地址,alloc->buffer就是内核空间地址,由于进程的空间时连续的(物理页面不连续),所以它们的差值是固定差值,只要知道其中一个,即可算出另外的地址。

2.分配物理页面

alloc->pages = kzalloc(sizeof(alloc->pages[0]) *                   ((vma->vm_end - vma->vm_start) / PAGE_SIZE),                   GFP_KERNEL);

PAGE_SIZE为物理页面大小4K,物理页面的指针大小为((vma->vm_end - vma->vm_start) / PAGE_SIZE),sizeof(alloc->pages[0])为单个页面的物理页面大小。

  1. 处理内核缓冲区
struct binder_buffer *buffer;    ....    alloc->buffer_size = vma->vm_end - vma->vm_start;    buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);    if (!buffer) {        ret = -ENOMEM;        failure_string = "alloc buffer struct";        goto err_alloc_buf_struct_failed;    }    buffer->data = alloc->buffer;    list_add(&buffer->entry, &alloc->buffers);    buffer->free = 1;    binder_insert_free_buffer(alloc, buffer);    alloc->free_async_space = alloc->buffer_size / 2;    barrier();    alloc->vma = vma;    alloc->vma_vm_mm = vma->vm_mm;    mmgrab(alloc->vma_vm_mm);

这部分使用binder_buffer来描述,并将其加入到alloc->buffers列表中。紧接着将buffer加入到空闲内核缓冲区红黑树中。并将异步事务的内核缓冲区大小设置为总缓冲区大小的一半。

3.2 binder_insert_free_buffer()

binder_proc的结构可得,每个binder进程会管理一个空闲内核缓冲区红黑树,当binder_mmap映射内存到进程空间时,将会把一个新创建的空闲buffer加入到红黑树中。

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);    new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer);    binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,             "%d: add free buffer, size %zd, at %pK\n",              alloc->pid, new_buffer_size, new_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(&new_buffer->rb_node, parent, p);    rb_insert_color(&new_buffer->rb_node, &alloc->free_buffers);}

红黑树的流程简单而言就是,先获取新创建的buffer的大小,并在红黑树中找到合适的位置,并将其放入。规则是与二叉树相同。

其中涉及的计算buffer大小的方法binder_alloc_buffer_size,其流程如下:

static size_t binder_alloc_buffer_size(struct binder_alloc *alloc,                       struct binder_buffer *buffer){    if (list_is_last(&buffer->entry, &alloc->buffers))        return (u8 *)alloc->buffer +            alloc->buffer_size - (u8 *)buffer->data;    return (u8 *)binder_buffer_next(buffer)->data - (u8 *)buffer->data;}

进程中的alloc对象会有一个buffers链表来记录所有的buffer,假如buffer是在链表的尾部,那么就直接使用alloc->buffer(进程申请的内核空间的起始地址) + alloc->buffer_size(进程申请的内核空间的大小) - buffer->data(buffer的data起始位置),即buffer->data的大小。

|--|--sizeof(struct binder_buffer)---|---sizeof(buffer->data)-----|alloc->buffer                   buffer->data  (alloc->buffer + alloc->buffer_size)

假如为非最后的buffer,即下一个buffer的起始位置,减去buffer->data的地址。

由于在开始调用binder_mmap()的时候,binder驱动只为进分配了一个buffer,即只分配了一个物理内存,所以这里的alloc->buffer与buffer->data地址相同,但后面可能根据需要,分配更多的物理内存。

另外在红黑树插入空闲buffer的时候,获取每个节点的操作也值得玩味:

struct binder_buffer *buffer;buffer = rb_entry(parent, struct binder_buffer, rb_node);

rb_entry的定义如下:

#define    rb_entry(ptr, type, member) container_of(ptr, type, member)

其中ptr指向的红黑树的特定节点,type是binder_buffer,rb_nodebinder_buffer类型的成员。

至于container_of的定义如下:

#undef offsetof#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)/** * container_of - cast a member of a structure out to the containing structure * @ptr:        the pointer to the member. * @type:       the type of the container struct this is embedded in. * @member:     the name of the member within the struct. * */#define container_of(ptr, type, member) ({                      \    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \    (type *)( (char *)__mptr - offsetof(type,member) );})

offsetof:将地址0强制转换为type类型的指针,从而定位到member在结构体中偏移位置。编译器认为0是一个有效的地址,从而认为0是type指针的起始地址。

container_of:

第一部分:const typeof( ((type *)0)->member ) *__mptr = (ptr);
通过typeof定义一个member指针类型的指针变量__mptr,(即__mptr是指向member类型的指针),并将__mptr赋值为ptr。

第二部分: (type *)( (char *)__mptr - offsetof(type,member) ),通过offsetof宏计算出member在type中的偏移,然后用member的实际地址__mptr减去偏移,得到type的起始地址,即指向type类型的指针。

由此可得buffer = rb_entry(parent, struct binder_buffer, rb_node);,是通过获得一个成员变量类型为rb_node的红黑树节点,去获得包含这个成员变量的结构体的起始地址,即binder_buffer,从而完成从局部到整体的转换。

3.3. binder_update_page_range

上述步骤创建了内核空间以及物理页面,但是还没有将用户虚拟地址与内核的虚拟地址映射在物理页面中。而在进行该行为就binder_update_page_range中实现。

该方法在4.16的调用栈如下:

  1. binder_transaction
  2. binder_alloc_new_buf
  3. binder_alloc_new_buf_locked
  4. binder_update_page_range

以往binder_update_page_rangebinder_mmap就已经调用了,如今在调用binder_transaction时才开始映射。

static int binder_update_page_range(struct binder_alloc *alloc, int allocate,                    void *start, void *end)

该方法通过allocate参数判断是分配物理页面还是释放物理页面,故流程分为两部分:

3.3.1 分配物理页面

1.检查每个物理页面的地址是否为空,如果为空执行操作。

void *page_addr;bool need_mm = false;...for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {        page = &alloc->pages[(page_addr - alloc->buffer) / PAGE_SIZE];        if (!page->page_ptr) {            need_mm = true;            break;        }    }

假如存在物理页面的地址为空,则重新尝试在用户空间映射页面,如果失败,则报错。

if (need_mm && mmget_not_zero(alloc->vma_vm_mm))        mm = alloc->vma_vm_mm;    if (mm) {        down_write(&mm->mmap_sem);        vma = alloc->vma;    }    if (!vma && need_mm) {        pr_err("%d: binder_alloc_buf failed to map pages in userspace, no vma\n",            alloc->pid);        goto err_no_vma;    }

2.映射虚拟地址

映射内核地址空间的基本逻辑如下:

for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {    size_t index;    index = (page_addr - alloc->buffer) / PAGE_SIZE;    page = &alloc->pages[index];//获取每一个物理页面    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;    ret = vm_insert_page(vma, user_page_addr, page[0].page_ptr);//映射用户空间地址

3.3.2 释放物理页面

释放物理页面的逻辑如下:

if (allocate == 0)        goto free_range;free_range:    for (page_addr = end - PAGE_SIZE; page_addr >= start;         page_addr -= PAGE_SIZE) {        bool ret;        size_t index;        index = (page_addr - alloc->buffer) / PAGE_SIZE;        page = &alloc->pages[index];        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);        __free_page(page->page_ptr);        page->page_ptr = NULL;    }

转载地址:http://izxkm.baihongyu.com/

你可能感兴趣的文章
linux命令 dirname
查看>>
8/9
查看>>
Leangoo英文版来了~
查看>>
Leangoo敏捷工具Jenkins配置指南
查看>>
又是一个开始
查看>>
java创建XML及开源DOM4J的使用
查看>>
移动端的拼图游戏
查看>>
installp 软件的4种状态
查看>>
定时清理clientmqueue目录垃圾文件防止占满磁盘空间
查看>>
计算将一个十进制整数转换成二进制含多少个1
查看>>
Flex 当鼠标悬停在DataGrid某行上时用datatoolField显示当前行
查看>>
关于Integer包装类对象之间值的比较
查看>>
7.4 括号匹配
查看>>
nginx + fastDFS 设置开机自动启动
查看>>
腾讯云服务器 安装fastdfs文件服务器
查看>>
经典的计算机语言
查看>>
X5内核浏览器video自动全屏解决办法-canvas
查看>>
网络管理中的常见软件和管理维护
查看>>
myeclipse2014如何添加源码反编译工具插件
查看>>
ASP.NET网站入侵第二波(LeaRun.信息化快速开发框架 已被笔者拿下)
查看>>