細(xì)讀《深入理解 Android 內(nèi)核設(shè)計思想》(三)Binder 機(jī)制 [上]

對冗余挑揀重點(diǎn),對重點(diǎn)深入補(bǔ)充,輸出結(jié)構(gòu)清晰的精簡版

1.必備知識
設(shè)備驅(qū)動
文件描述符
頁框
2.Binder 概述
3.binder 驅(qū)動
binder_open
binder_mmap
binder_ioctl
4.Service Manager
啟動
注冊與查詢
5.最后

必備知識

設(shè)備驅(qū)動

Linux 把所有的硬件訪問都抽象為對文件的讀寫、設(shè)置,這一"抽象"的具體實(shí)現(xiàn)就是驅(qū)動程序。驅(qū)動程序充當(dāng)硬件和軟件之間的樞紐,提供了一套標(biāo)準(zhǔn)化的調(diào)用,并將這些調(diào)用映射為實(shí)際硬件設(shè)備相關(guān)的操作,對應(yīng)用程序來說隱藏了設(shè)備工作的細(xì)節(jié)。

Linux 設(shè)備分為三類,分別是字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備:

1.字符設(shè)備: 能夠像字節(jié)流(類似文件)一樣被訪問的設(shè)備。對字符設(shè)備進(jìn)行讀/寫操作時,實(shí)際硬件的 I/O 操作一般也緊接著發(fā)生。字符設(shè)備驅(qū)動程序通常都會實(shí)現(xiàn) open、close、read 和 write 系統(tǒng)調(diào)用,比如觸摸屏、鍵盤、串口、LCD、LED 等。
2.塊設(shè)備: 指通過傳輸數(shù)據(jù)塊(一般為 512 或 1k)來訪問的設(shè)備,比如硬盤、SD卡、U盤、光盤等。
3.網(wǎng)絡(luò)設(shè)備: 能夠和其他主機(jī)交換數(shù)據(jù)的設(shè)備,比如網(wǎng)卡設(shè)備、藍(lán)牙設(shè)備等。

通過 cat /proc/devices 命令可以查看字符設(shè)備和塊設(shè)備:

Character devices:
  1 mem
  4 ttyS
 10 misc
   ...
Block devices:
  1 ramdisk
  7 loop
  8 sd
   ...

可以看到屬于字符設(shè)備的 misc 雜項(xiàng)設(shè)備,設(shè)備號為 10。通過 ls /dev -l 命令可以查看具體的注冊設(shè)備:

crw-rw-rw- 1 root   root    10,  61 2020-03-16 16:52 ashmem
crw-rw-rw- 1 root   root    10,  58 2020-03-16 16:52 binder
...

其中 Ashmem、Binder 的設(shè)備號是 10,都屬于 misc 雜項(xiàng)設(shè)備,10 是 主設(shè)備號,61、58 叫做 從設(shè)備號,有了主、從設(shè)備號,就可以唯一標(biāo)識一個設(shè)備。

文件描述符

Linux 中一切都可以看作文件,包括普通文件、鏈接文件、Socket 以及設(shè)備驅(qū)動等,對其進(jìn)行相關(guān)操作時,都可能會創(chuàng)建對應(yīng)的文件描述符。文件描述符(file descriptor)是內(nèi)核為了高效管理已被打開的文件所創(chuàng)建的索引,用于指代被打開的文件,對文件所有 I/O 操作相關(guān)的系統(tǒng)調(diào)用都需要通過文件描述符。

文件描述符與文件是什么關(guān)系呢?下圖 Linux 中的三張表可以體現(xiàn):


  • 進(jìn)程級別的文件描述符表:內(nèi)核為每個進(jìn)程維護(hù)一個文件描述符表,該表記錄了文件描述符的相關(guān)信息,包括文件描述符、指向打開文件表中記錄的指針。

  • 系統(tǒng)級別的打開文件表:內(nèi)核對所有打開文件維護(hù)的一個進(jìn)程共享的打開文件描述表,表中存儲了處于打開狀態(tài)文件的相關(guān)信息,包括文件類型、訪問權(quán)限、文件操作函數(shù)(file_operations)等。

  • 系統(tǒng)級別的 i-node 表:i-node 結(jié)構(gòu)體記錄了文件相關(guān)的信息,包括文件長度,文件所在設(shè)備,文件物理位置,創(chuàng)建、修改和更新時間等,"ls -i" 命令可以查看文件 i-node 節(jié)點(diǎn)

文件描述符是一種系統(tǒng)資源,可以通過以下命令來查看文件描述符的上限:

#查看所有進(jìn)程允許打開的最大 fd 數(shù)量
126|generic_x86:/ # cat /proc/sys/fs/file-max
174139

#查看所有進(jìn)程已經(jīng)打開的 fd 數(shù)量以及允許的最大數(shù)量
generic_x86:/ # cat /proc/sys/fs/file-nr
11040   0       174139

#查看單個進(jìn)程允許打開的最大 fd 數(shù)量.
generic_x86:/ # ulimit -n
32768

也可以查看某進(jìn)程當(dāng)前已使用的 fd :

#查看某進(jìn)程(進(jìn)程 id 為 15077)已經(jīng)打開的 fd
generic_x86:/ # ls -l /proc/15077/fd/
total 0
lrwx------ 1 u0_a136 u0_a136 64 2020-04-15 23:04 0 -> /dev/null
lrwx------ 1 u0_a136 u0_a136 64 2020-04-15 23:04 1 -> /dev/null
lrwx------ 1 u0_a136 u0_a136 64 2020-04-15 23:04 35 -> /dev/binder
lrwx------ 1 u0_a136 u0_a136 64 2020-04-09 01:01 44 -> socket:[780404]
lrwx------ 1 u0_a136 u0_a136 64 2020-04-15 23:04 55 -> /dev/ashmem
lrwx------ 1 u0_a136 u0_a136 64 2020-04-15 23:04 60 -> /dev/ashmem
...

上面這個進(jìn)程是一個 Android 應(yīng)用進(jìn)程,所以能看到 ashmem、binder 等 Android 特有設(shè)備文件相關(guān)的 fd 。再來看一個實(shí)際打開磁盤文件的例子:

     File file = new File(getCacheDir(), "testFdFile");
     FileOutputStream out = new FileOutputStream(file);

執(zhí)行上面代碼后會申請一個對應(yīng)的 fd:

# ls -l /proc/{pid}/fd/
...
l-wx------ u0_a55   u0_a55  2020-04-16 00:24 995 -> /data/data/com.example.test/cache/testFdFile
...

實(shí)際開發(fā)中,可能會遇到 fd 資源超過上限導(dǎo)致的 "Too many open files" 之類的問題,一般都是因?yàn)闆]有及時釋放掉 fd,比如上面代碼中 FileOutputStream 沒有關(guān)閉,若循環(huán)執(zhí)行超過單個進(jìn)程允許打開的最大 fd 數(shù)量,程序就會出現(xiàn)異常。

頁框

頁框(Page Frame)是指一塊實(shí)際的物理內(nèi)存塊,頁是指程序的一塊內(nèi)存數(shù)據(jù)單元。內(nèi)存數(shù)據(jù)一定是存儲在實(shí)際的物理內(nèi)存上,即頁必然對應(yīng)于一個頁框,頁數(shù)據(jù)實(shí)際是存儲在頁框上的。

頁框和頁一樣大,都是內(nèi)核對內(nèi)存的分塊單位。一個頁框可以映射給多個頁,也就是說一塊實(shí)際的物理存儲空間可以映射給多個進(jìn)程的多個虛擬內(nèi)存空間,這也是 mmap 機(jī)制依賴的基礎(chǔ)規(guī)則。

Binder 概述

不同進(jìn)程處于不同的內(nèi)存空間,具有不同的虛擬地址映射規(guī)則,所以不能直接通信。 Binder 是 Android 中使用最廣泛的 IPC 機(jī)制,正因?yàn)橛辛?Binder,Android 系統(tǒng)中形形色色的進(jìn)程與組件才能真正統(tǒng)一成有機(jī)的整體。Binder 通信機(jī)制與 TCP/IP 有共通之處,其組成元素可以這樣來類比:

  • binder 驅(qū)動 -> 路由器
  • Service Manager -> DNS
  • Binder Client -> 客戶端
  • Binder Server -> 服務(wù)器

Binder 的本質(zhì)目標(biāo)就是客戶端要與服務(wù)器通信,但由于是不同的進(jìn)程,必須通過 binder 驅(qū)動(路由器)把請求正確投遞到對方進(jìn)程中,所以通信的進(jìn)程需要持有一個唯一的 Binder 標(biāo)志(IP 地址)。

而 Binder 標(biāo)志可能是會動態(tài)更新的 "IP 地址",對通信進(jìn)程來說獲取難度較大且可讀性差,這就需要一個 Service Manager(DNS)來解決這個問題。但 Service Manager 自身也是一個 Binder Server(服務(wù)器),怎么找到它的 "IP 地址"呢?Binder 機(jī)制對此做了特別規(guī)定:Service Manager 在 Binder 通信過程中的唯一標(biāo)志永遠(yuǎn)是 0。

binder 驅(qū)動

binder 驅(qū)動運(yùn)行在內(nèi)核態(tài),向上層提供 /dev/binder 設(shè)備節(jié)點(diǎn),并不對應(yīng)真實(shí)的硬件設(shè)備。binder 驅(qū)動的注冊邏輯在 Binder.c 中:

//drivers/staging/android/Binder.c
static init __init binder_init(void){
    ...
    ret = misc_register(&binder_miscdev); //注冊為 misc 驅(qū)動
}

binder_miscdev 即 Binder 設(shè)備描述如下:

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR, //自動分配次設(shè)備號
    .name = "binder", //驅(qū)動名稱
    .fops = &binder_fops //binder 驅(qū)動支持的文件操作
}

binder_fops 為 Binder 設(shè)備支持的操作函數(shù),如下:

static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .mmap = binder_mmap,
    .open = binder_open,
    .flush = binder_flush,
    .release = binder_release,
};

與 Ashmem 設(shè)備類似,最關(guān)鍵的是 binder_open()、binder_mmap()、binder_ioctl(),下面分別介紹這三個函數(shù)。

binder_open

用戶應(yīng)用程序通過 Binder 通信時,需先調(diào)用 binder_open() 方法打開 binder 驅(qū)動,binder_open() 中主要做了兩個工作,對應(yīng)的分為兩部分來看:

//binder.c
static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
    ...
    proc = kzalloc(sizeof(*proc), GFP_KERNEL); //創(chuàng)建 binder_proc
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo); //初始化 todo 隊(duì)列
    init_waitqueue_head(&proc->wait); //初始化 todo 隊(duì)列
    proc->default_priority = task_nice(current);

上面代碼的主要工作是 創(chuàng)建及初始化 binder_proc,binder_proc 就是用來存放 binder 相關(guān)數(shù)據(jù)的結(jié)構(gòu)體,每個進(jìn)程獨(dú)有一份。

    binder_lock(__func__);
    binder_stats_created(BINDER_STAT_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;
    binder_unlock(__func__);
    ...
}

第二個主要工作是 將 binder_proc 記錄起來,方便后續(xù)使用,如上代碼所示,通過 hlist_add_head() 方法將 binder_proc 記錄到了內(nèi)核的 binder_procs 表中,另外還將 binder_proc 存放在 filp 的 private_data 域,以便于在后續(xù)調(diào)用 mmap、ioctl 等方法時獲取。

binder_mmap

對于 binder 驅(qū)動來說,上層應(yīng)用調(diào)用的 mmap() 最終會執(zhí)行到 binder_mmap() 方法,binder_mmap() 的主要工作是將上層應(yīng)用的虛擬內(nèi)存塊和 Binder 申請的物理內(nèi)存塊建立映射,應(yīng)用程序和 Binder 就擁有了共享的內(nèi)存空間,這樣不同的應(yīng)用程序之間可以通過 Binder 實(shí)現(xiàn)數(shù)據(jù)共享。舉個例子:

  • Binder 中有一物理內(nèi)存塊 P;A 進(jìn)程中有一內(nèi)存塊 a ;B 進(jìn)程中有一內(nèi)存塊 b
  • 將 P 分別與 a、b 建立映射,這樣 a、b 就可以看作同一塊內(nèi)存
  • 若 A 進(jìn)程想要發(fā)送數(shù)據(jù)給 B 進(jìn)程,只需將數(shù)據(jù)拷貝到 a 內(nèi)存,B 進(jìn)程就能直接讀取到了

所以 Binder 只需一次拷貝,binder_mmap() 要做的就是將 P 與 a 建立映射,該方法代碼較長,分段看關(guān)鍵部分代碼:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma){
    struct vm_struct *area;
    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;
    //檢查 vma 是否被禁用
    if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
          ret = -EPERM;
          failure_string = "bad vm_flags";
          goto err_bad_arg;
    }
  • vma(vm_area_struct) 是用戶態(tài)虛擬內(nèi)存地址空間,也就是 a
  • area(vm_struct) 是內(nèi)核態(tài)虛擬地址空間,指向 P
  • proc(binder_proc) 即 binder_open() 中創(chuàng)建的、存放 binder 相關(guān)數(shù)據(jù)的結(jié)構(gòu)體
  • 另外還做了限制映射空間至多 4M 等映射規(guī)則的檢查和處理
     mutex_lock(&binder_mmap_lock);
    //檢查是否已執(zhí)行過 binder_mmap 映射過
    if (proc->buffer) { 
          ret = -EBUSY;
          failure_string = "already mapped";
          goto err_already_mapped;
    }
    //申請內(nèi)核虛擬內(nèi)存地址空間
    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;
    }
    //將內(nèi)核虛擬內(nèi)存地址記錄在 proc 中 
    proc->buffer = area->addr;
    //記錄用戶態(tài)虛擬內(nèi)存地址和內(nèi)核態(tài)虛擬內(nèi)存地址的偏移量
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
    mutex_unlock(&binder_mmap_lock);
  • proc->buffer 用于存儲最終映射的內(nèi)核態(tài)虛擬地址,并通過此變量控制只能映射一次
  • get_vm_area() 方法申請了與用戶態(tài)空間大小一致的內(nèi)核態(tài)虛擬地址空間,注意此時還沒分配實(shí)際的物理內(nèi)存
  • proc->user_buffer_offset 記錄了用戶態(tài)虛擬內(nèi)存和內(nèi)核態(tài)虛擬內(nèi)存地址的偏移量,這樣后續(xù)方便獲取用戶態(tài)虛擬內(nèi)存地址
    //分配存放物理頁地址的數(shù)組
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    proc->buffer_size = vma->vm_end - vma->vm_start;
    //申請一頁物理內(nèi)存
    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;
    }
    //最后的收尾工作:將內(nèi)存記錄到相應(yīng)鏈表中,設(shè)置狀態(tài)等
    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;
    proc->files = get_files_struct(current);
    proc->vma = vma;
  • proc->pages 是一個二維指針,用于存放管理物理頁面
  • binder_update_page_range() 方法真正的申請物理頁面,并分別映射到內(nèi)核態(tài)和用戶態(tài)的虛擬內(nèi)存地址空間

至此 binder_mmap 方法執(zhí)行結(jié)束,書中并沒有對 binder_update_page_range() 方法具體展開介紹,但個人認(rèn)為此方法代碼非常有助于我們理解頁框以及與虛擬內(nèi)存地址的映射邏輯,所以繼續(xù)分析,先了解此方法的參數(shù):

  • proc:申請內(nèi)存的進(jìn)程所持有的 binder_proc 對象
  • allocate:1 表示申請內(nèi)存,0 表示釋放內(nèi)存
  • start:虛擬內(nèi)存地址起點(diǎn)
  • end:虛擬內(nèi)存地址終點(diǎn)
  • vma:用戶態(tài)虛擬內(nèi)存地址空間
static int binder_update_page_range(struct binder_proc *proc, int allocate,
                    void *start, void *end,
                    struct vm_area_struct *vma){
    if (allocate == 0)  //區(qū)分是申請還是釋放
         goto free_range;
    //依據(jù) start、end 循環(huán)分配物理頁
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
          //每次分配 1 個頁框*/
         *page = **alloc_page**(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
         //將頁框映射到內(nèi)核態(tài)虛擬內(nèi)存地址
         ret = **map_kernel_range_noflush**((unsigned long)page_addr, PAGE_SIZE, PAGE_KERNEL, page);
         //根據(jù) binder_mmap 方法中記錄的偏移量計算出用戶態(tài)虛擬內(nèi)存地址
         user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
         //將頁框映射到用戶態(tài)虛擬內(nèi)存地址
         ret = vm_insert_page(vma, user_page_addr, page[0]);
     }
     return 0;

binder_mmap() 的 allocate 參數(shù)傳入 1 為申請內(nèi)存,執(zhí)行上面的代碼。若為釋放則執(zhí)行以下代碼:

free_range:
    //依據(jù) start、end 從后往前遍歷
    for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        if (vma)
            //解除用戶態(tài)虛擬地址和物理頁框的映射
            zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
        //解除內(nèi)核態(tài)虛擬地址和物理頁框的映射
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
        //釋放頁框物理內(nèi)存
        __free_page(*page);
        *page = NULL;
}

binder_ioctl

binder 驅(qū)動并不提供常規(guī)的 read()、write() 等文件操作,全部通過 binder_ioctl() 實(shí)現(xiàn),所以 binder_ioctl() 是 binder 驅(qū)動中工作量最大的一個,它承擔(dān)了 binder 驅(qū)動的大部分業(yè)務(wù)。這里不深入分析源碼,只列出 binder_ioctl() 支持的命令列表:

命令 說明
BINDER_WRITE_READ 向 binder 驅(qū)動寫入或讀取數(shù)據(jù)
BINDER_SET_MAX_THREADS 設(shè)置支持的最大線程數(shù)
BINDER_SET_CONTEXT_MGR Service Manager 專用的注冊命令
BINDER_THREAD_EXIT 通知 binder 驅(qū)動某線程退出,釋放相應(yīng)資源
BINDER_VERSION 獲取 Binder 版本號

其中 BINDER_WRITE_READ 最為關(guān)鍵,分為若干子命令:

命令 說明
BC_INCREFS、BC_ACQUIRE、BC_RELEASE、BC_DECREFS 管理 binder_ref 的引用計數(shù)
BC_INCREFS_DONE、BC_ACQUIRE_NODE 管理 binder_node 的引用計數(shù)
BC_FREE_BUFFER 釋放 Binder 內(nèi)存緩沖區(qū)
BC_TRANSACTION 向 binder 驅(qū)動發(fā)送通信數(shù)據(jù)(主動調(diào)用)
BC_REPLY 向 binder 驅(qū)動發(fā)送通信數(shù)據(jù)(返回結(jié)果)
BC_REGISTER_LOOPER、BC_ENTER_LOOPER、BC_EXIT_LOOPER 設(shè)置 Binder looper 狀態(tài)
BC_REQUEST_DEATH_NOTIFICATION 注冊 Binder 死亡通知
BC_CLEAR_DEATH_NOTIFICATION 清除 Binder 死亡通知
BC_DEAD_BINDER_DONE 告知 Binder 已處理完 Binder 死亡通知

以上均為 binder 驅(qū)動作為接收方 binder_ioctl() 方法接收的命令,還有一些與之對應(yīng)的 BR_ 開頭的命令,由 binder 驅(qū)動主動發(fā)出,比如 BR_TRANSACTION、BR_REPLY,在一次 IPC 調(diào)用中是這樣應(yīng)用的:


Service Manager

Service Manager 是為了完成 Binder Server Name(域名)和 Service Handle(IP 地址)之間對應(yīng)關(guān)系的查詢而存在的,可以推測它主要包含的功能:
注冊:當(dāng)一個 Binder Server 創(chuàng)建后,應(yīng)該將這個 Server 的 name 和 Handle 對應(yīng)關(guān)系記錄到 Service Manager 中
查詢:其他應(yīng)用可以根據(jù) Server 的 name 查詢到對應(yīng)的 Service Handle

啟動

Android 系統(tǒng)第一個啟動的 init 進(jìn)程解析 init.rc 腳本時構(gòu)建出系統(tǒng)的初始運(yùn)行狀態(tài),Android 系統(tǒng)服務(wù)程序大多是在這個腳本中描述并被相繼啟動的,包括 zygote、mediaserver、surfaceflinger 以及 servicemanager 等,其中 servicemanager 描述如下:

#init.rc
service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

可以看到,當(dāng) servicemanager 發(fā)生問題重啟時,其他 healthd、zygote、media 等服務(wù)也會被重啟。servicemanager 服務(wù)啟動后執(zhí)行 service_manager.c 的 main 函數(shù),關(guān)鍵代碼如下:

//frameworks/native/cmds/servicemanager/service_manager.c
int main(){
    struct binder_state *bs;
    bs = binder_open(128*1024);
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    ...
    binder_loop(bs, svcmgr_handler);
    return 0;
}

其中三個函數(shù)對應(yīng)了 servicemanager 初始化的三個關(guān)鍵工作:

  1. binder 驅(qū)動并映射內(nèi)存塊大小為 128KB :binder_open()
  2. 將自己設(shè)置為 Binder "DNS" 管理者 :binder_become_context_manager()
  3. 進(jìn)入循環(huán),等待 binder 驅(qū)動發(fā)來消息 :binder_loop()

下面分別來分析這三個函數(shù),binder_open() 關(guān)鍵代碼如下:

struct binder_state *binder_open(size_t mapsize){
    struct binder_state *bs;
    struct binder_version vers;
    bs = malloc(sizeof(*bs));
    ...
    //打開 binder 驅(qū)動,最終調(diào)用 binder_open() 函數(shù)
    bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    ...
    //獲取 Binder 版本,最終調(diào)用 binder_ioctl() 函數(shù)
    ioctl(bs->fd, BINDER_VERSION, &vers)
    ...
    //將虛擬內(nèi)存映射到 Binder,最終調(diào)用 binder_mmap() 函數(shù)
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    ...
    return bs;
}

binder_become_context_manager() :

int binder_become_context_manager(struct binder_state *bs){
    //發(fā)送 BINDER_SET_CONTEXT_MGR 命令,最終調(diào)用 binder_ioctl() 函數(shù)
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

binder_loop() 關(guān)鍵代碼如下:

void binder_loop(struct binder_state *bs, binder_handler func){
    int res;
    //執(zhí)行 BINDER_WRITE_READ 命令所需的數(shù)據(jù)格式:
    struct binder_write_read bwr;
    uint32_t readbuf[32]; //每次讀取數(shù)據(jù)的大小
    readbuf[0] = BC_ENTER_LOOPER; 
    //先將 binder 驅(qū)動的進(jìn)入循環(huán)命令發(fā)送給 binder 驅(qū)動:
    binder_write(bs, readbuf, sizeof(uint32_t));
    for (;;) { //進(jìn)入循環(huán)
        bwr.read_size = sizeof(readbuf);
        bwr.read_buffer = (uintptr_t) readbuf; //讀取到的消息數(shù)據(jù)存儲在 readbuf
        //執(zhí)行 BINDER_WRITE_READ 命令讀取消息數(shù)據(jù)
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
        //處理讀取到的消息數(shù)據(jù)
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        ...
    }
}

BINDER_WRITE_READ 命令既可以用來讀取數(shù)據(jù)也可以寫入數(shù)據(jù),具體是寫入還是讀取依賴 binder_write_read 結(jié)構(gòu)體的 write_size 和 read_size 哪個大于 0,上面代碼通過 bwr.read_size = sizeof(readbuf) 賦值,所以是讀取消息。

binder_parse() 方法內(nèi)部處理由 binder 驅(qū)動主動發(fā)出的、一系列 BR_ 開頭的命令,包括上面提到過的 BR_TRANSACTION、BR_REPLY 等,簡化后的代碼如下:

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func){
    switch(cmd) {
        case BR_TRANSACTION: {
            ...
            res = func(bs, txn, &msg, &reply); //處理消息
            //返回處理結(jié)果
            inder_send_reply(bs, &reply, txn->data.ptr.buffer, res); 
            ...
            break;
        }
        case BR_REPLY: {...}
        case BR_DEAD_BINDER: {...}
        ...
    }
}

對于 BR_TRANSACTION 命令主要做了兩個工作,一是調(diào)用 func() 具體處理消息;二是調(diào)用 inder_send_reply() 將消息處理結(jié)果告知給 binder 驅(qū)動,注意這里的 func 是由 service_manager.c main 函數(shù)中傳過來的方法指針,也就是 svcmgr_handler() 方法。

注冊與查詢

經(jīng)過上面 Service Manager 服務(wù)啟動的過程分析,已經(jīng)知道由 binder 驅(qū)動主動發(fā)過來的 BR_TRANSACTION 命令最終在 service_manager.c 的 svcmgr_handler() 方法中處理,那服務(wù)的注冊與查詢請求想必就是在這個方法中實(shí)現(xiàn)的了,確實(shí)如此,簡化后的關(guān)鍵代碼如下:

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply){
    switch(txn->code) {
         case SVC_MGR_GET_SERVICE:
         case SVC_MGR_CHECK_SERVICE:
              //查詢服務(wù),根據(jù) name 查詢 Server Handle
              handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
              return 0;
         case SVC_MGR_ADD_SERVICE:
             //注冊服務(wù),記錄服務(wù)的 name(下面的參數(shù) s) 與 handle
             if (do_add_service(bs, s, len, handle, txn->sender_euid,
                 allow_isolated, txn->sender_pid))
                 return -1;
             break;
         case SVC_MGR_LIST_SERVICES: {
             //查詢所有服務(wù),返回存儲所有服務(wù)的鏈表 svclist
             si = svclist;
             while ((n-- > 0) && si)
                 si = si->next;
             if (si) {
                 bio_put_string16(reply, si->name);
                 return 0;
             }
             return -1;
    }
    bio_put_uint32(reply, 0);
    return 0;
}

其中 bio_XX 系列函數(shù)的作用是方便讀寫數(shù)據(jù)。注冊的服務(wù)都會存儲在 svclist 鏈表上,do_find_service() 方法遍歷 svclist 查找對應(yīng)的服務(wù),do_add_service() 則是將服務(wù)插入到 svclist 鏈表上記錄下來。

svcmgr_handler() 方法執(zhí)行完后會進(jìn)一步調(diào)用 inder_send_reply() 將執(zhí)行結(jié)果回復(fù)給 binder 驅(qū)動,然后進(jìn)入下一輪的循環(huán)繼續(xù)等待處理消息。

最后

非常認(rèn)可書中一個觀點(diǎn):或許我們不應(yīng)該從已經(jīng)完善的架構(gòu)類圖去推導(dǎo)它們各自的作用,而是從 Binder 設(shè)計者的角度出發(fā),去思考如果要提供某個功能,應(yīng)該怎么做?

通過上面的分析知道,Service Manager 開始工作后等待執(zhí)行 binder 驅(qū)動發(fā)來的命令就行了,那其他進(jìn)程如何訪問 Service Manager 呢?無非就是以下步驟:

  • 1.打開 binder 驅(qū)動,將進(jìn)程內(nèi)虛擬內(nèi)存塊與 binder 驅(qū)動進(jìn)行 mmap
  • 2.通過 ioctl 通信,讓 binder 驅(qū)動向 Service Manager 發(fā)送命令
  • 3.binder 驅(qū)動返回結(jié)果

不要懷疑,核心工作確實(shí)只有這些,只是還有一些細(xì)節(jié)待商榷,比如:

  • 打開并映射 binder 驅(qū)動需占用進(jìn)程內(nèi)存,可以限制一下打開次數(shù)或映射大小
  • binder 驅(qū)動的操作比較繁瑣,native 層應(yīng)該封裝一下
  • Java 層也需要封裝接口,方便上層應(yīng)用調(diào)用...

然后 ProcessState、IPCThreadState、BBinder、BpBinder 等等相繼出現(xiàn)...

鏈接:細(xì)讀《深入理解 Android 內(nèi)核設(shè)計思想》系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容