Binder驅動 映射內存分配

參考源碼

binder.h
binder.c
page.h
kernel.h
vmalloc.c



binder_proc->buffer

指向【內核虛擬空間】管理結構體vm_struct vm的開始地址

binder_proc->buffer_size

vm的大小,等於進程調用mmap("dev/binder")時請求分配的虛擬空間大小。

binder_proc->pages

物理頁數組,用於存放物理頁管理結構體page,通過數組下標與虛擬空間形成相對位置關係

linxu將物理內存劃分成大小爲PAGE_SIZE的塊進行管理,稱爲物理頁,page則是物理頁的管理結構體。

  • pages的下標

    通過數組下標與虛擬空間形成相對位置關係:

        PAGE_SIZE=4k時
    
        vm[0k ~  4k] = page[0]
        vm[4k ~  8k] = page[1]
        vm[8k ~ 12k] = page[2]
    

    虛擬地址可以通過計算,查找所在的page在數組中的下標。

        index = (虛擬地址 - binder_proc->buffer(vm起始地址)) / PAGE_SIZE
        page = pages[index]
    
  • 數組大小

    請求分配的虛擬空間大小所需要的物理頁數量,對應page結構體數量所需要請求的物理內存大小。

        所需內存大小 = page結構體大小 * (虛擬空間大小 / PAGE_SIZE)
    
  • PAGE_SIZE

    PAGE_SIZE一般=4k。
    從ARMv8架構開始,可以啓用CONFIG_ARM64_64K_PAGES使PAGE_SIZE=64k。

    #ifdef CONFIG_ARM64_64K_PAGES
    #define PAGE_SHIFT      16
    #else
    #define PAGE_SHIFT      12
    #endif
    #define PAGE_SIZE       (_AC(1,UL) << PAGE_SHIFT) 
    #define PAGE_MASK       (~(PAGE_SIZE-1))
    
  • PAGE_MASK

    虛擬地址通過&PAGE_MASK,可以獲取所在物理頁的起始虛擬地址。

        PAGE_SIZE=4k時
        
        PAGE_SIZE = 0000-0000 0000-0000 0001-0000 0000-0000
        PAGE_MASK = 1111-1111 1111-1111 1111-0000 0000-0000
    


binder_mmap()

進程在打開binder驅動後,通過mmap("dev/binder")指定內存映射區域的大小。mmap系統調用過程中,會調用binder驅動的binder_mmap()函數。

  • 申請vm

    通過get_vm_area()申請與【用戶虛擬空間】vm_area_struct vma大小相同的vm,並記錄vm的起始地址到proc->buffer

    get_vm_area()__get_vm_area_node()函數的調用包裝:

    • 通過kmalloc_node()申請vm_struct結構體的所需內存;
    • 通過alloc_vmap_area()在vmalloc區域中尋找合適的區域,獲取返回的vmap_area結構體;
    • 通過setup_vmalloc_vm()vmap_area的數據同步到vm_struct中;
    • 最後返回vm_struct

    get_vm_area()vmalloc()的區別:
    vmalloc()在調用__get_vm_area_node()獲取vm_struct之後,還通過__vmalloc_area_node()進行了物理頁映射。

  • 初始化pages數組

    通過kzalloc()申請pages數組所需的內存。

  • 構建首個binder_buffer

    proc->buffer(即vm的起始地址)創建首個binder_buffer,並添加到空閒隊列binder->free_buffers中。
    這使得當前進程binder通信的可用負載容量等於這個binder_buffer負載容量

    在創建binder_buffer之前,因爲proc->buffer並沒有映射到物理頁,需要先進行映射。映射過程封裝在binder_update_page_range()函數,此處通過該函數申請了大小爲PAGE_SIZE的物理頁映射。

static int binder_mmap(
        struct file *filp,              // 進程打開binder驅動時分配的file結構體
        struct vm_area_struct *vma      // mmap過程中分配的用戶虛擬空間
) { 
        
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    struct binder_buffer *buffer;
    
    /*
     * 限制vma大小,最大4M
     */
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
        
    /*
     * 通過get_vm_area()獲取vm
     */
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    
    /*
     * proc->buffer 指向vm的開始地址
     */
    proc->buffer = area->addr;
    
    /*
     * proc->user_buffer_offset 記錄 vma/vm 起始地址的差值
     * 通過差值,可以快速計算 vma/vm 中對應的虛擬內存
     */
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
   
    /*
     * 通過kzalloc()申請pages數組所需內存
     */
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    
    /*
     * proc->buffer_size 記錄vm的大小
     */
    proc->buffer_size = vma->vm_end - vma->vm_start;
    
    /*
     * 覆蓋vma的鉤子函數,其中:
     *      .fault:不執行缺頁處理,直接返回VM_FAULT_SIGBUS,進程在收到SIGBUS信號時會終止
     * 這是因爲binder驅動在發生虛擬內存的讀寫前,會先處理好相關的物理頁映射,正常情況下不會產生缺頁異常
     */
    vma->vm_ops = &binder_vm_ops;
    
    /*
     * 爲首個binder_buffer 所在的虛擬空間映射物理頁
     * page = pages[0]
     */
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
        goto err_alloc_small_buf_failed;
    }
    
    /*
     * 初始化binder_buffer
     */
    buffer = proc->buffer;
    
    /*
     * 初始化proc->buffers鏈表
     * 將binder_buffer添加到proc->buffers鏈表
     */
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);
    
    /*
     * 將binder_buffer標記爲空閒
     * 將binder_buffer添加到free_buffers紅黑樹
     */ 
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    
    /*
     * 更新異步消息的空閒負載容量,大小等於同步消息空閒負載容量的一半
     */
    proc->free_async_space = proc->buffer_size / 2;
}




binder_update_page_range()

爲指定的虛擬空間建立物理內存映射,或釋放物理內存映射。

  • vma區間

    傳入參數是vm中的區間,函數中通過proc->user_buffer_offset找到對應的vma區間。

  • 建立與釋放映射

    在指定虛擬空間區間執行步進爲PAGE_SIZE的for循環,在循環中:

    • 如果是建立映射,則創建對應區間所在的page,並建立與vma/vm的映射;
    • 如果是釋放映射,則釋放對應區間所在的pagevma/vm的映射,並釋銷燬page
static int binder_update_page_range(
        struct binder_proc *proc,       // binder_proc
        int allocate,                   // 0: 釋放映射 1: 建立映射
        void *start,                    // vm區間起始地址
        void *end,                      // vm區間結尾地址
        struct vm_area_struct *vma      // vma
) {
        
    void *page_addr;                    // 臨時變量,當前操作的vm起始地址
    unsigned long user_page_addr;       // 臨時變量,當前操作的vma起始地址
    
    struct vm_struct tmp_area;
    struct page **page;

    /*
     * 省略源碼:
     * - 校驗邏輯
     * - vma獲取和校驗邏輯
     */
     
    /*
     * 執行釋放邏輯,跳到free_range
     */
    if (allocate == 0)
        goto free_range;

    /*
     * for循環,創建對應區間所在的page,並建立映射
     */
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
        int ret;
        
        /*
         * 獲取pages數組中對應位置的page結構體
         */
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        
        /*
         * 通過alloc_page()申請物理頁
         */
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);

        /*
         * 通過map_vm_area()建立內核虛擬空間和物理內存的映射
         *
         * 在映射區間的最後添加大小爲PAGE_SIZE的保護頁
         *
         * 當進程讀寫操作超出映射區域時,由於保護頁沒有綁定實際物理內存,
         * 會拋出缺頁異常 page fault,而binder驅動對缺頁異常的處理是直接返回VM_FAULT_SIGBUS,
         * 進程就會收到SIGBUS信號,默認處理是終止當前進程。
         */
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, page);
    
        /*
         * 計算vma對應區間的起始地址
         */
        user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
        
        /*
         * 通過vm_insert_page()建立用戶虛擬空間和物理內存的映射
         */
        ret = vm_insert_page(vma, user_page_addr, page[0]);
    }
    return 0;


free_range:
    /*
     * 釋放邏輯
     */
    for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
    
        /*
         * 獲取pages數組中對應位置的page結構體
         */
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        
        /*
         * 通過zap_page_range()解除vma區間的物理內存映射
         */ 
        if (vma)
            zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);
            
err_vm_insert_page_failed:
        /*
         * 通過unmap_kernel_range()解除vm區間的物理內存映射
         */ 
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);

err_map_kernel_failed:
        /*
         * 銷燬page
         */ 
        __free_page(*page);
        *page = NULL;
        
err_alloc_page_failed:
        ;
    }
    return -ENOMEM;
}




binder_insert_free_buffer()

將指定的binder_buffer插入到binder_proc->free_buffers紅黑樹,索引爲buffer的負載容量,通過binder_buffer_size()計算。

static void binder_insert_free_buffer(
        struct binder_proc *proc,           // binder_proc
        struct binder_buffer *new_buffer    // buffer
) {

    /*
     * 計算binder_buffer的負載大小
     */
    size_t new_buffer_size;
    new_buffer_size = binder_buffer_size(proc, new_buffer);
    
    /*
     * 省略源碼:
     * - 插入到紅黑樹合適的位置
     */
}




binder_buffer_size()

計算binder_buffer的負載容量。

  • binder_buffer負載空間

    binder_buffer的負載空間指的是在proc->buffer中,當前binder_buffer到下一個binder_buffer之間的空間。binder_buffer本身沒有記錄負載空間的信息,通過結構體尾端的柔性數組uint8_t data[0]充當負載空間的起始地址。

  • 柔性數組
      在一個結構體的最後,申明一個長度爲0的數組,就可以使得這個結構體是可變長的。
      對於編譯器來說,此時長度爲0的數組並不佔用空間。
      因爲數組名本身不佔空間,它只是一個偏移量, 數組名這個符號本身代表了一個不可修改的地址常量。
        
      優點:
      比起在結構體中聲明一個指針變量、再進行動態分 配的辦法,這種方法效率要高。
      因爲在訪問數組內容時,不需要間接訪問,避免了兩次訪存。
    
      缺點:
      在結構體中,數組爲0的數組必須在最後聲明,使用上有一定限制。
    

負載容量的大小不能通過binder_buffer直接獲取,需要通過計算獲得。

static size_t binder_buffer_size(
        struct binder_proc *proc,       // binder_proc
        struct binder_buffer *buffer    // buffer
) {
    
    /*
     * binder->buffers的末尾項時:
     * binder->buffer末尾地址 - binder_buffer->data
     */
    if (list_is_last(&buffer->entry, &proc->buffers))
        return proc->buffer + proc->buffer_size - (void *)buffer->data;
        
    /*
     * binder->buffers的非末尾項時:
     * binder_buffer(next) - binder_buffer->data
     */
    return (size_t)list_entry(buffer->entry.next, struct binder_buffer, entry) - (size_t)buffer->data;
}

其實可以將虛擬空間看成一個容器,binder_buffer看成容器內分割空間的隔板,隔板與隔板之間就用於存放數據的負載空間了。



binder_alloc_buf()

binder_transaction()跨進程傳輸數據的過程中,爲傳輸的數據分配一個負載空間大小合適的binder_buffer

  • 邏輯流程

    函數的邏輯分爲2個部分,查找合適的空閒binder_buffer和分割binder_buffer

  • 查找負載空間合適大小的空閒binder_buffer

    proc->free_buffers紅黑樹中,查找負載空間滿足存放傳輸數據、且最接近傳輸數據大小的空閒binder_buffer

    proc->free_buffers紅黑樹在mmap("def/binder")過程中添加了首個binder_buffer。首次查找時,則會命中這個binder_buffer

static struct binder_buffer *binder_alloc_buf(
        struct binder_proc *proc,   // binder_proc
        size_t data_size,           // 傳輸的普通數據的大小
        size_t offsets_size,        // 傳輸的binder數據的大小
        int is_async                // 是否異步通信
) {
    
    /*
     * 定義臨時變量
     */
    struct rb_node *n = proc->free_buffers.rb_node;
    struct binder_buffer *buffer;
    size_t buffer_size;
    struct rb_node *best_fit = NULL;
    void *has_page_addr;
    void *end_page_addr;
    size_t size;

    /*
     * 計算傳輸數據的總大小
     *
     * 對數據進行字節對齊
     * ALIGN:字節對齊(void指針:32-4bit,64-8bit)
     */
    size = ALIGN(data_size, sizeof(void *)) + ALIGN(offsets_size, sizeof(void *));

    /* 
     * 檢查異步任務的空閒負載容量是否滿足所需傳輸需求
     * 傳輸數據所需容量 = binder_buffer結構體大小 + 傳輸數據大小
     *
     * binder驅動定義:
     *      進程異步任務的空閒負載容量 = 當前總空閒負載容量 / 2
     * 所以要做額外檢測。
     *
     * 空閒容量滿足要求,也不一定可以成功獲取binder_buffer,
     * 如果碎片化嚴重,可能會不存在大容量的binder_buffer。
     */
    if (is_async && proc->free_async_space < size + sizeof(struct binder_buffer)) {
        return NULL;
    }

    /*
     * 遍歷binder->free_buffers,獲取合適的binder_buffer
     */
    while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, buffer);

        /*
         * [負載容量 > 傳輸數據]:記錄下來,繼續遍歷容量更小的子樹
         */
        if (size < buffer_size) {
            best_fit = n;               
            n = n->rb_left;
        }
        /*
         * [負載容量 < 傳輸數據]:繼續遍歷容量更大的子樹
         */
        else if (size > buffer_size) {
            n = n->rb_right;
        }
        /*
         * [負載容量 == 傳輸數據]:完全貼合可以直接使用,退出循環
         */ 
        else {
            best_fit = n;
            break;
        }
    }
    
    /*
     * 沒有合適的binder_buffer
     * 正常情況可能是因爲數據過大,也可能是因爲碎片化
     */
    if (best_fit == NULL) {
        return NULL;
    }
    
    /*
     * binder_buffer、binder_size 重新定位best_fit對應的項
     * 
     * n == null,表示循環不是通過break結束的,而是遍歷至n == null結束的,
     * 此時 binder_buffer、binder_size 都是循環中的臨時項,所以需要重新定位到best_fit對應的項
     */
    if (n == NULL) {
        buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, buffer);
    }
    
    /*
     * -----------------------
     * 已找到合適的binder_buffer
     * -----------------------
     */
     
    ...
  • 分割binder_buffer

    嘗試對查找到的binder_buffer進行分割,減少內存消耗。

    查找算法找出的binder_buffer負載空間一般情況下會比傳輸數據的體積要大。例如首次查找時,找到的首個binder_buffer負載空間幾乎等於mmap的內存大小(需要減去binder_buffer的體積)。

    分割過程如上訴的比喻一樣,往容器中插入隔板。
    在原來的binder_buffer的負載空間中滿足傳輸要求大小的位置後面,插入一個新的binder_buffer

    最後,還需要爲分割出來的binder_buffer負載空間與新加入的binder_buffer分配物理內存。

    ...
    
    /*
     * -----------------------
     * 已找到合適的binder_buffer
     * -----------------------
     */
     
    /*
     * 負載空間 需要建立映射的最後一個物理頁的下邊界
     *
     * 通過負載空間的末尾地址,計算出負載空間末尾所在物理頁的起始虛擬地址
     * 在此地址前的虛擬空間,需要在本次處理中進行物理頁映射
     * 
     * 負載空間的末尾地址有2種情況:
     * 1. 下一個binder_buffer的起點
     *    意味虛擬空間的末尾地址已映射物理頁,
     *    所以通過[&PAGE_MASK],將內存地址在物理頁中的頁內偏移部分設置位0,消除頁內偏移,
     *    以定位到所在物理頁的上邊界
     *
     * 2. vma末尾地址:
     *    vma末尾地址應該是物理頁對齊的,此時內存地址等於最後的物理頁下邊界
     *    假如不是對齊,由於binder->pages申請內存的邏輯,放不下最後的沒對齊page結構體,
     *    運行到物理頁映射的階段會導致pages數組越界
     * 
     * 此時 buffer_size = [binder_buffer負載容量]
     */
    has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK);
    
    ...
    ...
    
    /*
     * 計算所需映射物理內存的大小,保存到buffer_size
     *
     * n == null 的情況,表示binder_buffer [負載容量 > 傳輸數據],嘗試分割
     * 
     * 負載空間需要滿足填充傳輸數據後,還可以存放新的binder_buffer結構體,
     * 如果分割後剩餘空間不滿足存放新的binder_buffer結構體,那麼放棄分割
     */ 
    if (n == NULL) {
        /*
         * 是否能放得下新的binder_buffer結構體
         *
         * +4用於新的binder_buffer負載檢測,
         * 如果新的binder_buffer結構體負載空間比4還小,那麼也沒有分割的必要
         *
         * 不太清楚4bit的空間能幹什麼...
         */
        if (size + sizeof(struct binder_buffer) + 4 >= buffer_size)
            /*
             * 放不下
             * 需要分配的物理內存大小 = 數據大小
             */
            buffer_size = size; /* no room for other buffers */
        else
            /*
             * 放得下
             * 需要分配的物理內存大小 = 數據大小 + buffer結構體大小
             */
            buffer_size = size + sizeof(struct binder_buffer);
    }
    
    /*
     * 實際所需內存 需要建立映射的最後一個物理頁的下邊界
     */
    end_page_addr = (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size);
   
    ... 
    ...
    
    /*
     * 防止越界,具體看圖
     */
    if (end_page_addr > has_page_addr)
        end_page_addr = has_page_addr;
    
    ...    

    ...
    /*
     * 通過binder_update_page_range()進行物理頁映射
     * 
     * 起始地址也進行PAGE對齊,得到binder_buffer所在物理頁的下邊界,
     * 邏輯一樣,binder_buffer所在的內存空間必然是已映射物理頁的
     */
    if (binder_update_page_range(proc, 1, (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL))
        return NULL;
        
    /*
     * -------------
     * 已完成物理頁映射
     * -------------
     */
     
    ...
    ...

    /*
     * -------------
     * 已完成物理頁映射
     * -------------
     */

    /*
     * 設置binder_buffer使用狀態
     * 
     * 移出binder_proc->free_buffers
     * binder_buffer->free設置0(使用中)
     * 加入binder_proc->allocated_buffers
     * 
     * binder_insert_allocated_buffer()
     * 只是簡單的紅黑樹操作,排序也沒有邏輯上的意義,此處忽略解析
     */
    rb_erase(best_fit, &proc->free_buffers);
    buffer->free = 0;
    binder_insert_allocated_buffer(proc, buffer);
    
    /*
     * 如果buffer_size不等於size,說明需要分割,
     * 需要創建新的binder_buffer
     * 
     * 注意在binder->buffer中,
     * 新的binder_buffer添加到被分割的binder_buffer之後,
     * 保持鏈表的binder_buffer元素在邏輯內存上的順序
     */
    if (buffer_size != size) {
        struct binder_buffer *new_buffer = (void *)buffer->data + size;
    
        list_add(&new_buffer->entry, &buffer->entry);
        new_buffer->free = 1;
        binder_insert_free_buffer(proc, new_buffer);
    }

    /*
     * 設置binder_buffer數據
     * 
     * buffer->data_size            設置爲傳輸普通數據的大小
     * buffer->offsets_size         設置爲傳輸binder數據的大小
     * buffer->async_transaction    設置是否爲異步消息
     */
    buffer->data_size = data_size;
    buffer->offsets_size = offsets_size;
    buffer->async_transaction = is_async;
    
    /*
     * 如果是異步任務,還需要更新異步任務的空閒負載容量
     * 
     * 減少大小 = binder_buffer結構體 + 傳輸數據的大小
     */
    if (is_async) {
        proc->free_async_space -= size + sizeof(struct binder_buffer);
    }
    
    return buffer;
}




binder_free_buf()

回收目標binder_buffer。

  • 邏輯流程

    函數的邏輯分爲2個部分,解除負載空間內存映射和合並空閒的binder_buffer

  • 解除負載空間內存映射

    釋放binder_buffer負載空間已映射的物理頁。

  • 合併空閒的binder_buffer

    分割的逆向過程,嘗試合併兩端的binder_buffer,以還原爲負載空間較大的binder_buffer

    binder_buffer是在邏輯內存上有序的,順序就是binder_proc->buffers鏈表的順序,binder_buffer的合併即是嘗試合併鏈表中的前後項。

static void binder_free_buf(
        struct binder_proc *proc,       // binder_proc
        struct binder_buffer *buffer    // 需要釋放的binder_buffer
) {
    
    size_t size, buffer_size;
    
    /*
     * 獲取負載容量
     */
    buffer_size = binder_buffer_size(proc, buffer);

    /*
     * 獲取傳輸數據大小
     *
     * 對數據進行字節對齊
     */
    size = ALIGN(buffer->data_size, sizeof(void *)) + ALIGN(buffer->offsets_size, sizeof(void *));

    /*
     * 如果是異步任務,還需要更新異步任務的空閒負載容量
     * 
     * 增加大小 = binder_buffer結構體 + 傳輸數據的大小
     * binder_alloc_buf()的逆向過程
     */
    if (buffer->async_transaction) {
        proc->free_async_space += size + sizeof(struct binder_buffer);
    }

    /*
     * 通過binder_update_page_range()釋放物理頁映射
     * 
     * 釋放負載空間的除 邊界物理頁 外的物理頁映射
     * - 起始地址: 負載空間映射的起始物理頁的下邊界
     * - 結尾地址: 負載空間映射的末尾物理頁的上邊界
     *
     * 因爲邊界物理頁必然被binder_buffer使用中,所以先處理確定可以釋放的區間
     * - 負載空間起始地址:不是物理頁邊界的情況下,所在物理頁被當前binder_buffer佔用
     * - 負載空間結尾地址:不是物理頁邊界的情況下,所在物理頁被下一個binder_buffer佔用
     */
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);
    
    /*
     * 設置buffer使用狀態
     * 
     * 移出binder_proc->allocated_buffers
     * binder_buffer->free設置1(空閒)
     * 
     * 暫時不加入binder_proc->free_buffers,合併過程可能會銷燬這個binder_buffer
     */
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);
    buffer->free = 1;
    
    /*
     * 嘗試合併下一個binder_buffer
     * 如果buffer是邏輯內存的最後一項,則跳過這個流程
     */
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        /*
         * 找到下一個binder_buffer next
         */
        struct binder_buffer *next = list_entry(buffer->entry.next, truct binder_buffer, entry);
        
        /*
         * 如果next是空閒的,則通過移除next進行合併
         */
        if (next->free) {
            /*
             * 先把next移出proc->free_buffers,
             * 然後通過binder_delete_free_buffer()銷燬next
             */
            rb_erase(&next->rb_node, &proc->free_buffers);
            binder_delete_free_buffer(proc, next);
        }
    }
    
    /*
     * 嘗試合併上一個binder_buffer
     * 如果是buffer是邏輯內存的第一項,則跳過這個流程
     */
    if (proc->buffers.next != &buffer->entry) {
        /*
         * 找到上一個binder_buffer prev
         */
        struct binder_buffer *prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);
        
        /*
         * 如果prev是空閒的,則通過移除buffer進行合併
         */
        if (prev->free) {
            /*
             * 通過binder_delete_free_buffer()銷燬buffer
             */
            binder_delete_free_buffer(proc, buffer);
            
            /*
             * 合併後,prev的負載容量發生了改變,
             * 所以要移出proc->free_buffers,後面重新插入
             */
            rb_erase(&prev->rb_node, &proc->free_buffers);
            buffer = prev;
        }
    }
    
    /*
     * 把合併後的buffer添加到binder_proc->free_buffers
     */
    binder_insert_free_buffer(proc, buffer);
}




binder_delete_free_buffer()

銷燬空閒的binder_buffer,並判斷是否需要解除所在物理頁的映射。

/*
 * 專用函數,獲取傳入binder_buffer的起始地址,所在的物理頁的起始地址
 */
static void *buffer_start_page(struct binder_buffer *buffer) {
    return (void *)((uintptr_t)buffer & PAGE_MASK);
}

/*
 * 專用函數,獲取傳入binder_buffer的結束地址,所在的物理頁的起始地址
 */
static void *buffer_end_page(struct binder_buffer *buffer) {
    return (void *)(((uintptr_t)(buffer + 1) - 1) & PAGE_MASK);
}

static void binder_delete_free_buffer(
        struct binder_proc *proc,           // binder_proc
        struct binder_buffer *buffer        // 需要銷燬的binder_buffer
) {
    
    struct binder_buffer *prev, *next = NULL;
    int free_page_end = 1;          // 是否釋放起始地址所在的物理頁
    int free_page_start = 1;        // 是否釋放結尾地址所在的物理頁

    /*
     * 前向對比
     * 獲取前一個binder_buffer prev
     *
     * 這裏不需要做是否是首個binder_buffer的校驗,不可能出現這樣的情況
     */
    prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);
    
    /*
     * prev末尾地址是否與buffer的起始地址在同一個物理頁,是則不要釋放該物理頁
     */
    if (buffer_end_page(prev) == buffer_start_page(buffer)) {
        free_page_start = 0;
        
        /*
         * prev末尾地址是否與buffer的末尾地址在同一個物理頁,是則不要釋放該物理頁
        */
        if (buffer_end_page(prev) == buffer_end_page(buffer))
            free_page_end = 0;
    }

    /* 
     * 校驗是否末尾項,是則不進行後向對比
     * 原理與前向對比一致
     */
    if (!list_is_last(&buffer->entry, &proc->buffers)) {
        next = list_entry(buffer->entry.next, struct binder_buffer, entry);
        if (buffer_start_page(next) == buffer_end_page(buffer)) {
            free_page_end = 0;
            if (buffer_start_page(next) == buffer_start_page(buffer))
                free_page_start = 0;
        }
    }
    
    /*
     * 將buffer從proc->buffers中移除
     */
    list_del(&buffer->entry);
    
    /*
     * 如果需要釋放物理頁,則通過binder_update_page_range()處理
     * 根據 free_page_start / free_page_end 進行了物理頁的邊界處理
     */
    if (free_page_start || free_page_end) {
        binder_update_page_range(proc, 0, 
            free_page_start ? buffer_start_page(buffer) : buffer_end_page(buffer),
            (free_page_end ? buffer_end_page(buffer) : buffer_start_page(buffer)) + PAGE_SIZE, 
            NULL);
    }
}




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