對冗余挑揀重點(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)鍵工作:
- binder 驅(qū)動并映射內(nèi)存塊大小為 128KB :binder_open()
- 將自己設(shè)置為 Binder "DNS" 管理者 :binder_become_context_manager()
- 進(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)...