上篇文章以 MediaPlayerService 爲例,分析了 Service 通過 Binder 驅動發送註冊請求給 servicemanager 的過程。本文在此基礎上分析 servicemanager 如何處理註冊請求,以及如何反饋處理結果給 MediaPlayerService。
1. servicemanager 被喚醒
在上一篇文章 Binder之請求註冊Service組件 中,分析到 binder_transaction 函數會創建一個待處理事務 t(事務類型是 BINDER_WORK_TRANSACTION),並將其添加到 servicemanager 進程的待處理工作隊列 target_list 中,然後喚醒睡眠中的 servicemanager 進程,如下所示:
代碼路徑:linux/drivers/staging/android/binder.c
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
......
list_add_tail(&t->work.entry, target_list);
....
if (target_wait)
wake_up_interruptible(target_wait);
......
}
在 Binder之守護進程servicemanager 這篇文章中,講到 servicemanager 進程通過 ioctl 系統調用檢查 Binder 驅動是否有進程間通信請求需要它來處理。如果沒有請求需要處理,那麼 servicemanager 進程會在 binder_thread_read 函數中調用 wait_event_freezable_exclusive 進入睡眠等待狀態。
代碼路徑:linux/drivers/staging/android/binder.c
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
void __user *buffer, int size,
signed long *consumed, int non_block)
....
if (wait_for_proc_work) {
......
if (non_block) {
if (!binder_has_proc_work(proc, thread))
ret = -EAGAIN;
} else
ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
} else {
......
}
當 servicemanager 進程被喚醒時,會通過 binder_has_proc_work 函數來檢查是否有新的請求需要處理,也就是檢查當前進程的待處理工作隊列 todo 是否爲空,如下所示:
代碼路徑:linux/drivers/staging/android/binder.c
static int binder_has_proc_work(struct binder_proc *proc,
struct binder_thread *thread)
{
return !list_empty(&proc->todo) ||
(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);
}
如果檢測到待處理工作隊列 todo 不爲空,因此退出睡眠狀態,接下來處理待處理工作隊列中的進程間通信請求。
2. Binder 驅動處理待處理事務 - servicemanager 內核空間
servicemanager 退出睡眠後,繼續執行 binder_thread_read 函數,將待處理事務從 Binder 驅動轉發到 servicemanager 用戶空間。過程如下所示:
代碼路徑:linux/drivers/staging/android/binder.c
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
void __user *buffer, int size,
signed long *consumed, int non_block)
{
......
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
if (!list_empty(&thread->todo))
w = list_first_entry(&thread->todo, struct binder_work, entry);
else if (!list_empty(&proc->todo) && wait_for_proc_work)
w = list_first_entry(&proc->todo, struct binder_work, entry); [1]
else {
if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
goto retry;
break;
}
if (end - ptr < sizeof(tr) + 4)
break;
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
t = container_of(w, struct binder_transaction, work); [2]
} break;
......
}
if (!t)
continue;
BUG_ON(t->buffer == NULL);
if (t->buffer->target_node) {
struct binder_node *target_node = t->buffer->target_node;
tr.target.ptr = target_node->ptr; [3]
tr.cookie = target_node->cookie; [4]
t->saved_priority = task_nice(current);
if (t->priority < target_node->min_priority &&
!(t->flags & TF_ONE_WAY))
binder_set_nice(t->priority);
else if (!(t->flags & TF_ONE_WAY) ||
t->saved_priority > target_node->min_priority)
binder_set_nice(target_node->min_priority);
cmd = BR_TRANSACTION;
} else {
......
}
tr.code = t->code;
tr.flags = t->flags;
tr.sender_euid = t->sender_euid;
if (t->from) {
struct task_struct *sender = t->from->proc->tsk;
tr.sender_pid = task_tgid_nr_ns(sender, [5]
current->nsproxy->pid_ns);
} else {
tr.sender_pid = 0;
}
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
tr.data.ptr.buffer = (void *)t->buffer->data +
proc->user_buffer_offset; [6]
tr.data.ptr.offsets = tr.data.ptr.buffer +
ALIGN(t->buffer->data_size, sizeof(void *)); [7]
if (put_user(cmd, (uint32_t __user *)ptr)) [8]
return -EFAULT;
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, sizeof(tr))) [9]
return -EFAULT;
ptr += sizeof(tr);
......
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t; [10]
} else {
......
}
break;
}
......
return 0;
}
整個過程實際上是初始化描述事務數據的結構體 binder_transaction_data,並通過 copy_to_user 函數將其傳遞給 servicemanager 用戶空間。
[1] 從待處理工作隊列 todo 中取出一個工作項 w。
[2] 由於工作項類型爲 BINDER_WORK_TRANSACTION,獲取待處理事務 t 作爲當前正在處理的事務。
[3] 由於 target_node 爲 binder_context_mgr_node,所以 target_node->ptr 值爲 NULL。
[4] 由於 target_node 爲 binder_context_mgr_node,所以 target_node->cookie 值爲 NULL。
[5] 將事務發起方的 pid 保存在 tr.sender_pid
中。
[6] 將通信數據 buffer 在內核空間的地址加上一個固定差值 proc->user_buffer_offset,得到通信數據 buffer 在 servicemanager 用戶空間的地址。
[7] 通過通信數據 buffer 在 servicemanager 用戶空間的地址,得到偏移數組在 servicemanager 用戶空間的地址。
拷貝有深拷貝和淺拷貝之分:深拷貝需要重新分配內存資源,並且將內容完整的拷貝到分配好的內存空間;淺拷貝只是複製指向同一內存空間的地址,不需要分配新的內存資源。上述過程通過淺拷貝,實現了內存在內核空間和用戶空間之間的共享,詳細過程可以參考 Binder之守護進程servicemanager 中的
打開和映射 Binder 設備文件
小節。
[8] 調用 put_user 系統函數將協議 BR_TRANSACTION 拷貝回 servicemanager 用戶空間。
[9] 調用 copy_to_user 系統函數將初始化好的結構體 binder_transaction_data 拷貝回 servicemanager 用戶空間。
[10] 將當前處理事務 t 添加到 thread->transaction_stack 中,下面流程中還會用到這個事務。
3. servicemanager 處理註冊請求 - servicemanager 用戶空間
binder_thread_read 函數執行完返回到 binder_ioctl 函數,然後 ioctl 系統調用返回到 binder_loop 函數中,也就是從 Binder 驅動重新回到了 servicemanager 用戶空間。接下來調用 binder_parse 函數解析從 Binder 驅動程序拷貝回來的 binder_transaction_data 結構體,將解析得到的 MediaPlayerService 名稱以及句柄值保存到結構體 svcinfo,然後將結構體 svcinfo 添加到 svclist 鏈表中。詳細過程請參考 Binder之守護進程servicemanager 中的 servicemanager 如何提供服務
小節。
當 Client 進程需要獲取某個 Service 的代理對象時,servicemanager 根據服務名稱遍歷 svclist 鏈表,然後將對應的句柄值返回給 Client 進程,最後 Client 進程根據句柄值獲得 Service 的代理對象。
servicemanager 處理完註冊請求後,繼續調用 binder_send_reply 函數返回處理結果。binder_send_reply 函數定義如下:
代碼路徑:frameworks/native/cmds/servicemanager/binder.c
void binder_send_reply(struct binder_state *bs,
struct binder_io *reply,
void *buffer_to_free,
int status)
{
struct {
uint32_t cmd_free;
void *buffer;
uint32_t cmd_reply;
struct binder_txn txn;
} __attribute__((packed)) data; [1]
data.cmd_free = BC_FREE_BUFFER; [2]
data.buffer = buffer_to_free;
data.cmd_reply = BC_REPLY; [3]
data.txn.target = 0;
data.txn.cookie = 0;
data.txn.code = 0;
if (status) {
data.txn.flags = TF_STATUS_CODE;
data.txn.data_size = sizeof(int);
data.txn.offs_size = 0;
data.txn.data = &status;
data.txn.offs = 0;
} else {
data.txn.flags = 0;
data.txn.data_size = reply->data - reply->data0;
data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0);
data.txn.data = reply->data0;
data.txn.offs = reply->offs0;
}
binder_write(bs, &data, sizeof(data)); [4]
}
[1] 定義結構體 data 用於保存返回給 Binder 驅動的內容。其中變量 cmd_free 用於保存釋放內存的協議,指針 buffer 保存需要釋放的內存地址。變量 cmd_reply 用於保存返回協議,結構體 binder_txn 用於保存請求處理返回結果。
[2] 釋放內存協議爲 BC_FREE_BUFFER。註冊請求處理完後,需要釋放之前 Binder 驅動在 binder_transaction 函數中分配的用於保存通信數據的內存。
[3] 返回協議爲 BC_REPLY。
[4] 調用 binder_write 函數將結構體 data 傳遞給 Binder 驅動,binder_write 函數內部也是通過系統調用 ioctl(bs->fd, BINDER_WRITE_READ, &bwr)
來與 Binder 驅動進行交互。
4. Binder 驅動將註冊結果返回給 Service - servicemanager 內核空間
上述 ioctl 系統調用最終也會執行到 Binder 驅動的 binder_ioctl 函數。通過之前幾篇文章分析可知,binder_ioctl 中會調用 binder_thread_write 函數,後者又會調用 Binder 驅動的核心處理函數 binder_transaction,需要注意的是此時參數 reply 的值爲 true。接下來看看這個過程:
代碼路徑:linux/drivers/staging/android/binder.c
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
struct binder_work *tcomplete;
size_t *offp, *off_end;
struct binder_proc *target_proc;
struct binder_thread *target_thread = NULL;
struct binder_node *target_node = NULL;
struct list_head *target_list;
wait_queue_head_t *target_wait;
struct binder_transaction *in_reply_to = NULL;
......
if (reply) {
in_reply_to = thread->transaction_stack; [1]
......
thread->transaction_stack = in_reply_to->to_parent; [2]
target_thread = in_reply_to->from; [3]
......
target_proc = target_thread->proc; [4]
} else {
......
}
if (target_thread) {
e->to_thread = target_thread->pid;
target_list = &target_thread->todo; [5]
target_wait = &target_thread->wait; [6]
} else {
target_list = &target_proc->todo;
target_wait = &target_proc->wait;
}
e->to_proc = target_proc->pid;
/* TODO: reuse incoming transaction for reply */
t = kzalloc(sizeof(*t), GFP_KERNEL);
if (t == NULL) {
return_error = BR_FAILED_REPLY;
goto err_alloc_t_failed;
}
binder_stats_created(BINDER_STAT_TRANSACTION);
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
......
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;
else
t->from = NULL;
t->sender_euid = proc->tsk->cred->euid;
t->to_proc = target_proc;
t->to_thread = target_thread;
t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
trace_binder_transaction(reply, t, target_node);
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
if (t->buffer == NULL) {
return_error = BR_FAILED_REPLY;
goto err_binder_alloc_buf_failed;
}
t->buffer->allow_user_free = 0;
t->buffer->debug_id = t->debug_id;
t->buffer->transaction = t;
t->buffer->target_node = target_node;
trace_binder_transaction_alloc_buf(t->buffer);
if (target_node)
binder_inc_node(target_node, 1, 0, NULL);
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
......
}
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
......
}
off_end = (void *)offp + tr->offsets_size;
for (; offp < off_end; offp++) { [7]
......
}
if (reply) {
BUG_ON(t->buffer->async_transaction != 0);
binder_pop_transaction(target_thread, in_reply_to); [8]
}
......
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list);
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
list_add_tail(&tcomplete->entry, &thread->todo);
if (target_wait)
wake_up_interruptible(target_wait);
return;
......
}
[1] 初始化 in_reply_to 爲指向當前線程 thread(servicemanager 線程) 的事務堆棧 binder_transaction 的指針。前面 binder_thread_read 函數將處理事務加入到了 thread 的事務堆棧,因此 in_reply_to 指針實際指向了之前加入的事務。
[2] 將 in_reply_to 指向的事務從當前線程的事務堆棧中退棧。
[3] 根據 in_reply_to 初始化目標線程 target_thread 爲 MediaPlayerService 線程。
[4] 根據 target_thread 初始化目標進程 target_proc 爲 MediaPlayerService 進程。
[5] 初始化 target_list 爲目標線程 target_thread 的待處理工作隊列。
[6] 初始化 target_wait 爲目標線程 target_thread 的等待隊列。
[7] 由於返回結果數據中不存在 Binder 對象,因此不會進入 for 循環。
[8] 調用 binder_pop_transaction 函數將 in_reply_to 指向的事務從目標線程的事務堆棧中退棧。
這裏省略了與文章 Binder之請求註冊Service組件中分析binder_transaction 函數重複的內容。
和之前的分析一樣,最後會將待處理事務 t 和待完成工作項 tcomplete 分別添加到 target_list 和 thread->todo
中,因此待處理事務 t 將由 MediaPlayerService 來處理,而待完成工作項 tcomplete 將由 servicemanager 來處理,處理過程之前也都分析過。
至此,Service 註冊流程終於分析完成,下圖爲註冊流程所涉及的 MediaPlayerService,Binder 驅動和 servicemanager 三者之間的交互過程:
參考文獻:
1. Android系統進程間通信(IPC)機制Binder中的Server啓動過程源代碼分析
2. Android Binder機制(六) addService詳解之請求的處理