本篇是第二篇,主要是涉及Binder執行緒與程式的喚醒,傳輸資料的封裝與解析等知識點。
- Binder執行緒的睡眠與喚醒(請求執行緒睡在哪個等待佇列上,喚醒目標端哪個佇列上的執行緒)
- Binder協議中BC與BR的區別
- Binder在傳輸資料的時候是如何層層封裝的--不同層次使用的資料結構(命令的封裝)
- Binder驅動傳遞資料的釋放(釋放時機)
- 一個簡單的Binder通訊C/S模型
Client端執行緒睡眠在哪個佇列上,喚醒Server端哪個等待佇列上的執行緒
先看第一部分:傳送端執行緒睡眠在哪個佇列上?
傳送端執行緒一定睡眠在自己binder_thread的等待佇列上,並且,該佇列上有且只有自己一個睡眠執行緒
再看第二部分:在Binder驅動去喚醒執行緒的時候,喚醒的是哪個等待佇列上的執行緒?
理解這個問題需要理解binder_thread中的 struct binder_transaction transaction_stack棧,這個棧規定了transaction的執行順序:*棧頂的一定先於棧內執行。
如果本地操作是BC_REPLY,一定是喚醒之前傳送等待的執行緒,這個是100%的,但是如果是BC_TRANSACTION,那就不一定了,尤其是當兩端互為服務相互請求的時候,場景如下:
- 程式A的普通執行緒AT1請求B程式的B1服務,喚醒B程式的Binder執行緒,AT1睡眠等待服務結束
- B程式的B1服務在執行的的時候,需要請求程式A的A1服務,則B程式的Binder執行緒BT1睡眠,喚醒A程式的Binder執行緒,等待服務結束
- A程式的A1服務在執行的時候,又需要B程式的B2服務,則A程式的binder執行緒AT2睡眠,喚醒B程式的Binder執行緒,等待服務結束
這個時候就會遇到一個問題:喚醒哪個執行緒比較合適?是睡眠在程式佇列上的執行緒,還是之前睡眠的執行緒BT1?答案是:之前睡眠的執行緒BT1,具體看下面的圖解分析
首先第一步A普通執行緒去請求B程式的B1服務,這個時候在A程式的AT1執行緒的binder_ref中會將binder_transaction1入棧,而同樣B的Binder執行緒在讀取binder_work之後,也會將binder_transaction1加入自己的堆疊,如下圖:
而當B的Binder執行緒被喚醒後,執行Binder實體中的服務時,發現服務函式需要反過來去請求A端的A1服務,那就需要通過Binder向A程式傳送請求,並新建binder_transaction2壓入自己的binder_transaction堆疊,而A程式的Binder執行緒被喚醒後也會將binder_transaction2加入自己的堆疊,會後效果如下:
這個時候,還是沒有任何問題,但是恰好在執行A1服務的時候,又需要請求B2服務,這個時候,A1執行緒重複上述壓棧過程,新建binder_transaction3壓入自己的棧,不過在寫入到目標端B的時候,會面臨一個抉擇,寫入那個佇列,是binder_proc上的佇列,還是正在等候A返回的BT1執行緒的佇列?
結果已經說過,是BT1的佇列,為什麼呢?因為BT1佇列上的之前的binder_transaction2在等待A程式執行完,但是A端的binder_transaction3同樣要等待binder_transaction3在B程式中執行完畢,也就是說,binder_transaction3在B端一定是先於binder_transaction2執行的,因此喚醒BT1執行緒,並將binder_transaction3壓入BT2的棧,等binder_transaction3執行完畢,出棧後,binder_transaction2才能執行,這樣,既不妨礙binder_transaction2的執行,同樣也能讓睡眠的BT1程式提高利用率,因為最終的堆疊效果就是:
而當binder_transaction3完成,出棧的過程其實就簡單了,
- BT1 執行binder_transaction3,喚醒A端AT2 Binder執行緒,並且BT1繼續睡眠(因為還有等待的transaction)
- AT2 執行binder_transaction2,喚醒BT1
- BT1 執行binder_transaction1,喚醒AT1
- 執行結束
從這裡可以看出,其實設計的還是很巧妙的,讓執行緒複用,提高了效率,還避免了新建不必要的Binder執行緒,在binder驅動中島實現程式碼,其實就是根據binder_transaction中堆疊記錄查詢,
static void binder_transaction(struct binder_proc proc,
struct binder_thread thread,
struct binder_transaction_data *tr, int reply)
{..
while (tmp) {
// 找到對方正在等待自己程式的執行緒,如果執行緒沒有在等待自己程式的返回,就不要找了
// 判斷是不target_proc中,是不是有執行緒,等待當前執行緒
// thread->transaction_stack,這個時候,
// 是binder執行緒的,不是普通執行緒 B去請求A服務,
// 在A服務的時候,又請求了B,這個時候,A的服務一定要等B處理完,才能再返回B,可以放心用B
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
... }
} }複製程式碼
Binder協議中BC與BR的區別
BC與BR主要是標誌資料及Transaction流向,其中BC是從使用者空間流向核心,而BR是從核心流線使用者空間,比如Client向Server傳送請求的時候,用的是BC_TRANSACTION,當資料被寫入到目標程式後,target_proc所在的程式被喚醒,在核心空間中,會將BC轉換為BR,並將資料與操作傳遞該使用者空間。
Binder在傳輸資料的時候是如何層層封裝的--不同層次使用的資料結構(命令的封裝)
核心中,與使用者空間對應的結構體物件都需要新建,但傳輸資料的資料只拷貝一次,就是一次拷貝的時候。
從Client端請求開始分析,暫不考慮java層,只考慮Native,以ServiceManager的addService為例,具體看一下
MediaPlayerService::instantiate();複製程式碼
MediaPlayerService會新建Binder實體,並將其註冊到ServiceManager中:
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
} 複製程式碼
這裡defaultServiceManager其實就是獲取ServiceManager的遠端代理:
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
if (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}複製程式碼
如果將程式碼簡化其實就是
return gDefaultServiceManager = BpServiceManager (new BpBinder(0));複製程式碼
addService就是呼叫BpServiceManager的addService,
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}複製程式碼
這裡會開始第一步的封裝,資料封裝,其實就是講具體的傳輸資料寫入到Parcel物件中,與Parcel對應是ADD_SERVICE_TRANSACTION等具體操作。比較需要注意的就是data.writeStrongBinder,這裡其實就是把Binder實體壓扁:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}複製程式碼
具體做法就是轉換成flat_binder_object,以傳遞Binder的型別、指標之類的資訊:
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}
return finish_flatten_binder(binder, obj, out);
}複製程式碼
接下來看 remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); 在上面的環境中,remote()函式返回的就是BpBinder(0),
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive) {
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}複製程式碼
之後通過 IPCThreadState::self()->transact( mHandle, code, data, reply, flags)進行進一步封裝:
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags){
if ((flags & TF_ONE_WAY) == 0) {
if (err == NO_ERROR) {
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
if (reply) {
err = waitForResponse(reply);
}
..
return err;
}複製程式碼
writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);是進一步封裝的入口,在這個函式中Parcel& data、handle、code、被進一步封裝成binder_transaction_data物件,並拷貝到mOut的data中去,同時也會將BC_TRANSACTION命令也寫入mOut,這裡與binder_transaction_data對應的CMD是BC_TRANSACTION,binder_transaction_data也儲存了資料的指引新資訊:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
tr.data.ptr.offsets = data.ipcObjects();
} ..
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}複製程式碼
mOut封裝結束後,會通過waitForResponse呼叫talkWithDriver繼續封裝:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
// Is the read buffer empty? 這裡會有同時返回兩個命令的情況 BR_NOOP、BR_COMPLETE
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (long unsigned int)mOut.data(); // This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (long unsigned int)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
。。
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
} while (err == -EINTR);
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < (ssize_t)mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}複製程式碼
talkWithDriver會將mOut中的資料與命令繼續封裝成binder_write_read物件,其中bwr.write_buffer就是mOut中的data(binder_transaction_data+BC_TRRANSACTION),之後就會通過ioctl與binder驅動互動,進入核心,這裡與binder_write_read物件對應的CMD是BINDER_WRITE_READ,進入驅動後,是先寫後讀的順序,所以才叫BINDER_WRITE_READ命令,與BINDER_WRITE_READ層級對應的幾個命令碼一般都是跟執行緒、程式、資料整體傳輸相關的操作,不涉及具體的業務處理,比如BINDER_SET_CONTEXT_MGR是將執行緒程式設計ServiceManager執行緒,並建立0號Handle對應的binder_node、BINDER_SET_MAX_THREADS是設定最大的非主Binder執行緒數,而BINDER_WRITE_READ就是表示這是一次讀寫操作:
#define BINDER_CURRENT_PROTOCOL_VERSION 7
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
#define BINDER_THREAD_EXIT _IOW('b', 8, int)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)複製程式碼
詳細看一下binder_ioctl對於BINDER_WRITE_READ的處理,
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
..
<!--拷貝binder_write_read物件到核心空間-->
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
<!--根據是否需要寫資料處理是不是要寫到目標程式中去-->
if (bwr.write_size > 0) {
ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
}
<!--根據是否需要寫資料處理是不是要讀,往自己程式裡讀資料-->
if (bwr.read_size > 0) {
ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
<!--是不是要同時喚醒程式上的阻塞佇列-->
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);
}
break;
}
case BINDER_SET_MAX_THREADS:
if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
}
break;
case BINDER_SET_CONTEXT_MGR:
.. break;
case BINDER_THREAD_EXIT:
binder_free_thread(proc, thread);
thread = NULL;
break;
case BINDER_VERSION:
..
}複製程式碼
binder_thread_write(proc, thread, (void __user )bwr.write_buffer, bwr.write_size, &bwr.write_consumed)這裡其實就是把解析的binder_write_read物件再剝離,*bwr.write_buffer 就是上面的(BC_TRANSACTION+ binder_transaction_data),
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
void __user *buffer, int size, signed long *consumed)
{
uint32_t cmd;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
while (ptr < end && thread->return_error == BR_OK) {
// binder_transaction_data BC_XXX+binder_transaction_data
if (get_user(cmd, (uint32_t __user *)ptr)) (BC_TRANSACTION)
return -EFAULT;
ptr += sizeof(uint32_t);
switch (cmd) {
..
case BC_FREE_BUFFER: {
...
}
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
break;
}
case BC_REGISTER_LOOPER:
..
case BC_ENTER_LOOPER:
...
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
break;
case BC_EXIT_LOOPER:
// 這裡會修改讀取的資料,
*consumed = ptr - buffer;
}
return 0;
}複製程式碼
binder_thread_write會進一步根據CMD剝離出binder_transaction_data tr,交給binder_transaction處理,其實到binder_transaction資料幾乎已經剝離極限,剩下的都是業務相關的,但是這裡牽扯到一個Binder實體與Handle的轉換過程,同城也牽扯兩個程式在核心空間共享一些資料的問題,因此這裡又進行了一次進一步的封裝與拆封裝,這裡新封裝了連個物件 binder_transaction與binder_work,有所區別的是binder_work可以看做是程式私有,但是binder_transaction是兩個互動的程式共享的:binder_work是插入到執行緒或者程式的work todo佇列上去的:
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
int pid;
int looper;
struct binder_transaction *transaction_stack;
struct list_head todo;
uint32_t return_error; /* Write failed, return error code in read buf */
uint32_t return_error2; /* Write failed, return error code in read */
wait_queue_head_t wait;
struct binder_stats stats;
};複製程式碼
這裡主要關心一下binder_transaction:binder_transaction主要記錄了當前transaction的來源,去向,同時也為了返回做準備,buffer欄位是一次拷貝後資料在Binder的記憶體地址。
struct binder_transaction {
int debug_id;
struct binder_work work;
struct binder_thread *from;
struct binder_transaction *from_parent;
struct binder_proc *to_proc;
struct binder_thread *to_thread;
struct binder_transaction *to_parent;
unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */
struct binder_buffer *buffer;
unsigned int code;
unsigned int flags;
long priority;
long saved_priority;
uid_t sender_euid;
};複製程式碼
binder_transaction函式主要負責的工作:
- 新建binder_transaction物件,並插入到自己的binder_transaction堆疊中
- 新建binder_work物件,插入到目標佇列
Binder與Handle的轉換 (flat_binder_object)
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; **關鍵點1** if (reply) { in_reply_to = thread->transaction_stack; thread->transaction_stack = in_reply_to->to_parent; target_thread = in_reply_to->from; target_proc = target_thread->proc; }else { if (tr->target.handle) { struct binder_ref * ref; ref = binder_get_ref(proc, tr->target.handle); target_node = ref->node; } else { target_node = binder_context_mgr_node; } ..。 **關鍵點2** t = kzalloc(sizeof( * t), GFP_KERNEL); ... tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); **關鍵點3 ** off_end = (void *)offp + tr->offsets_size; for (; offp < off_end; offp++) { struct flat_binder_object *fp; fp = (struct flat_binder_object *)(t->buffer->data + *offp); switch (fp->type) { case BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: { struct binder_ref *ref; struct binder_node *node = binder_get_node(proc, fp->binder); if (node == NULL) { node = binder_new_node(proc, fp->binder, fp->cookie); }.. ref = (target_proc, node); if (fp->type == BINDER_TYPE_BINDER) fp->type = BINDER_TYPE_HANDLE; else fp->type = BINDER_TYPE_WEAK_HANDLE; fp->handle = ref->desc; } break; case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE: { struct binder_ref *ref = binder_get_ref(proc, fp->handle); if (ref->node->proc == target_proc) { if (fp->type == BINDER_TYPE_HANDLE) fp->type = BINDER_TYPE_BINDER; else fp->type = BINDER_TYPE_WEAK_BINDER; fp->binder = ref->node->ptr; fp->cookie = ref->node->cookie; } else { struct binder_ref *new_ref; new_ref = binder_get_ref_for_node(target_proc, ref->node); fp->handle = new_ref->desc; } } break; **關鍵點4** 將binder_work 插入到目標佇列 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,找到目標程式,關鍵點2 建立binder_transaction與binder_work,關鍵點3 處理Binder實體與Handle轉化,關鍵點4,將binder_work插入目標佇列,並喚醒相應的等待佇列,在處理Binder實體與Handle轉化的時候,有下面幾點注意的:
- 第一次註冊Binder實體的時候,是向別的程式註冊的,ServiceManager,或者SystemServer中的AMS服務
- Client請求服務的時候,一定是由Binder驅動為Client分配binder_ref,如果本程式的執行緒請求,fp->type = BINDER_TYPE_BINDER,否則就是fp->type = BINDER_TYPE_HANDLE。
- Android中的Parcel裡面的物件一定是flat_binder_object
如此下來,寫資料的流程所經歷的資料結構就完了。再簡單看一下被喚醒一方的讀取流程,讀取從阻塞在核心態的binder_thread_read開始,以傳遞而來的BC_TRANSACTION為例,binder_thread_read會根據一些場景新增BRXXX引數,標識驅動傳給使用者空間的資料流向:
enum BinderDriverReturnProtocol {
BR_ERROR = _IOR_BAD('r', 0, int),
BR_OK = _IO('r', 1),
BR_TRANSACTION = _IOR_BAD('r', 2, struct binder_transaction_data),
BR_REPLY = _IOR_BAD('r', 3, struct binder_transaction_data),
BR_ACQUIRE_RESULT = _IOR_BAD('r', 4, int),
BR_DEAD_REPLY = _IO('r', 5),
BR_TRANSACTION_COMPLETE = _IO('r', 6),
BR_INCREFS = _IOR_BAD('r', 7, struct binder_ptr_cookie),
BR_ACQUIRE = _IOR_BAD('r', 8, struct binder_ptr_cookie),
BR_RELEASE = _IOR_BAD('r', 9, struct binder_ptr_cookie),
BR_DECREFS = _IOR_BAD('r', 10, struct binder_ptr_cookie),
BR_ATTEMPT_ACQUIRE = _IOR_BAD('r', 11, struct binder_pri_ptr_cookie),
BR_NOOP = _IO('r', 12),
BR_SPAWN_LOOPER = _IO('r', 13),
BR_FINISHED = _IO('r', 14),
BR_DEAD_BINDER = _IOR_BAD('r', 15, void *),
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR_BAD('r', 16, void *),
BR_FAILED_REPLY = _IO('r', 17),
};複製程式碼
之後,read執行緒根據binder_transaction新建binder_transaction_data物件,再通過copy_to_user,傳遞給使用者空間,
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);
else {
if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN)) /* no data added */
goto retry;
break;
}
// 資料大小
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;
tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *));
// 寫命令
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
// 寫資料結構體到使用者空間,
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
}複製程式碼
上層通過ioctrl等待的函式被喚醒,假設現在被喚醒的是服務端,一般會執行請求,這裡首先通過Parcel的ipcSetDataReference函式將資料將資料對映到Parcel物件中,之後再通過BBinder的transact函式處理具體需求;
status_t IPCThreadState::executeCommand(int32_t cmd)
{
...
// read到了資料請求,這裡是需要處理的邏輯 ,處理完畢,
case BR_TRANSACTION:
{
binder_transaction_data tr;
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(size_t), freeBuffer, this);
...
// 這裡是處理 如果非空,就是資料有效,
if (tr.target.ptr) {
// 這裡什麼是tr.cookie
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
} 複製程式碼
這裡的 b->transact(tr.code, buffer, &reply, tr.flags);就同一開始Client呼叫transact( mHandle, code, data, reply, flags)函式對應的處理類似,進入相對應的業務邏輯。
Binder驅動傳遞資料的釋放(釋放時機)
在Binder通訊的過程中,資料是從發起通訊程式的使用者空間直接寫到目標程式核心空間,而這部分資料是直接對映到使用者空間,必須等使用者空間使用完資料才能釋放,也就是說Binder通訊中核心資料的釋放時機應該是使用者空間控制的,內種中釋放記憶體空間的函式是binder_free_buf,其他的資料結構其實可以直接釋放掉,執行這個函式的命令是BC_FREE_BUFFER。上層使用者空間常用的入口是IPCThreadState::freeBuffer:
void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t dataSize,
const size_t* objects, size_t objectsSize,
void* cookie)
{
if (parcel != NULL) parcel->closeFileDescriptors();
IPCThreadState* state = self();
state->mOut.writeInt32(BC_FREE_BUFFER);
state->mOut.writeInt32((int32_t)data);
}複製程式碼
那什麼時候會呼叫這個函式呢?在之前分析資料傳遞的時候,有一步是將binder_transaction_data中的資料對映到Parcel中去,其實這裡是關鍵
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
int32_t cmd;
int32_t err;
while (1) {
...
case BR_REPLY:
{
binder_transaction_data tr;
// 注意這裡是沒有傳輸資料拷貝的,只有一個指標跟資料結構的拷貝,
err = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
if (err != NO_ERROR) goto finish;
// free buffer,先設定資料,直接
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
// 牽扯到資料利用,與記憶體釋放
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(size_t),
freeBuffer, this);複製程式碼
Parcel 的ipcSetDataReference函式不僅僅能講資料對映到Parcel物件,同時還能將資料的清理函式對映進來
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)複製程式碼
看函式定義中的release_func relFunc引數,這裡就是指定記憶體釋放函式,這裡指定了IPCThreadState::freeBuffer函式,在Native層,Parcel在使用完,並走完自己的生命週期後,就會呼叫自己的解構函式,在其解構函式中呼叫了freeDataNoInit(),這個函式會間接呼叫上面設定的記憶體釋放函式:
Parcel::~Parcel()
{
freeDataNoInit();
}複製程式碼
這就是資料釋放的入口,進入核心空間後,執行binder_free_buf,將這次分配的記憶體釋放,同時更新binder_proc的binder_buffer表,重新標記那些記憶體塊被使用了,哪些沒被使用。
static void binder_free_buf(struct binder_proc *proc,
struct binder_buffer *buffer)
{
size_t size, buffer_size;
buffer_size = binder_buffer_size(proc, buffer);
size = ALIGN(buffer->data_size, sizeof(void *)) +
ALIGN(buffer->offsets_size, sizeof(void *));
binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
"binder: %d: binder_free_buf %p size %zd buffer"
"_size %zd\n", proc->pid, buffer, size, buffer_size);
if (buffer->async_transaction) {
proc->free_async_space += size + sizeof(struct binder_buffer);
binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
"binder: %d: binder_free_buf size %zd "
"async free %zd\n", proc->pid, size,
proc->free_async_space);
}
binder_update_page_range(proc, 0,
(void *)PAGE_ALIGN((uintptr_t)buffer->data),
(void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
NULL);
rb_erase(&buffer->rb_node, &proc->allocated_buffers);
buffer->free = 1;
if (!list_is_last(&buffer->entry, &proc->buffers)) {
struct binder_buffer *next = list_entry(buffer->entry.next,
struct binder_buffer, entry);
if (next->free) {
rb_erase(&next->rb_node, &proc->free_buffers);
binder_delete_free_buffer(proc, next);
}
}
if (proc->buffers.next != &buffer->entry) {
struct binder_buffer *prev = list_entry(buffer->entry.prev,
struct binder_buffer, entry);
if (prev->free) {
binder_delete_free_buffer(proc, buffer);
rb_erase(&prev->rb_node, &proc->free_buffers);
buffer = prev;
}
}
binder_insert_free_buffer(proc, buffer);
}複製程式碼
Java層類似,通過JNI呼叫Parcel的freeData()函式釋放記憶體,在使用者空間,每次執行BR_TRANSACTION或者BR_REPLY,都會利用freeBuffer傳送請求,去釋放核心中的記憶體
簡單的Binder通訊C/S模型
聽說你Binder機制學的不錯,來解決下這幾個問題(一)
聽說你 Binder 機制學的不錯,來解決下這幾個問題(二)
聽說你 Binder 機制學的不錯,來解決下這幾個問題(三)
僅供參考,歡迎指正