Android Binder 驅動 - 內核驅動層源碼分析

相關文章鏈接:

1. Android FrameWork - 學習啓動篇
2. Android FrameWork - 開機啓動 Init 進程
3. Android 開發者需要知道的 Linux 知識
4. 從 Linux 內核的角度來看 Binder 驅動
5. JNI 基礎 - Android 共享內存的序列化過程
6. Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
7. Android Binder 驅動 - Media 服務的添加過程
8. Android Binder 驅動 - 啓動 ServiceManager 進程
9. Android Binder 驅動 - 內核驅動層源碼分析

相關源碼文件:

/drivers/android/binder.c
/drivers/staging/android/binder.c

《Android Binder 驅動 - Media 服務的添加過程》《Android Binder 驅動 - 啓動 ServiceManager 進程》 中遺留幾個驅動層的方法 binder_open、binder_mmap 和 binder_ioctl,本文我們從內核驅動層來做一次徹底的分析。

1. binder_init

用戶態的程序調用 Kernel 層方法需要陷入內核態,進行系統調用(syscall),打開 Binder 驅動方法的調用鏈爲: open-> __open() -> binder_open()。 open() 爲用戶空間的方法,__open() 便是系統調用中相應的處理方法,內部通過查找會找到對應的內核 binder 驅動的 binder_open() 方法。但在驅動啓動時首先會調用驅動的 xxx_init 方法。

static int __init binder_init(void)
{
    int ret;
    //創建名爲binder的工作隊列
    binder_deferred_workqueue = create_singlethread_workqueue("binder");
    ...

     // 基於 misc_class 構造一個設備,將 miscdevice 結構掛載到 misc_list 列表上,並初始化與 linux 設備模型相關的結構  
    ret = misc_register(&binder_miscdev);
    return ret;
}

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

// 相對應的一些操作
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,
};

2. binder_open

static int binder_open(struct inode *nodp, struct file *filp)
{
    // 當前 binder 進程結構體
    struct binder_proc *proc; 
    // 在內核開闢連續空間,大小不能超過 128K,默認初始化值爲 0 
    proc = kzalloc(sizeof(*proc), GFP_KERNEL); 

    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    // 將當前線程的 task 保存到 binder 進程的 tsk
    proc->tsk = current;   
    // 初始化 todo 列表
    INIT_LIST_HEAD(&proc->todo); 
    // 初始化 wait 隊列
    init_waitqueue_head(&proc->wait); 
    // 將當前進程的 nice 值轉換爲進程優先級
    proc->default_priority = task_nice(current);  
    // 同步鎖,因爲 binder 支持多線程訪問
    binder_lock(__func__);  
    // BINDER_PROC 對象創建數加1 
    binder_stats_created(BINDER_STAT_PROC); 
    // 將 proc_node 節點添加到 binder_procs 爲表頭的隊列
    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;
}

struct binder_proc {
	struct hlist_node proc_node;
    // threads 樹 保存 binder_proc 進程內用於處理用戶請求的線程
	struct rb_root threads;
    // nodes樹 保存 binder_proc 進程內的 Binder 實體;
	struct rb_root nodes;
    // 進程內的 Binder 引用,即引用的其它進程的 Binder 實體,以句柄作 key 值來組織
	struct rb_root refs_by_desc;
    // 進程內的 Binder 引用,即引用的其它進程的 Binder 實體,以地址作 key 值來組織
	struct rb_root refs_by_node;
    // 當前進程 id
	int pid;
	struct vm_area_struct *vma;
	struct mm_struct *vma_vm_mm;
    // 將當前線程的 task_struct
	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_proc 對象,該對象管理 IPC 所需的各種信息並擁有其他結構體的根結構體;再把 binder_proc 對象保存到文件指針 filp,以及把 binder_proc 加入到全局鏈表 binder_procs。

3. binder_mmap

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    // 內核虛擬空間
    struct vm_struct *area;
    // 從 filp 中獲取之前打開保存的
    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;  
    // 同步鎖
    mutex_lock(&binder_mmap_lock);  
    // 採用 IOREMAP 方式,分配一個連續的內核虛擬空間,與進程虛擬空間大小一致
    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); 

    ...
    // 分配物理頁的指針數組,數組大小爲 vma 的等效 page 個數;
    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個物理頁
    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 對象 指向 proc 的 buffer 地址
    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;

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

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;
  // 內存結構體
  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;
    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;
}

上面這些代碼大部分都是 linux 內核方面的知識了,我們來簡單解釋下:task_struct 代表的是進程或者線程管理的進程控制結構體,mm_struct 是 task_struct 結構體中虛擬地址管理的結構體,vm_area_struct 代表的是虛擬用戶空間映射管理的結構體,vm_struct 代表的是內核空間管理的結構體。alloc_page 方法的作用是分配一個物理內存,map_kernel_range_noflush 方法的作用是將物理空間映射到虛擬內核空間,vm_insert_page 方法的作用是將物理空間映射到虛擬用戶空間。binder_mmap 的主要作用就是開闢一塊連續的內核空間,並且開闢一個物理頁的地址空間,同時映射到用戶空間和內核空間。

4. 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) {
    // 進行 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 成爲守護進程
    case BINDER_SET_CONTEXT_MGR: 
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
    // 當 binder 線程退出,釋放 binder 線程
    case BINDER_THREAD_EXIT:   
        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);

err_unlocked:
    trace_binder_ioctl_done(ret);
    return ret;
}

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;
    // 根據當前進程的 pid,從 binder_proc 中查找相應的 binder_thread
    while (*p) {  
        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) {
        // 新建 binder_thread 結構體
        thread = kzalloc(sizeof(*thread), GFP_KERNEL); 
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
        // 保存當前進程(線程)的 pid
        thread->pid = current->pid;  
        init_waitqueue_head(&thread->wait);
        // 初始化線程的 todo 隊列
        INIT_LIST_HEAD(&thread->todo);
        // 把線程加入 proc->threads 
        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;
}

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;
    }
    // 把用戶空間數據 ubuf 拷貝到 bwr
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { 
        ret = -EFAULT;
        goto out;
    }

    // 當寫緩存中有數據,則執行 binder 寫操作
    if (bwr.write_size > 0) {
        ret = binder_thread_write(proc, thread,
                      bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
        trace_binder_write_done(ret);
        // 當寫失敗,再將 bwr 數據寫回用戶空間,並返回
        if (ret < 0) { 
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    // 當讀緩存中有數據,則執行 binder 讀操作
    if (bwr.read_size > 0) {
        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); 
        // 當讀失敗,再將bwr數據寫回用戶空間,並返回
        if (ret < 0) { 
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    // 將內核數據 bwr 拷貝到用戶空間 ubuf
    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { 
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}

通過以上分析,ioctl 命令有 BINDER_WRITE_READ (binder 讀寫交互)、BINDER_SET_CONTEXT_MGR(servicemanager進程成爲上下文管理者)、BINDER_SET_MAX_THREADS(設置最大線程數)、BINDER_VERSION(獲取 binder 版本)。有兩個核心複雜方法 binder_thread_write 和 binder_thread_read,由於這裏面的代碼邏輯較爲複雜,因此到後面我們帶着線索再去具體分析。

視頻地址:https://pan.baidu.com/s/1j_wgzITcgABVbThvO0VBPA
視頻密碼:jj4b

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章