Android IPC機制5-Binder驅動探究

1 概述

Client進程通過RPC(Remote Procedure Call Protocol)與Server通信,整個通信過程從client進程到server進程,從內核態到用戶態都涉及到了,前面幾篇文章也講了很多。而真正在Client和Server兩端建立通信的基礎設施便是Binder Driver。總結一下,Android系統Binder機制中的四個組件Client、Server、Service Manager和Binder驅動程序的關系如下圖所示 (圖片來自老羅博客

Binder通信架構

Binder驅動是Android專用的,但底層的驅動架構與Linux驅動一樣。binder驅動在以misc設備進行注冊,作為虛擬設備,沒有直接操作硬件,只是對設備內存的處理。主要是驅動設備的初始化(binder_init),打開 (binder_open),映射(binder_mmap),數據操作(binder_ioctl)。后面會詳細講解這幾個函數,但為了后面的理解,先來看看內核態、用戶態、系統調用等概念。

1.1 用戶態與內核態

以創建進程來說,無論是Linux亦或是windows,對于任何操作系統來說,其都是屬于核心功能,因為它要做很多底層細致地工作,消耗系統的物理資源,比如分配物理內存,從父進程拷貝相關信息,拷貝設置頁目錄頁表等等,這些顯然不能隨便讓哪個程序就能去做。類似的操作還有很多,所以就操作系統就自然引出特權級別的概念,顯然,最關鍵性的權力必須由高特權級的程序來執行,這樣才可以做到集中管理,減少有限資源的訪問和使用沖突。

特權級顯然是非常有效的管理和控制程序執行的手段,因此在硬件上對特權級做了很多支持,就Intel x86架構的CPU來說一共有0~3四個特權級,0級最高,3級最低,硬件上在執行每條指令時都會對指令所具有的特權級做相應的檢查,相關的概念有CPL、DPL和RPL,這里不再過多闡述。硬件已經提供了一套特權級使用的相關機制,軟件自然就是好好利用的問題,這屬于操作系統要做的事情,對于Unix/Linux來說,只使用了0級特權級和3級特權級。也就是說在Unix/Linux系統中,一條工作在0級特權級的指令具有了CPU能提供的最高權力,而一條工作在3級特權級的指令具有CPU提供的最低或者說最基本權力。

而arm cpu的特權模式就比較多了,有7種。但是只有一種是用戶模式,其他的都是特權模式。

現在我們從特權級的調度來理解用戶態和內核態就比較好理解了,以x86來說當程序運行在3級特權級上時,就可以稱之為運行在用戶態,因為這是最低特權級,是普通的用戶進程運行的特權級,大部分用戶直接面對的程序都是運行在用戶態;反之,當程序運行在0級特權級上時,就可以稱之為運行在內核態。
雖然用戶態下和內核態下工作的程序有很多差別,但最重要的差別就在于特權級的不同,即權力的不同。

1.2 如何切換用戶態和內核態

Linux提供了系統調用作為切換用戶態與內核態的機制。其底層是通過中斷機制來實現的。簡單的說,當用戶態執行到需要內核態執行的代碼時,會產生一個中斷信號,cpu收到信號后會根據中斷信號傳遞的信息進入不同的中斷函數,當中斷函數執行完畢后,就會回到用戶態。

2. 核心方法

下面以serviceManager的啟動為例,詳細分析Binder驅動的幾個核心函數,先來看看ServiceManager的啟動過程(代碼位于service_manager.c)

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

    bs = binder_open(128*1024);

    if (binder_become_context_manager(bs)) {
        LOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}

可以發現,其主要有以下幾個步驟

  • 打開binder驅動: binder_open(128*1024);
  • 通知Binder驅動程序它成為Binder通信的上下文管理者:binder_become_context_manager(bs);
  • 進入循環等待請求的到來:binder_loop(bs, svcmgr_handler);

2.1 binder_open(service_manager)

struct binder_state *binder_open(unsigned mapsize)
{
    struct binder_state *bs;

    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return 0;
    }

    bs->fd = open("/dev/binder", O_RDWR);
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open device (%s)\n",
                strerror(errno));
        goto fail_open;
    }

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }

        /* TODO: check version */

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return 0;
}

通過文件操作函數open來打開/dev/binder設備文件。設備文件/dev/binder是在Binder驅動程序模塊初始化的時候創建的,我們先看一下這個設備文件的創建過程。進入到kernel/common/drivers/staging/android目錄中,打開binder.c文件,可以看到模塊初始化入口binder_init:

2.2 binder_init:

static int __init binder_init(void)
{
    int ret;
    //創建名為binder的工作隊列
    binder_deferred_workqueue = create_singlethread_workqueue("binder"); 
    if (!binder_deferred_workqueue)
        return -ENOMEM;

    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);

     // 注冊misc設備
    ret = misc_register(&binder_miscdev);   
    if (binder_debugfs_dir_entry_root) {
        ... //在debugfs文件系統中創建一系列的文件
    }
    return ret;
}

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR, //次設備號 動態分配
    .name = "binder",     //設備名
    .fops = &binder_fops  //設備的文件操作結構,這是file_operations結構
};

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

debugfs_create_dir是指在debugfs文件系統中創建一個目錄,返回值是指向dentry的指針。注冊misc設備,miscdevice結構體,便是前面注冊misc設備時傳遞進去的參數,file_operations結構體,指定相應文件操作的方法。

回到2.1,前面的前面的binder_open函數執行語句會執行到bs->fd = open("/dev/binder", O_RDWR);,這句代碼就是一個系統調用,最終會執行到binder驅動的binder_open方法

2.3 binder_open(Binder驅動)

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc; // binder進程 【見附錄3.1】

    proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 為binder_proc結構體在分配kernel內存空間
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;   //將當前線程的task保存到binder進程的tsk
    INIT_LIST_HEAD(&proc->todo); //初始化todo列表
    init_waitqueue_head(&proc->wait); //初始化wait隊列
    proc->default_priority = task_nice(current);  //將當前進程的nice值轉換為進程優先級

    binder_lock(__func__);   //同步鎖,因為binder支持多線程訪問
    binder_stats_created(BINDER_STAT_PROC); //BINDER_PROC對象創建數加1
    hlist_add_head(&proc->proc_node, &binder_procs); //將proc_node節點添加到binder_procs為表頭的隊列
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;       //file文件指針的private_data變量指向binder_proc數據
    binder_unlock(__func__); //釋放同步鎖

    return 0;
}

這個函數的主要作用是創建一個struct binder_proc數據結構來保存打開設備文件/dev/binder的進程的上下文信息,并且將這個進程上下文信息保存在打開文件結構struct file的私有數據成員變量private_data中,這樣,在執行其它文件操作時,就通過打開文件結構struct file來取回這個進程上下文信息了。這個進程上下文信息同時還會保存在一個全局哈希表binder_procs中,驅動程序內部使用。binder_procs定義在文件的開頭:static HLIST_HEAD(binder_procs);其保存了所有的binder_proc結構,每次新創建的binder_proc對象都會加入binder_procs鏈表中。binder_proc見4.1

這樣,打開設備文件/dev/binder的操作就完成了,接著是對打開的設備文件進行內存映射操作mmap:

2.4 binder_mmap

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    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;

    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;  //保證映射內存大小不超過4M

    mutex_lock(&binder_mmap_lock);  //同步鎖
    //分配一個連續的內核虛擬空間,與進程虛擬空間大小一致
    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->buffer = area->addr; //指向內核虛擬空間的地址
    //地址偏移量 = 用戶虛擬地址空間 - 內核虛擬地址空間
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; 
    mutex_unlock(&binder_mmap_lock); //釋放鎖
    
    ...
    //分配物理頁的指針數組,大小等于用戶虛擬地址內存/4k;
    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;
    vma->vm_private_data = proc;

    //分配物理頁面,同時映射到內核空間和進程空間,目前只分配1個page的物理頁 【見2.5】
    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; //binder_buffer對象 指向proc的buffer地址
    INIT_LIST_HEAD(&proc->buffers); //創建進程的buffers鏈表頭
    list_add(&buffer->entry, &proc->buffers); //將binder_buffer地址 加入到所屬進程的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;

    ...// 錯誤flags跳轉處,free釋放內存之類的操作
    return ret;
}

binder_mmap通過加鎖,保證一次只有一個進程分配內存,保證多進程間的并發訪問。其中user_buffer_offset是虛擬進程地址與虛擬內核地址的差值,也就是說同一物理地址,當內核地址為kernel_addr,則進程地址為proc_addr = kernel_addr + user_buffer_offset??梢杂孟旅娴膱D片來方便理解


binder_mmap

在上面的函數里同時出現了struct vm_area_struct和struct vm_struct。struct vm_area_struct,它表示的是一塊連續的虛擬地址空間區域,struct vm_struct,這個數據結構也是表示一塊連續的虛擬地址空間區域,那么,這兩者的區別是什么呢?在Linux中,struct vm_area_struct表示的虛擬地址是給進程使用的,而struct vm_struct表示的虛擬地址是給內核使用的,它們對應的物理頁面都可以是不連續的。struct vm_area_struct表示的地址空間范圍是0~3G,而struct vm_struct表示的地址空間范圍是(3G + 896M + 8M) ~ 4G。struct vm_struct表示的地址空間范圍為什么不是3G~4G呢?原來,3G ~ (3G + 896M)范圍的地址是用來映射連續的物理頁面的,這個范圍的虛擬地址和對應的實際物理地址有著簡單的對應關系,即對應0~896M的物理地址空間,而(3G + 896M) ~ (3G + 896M + 8M)是安全保護區域(例如,所有指向這8M地址空間的指針都是非法的),因此struct vm_struct使用(3G + 896M + 8M) ~ 4G地址空間來映射非連續的物理頁面。

2.5 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)    
{
    ...
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
        int ret;
        struct page **page_array_ptr;
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);  //分配物理內存
        if (*page == NULL) {
            goto err_alloc_page_failed;
        }
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE;
        page_array_ptr = page;
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); //物理空間映射到虛擬內核空間
        if (ret) {
            goto err_map_kernel_failed;
        }
        user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
        ret = vm_insert_page(vma, user_page_addr, page[0]); //物理空間映射到虛擬進程空間
        if (ret) {
            goto err_vm_insert_page_failed;
        }
    }
    ...
}

binder_update_page_range 主要完成工作:分配物理空間,將物理空間映射到內核空間,將物理空間映射到進程空間。 當然binder_update_page_range
既可以分配物理頁面,也可以釋放物理頁面。

2.6 binder_ioctl

講了這么多,service_managermain()的binder_open執行完畢了,接下來代碼就走到了binder_become_context_manager(bs),來看看這個函數

int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

這個函數內部非常簡單,直接調用的ioctl函數,通過上面的經驗,我們可以知道ioctl也是個系統調用,最終是執行的是內核態的binder_ioctl函數

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;  // binder線程
    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_thread
    thread = binder_get_thread(proc); 
    if (thread == NULL) {
        ret = -ENOMEM;
        goto err;
    }

    switch (cmd) {
    case BINDER_WRITE_READ:  //進行binder的讀寫操作
        ret = binder_ioctl_write_read(filp, cmd, arg, thread); //
        if (ret)
            goto err;
        break;
    case BINDER_SET_MAX_THREADS: //設置binder最大支持的線程數
        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;
    case BINDER_SET_CONTEXT_MGR: //成為binder的上下文管理者,也就是ServiceManager成為守護進程
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
    case BINDER_THREAD_EXIT:   //當binder線程退出,釋放binder線程
        binder_free_thread(proc, thread);
        thread = NULL;
        break;
    case BINDER_VERSION: {  //獲取binder的版本號
        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);
        
err_unlocked:
    trace_binder_ioctl_done(ret);
    return ret;
}

binder_ioctl()函數負責在兩個進程間收發IPC數據和IPC reply數據。是一個非常重要的函數,其參數列表為ioctl(文件描述符,ioctl命令,數據類型)

  • 文件描述符,是通過open()方法打開Binder Driver后返回值;
  • ioctl命令和數據類型是一體的,不同的命令對應不同的數據類型,如聲明成為service_manager聲明稱為上下文管理者就是BINDER_SET_CONTEXT_MGR
    繼續分析這個函數之前,又要解釋兩個數據結構了,一個是struct binder_thread結構體,顧名思久,它表示一個線程,這里就是執行binder_become_context_manager函數的線程了。具體定義見4.2
    proc表示這個線程所屬的進程。struct binder_proc有一個成員變量threads,它的類型是rb_root,它表示一查紅黑樹,把屬于這個進程的所有線程都組織起來,struct binder_thread的成員變量rb_node就是用來鏈入這棵紅黑樹的節點了。looper成員變量表示線程的狀態,它可以取下面這幾個值:
enum {  
    BINDER_LOOPER_STATE_REGISTERED  = 0x01,  // 已注冊
    BINDER_LOOPER_STATE_ENTERED     = 0x02,  // 已進入
    BINDER_LOOPER_STATE_EXITED      = 0x04,  // 已退出
    BINDER_LOOPER_STATE_INVALID     = 0x08,  // 非法
    BINDER_LOOPER_STATE_WAITING     = 0x10,  // 等待中
    BINDER_LOOPER_STATE_NEED_RETURN = 0x20  // 需要返回
};  

另外一個數據結構是struct binder_node,詳見4.3。它表示一個binder實體, rb_node和dead_node組成一個聯合體。 如果這個Binder實體還在正常使用,則使用rb_node來連入proc->nodes所表示的紅黑樹的節點,這棵紅黑樹用來組織屬于這個進程的所有Binder實體;如果這個Binder實體所屬的進程已經銷毀,而這個Binder實體又被其它進程所引用,則這個Binder實體通過dead_node進入到一個哈希表中去存放。proc成員變量就是表示這個Binder實例所屬于進程了。

下面來看看ioctl中的幾個重要函數

2.6.1 binder_get_thread()

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;
    while (*p) {  //根據當前進程的pid,從binder_proc中查找相應的binder_thread
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);
        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            break;
    }
    if (*p == NULL) {
        thread = kzalloc(sizeof(*thread), GFP_KERNEL); //新建binder_thread結構體
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
        thread->pid = current->pid;  //保存當前進程(線程)的pid
        init_waitqueue_head(&thread->wait);
        INIT_LIST_HEAD(&thread->todo);
        rb_link_node(&thread->rb_node, parent, p);
        rb_insert_color(&thread->rb_node, &proc->threads);
        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
        thread->return_error = BR_OK;
        thread->return_error2 = BR_OK;
    }
    return thread;
}

這里把當前線程current的pid作為鍵值,在進程proc->threads表示的紅黑樹中進行查找,看是否已經為當前線程創建過了binder_thread信息。在這個場景下,由于當前線程是第一次進到這里,所以肯定找不到,即*p == NULL成立,于是,就為當前線程創建一個線程上下文信息結構體binder_thread,并初始化相應成員變量,并插入到proc->threads所表示的紅黑樹中去,下次要使用時就可以從proc中找到了。

2.6.2 binder_ioctl_write_read()

static int binder_ioctl_write_read(struct file *filp,
                unsigned int cmd, unsigned long arg,
                struct binder_thread *thread)
{
    int ret = 0;
    struct binder_proc *proc = filp->private_data;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;
    struct binder_write_read bwr;

    if (size != sizeof(struct binder_write_read)) {
        ret = -EINVAL;
        goto out;
    }
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { //把用戶空間數據ubuf拷貝到bwr
        ret = -EFAULT;
        goto out;
    }

    if (bwr.write_size > 0) {
        //當寫緩存中有數據,則執行binder寫操作
        ret = binder_thread_write(proc, thread,
                      bwr.write_buffer, bwr.write_size, &bwr.write_consumed); 
        trace_binder_write_done(ret); 
        if (ret < 0) { //當寫失敗,再將bwr數據寫回用戶空間,并返回
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr))) 
                ret = -EFAULT;
            goto out;
        }
    }
    if (bwr.read_size > 0) {
        //當讀緩存中有數據,則執行binder讀操作
        ret = binder_thread_read(proc, thread, 
                      bwr.read_buffer, bwr.read_size, &bwr.read_consumed,
                      filp->f_flags & O_NONBLOCK); 
        trace_binder_read_done(ret);
        if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait); //進入休眠,等待中斷喚醒
        if (ret < 0) { //當讀失敗,再將bwr數據寫回用戶空間,并返回
            if (copy_to_user(ubuf, &bwr, sizeof(bwr))) 
                ret = -EFAULT;
            goto out;
        }
    }

    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { //將內核數據bwr拷貝到用戶空間ubuf
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}

具體流程如下:

  1. 首先把用戶空間數據拷貝到內核空間bwr;
  2. 當bwr寫緩存中有數據,則執行binder寫操作;當寫失敗,再將bwr數據寫回用戶空間,并退出;
  3. 當bwr讀緩存中有數據,則執行binder讀操作;當讀失敗,再將bwr數據寫回用戶空間,并退出;
  4. 最后把內核數據bwr拷貝到用戶空間。

這里涉及兩個核心方法binder_thread_write()和binder_thread_read()方法,請求處理過程是通過binder_thread_write()方法,該方法用于處理Binder協議中的請求碼。當binder_buffer存在數據,binder線程的寫操作循環執行。

響應處理過程是通過binder_thread_read()方法,該方法根據不同的binder_work->type以及不同狀態,生成相應的響應碼。

2.7 小節

上面列出了這么多代碼,其實有些細節我也沒有深入學習,不過對于binder通信的主要流程還算是比較熟悉了。其主要流程有以下幾點(以service_manager啟動并成為上下文管理者為例)

  1. binder_init: 注冊驅動,
  2. binder_open: 創建一個struct binder_proc數據結構來保存打開設備文件/dev/binder的進程的上下文信息
  3. binder_mmap : 映射進程虛擬地址和內核虛擬地址到同一個物理地址
  4. binder_ioctl : 兩個進程間收發IPC數據和IPC reply數據

總結

在上面的一章里,通過代碼詳細分析了binder通信的幾個核心函數(binder_thread_write()與binder_thread_write()沒有展開講),下面從宏觀上做些總結

內存關系

先上個圖(感謝gityuan大大)

binder_physical_memory

這個圖生動的說明了binder通信的內存原理。一直說的binder驅動的只需一次copy的奧妙就是內存映射,android通過一塊物理內存地址映射到一個進程中的用戶空間和內核空間的兩個地址,就可以讓client到server的數據傳遞只需從client的用戶空間copy到內核空間,然后由于server端的用戶空間映射了和內核空間data同一個地址,所以就能直接得到數據,避免二次copy,提高了性能。

圖中的進程所有的4g內存是虛擬內存,并非進程實際能得到4g,至于為什么是4g,主要是由于32位cpu的內存尋址能力2^32,實際上binder能映射的虛擬地址空間大小為3G+896M+8M~4G=120m,而實際的物理內存更是被限制為<4m,所以binder機制不適合大數據的傳輸。

對于進程和內核虛擬地址映射到同一個物理內存的操作是發生在數據接收端,而數據發送端還是需要將用戶態的數據復制到內核態。那為何不直接讓發送端和接收端直接映射到同一個物理空間,那樣就連一次復制的操作都不需要了,0次復制操作那就與Linux標準內核的共享內存的IPC機制沒有區別了,對于共享內存雖然效率高,但是對于多進程的同步問題比較復雜,而管道/消息隊列等IPC需要復制2兩次,效率較低。這里就不先展開討論Linux現有的各種IPC機制跟Binder的詳細對比,總之Android選擇Binder的基于速度和安全性的考慮。
下面這圖是從Binder在進程間數據通信的流程圖,從圖中更能明了Binder的內存轉移關系:


通信模型

下面這張張圖生動的描述一次ipc 事物的完整流程:


結構體附錄

4.1 binder_proc

struct binder_proc {  
    struct hlist_node proc_node;  
    struct rb_root threads;  //binder_thread紅黑樹的根節點
    struct rb_root nodes;   //binder_node紅黑樹的根節點,保存進程中的binder實體
    struct rb_root refs_by_desc;  //binder_ref紅黑樹的根節點(以handle為key)
    struct rb_root refs_by_node;  //binder_ref紅黑樹的根節點(以ptr為key)
    int pid;                //創建binder_proc的進程id
    struct vm_area_struct *vma;   //指向進程虛擬地址空間的指針(用戶態使用)
    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;    //最大binder線程數
    int requested_threads;  
    int requested_threads_started;  
    int ready_threads;  
    long default_priority;  
};  

4.2 binder_thread

struct binder_thread {  
    struct binder_proc *proc;   //線程所屬的進程
    struct rb_node rb_node;  
    int pid;    //線程pid
    int looper;  //looper的狀態
    struct binder_transaction *transaction_stack;  
    struct list_head todo;  
    uint32_t return_error; /* Write failed, return error code in read buf */  
    uint32_t return_error2; /* Write failed, return error code in read */  
        /* buffer. Used when sending a reply to a dead process that */  
        /* we are also waiting on */  
    wait_queue_head_t wait;  
    struct binder_stats stats;  
};  

4.3 binder_node

struct binder_node {  
    int debug_id;  
    struct binder_work work;  
    union {  
        struct rb_node rb_node;  
        struct hlist_node dead_node;  
    };  
    struct binder_proc *proc;  
    struct hlist_head refs;  
    int internal_strong_refs;  
    int local_weak_refs;  
    int local_strong_refs;  
    void __user *ptr;  
    void __user *cookie;  
    unsigned has_strong_ref : 1;  
    unsigned pending_strong_ref : 1;  
    unsigned has_weak_ref : 1;  
    unsigned pending_weak_ref : 1;  
    unsigned has_async_transaction : 1;  
    unsigned accept_fds : 1;  
    int min_priority : 8;  
    struct list_head async_todo;  
};  

4.4 binder_buffer

BInder地址空間被劃分為一段一段,每一段都是由binder_buffer 來描述

struct binder_buffer {  
    struct list_head entry;   //    buffer實體的地址
    struct rb_node rb_node;   //紅黑樹節點,用于掛在binder_proc中的紅黑樹上
    unsigned free : 1;  
    unsigned allow_user_free : 1;  
    unsigned async_transaction : 1;  
    unsigned debug_id : 29;  
  
    struct binder_transaction *transaction;  
  
    struct binder_node *target_node;  
    size_t data_size;  
    size_t offsets_size;  
    uint8_t data[0];  
};  

寫在最后

Binder機制確實是android中非常重要的一環,學習難度也很大。不過通過Binder的學習,也讓我對android系統有了更深入的認識。在這個過程中,也對linux的ipc機制、系統調用、內存管理有了些了解,也終于是搞懂了紅黑樹,這也算是一些附帶成果吧。
最后再次感謝羅升陽gityuan兩位老師,他們的博客使我受益匪淺。希望以后自己也能有更多高質量的原創博文。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容