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,只需要讀這一篇