Android进程通信之binder驱动源码的理解

首先我们看binder开始定义的一些链表:

//设置对象的访问方式为互斥的
static DEFINE_MUTEX(binder_lock);
static DEFINE_MUTEX(binder_deferred_lock);
static DEFINE_MUTEX(binder_mmap_lock);
//创建一些双向链表
static HLIST_HEAD(binder_procs);
static HLIST_HEAD(binder_deferred_list);
static HLIST_HEAD(binder_dead_nodes);
//定义一些目录项
static struct dentry *binder_debugfs_dir_entry_root;
static struct dentry *binder_debugfs_dir_entry_proc;
//binder结点
static struct binder_node *binder_context_mgr_node;
static uid_t binder_context_mgr_uid = -1;
static int binder_last_id;
//工作队列
static struct workqueue_struct *binder_deferred_workqueue;

然后我们分析下源码
首先我们看binder驱动的初始化方法:

static int __init binder_init(void)
{
    int ret;
    //生成一个单线程的工作队列
    binder_deferred_workqueue = create_singlethread_workqueue("binder");
    if (!binder_deferred_workqueue)
        return -ENOMEM;
    //创建binder文件夹
    binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
    if (binder_debugfs_dir_entry_root)
//在binder下创建proc文件夹
        binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
                         binder_debugfs_dir_entry_root);
    //注册为misc驱动
    ret = misc_register(&binder_miscdev);
    if (binder_debugfs_dir_entry_root) {
        //在binder下创建state文件
        debugfs_create_file("state",
                    S_IRUGO,
                    binder_debugfs_dir_entry_root,
                    NULL,
                    &binder_state_fops);
//在binder下创建stats文件
        debugfs_create_file("stats",
                    S_IRUGO,
                    binder_debugfs_dir_entry_root,
                    NULL,
                    &binder_stats_fops);
//在binder下创建transactions文件
        debugfs_create_file("transactions",
                    S_IRUGO,
                    binder_debugfs_dir_entry_root,
                    NULL,
                    &binder_transactions_fops);
 //在binder下创建transaction_log文件
        debugfs_create_file("transaction_log",
                    S_IRUGO,
                    binder_debugfs_dir_entry_root,
                    &binder_transaction_log,
                    &binder_transaction_log_fops);
         //在binder下创建failed_transaction_log文件
        debugfs_create_file("failed_transaction_log",
                    S_IRUGO,
                    binder_debugfs_dir_entry_root,
                    &binder_transaction_log_failed,
                    &binder_transaction_log_fops);
    }
    return ret;
}
该方法做了一下几件事:
1:初始化一个工作队列,由单线程执行
2:注册了一个misv类型的驱动
3:创建binder/proc文件夹;创建binder/state、binder/stats、binder/transactions、
Binder/ transaction_log、binder/ failed_transaction_log文件
在看注册驱动的结构体:
//注册binder驱动的名字,有的接口
static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
     //名字
    .name = "binder",
 //支持的操作
    .fops = &binder_fops
};
我们再看看支持的操作:
//定义binder驱动的接口
static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
     //多binder驱动执行的poll对应驱动里面的binder_poll方法,下同一个
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .mmap = binder_mmap,
    .open = binder_open,
    .flush = binder_flush,
    .release = binder_release,
};
首先看binder_proc结构:
struct binder_proc {
    //里面有两个指针,一个前驱和一个后驱结点
    struct hlist_node proc_node;
    // 指向红黑树根结点的指针: 
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    //进程标识id
    int pid;
    //地址空间
    struct vm_area_struct *vma;
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;
    struct files_struct *files;
    struct hlist_node deferred_work_node;
    int deferred_work;
    void *buffer;
    ptrdiff_t user_buffer_offset;
    //
    struct list_head buffers;
    struct rb_root free_buffers;
    struct rb_root allocated_buffers;
    size_t free_async_space;

    struct page **pages;
    size_t buffer_size;
    uint32_t buffer_free;
    struct list_head todo;
    wait_queue_head_t wait;
    struct binder_stats stats;
    struct list_head delivered_death;
    //进程支持的最大线程数目
    int max_threads;
    int requested_threads;
    int requested_threads_started;
    int ready_threads;
    long default_priority;
    struct dentry *debugfs_entry;
};

由以上结构可以看出,binder_proc应该是记录进程的一些相关信息,一个进程的信息在binder驱动中就是由一个binder_proc来进行存储;因此当binder查询一个进程时,就会根据binder_proc所在的结构里来查找。

然后我们先看看binder_open操作;在看之前大家想想,zai 打开驱动操作中,应该执行什么操作;首先我们知道,binder驱动是为了让进程间通信的一个驱动;而之前我们分析过binder驱动的进行通信原理;即binder中保存有每个进程的信息;然后进程之间通过binder作为中间人来进行数据的传递;

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

    binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
             current->group_leader->pid, current->pid);
    //生成一个binder_proc结点(记录线程信息)
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    if (proc == NULL)
        return -ENOMEM;
    //初始化proc结点
    get_task_struct(current);
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo);
    init_waitqueue_head(&proc->wait);
    proc->default_priority = task_nice(current);
    mutex_lock(&binder_lock);
    binder_stats_created(BINDER_STAT_PROC);
    //将结点插入binder_procs链表中,binder_proc的结构是
    hlist_add_head(&proc->proc_node, &binder_procs);
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;
    mutex_unlock(&binder_lock);
    if (binder_debugfs_dir_entry_proc) {
        char strbuf[11];
        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
        proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
            binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
    }
    return 0;
}
由上面的代码可以,binder_open主要是根据进程信息生成一个binder_proc结点;然后初始化结点信息;然后将结点插入到bincer_procs链表中;也就是说,binder驱动内部用一个链表保存进程信息;而从binder_proc结构可以看出,一个进程里面定义了4个红黑树; 分别是threads、nodes、
refs_by_desc、refs_by_node;其中threads我们看名字就可以看出,是用来存储线程信息的;至于别的肯定都有它的用处,这里不做过多研究。
然后我们再看:binder_mmap操作:我们知道mmap()操作本意是将文件映射到内存中进行操作,这是一种最快的操作文件的方式;在binder中是为了进程间进行通信使用。而我们通过以前的调用过程可以,一般都是先open,然后进行mmap操作;在进行读写命令的操作;open操作我们已经知道,主要是生成一个记录进程信息的结点并存储;因此在mmap中,应该是为读写数据做准备,也就是需要在mmap中创建进程间通信的内存空间;为下一步进程间进行通信创造空间条件。
/注意入参,一个file类型,一个虚拟地址空间
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    //proc指向打开设备文件的自己的结构;其实也就是在open时候创建的proc结点
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    //最大申请4M内存
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
………..
    mutex_lock(&binder_mmap_lock);
    //申请虚拟空间
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    ……
    //将虚拟地址对应到实际的物理内存地址
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;
     //初始化proc->buffers该链表的表头
    INIT_LIST_HEAD(&proc->buffers);
    //将新申请的空间保存到链表中
    list_add(&buffer->entry, &proc->buffers);
    //设置未使用的空间数量
    buffer->free = 1;
    //将这一段空间插入该进程结点中管理未使用空间的数据结构中
    binder_insert_free_buffer(proc, buffer);
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(proc->tsk);
    proc->vma = vma;
    proc->vma_vm_mm = vma->vm_mm;
//

…………
}
由此可以看出binder_mmap的作用主要是为了进程下一步读写进行物理条件的准备;包括申请物理空间;将该物理空间信息保存进进程结点信息中,以方便后期查找使用。
最后看binder_ioctl方法,这个方法实现了主要的进程通信操作;首先我们知道,binder进程通信的原理主要是binder将一个进程内存的数据复制到另一个进程内存中;因此我们围绕着这个实现,可以预先思考一下实现思路:
首先,是不是应该找到两个进程的结点信息;
然后进行复制操作;
。。。。。。
按照这个思路我们来看看binder_ioct1的具体源码:代码较多,我删除了部分代码;
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
     //获取到发起者进程所在的结点
    struct binder_proc *proc = filp->private_data;
    ……
    mutex_lock(&binder_lock);
    thread = binder_get_thread(proc);
    ……
    switch (cmd) {
    //读写命令,重点分析
    case BINDER_WRITE_READ: {
        struct binder_write_read bwr;
        …….
         //当写入的数据大于0时,证明是给别的驱动发消息
        if (bwr.write_size > 0) {
            ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
            ………….
        }
         //当读入的数据大于0时,证明是想从binder获取消息
        if (bwr.read_size > 0) {
            ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
            ………
        }
        …….
        break;
    }
    //设置最大的线程数
    case BINDER_SET_MAX_THREADS:
………
        break;
     //设置为binder大管家
    case BINDER_SET_CONTEXT_MGR:
        …….
        break;
    //退出binder线程
    case BINDER_THREAD_EXIT:
        ………
        break;
    //获取binder版本号
    case BINDER_VERSION:
        ………
        break;
    default:
        ………..
    return ret;
}
Binder驱动在ioc1中,首先根据命令类型执行相关的操作;我们主要分析读写命令:因此后面我们分析一下binder_thread_write的源码来大概理解下binder里面的操作细节: 
给别的进程发送命令的binder_thread_write 方法:(方法很长,我挑着简要的分析)
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
            void __user *buffer, int size, signed long *consumed)
{
    uint32_t cmd;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;

     //循环读取命令执行
    while (ptr < end && thread->return_error == BR_OK) {
        //根据命令类型
        switch (cmd) {
        …………..
         //主要分析BC_TRANSACTION和BC_REPLY,因为读写命令在BC_TRANSACTION中
        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;

            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
             //注意该方法            
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
            break;
         ……….
        }
……….
        }
        *consumed = ptr - buffer;
    }
    return 0;
}
我们发现在write中binder驱动根据命令类型来进行处理;读写操作在binder_transaction方法里;
首先我们看到在binder_ioc1方法里面并没有进程之间复制数据的操作;因此在binder_transaction方法里面必定要进行核心操作;
再看看入参:貌似也没有对方的进程结点;因此binder_transaction方法必须首先要找到接受数据的进程的结点,根据结点找到空间;然后进行复制操作。
看binder_transaction方法:
static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    …………..
    if (reply) {
        ………….
    } else {
         //找到目标进程结点
        if (tr->target.handle) {
            struct binder_ref *ref;
            ref = binder_get_ref(proc, tr->target.handle);
            ………….
            target_node = ref->node;
        } else {
            target_node = binder_context_mgr_node;
            ………
        }
        e->to_node = target_node->debug_id;
        target_proc = target_node->proc;
        if (target_proc == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
         // transaction_stack我百度了一下,应该是保存和别的进程的会话线程;因此下面的代码应该是检查是否有会话线程
        if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
            struct binder_transaction *tmp;
            tmp = thread->transaction_stack;
            ……….
            while (tmp) {
                if (tmp->from && tmp->from->proc == target_proc)
                    target_thread = tmp->from;
                tmp = tmp->from_parent;
            }
        }
    }
     ……….
     //进程空间中复制数据,这里应该是进行进程通信的地方
    if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
        binder_user_error("binder: %d:%d got transaction with invalid "
            "data ptr\n", proc->pid, thread->pid);
        return_error = BR_FAILED_REPLY;
        goto err_copy_data_failed;
    }
    if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
        binder_user_error("binder: %d:%d got transaction with invalid "
            "offsets ptr\n", proc->pid, thread->pid);
        return_error = BR_FAILED_REPLY;
        goto err_copy_data_failed;
    }
    ………………
}

Linux的基础不怎么样,大概凭着自己的理解写下的流程。
通过对binder驱动源码的分析;我们大概了解到驱动所做的工作;但是我觉得更重要的是思考为什么会这么做;当给你这个需求的时候,你能不能完成它;binder驱动本身并不复杂,我们觉得复杂更多的是因为我们没有身处其境。
Binder驱动主要作用进行进程通信;并且管理进程资源;因此为了实现这个目标,首先他必须要保存每一个进程的信息;然后定义一些命令来进行对进程的操作供上层应用使用;只要一切都是围绕这个基础上来思考问题。那么我就觉得这一块就没有什么难理解的。虽然我仅仅能看懂C/C++语言,但是对Linux函数可以说是很陌生;大概就分析到这里。

binder

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值