Android之IPC4————Bander1 概述與Bander驅動
Android之IPC4————Bander1 概述與Bander驅動
文章目錄
一.概述
最近才看AndroidIPC中,Binder一直是繞不過的坎,他是AndroidIPC很重要的一種方式,在Android系統中也有著舉足輕重的作用。在之前的部落格裡,特別是AIDL中,我們只是說了AIDL實際上是實現的binder的介面。也在文章的最後簡單說了一下,binder是如何進行資料通訊的。但是由於Binder的封裝,我還是沒有發現,binder是如何做的跨程序通訊的。
在這段時間裡,我也是翻看了許多資料,也查看了許多部落格。但是,多數部落格,要不然講的只是Frameworks層,要不然只是雲裡霧裡長篇大論,要不然是很簡單的描述了一下大體框架。
不過,後來發現一位大神的部落格,跟著他的思考路看原始碼,從Driver層看到native層,再從native看到Frameworks層,看的腦袋很暈,但是收穫也很大。所以想將看的過程記錄下來,做一個學習筆記。
二.儲備知識
在正式講之前,先簡單介紹一些基礎知識
1.程序空間的劃分
每個Android的程序中,都只能執行在自己程序所擁有的虛擬地址空間,對應一個4GB的虛擬地址空間,其中3GB是使用者空間,1GB是核心空間。當然兩者的大小時可以程序跳轉的。
使用者空間:不同程序之間無法共享
核心空間: 不同程序之間可以共享
2.程序隔離和跨程序通訊
程序隔離: 為了保證安全性和獨立性,一個程序不能直接操作和訪問另一個程序
程序間通訊: 不同程序之間傳遞資料
3.Android的框架
讓我們來看一看,Android的整體框架
從下往上依次為:
- 核心層:Linux核心和各類硬體裝置的驅動,BInderIPC驅動也就是在這一層
- 硬體抽象層:封裝核心層的硬體驅動,提供系統服務層統一的硬體介面
- 系統層:提供核心服務,並且提供可供「應用程式框架層」呼叫的介面
- BinderIPC層:作為系統服務層與應用程式框架的IPC橋樑,互相傳遞介面呼叫的資料,實現跨進層的停下
- 應用程式框架層:這一層可以理解為 Android SDK,提供四大元件,View 繪製體系等平時開發中用到的基礎部件
在上面的層次裡,核心層與硬體抽象層均使用C/C++實現,系統服務是以Java實現,硬體層抽象編譯為so檔案,以jni的形式提供給系統服務層使用,系統服務層中的服務隨系統的啟動而啟動,這些服務提供給手機諸如,簡訊接收,電話的接聽,Activity的管理等等功能。每一個服務都執行在一個獨立程序的Dalvik虛擬機器中,那麼問題來了,開發者的app執行在一個新的程序空間,如果呼叫系統服務層中的介面呢?答案就是ipc,而Android中大多數的ipc都是通過Binder實現的。
三.Binder概述
1.Binder是什麼
Binder中文意思為粘合劑,意思是粘合了兩個不同的程序
而在不同的語境下,Binder有不同的含義。
- 從機制,模型來說,Binder是指Android中實現Ipc的一種方式。
- 從模型的結構中來說,Binder來說是一種虛擬的物理層裝置,即Binder驅動。
- 從程式碼的角度來說,Binder是一個類,實現類IBInder介面。
2.Binder的優勢
Android是基於linux的作業系統,而作業系統中已經有了多種IPC機制,為什麼還要Binder機制?
看看Linux中現有的IPC機制:
- 管道:在建立是分配一個page大小的記憶體,快取區比較有限
- 訊息佇列:資訊複製兩次,有額外的CPU消耗,不適合頻繁或者資訊量大的通訊
- 共享記憶體:無需複製,共享緩衝區直接付附加到程序虛擬地址空間,速度快;但程序間的同步問題作業系統無法實現,必須各程序利用同步工具解決;
- socket:作為更通用的介面,傳輸效率低,主要用於不通機器或跨網路的通訊;
多個角度說明為什麼使用Binder:
- 從效能上來說:Binder資料拷貝只需要一次,而管道,訊息記憶體,Socket都需要兩次,共享記憶體不需要。從效能上來說,Binder效能僅次於共享記憶體。
- 從穩定性來說,Binder基於c/s架構,架構清晰,穩定性較好
- 從安全性來說,Binder中,客戶端和服務端通訊時,會根據UID/PID進行一次安全檢查
- 從語言來說,linux中的機制都是基於c,也就是說面向過程。而android是基於Java,binder也正好是符合面向物件的思想。
3.Binder原理
binder通訊採用C/S架構,從元件的角度來說,Binder分為Client,Service,ServiceManager,binder驅動。構架圖如下:
圖中處理客戶端和服務端外,還有兩個角色,即ServiceManager和BInder驅動。下面分別簡單介紹一下。
- ServiceManager:此處的Service並非指framework層的,而是指Native層的。它是整個Binder通訊機制的大管家。它的作用就是給服務端提供註冊服務,給客戶端提供獲取服務的功能。
- Binder驅動,binder驅動是一種虛擬字元裝置,沒有直接操作硬體。它的作用是連線服務端程序,客戶端程序,ServiceManager的橋樑。它提供了4個方法。驅動裝置的初始化(binder_init),開啟 (binder_open),對映(binder_mmap),資料操作(binder_ioctl)
圖中出現了IPC時需要的三步,即:
- 註冊服務:Server程序要先註冊Service到ServiceManager。該過程:Server是客戶端,ServiceManager是服務端。
- 獲取服務:Client程序使用某個Service前,須先向ServiceManager中獲取相應的Service。該過程:Client是客戶端,ServiceManager是服務端。
- 使用服務:Client根據得到的Service資訊建立與Service所在的Server程序通訊的通路,然後就可以直接與Service互動。該過程:client是客戶端,server是服務端。
圖中Client,Service,ServiceManager之間互動是虛線表示,但他們都處於不同的程序,所以他們之間並不是真正的互動,而是通過與Binder驅動進行互動,從而實現IPC通訊方式。
4.Binder框架
上面的圖主要是native層Binder的框架,下面的圖是Binder在android中整體框架
- 圖中紅色部分表示整個framwork層binder框架相關元件
- 藍色元件表示Native層Binder架構相關元件;
- 上層framework層的Binder邏輯是建立在Native層架構基礎之上的,核心邏輯都是交予Native層方法來處理。
Binder涉及的類
5. C/S模式
BpBinder(客戶端)和BBinder(服務端)都是Android中Binder通訊相關的代表,它們都從IBinder類中派生而來,關係圖如下:
- 客戶端:BpBinder.transact()來發送事物請求
- 服務端:BBinder.onTransact()會接收到相應事務。
四.Binder驅動
1.概述
Binder驅動是Android專用的,但底層的驅動框架和Linux驅動一樣,binder驅動在以misc裝置進行註冊,作為虛擬字元裝置,沒有直接操作硬體,只是對裝置記憶體的處理。主要操作有:
- 通過init()建立/dev/binder裝置節點
- 通過open()獲取Binder1驅動檔案描述符
- 通過mmap()在核心上分配一塊記憶體,用於存放資料
- 通過ioctl()將IPC資料作為引數傳遞給binder驅動
2.系統呼叫
使用者態程式呼叫Kernel層驅動是需要陷入核心態,進行系統呼叫(syscall),比如呼叫Binder驅動方法呼叫鏈為:open-> __open() -> binder_open()。 open()為使用者空間的方法,__open()便是系統呼叫中相應的處理方法,通過查詢,對應呼叫到核心binder驅動的binder_open()方法,至於其他的從使用者態陷入核心態的流程也基本一致。
簡單來說,當用戶空間呼叫open()方法,最終會呼叫binder驅動的binder_open()方法,mmap()/ioctl都是如此。
3.binder_open
binder_init的主要工作就是註冊misc裝置。並沒有什麼可說的,我們就直接從第二個方法來看。
binder_open()作用是開啟binder驅動,並進行如下過程:
- 建立binder_proc,將當前程序的資訊儲存儲存在binder_porc物件總,該物件管理IPC所需的各種資訊並具有其他結構體的根結構體,在把binder_proc物件儲存到檔案指標filp,以及把binder_proc加入到全域性連結串列binder_procs。
- binder_proc結構體中包含了程序節點,binder實體/引用/執行緒所組成紅黑樹的根節點,記憶體資訊,執行緒資訊等等
3. binder_mmap
主要功能:首先在核心虛擬地址空間中,申請一塊與使用者虛擬記憶體大小相同的記憶體。在申請一個page大小的實體記憶體,再講同一塊實體記憶體分別對映到核心虛擬地址空間個使用者虛擬記憶體空間,從而實現了使用者空間的Buffer和核心空間的Buffer同步操作的功能。最後建立一塊Binder_buffer物件,並放入當前binder_proc的proc->buffers連結串列。
上圖就是,使用mmap後的記憶體機制,虛擬程序地址空間(vm_area_struct))和虛擬核心地址空間(vm_struct)都對映到同一塊實體記憶體空間。當客戶端和服務端傳送資料是,客戶端先從自己的程序空間吧ipc通訊資料複製到核心空間,而Server端作為資料接受端與核心共享資料,所以不需要在拷貝資料,而是通過記憶體地址的偏移量,即可獲得記憶體地址。整個過程只發送一次記憶體複製。
比較關注的一個點就在,在這其中,空閒的記憶體塊和已用的記憶體塊都是用紅黑樹記錄的。
4.binder_ioctl
binder_ioctl()函式負責在兩個程序間收發IPC資料和IPC reply資料
ioctl(檔案描述符,ioctl命令,資料型別)
- 檔案描述符:是通過open()方法開啟的binder核心後的返回層。
- ioctl命令
header 1 | header 2
row 1 col 1 | row 1 col 2
row 2 col 1 | row 2 col 2
ioctl命令 | 資料型別 | 操作 | 使用場景 |
---|---|---|---|
BINDER_WRITE_READ | struct binder_write_read | 收發Binder IPC資料 | binder讀寫互動場景 |
BINDER_SET_MAX_THREADS | __u32 | 設定Binder執行緒最大個數 | 初始化ProcessState物件,初始化ProcessState物件 |
BINDER_SET_CONTEXT_MGR | __s32 | 設定Service Manager節點 | servicemanager程序成為上下文管理 |
BINDER_THREAD_EXIT | __s32 | 釋放Binder執行緒 | |
BINDER_VERSION | struct binder_version | 獲取Binder版本資訊 | 初始化ProcessState |
BINDER_SET_IDLE_TIMEOUT | __s64 | 沒有使用 | |
BINDER_SET_IDLE_PRIORITY | __s32 | 沒有使用 |
這一塊,簡單介紹了binder核心,包括binder核心提供的四個方法和binder是什麼。更詳細的內容,深入到原始碼層的分析可以檢視文章:Binder系列1—Binder Driver初探
五.啟動ServiceManager
接下來來看binder機制另一個很重要的角色,ServiceManager是客戶端和服務端溝通的橋樑。首先看看ServiceManager的啟動。
ServiceManager是通過init程序通過解析init.rc檔案,而建立的,所對應的可執行程式/system/bin/servicemanager,所對應的原始檔是service_manager.c,程序名為/system/bin/servicemanager。
1.啟動過程
啟動Service Manager的入口函式是service_manager.c中的main()方法,程式碼如下:
int main(int argc, char **argv) {
struct binder_state *bs;
//開啟binder驅動,申請128k位元組大小的記憶體空間
bs = binder_open(128*1024);
...
//成為上下文管理者
if (binder_become_context_manager(bs)) {
return -1;
}
selinux_enabled = is_selinux_enabled(); //selinux許可權是否使能
sehandle = selinux_android_service_context_handle();
selinux_status_open(true);
if (selinux_enabled > 0) {
if (sehandle == NULL) {
abort(); //無法獲取sehandle
}
if (getcon(&service_manager_context) != 0) {
abort(); //無法獲取service_manager上下文
}
}
...
//進入無限迴圈,處理client端發來的請求
binder_loop(bs, svcmgr_handler);
return 0;
}
由上面的程式碼可以看到,啟動過程主要涉及以下幾個階段:
- 開啟binder驅動 binder_open
- 註冊成為binder服務的大管家:binder_become_context_manager;
- 進入死迴圈,處理client端發來的請求:binder_loop
我們一點點來看
2.開啟binder驅動
struct binder_state *binder_open(size_t mapsize)
{
struct binder_state *bs;【見小節2.2.1】
struct binder_version vers;
bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return NULL;
}
//通過系統呼叫陷入核心,開啟Binder裝置驅動
bs->fd = open("/dev/binder", O_RDWR);
if (bs->fd < 0) {
goto fail_open; // 無法開啟binder裝置
}
//通過系統呼叫,ioctl獲取binder版本資訊
if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
goto fail_open; //核心空間與使用者空間的binder不是同一版本
}
bs->mapsize = mapsize;
//通過系統呼叫,mmap記憶體對映,mmap必須是page的整數倍
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
if (bs->mapped == MAP_FAILED) {
goto fail_map; // binder裝置記憶體無法對映
}
return bs;
fail_map:
close(bs->fd);
fail_open:
free(bs);
return NULL;
}
開啟binder驅動的先關操作:
先呼叫open()開啟binder裝置,open即上一節所說的binder核心提供的方法之一,最終會呼叫binder核心中的binder_open(),其方法作用間上一節.
之後呼叫mmap()方法進行記憶體對映。
3.註冊成為binder服務的大管家
int binder_become_context_manager(struct binder_state *bs) {
//通過ioctl,傳遞BINDER_SET_CONTEXT_MGR指令【見小節2.3.1】
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
在這裡面用過呼叫ioctl方法,傳送 BINDER_SET_CONTEXT_MGR,最終經過系統呼叫,進入binder驅動層的binder_ioctl()方法.
static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
int ret = 0;
struct binder_proc *proc = filp->private_data;
kuid_t curr_euid = current_euid();
//保證只建立一次mgr_node物件
if (binder_context_mgr_node != NULL) {
ret = -EBUSY;
goto out;
}
if (uid_valid(binder_context_mgr_uid)) {
...
} else {
//設定當前執行緒euid作為Service Manager的uid
binder_context_mgr_uid = curr_euid;
}
//建立ServiceManager實體
binder_context_mgr_node = binder_new_node(proc, 0, 0);
...
binder_context_mgr_node->local_weak_refs++;
binder_context_mgr_node->local_strong_refs++;
binder_context_mgr_node->has_strong_ref = 1;
binder_context_mgr_node->has_weak_ref = 1;
out:
return ret;
}
建立一個ServiceManager實體
static struct binder_node *binder_new_node(struct binder_proc *proc,
binder_uintptr_t ptr,
binder_uintptr_t cookie)
{
struct rb_node **p = &proc->nodes.rb_node;
struct rb_node *parent = NULL;
struct binder_node *node;
//首次進來為空
while (*p) {
parent = *p;
node = rb_entry(parent, struct binder_node, rb_node);
if (ptr < node->ptr)
p = &(*p)->rb_left;
else if (ptr > node->ptr)
p = &(*p)->rb_right;
else
return NULL;
}
//給新建立的binder_node 分配核心空間
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (node == NULL)
return NULL;
binder_stats_created(BINDER_STAT_NODE);
rb_link_node(&node->rb_node, parent, p);
rb_insert_color(&node->rb_node, &proc->nodes);
node->debug_id = ++binder_last_id;
node->proc = proc; //將open操作中建立的的proc賦值
node->ptr = ptr; //指向使用者空間binder_node的指標
node->cookie = cookie;
node->work.type = BINDER_WORK_NODE; //設定binder_work的type
INIT_LIST_HEAD(&node->work.entry);
INIT_LIST_HEAD(&node->async_todo);
return node;
}
在這裡面,在binder中建立ServiceManager實體()。並在建立是,將open操作中建立的proc,存入ServiceManager的proc中。
4.進入死迴圈中
void binder_loop(struct binder_state *bs, binder_handler func) {
int res;
struct binder_write_read bwr;
uint32_t readbuf[32];
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER;
//將BC_ENTER_LOOPER命令傳送給binder驅動,讓Service Manager進入迴圈
binder_write(bs, readbuf, sizeof(uint32_t));
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
//通過ioctl向binder驅動發起讀寫請求
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); //進入迴圈,不斷地binder讀寫過程
if (res < 0) {
break;
}
// 解析binder資訊
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
if (res == 0) {
break;
}
if (res < 0) {
break;
}
}
}
解析Binder資訊
...
//初始化reply
bio_init(&reply, rdata, sizeof(rdata), 4);
//從txn解析binder_io
bio_init_from_txn(&msg, txn);
//呼叫Service_manager中的svcmgr_handler,進行它的工作
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
...
根據binder驅動返回的資訊進行相應的操作
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: //查詢
s = bio_get_string16(msg, &len); //服務名
//根據名稱查詢相應服務
handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);
//【見小節3.1.2】
bio_put_ref(reply, handle);
return 0;
case SVC_MGR_ADD_SERVICE: //註冊
s = bio_get_string16(msg, &len); //服務名
handle = bio_get_ref(msg); //handle
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
//註冊指定服務
if (do_add_service(bs, s, len, handle, txn->sender_euid,
allow_isolated, txn->sender_pid))
return -1;
break;
case SVC_MGR_LIST_SERVICES: {
uint32_t n = bio_get_uint32(msg);
if (!svc_can_list(txn->sender_pid)) {
return -1;
}
si = svclist;
while ((n-- > 0) && si)
si = si->next;
if (si) {
bio_put_string16(reply, si->name);
return 0;
}
return -1;
}
default:
return -1;
}
bio_put_uint32(reply, 0);
return 0;
}
總結:在這個迴圈裡,ServiceManager通過ioctl向binder驅動發起讀寫請求,根據請求返回的資料,去進行相應的註冊服務和查詢服務。
六.總結
本篇文章中,先簡單介紹了一些作業系統中關於跨程序通訊的基礎知識,之後介紹了binder的優勢以及binder的框架。
然後介紹了binder核心,主要介紹了binder核心提供的4個方法
- init()註冊misc裝置
- open()開啟binder驅動。並建立binder_proc,並儲存在全域性連結串列中
- mmap()在記憶體中分配一塊記憶體,用於存放資料,實現是採用的是記憶體對映機制,將核心和Service對映在同一塊實體地址中
- ioctl()負責在兩個程序間傳遞轉發ipc資料和回覆資料
之後介紹了ServiceManager的啟動,ServiceManager是通過init程序載入init.rc檔案而建立的,建立步驟如下:
- 首先呼叫open()開啟binder驅動,並呼叫mmap()申請128k位元組大小的記憶體空間。
- 之後使用ioctl傳送BINDER_SET_CONTEXT_MGR,成為Service的管家,在這裡會在binder核心中建立一個ServiceManager實體,該實體是核心全域性變數,並把open操作中建立的proc,放入該實體中。
- 最後會啟動一個死迴圈,在這個迴圈中,ServiceManager通過ioctl不斷從binder核心中讀寫資料,並根據資訊進行相應的操作,即註冊服務,獲取服務。
之後的文章:
- BinderNative層分析
- Binderframework層的分析
- Binder通訊過程
- Binder執行緒池
- Binder的使用
- Binder總結
七.參考資料
Binder系列—開篇
Binder系列3—啟動ServiceManager
Android跨程序通訊:圖文詳解 Binder機制 原理
輕鬆理解 Android Binder,只需要讀這一篇