Linux 4.16 Binder驅動學習筆記--------接口簡析

1. binde設備初始化

1.1 binder_init()

static int __init binder_init(void)

binder設備初始化過程能夠簡化爲以下步驟:node

1.初始化binder緩衝區分配異步

ret = binder_alloc_shrinker_init();

2.建立binder相關目錄async

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_logui

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設備this

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設備的操做.spa

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_devicesdebug

2. Binde設備文件的打開過程

2.1 binder_open()

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

binder_open的流程以下:指針

1.建立binder進程code

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;
    }
相關文章
相關標籤/搜索