問題來源
做Android的都知道,我們綁定Service的代碼一般是這么寫的:
bindService(service, object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
}
}, Context.BIND_AUTO_CREATE)
在onServiceConnected回調(diào)中拿到IBinder對象,再調(diào)用asInterface即可轉(zhuǎn)成接口實例。但是大家想過沒有,這個IBinder對象到底是個啥?和我們在Service中的onBind方法返回的Binder對象是同一個嗎?稍微對進(jìn)程有點概念的人應(yīng)該都知道肯定不會是同一個對象。那么到底是什么呢?為什么拿到了這個IBinder對象就能調(diào)用Service中的方法了呢?可能很多人會覺得這都不算問題吧,反正多半和binder驅(qū)動有關(guān)唄。嗯,好吧,筆者以前也是這么覺得的,直到后面看了一些framework層的一些源碼后,發(fā)現(xiàn)binder對象跨進(jìn)程傳輸很頻繁。舉個例子:我們知道AMS能控制應(yīng)用程序的Activity的啟動和相關(guān)的生命周期,AMS是使用token來標(biāo)識一個應(yīng)用程序的Activity實例的,這個token就是IBinder對象。為什么IBinder對象可以作為token呢?如果IBinder對象可以作為唯一標(biāo)識,那不就說明Binder對象跨進(jìn)程來回傳輸?shù)臅r候在相同的進(jìn)程會恢復(fù)出相同的實例?有了一些疑問,筆記決定好好捋一捋binder對象到底是怎么傳到另一個進(jìn)程的。
Binder對象傳輸過程
應(yīng)用進(jìn)程部分
我們以服務(wù)的注冊過程來詳細(xì)闡述binder對象的傳輸過程。
從Java層的ServiceManager的addService方法開始分析,該方法用于向SMgr注冊服務(wù),代碼如下:
// ServiceManager.java
public static void addService(String name, IBinder service) {
getIServiceManager().addService(name, service, false);
}
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
return sServiceManager;
}
BinderInternal.getContextObject()這個方法是native方法,它的實現(xiàn)是android_util_binder.cpp的android_os_BinderInternal_getContextObject方法:
// android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
return javaObjectForIBinder(env, b);
}
這里兩個冒號在C++中的意思是指定命名空間調(diào)用函數(shù)。self函數(shù)一般就是返回單例對象。這里的ProcessState是進(jìn)程內(nèi)的單例對象,它在實例化時會打開binder驅(qū)動。我們繼續(xù)看其getContextObject方法:
// ProcessState.cpp
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
return getStrongProxyForHandle(0);
}
直接調(diào)用了另一個方法,注意傳的handle句柄是0,后面會看到,驅(qū)動通過handle句柄就能找到目標(biāo)進(jìn)程的binder實體對象,而0號句柄特指SMgr進(jìn)程的binder實體對象。我們現(xiàn)在跟的代碼當(dāng)前就是要獲取SMgr進(jìn)程提供的服務(wù),而服務(wù)說白了不就是Binder實體對象嘛,繼續(xù)跟進(jìn):
// ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
// 這里的handle_entry是緩存,如果不存在則創(chuàng)建新項,新項的binder域為空
handle_entry* e = lookupHandleLocked(handle);
if (e != NULL) {
IBinder* b = e->binder;
if (b == NULL || !e->refs->attemptIncWeak(this)) {
...
// 這里很關(guān)鍵,BpBinder是Binder Proxy的縮寫,也就是說BpBinder是C++層的binder代理對象,這里構(gòu)造傳入了handle句柄。
b = new BpBinder(handle);
e->binder = b;
...
result = b;
} ...
}
return result;
}
看代碼,注釋寫的很清楚了。
獲取到BpBinder對象后,我們繼續(xù)接上上面的流程,看javaObjectForIBinder方法,這個方法的主要作用就是獲取java層的BinderProxy對象:
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
...
// 這里先根據(jù)給定的IBinder對象嘗試獲取原來綁定的BinderProxy對象,如果原來綁定的對象還可以用,那么直接返回,否則就會new一個新的;
// 如果已經(jīng)不可用了,則會執(zhí)行解綁。
jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
if (object != NULL) {
jobject res = jniGetReferent(env, object);
if (res != NULL) {
return res;
}
// 不可用,解綁
val->detachObject(&gBinderProxyOffsets);
env->DeleteGlobalRef(object);
}
// 這里反射實例化BinderProxy,這里反射用到的類和構(gòu)造方法等信息是在進(jìn)程啟動時初始化的,具體初始化過程可以參考這篇博客:
// https://blog.csdn.net/fchyang/article/details/82260138
object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
if (object != NULL) {
// 這里對新的BinderProxy對象的mObject賦值為IBinder的指針,很關(guān)鍵。
// 這樣后續(xù)和遠(yuǎn)程進(jìn)程通信時,就能通過mObject找到BpBinder對象。
env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
...
// 進(jìn)行綁定。
val->attachObject(&gBinderProxyOffsets, refObject,
jnienv_to_javavm(env), proxy_cleanup);
...
}
return object;
}
這個返回了,我們就得到了一個java層的BinderProxy對象了,并且它通過mObject域與C++層的BpBinder對象進(jìn)行關(guān)聯(lián)。
接下來我們回到j(luò)ava層,Binder.allowBlocking只是把返回的BinderProxy對象的mWarnOnBlocking變量賦值為false,暫時不知是何含義。
緊接著看ServiceManagerNative.asInterface方法,該方法僅僅是new了一個ServiceManagerProxy對象,而其mRemote指向上面分析的從C++層返回的BinderProxy對象。
這個ServiceManagerProxy實現(xiàn)了IServiceManager接口,這個類的作用就是完成了參數(shù)寫入parcel的臟活累活。至此,我們有了一個java層的接口實例,并且通過mRemote域指向BinderProxy,而BinderProxy又和C++層的BpBinder綁定,BpBinder內(nèi)部包含handler句柄,這層層的持有關(guān)系都是為了拿到handler句柄。當(dāng)然了,這一系列的封裝最大的目的就是為了使遠(yuǎn)程方法調(diào)用像調(diào)用一般方法一樣簡單。
繼續(xù)分析之前,我們先來了解下Parcel。
對于這個Parcel,java層的Parcel只是對C++層Parcel的一個封裝,通過mNativePtr指針指向C++層的Parcel對象。C++層的Parcel其數(shù)據(jù)的組織方式就是一個字節(jié)數(shù)組,寫入數(shù)據(jù)的時候就直接追加到數(shù)組中,因此讀取數(shù)據(jù)時必須按照寫入的順序來讀取,不然類型就對不上了。既然是扁平的數(shù)組,那string和binder對象是怎么存儲和恢復(fù)的呢?答:存儲還是和基本類型數(shù)據(jù)一樣,只不過多存儲了String或者對象的大小,這樣當(dāng)我們readStrongBinder時,Parcel首先會讀取一個整型值,然后把其當(dāng)成len再讀取后面的數(shù)據(jù),最后再強轉(zhuǎn)成相應(yīng)的結(jié)構(gòu)體。
ok,了解了Parcel,我們繼續(xù)分析addService方法,此時我們知道addService的具體實現(xiàn)是ServiceManagerProxy類,ok,跟進(jìn)去:
// ServiceManagerProxy.java
public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
// 這里記一下我們寫入了什么數(shù)據(jù),以便理解后面經(jīng)過重重調(diào)用后,在遙遠(yuǎn)的smgr服務(wù)中是怎么解析數(shù)據(jù)的。
data.writeInterfaceToken(IServiceManager.descriptor); // descriptor是字符串,值為:android.os.IServiceManager
data.writeString(name); // 服務(wù)的名稱
data.writeStrongBinder(service); // 要注冊的binder實體對象
data.writeInt(allowIsolated ? 1 : 0);
// 如前所述,mRemote是BinderProxy對象
mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
writeStrongBinder方法用于向parcel中寫入binder實體對象,binder實體對象和一般的實體對象不同,首先是Binder并沒有實現(xiàn)Parcelable接口,因此寫入Parcel的僅僅是指針,另外因為binder驅(qū)動需要為傳輸?shù)腷inder實體對象建立數(shù)據(jù)結(jié)構(gòu)體,因此必須要標(biāo)記出parcel中哪里存在binder對象的傳輸。
binder實體對象在parcel中以flat_binder_object的結(jié)構(gòu)形式存儲,它的定義如下:
// binder.h
struct flat_binder_object {
/* 8 bytes for large_flat_header. */
unsigned long type;
unsigned long flags;
/* 8 bytes of data. */
union {
void *binder; /* local object */
signed long handle; /* remote object */
};
/* extra data associated with local object */
void *cookie;
};
type域表明了存儲的是binder實體對象還是binder引用,如果是實體對象,則后面的binder指針指向該實體對象,如果是引用,則handle域存儲binder句柄。
flags域只對第一次傳遞Binder實體時有效,因為此刻驅(qū)動需要在內(nèi)核中創(chuàng)建相應(yīng)的實體節(jié)點,有些參數(shù)需要從該域取出:第0-7位:代碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實體請求數(shù)據(jù)包的線程的最低優(yōu)先級。當(dāng)一個應(yīng)用程序提供多個實體時,可以通過該參數(shù)調(diào)整分配給各個實體的處理能力。第8位:代碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實體可以接收其它進(jìn)程發(fā)過來的文件形式的Binder。由于接收文件形式的Binder會在本進(jìn)程中自動打開文件,Server可以用該標(biāo)志禁止該功能,以防打開過多文件。
最后的cookie域則是額外攜帶的數(shù)據(jù),驅(qū)動不關(guān)心該值。
往parcel中寫入binder對象的時候,parcel內(nèi)部會記錄flat_binder_object的位置,以及數(shù)量,后續(xù)會用來賦值給binder_transaction_data結(jié)構(gòu)體:
// parcel.h
size_t* mObjects; // 指針,指向一個數(shù)組,數(shù)組的每一個元素對應(yīng)一個Parcel中保存的binder對象的偏移。
size_t mObjectsSize;
參數(shù)等數(shù)據(jù)寫入Parcel后,就調(diào)用mRemote的transact方法,回憶下上面的流程,這里的mRemote就是從C++層返回的BinderProxy對象,而BinderProxy對象是Binder.java的一個內(nèi)部類,它的transact方法的實現(xiàn)是一個native方法,具體實現(xiàn)是android_util_binder.cpp的android_os_BinderProxy_transact方法,我們跟進(jìn)去嘍:
// android_util_binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
...
// 使用java層Parcel的mNativePtr獲取C++層的Parcel對象。下同
Parcel* data = parcelForJavaObject(env, dataObj);
...
Parcel* reply = parcelForJavaObject(env, replyObj);
...
// 獲取前面分析的JNI層的BpBinder對象
IBinder* target = (IBinder*)
env->GetLongField(obj, gBinderProxyOffsets.mObject);
...
// 使用BpBinder執(zhí)行數(shù)據(jù)傳輸
status_t err = target->transact(code, *data, reply, flags);
//...
return err;
}
整體來看邏輯很簡單,就是根據(jù)mObject域獲取BpBinder對象,然后調(diào)用其transact方法。
BpBinder的transact的實現(xiàn)又進(jìn)一步委托給了IPCThreadState,這個類就是專門和驅(qū)動打交道的,包括server端那邊的BBinder也是委托給這個類來實現(xiàn)和驅(qū)動交互的。
PS:驅(qū)動傳輸數(shù)據(jù)會有自己的一套規(guī)則,IPCThreadState就是封裝屏蔽了規(guī)則的細(xì)節(jié),讓使用者只需關(guān)心業(yè)務(wù)處理即可:
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) {
// 特別注意這里傳了mHandle進(jìn)去,他就是目標(biāo)Binder實體的handle句柄,在我們這個環(huán)境handle是0,也就是會找到SMgr的Binder實體。
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
關(guān)于這個handle已經(jīng)強調(diào)了很多次了,之所以一直強調(diào)也是這次研究binder底層得到的最大收獲,也就是binder對象跨進(jìn)程傳輸?shù)谋举|(zhì)就是handle句柄,僅僅是一個整型值。
來看IPCThreadState的transact方法:
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
...
if (err == NO_ERROR) {
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
// 這里后面的代碼是處理回復(fù)的,我們暫時先把請求走通,后續(xù)再補充響應(yīng)(其實響應(yīng)也可以理解為是目標(biāo)進(jìn)程的一次寫入)
return err;
}
這里writeTransactionData的第一個參數(shù)BC_TRANSACTION是一個驅(qū)動命令字,具體可以查看binder驅(qū)動協(xié)議部分的筆記。我們跟進(jìn)這個方法,就會看到binder協(xié)議中提到的binder_transaction_data結(jié)構(gòu)體了:
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; // 此結(jié)構(gòu)體定義參見Binder驅(qū)動部分筆記
tr.target.ptr = 0; // 此時ptr是0,后面解析handle后就會賦值為目標(biāo)binder實體對象的地址。
tr.target.handle = handle; // handle句柄給target的handle域,很關(guān)鍵。
tr.code = code;
tr.flags = binderFlags;
...
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData(); // 這個方法其實就是返回parcel的起始地址
// 下面兩行代碼的賦值,就是大佬文章中提到的在buffer中標(biāo)記出傳輸?shù)腷inder對象,這里data是Parcel,
// 在往Parcel中寫入Strong binder時,Parcel內(nèi)部已經(jīng)對binder對象進(jìn)行計數(shù)了,并且記錄了其偏移量,
// 這一點去看writeStrongBinder的代碼就知道了。
tr.offsets_size = data.ipcObjectsCount() * sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects(); // 返回數(shù)組的起始地址
} // ...
// 先寫命令字,再寫結(jié)構(gòu)體數(shù)據(jù)
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
從上述代碼可以看到數(shù)據(jù)寫入了mOut中,這個mOut是Parcel類型的,用于暫存即將寫入驅(qū)動的數(shù)據(jù),還有一個mInt,用于暫存從驅(qū)動中接收到的數(shù)據(jù)。
那么,什么時候會將mOut真正寫入驅(qū)動呢?
答案是調(diào)用writeTransactionData方法之后的waitResponse方法,該方法內(nèi)部會調(diào)用talkWithDriver,進(jìn)一步的該方法內(nèi)部會通過ioctl方法將mOut中的數(shù)據(jù)傳入驅(qū)動。
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
...
// 這里binder_write_read的結(jié)構(gòu)可以看驅(qū)動協(xié)議部分,以下代碼就是對bwr相關(guān)的域進(jìn)行賦值,然后調(diào)用ioctl傳入驅(qū)動。
binder_write_read bwr;
...
// 先重點關(guān)注buffer部分,這里賦值為了parcel的起始地址,因此整個mOut的內(nèi)容會寫入驅(qū)動
bwr.write_buffer = (long unsigned int)mOut.data();
...
status_t err;
do {
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
...
} while (err == -EINTR);
...
return err;
}
OK,到此就要開始進(jìn)入驅(qū)動了,在這里總結(jié)一下目前為止mOut中都存了哪些內(nèi)容:
[cmd, binder_transaction_data] ....
其中cmd是BC_TRANSACTION,tr中:target.handle = 0,data域的buffer指向了我們最上層要傳輸?shù)膬?nèi)容。
驅(qū)動部分
對于字符設(shè)備驅(qū)動,以前也學(xué)過一點,驅(qū)動在實現(xiàn)的時候有個地方會注冊當(dāng)上層調(diào)用open、ioctl等方法時,驅(qū)動對應(yīng)的實現(xiàn)是哪個方法,對于binder驅(qū)動,ioctl方法對應(yīng)的就是binder_ioctl方法,該方法實現(xiàn)在binder.c,我們進(jìn)去看:
// binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
// binder_proc結(jié)構(gòu)體中存儲了和用戶進(jìn)程有關(guān)的信息,binder_open的時候會實例化。
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
...
// 這個方法用于獲取給定進(jìn)程一個空閑的binder線程,請注意,一個線程是不是binder線程本質(zhì)上就是
// 在驅(qū)動中是不是有相應(yīng)的binder_thread結(jié)構(gòu)體和它對應(yīng)(通過pid綁定)。
// 如果這個方法沒有找到binder_thread和當(dāng)前的線程綁定,則會實例化一個然后建立綁定關(guān)系。
// PS:線程池的內(nèi)容還有待進(jìn)一步研究,這里先不做過多討論,本次分析的重點在于handle如何解析,以及parcel中的實體對象如何傳遞
thread = binder_get_thread(proc);
...
switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
...
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) {
// 這里是讀,通常同步的ipc請求會等待響應(yīng),因此讀的時候可能會阻塞。
}
...
break;
}
...
return ret;
}
接著繼續(xù)看binder_thread_write方法,重點關(guān)注對buffer的處理:
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;
// 根據(jù)binder驅(qū)動協(xié)議的內(nèi)容,buffer中的結(jié)構(gòu)就是cmd+特定的數(shù)據(jù)結(jié)構(gòu),因此這里是一個循環(huán),取出每一個命令字處理
// 我們要關(guān)注的是BC_TRANSACTION這個命令字
while (ptr < end && thread->return_error == BR_OK) {
// 讀命令字
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
...
switch (cmd) {
...
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;
}
}
// ...
}
return 0;
}
可以看到,會拷貝BC_TRANSACTION命令后面的數(shù)據(jù)到內(nèi)核,并用binder_transaction_data結(jié)構(gòu)體存儲,然后調(diào)用了binder_transaction進(jìn)一步處理,以下代碼重點關(guān)注對tr結(jié)構(gòu)體的處理:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
... // 省略大量變量聲明
if (reply) {
... // 先忽略響應(yīng)的場景
} else {
// 高能,這里開始用handle解析出驅(qū)動中的引用
if (tr->target.handle) {
struct binder_ref *ref;
// 這里binder_get_ref會從紅黑樹中找binder_ref對象,對比的字段就是handle和ref的desc域是否一致,
// 而ref的desc域我們在后面會看到,其實就是紅黑樹的索引,從1開始的編號,可見貫穿Binder機制始終的句柄原來就是一個編號而已。。。
ref = binder_get_ref(proc, tr->target.handle);
if (ref == NULL) {
... // 錯誤處理,ref是一定要被找到的,不然就是無效的handle句柄
}
// 找到ref,輕松就能找到目標(biāo)節(jié)點,即binder_node對象,node里就有實體對象的ptr地址
target_node = ref->node;
} else {
// 這里binder_context_mgr_node特指smgr服務(wù)的binder_node節(jié)點,由此可見,如果handle不傳,驅(qū)動會默認(rèn)訪問的是smgr進(jìn)程。
target_node = binder_context_mgr_node;
...
}
...
}
...
// t的類型是binder_transaction,驅(qū)動協(xié)議中沒有討論這個結(jié)構(gòu)體,因為它是驅(qū)動內(nèi)部使用的,對它的賦值也很關(guān)鍵,重點關(guān)注以下幾個域的賦值:
...
t->code = tr->code;
...
t->buffer->target_node = target_node;
...
// t->buffer->data會用于存儲tr中的data.ptr.buffer + data.ptr.offsets的數(shù)據(jù),即把parcel中的數(shù)據(jù)和offsets數(shù)組扁平化了。
// 這里要注意理解,別把我們傳入驅(qū)動的數(shù)據(jù)跟丟了。這里offp會指向offsets數(shù)組的起始位置。
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
// 拷貝用戶數(shù)據(jù),先拷貝parcel數(shù)據(jù)部分
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
...
}
// 拷貝offsets數(shù)組
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
...
}
...
off_end = (void *)offp + tr->offsets_size;
// 開始遍歷處理parcel中傳輸?shù)腷inder對象
for (; offp < off_end; offp++) {
struct flat_binder_object *fp;
...
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
switch (fp->type) {
// 我們這里是注冊服務(wù),所以type是BINDER_TYPE_BINDER,后面的case處理的是引用的情況,
// 從smgr中獲取服務(wù)時,數(shù)據(jù)緩存區(qū)中存放的就是binder引用。
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) {
// 這里非常關(guān)鍵,前面嘗試用binder域,即binder實體的指針找出驅(qū)動和它綁定的node對象,
// 如果找不到,說明該binder實體是首次在驅(qū)動中傳輸,因此這里new了新的node節(jié)點和它綁定。
node = binder_new_node(proc, fp->binder, fp->cookie);
...
}
...
// 這里嘗試獲取一個和node相對應(yīng)的ref對象,如果不存在,就會生成一個,因為等下馬上要去smgr進(jìn)程了,
// node是不可能傳過去的,只能ref過去,也就相當(dāng)于binder實體對象嘗試跨驅(qū)動傳輸,但是卻止步于當(dāng)前進(jìn)程,傳過去的只是ref而已。
ref = binder_get_ref_for_node(target_proc, node);
// 這里把新的ref的desc域的賦值邏輯貼一下,以便理解handle句柄的本質(zhì):
// 以下片段是binder_get_ref_for_node方法里的,貼在這里方便分析:
// 片段start
new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1; // 賦起始值,對于smgr的node,起始賦為0。
// 傳進(jìn)來的proc是target的proc,refs_by_desc就是target進(jìn)程持有的所有的ref引用,用紅黑樹存儲,for循環(huán)就是遍歷這棵樹。
for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
// 這句代碼涉及到linux中紅黑樹的寫法,暫時不用管,只需要知道執(zhí)行完后,ref指向當(dāng)前遍歷到的引用對象。
ref = rb_entry(n, struct binder_ref, rb_node_desc);
if (ref->desc > new_ref->desc)
break;
new_ref->desc = ref->desc + 1; // 看到了吧,就是這么簡單,挨個加1就是handle句柄
}
// 片段end
...
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE; // 關(guān)鍵點:修改type為handle類型,后面即將對handle域賦值。
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc; // handle就是desc
// 以上代碼直接操作fp指向的內(nèi)存,修改了type和handle域,這樣的話數(shù)據(jù)區(qū)域只需整體拷貝到目標(biāo)進(jìn)程即可,
// 散落在parcel中的flat_binder_object的位置等是不變的。
...
} break;
... // 省略其他case
}
// 以上,for循環(huán)執(zhí)行完畢,則散落在parcel各個地方的binder實體對象就相應(yīng)的生成了node節(jié)點了,并且后續(xù)的傳輸已經(jīng)變成了handle的傳輸了。
if (reply) {
...
} else if (!(t->flags & TF_ONE_WAY)) {
... // 一般rpc調(diào)用都是同步的,所以看這個if
thread->transaction_stack = t;
} else {
...
}
t->work.type = BINDER_WORK_TRANSACTION;
// 把t中的binder_work工作項添加到target_list中,這個target_list就是目標(biāo)進(jìn)程的todo隊列,
// 每個參與進(jìn)程間通信的進(jìn)程在驅(qū)動中都有這樣的隊列,binder線程們會不斷從中取出work工作項處理。
list_add_tail(&t->work.entry, target_list);
...
return;
OK,目前為止,傳輸?shù)臄?shù)據(jù)被封裝成了工作項,放到了目標(biāo)進(jìn)程的todo隊列中了?,F(xiàn)在要把鏡頭轉(zhuǎn)向smgr進(jìn)程了,也就是我們的目標(biāo)進(jìn)程。
SMgr進(jìn)程部分
SMgr進(jìn)程啟動的時候,會open驅(qū)動 -> 注冊成為smgr服務(wù) -> 主線程注冊成為binder線程 -> 主線程循環(huán)等待請求數(shù)據(jù)。所謂的循環(huán)等待其實就是嘗試從驅(qū)動中讀取數(shù)據(jù),每次的嘗試讀,驅(qū)動會檢查有沒有你這個線程的工作項,以及檢查有沒有你這個線程所屬進(jìn)程的工作項,如果有,就會把工作項取出來,然后返回,如果沒有,則會等待在binder_proc的wait域上。具體我們看代碼。
smgr啟動過程可以看ServiceManager部分的筆記,這里直接從ioctl開始分析:
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
BINDER_WRITE_READ這個命令我們在上面已經(jīng)見過了,這里是讀,上面我們分析的是寫,讀部分的代碼如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// ...
switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
// ...
if (bwr.write_size > 0) {
// ... 寫已經(jīng)分析過了
}
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);
if (ret < 0) {
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto err;
}
}
// ...
break;
}
// ...
return ret;
}
OK,跟進(jìn)binder_thread_read方法:
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
void __user *buffer, int size,
signed long *consumed, int non_block)
{
// ...
int wait_for_proc_work;
// ...
// 這里判斷thread自己是否有工作項要處理
wait_for_proc_work = thread->transaction_stack == NULL &&
list_empty(&thread->todo);
// ...
if (wait_for_proc_work) {
// ...
// non_block是外面?zhèn)鬟^來的,表示線程沒工作時是否阻塞
if (non_block) {
if (!binder_has_proc_work(proc, thread))
ret = -EAGAIN;
} else {
// 等待在proc的wait上,第二個參數(shù)是等待的condition,即條件
ret = wait_event_interruptible_exclusive(proc->wait, binder_has_proc_work(proc, thread));
}
} else {
if (non_block) {
if (!binder_has_thread_work(thread))
ret = -EAGAIN;
} else
ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread));
}
// ...
// 代碼能走到這里,說明線程喚醒了,此時有工作項要處理了,因此進(jìn)入循環(huán)處理每一個work項。
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
// 優(yōu)先看線程自己有沒有todo,沒有就去處理進(jìn)程的todo
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 {
// ...
}
// ...
// 判斷工作項的類型,回憶前面的流程,我們這個場景的工作項類型是BINDER_WORK_TRANSACTION
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
// 可見,只是把t取出來了,t的類型就是前面提到的binder_transaction
t = container_of(w, struct binder_transaction, work);
} break;
// ... 省略其他case
}
// ...
if (t->buffer->target_node) {
struct binder_node *target_node = t->buffer->target_node;
// tr是剛剛定義的binder_transaction_data類型的變量,后面的代碼就是對這個結(jié)構(gòu)體進(jìn)行賦值,最后會傳給目標(biāo)進(jìn)程處理
tr.target.ptr = target_node->ptr;
tr.cookie = target_node->cookie;
t->saved_priority = task_nice(current);
// ...
cmd = BR_TRANSACTION;
} else {
// ...
}
tr.code = t->code;
tr.flags = t->flags;
// ...
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
// 前面說過,我們傳入驅(qū)動的數(shù)據(jù)被扁平化到了t->buffer->data里,這里則分別取出來賦值到tr中,
// 但是注意,數(shù)據(jù)依然是連續(xù)的,只不過此時用了兩個指針去存儲而已。
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 *));
// ptr就是smgr進(jìn)程讀的時候告訴內(nèi)核要寫入的地址,這里先寫命令字進(jìn)去,我們的場景該值為BR_TRANSACTION
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
// 隨后再寫入tr,即binder_transaction_data結(jié)構(gòu)體
if (copy_to_user(ptr, &tr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
// ...
break;
}
// ...
return 0;
}
ok,現(xiàn)在smgr進(jìn)程提供的緩沖區(qū)中已經(jīng)被驅(qū)動寫入binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)了,現(xiàn)在回到smgr進(jìn)程,看看如何處理的:
smgr的for循環(huán)中,一旦ioctl返回,就調(diào)用binder_parse解析數(shù)據(jù):
void binder_loop(struct binder_state *bs, binder_handler func)
{
// ...
for (;;) {
// ...
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
// ...
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
// ...
}
}
其中readbuf就是驅(qū)動寫入數(shù)據(jù)的地址,跟進(jìn)parse方法:
int binder_parse(struct binder_state *bs, struct binder_io *bio,
uint32_t *ptr, uint32_t size, binder_handler func)
{
int r = 1;
uint32_t *end = ptr + (size / 4);
// 因為驅(qū)動可能傳了很多個[cmd+數(shù)據(jù)]形式的數(shù)據(jù)過來,因此這里循環(huán)逐個處理,我們關(guān)注的是BR_TRANSACTION這個命令。
while (ptr < end) {
// 第一個是命令字,直接讀出來
uint32_t cmd = *ptr++;
// ...
switch(cmd) {
// ...
case BR_TRANSACTION: {
// 注意這里,非常騷氣,一度看的我很懵逼,ptr既然是cmd+數(shù)據(jù)格式的數(shù)據(jù),那么BR_TRANSACTION后面跟的
// 應(yīng)該是binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)啊,怎么這里強轉(zhuǎn)成了binder_txn呢?
// 后面仔細(xì)對比,原來這個binder_txn結(jié)構(gòu)體和binder_transaction_data結(jié)構(gòu)體驚人的相似,僅僅是把union去掉了,
// 在C++中,這種強轉(zhuǎn)是沒毛病的。
struct binder_txn *txn = (void *) ptr;
// 這里把binder_txn這個結(jié)構(gòu)體貼出來,然后總結(jié)下各個域目前代表的含義,畢竟數(shù)據(jù)經(jīng)過了一波驅(qū)動,記憶會有些模糊。
struct binder_txn
{
// 驅(qū)動已經(jīng)悄悄修改成了目標(biāo)binder實體的指針,代碼片段:tr.target.ptr = target_node->ptr;
void *target;
void *cookie;
// 方法編號,賦值片段:tr.code = t->code;
uint32_t code;
uint32_t flags;
uint32_t sender_pid;
uint32_t sender_euid;
// 數(shù)據(jù)區(qū)的大小,賦值片段:tr.data_size = t->buffer->data_size;
uint32_t data_size;
// 偏移數(shù)組的大小,片段:tr.offsets_size = t->buffer->offsets_size;
uint32_t offs_size;
// 原來tr這里的data是一個union,里面有個ptr結(jié)構(gòu)體存儲了兩個指針,一個是數(shù)據(jù)區(qū),一個offsets區(qū),
// 強轉(zhuǎn)后,第一個指針就給了這里的data域,而第二個指針就給了這里的offs域。
void *data;
void *offs;
};
// ...
if (func) { // func函數(shù)是傳進(jìn)來的,名稱是svcmgr_handler
// ...
struct binder_io msg;
// ...
// 這里把txn中的相關(guān)域賦值給了binder_io,后面會從這個binder_io中讀取數(shù)據(jù),它里面維護了當(dāng)前讀到了哪里。
bio_init_from_txn(&msg, txn);
res = func(bs, txn, &msg, &reply);
// 。。。
}
ptr += sizeof(*txn) / sizeof(uint32_t); // 偏移指針,處理下一個命令字。
break;
}
// ...
}
}
return r;
}
拿到binder_io后,調(diào)用了svcmgr_handler函數(shù),我們繼續(xù)跟進(jìn):
int svcmgr_handler(struct binder_state *bs,
struct binder_txn *txn,
struct binder_io *msg,
struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
unsigned len;
void *ptr;
uint32_t strict_policy;
int allow_isolated;
// ...
// 這里bio_get_unit32會從msg中的data數(shù)據(jù)區(qū)中讀一個指針出來,但是這個strict_policy后面沒有用到,
// 而且一路跟源碼過來,好像沒有哪里會往數(shù)據(jù)區(qū)中寫入指針?。???
// 其實在Server端的addService方法中,有這么一行代碼:
// data.writeInterfaceToken(IServiceManager.descriptor);
// descriptor的值就是:android.os.IServiceManager
// writeInterfaceToken這個方法在往parcel中寫入desc的之前,寫入了一個整型值:
// writeInt32(IPCThreadState::self()->getStrictModePolicy() | STRICT_MODE_PENALTY_GATHER);
// ok,現(xiàn)在知道指針的來歷了,不過現(xiàn)在不考究strict_policy的含義,之所以這里展開說明是為了要證明msg的data數(shù)據(jù)區(qū)
// 跟最開始我們寫入parcel中的數(shù)據(jù)是完全一致的。
strict_policy = bio_get_uint32(msg);
// 這里就是對應(yīng)獲取descriptor的值,讀字符串就是先讀len,然后讀len個字節(jié)的數(shù)據(jù)。
s = bio_get_string16(msg, &len);
if ((len != (sizeof(svcmgr_id) / 2)) ||
// svcmgr_id是一個字節(jié)數(shù)組,該值正好是android.os.IServiceManager
memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
fprintf(stderr,"invalid id %s\n", str8(s));
return -1;
}
// 根據(jù)code調(diào)用相應(yīng)的方法,我們關(guān)注ADD_SERVICE_TRANSACTION
switch(txn->code) {
// ...
case SVC_MGR_ADD_SERVICE:
// 讀出service的名稱
s = bio_get_string16(msg, &len);
// 這個方法下面要展開分析,因為涉及到handle句柄的讀取問題,這里先只需知道返回的ptr指針會指向一個整型值,而這個整型值
// 就是handle句柄。
ptr = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
if (do_add_service(bs, s, len, ptr, txn->sender_euid, allow_isolated))
return -1;
break;
// ..
}
bio_put_uint32(reply, 0);
return 0;
}
bio_get_ref這個方法我們展開分析:
void *bio_get_ref(struct binder_io *bio)
{
// 這個binder_object和flat_binder_object也是驚人的一致,C++中這種強轉(zhuǎn)的方式前面的binder_txn已經(jīng)見過了
struct binder_object *obj;
obj = _bio_get_obj(bio);
if (!obj)
return 0;
if (obj->type == BINDER_TYPE_HANDLE)
return obj->pointer;
return 0;
}
跟進(jìn)_bio_get_obj方法:
static struct binder_object *_bio_get_obj(struct binder_io *bio)
{
unsigned n;
// 這里bio.data和bio.data0是同時被初始化為數(shù)據(jù)區(qū)的起始地址的,但是bio后面被讀的時候data在偏移,但是data0是不動的,
// 因此這里data-data0不就是當(dāng)前讀的字節(jié)的偏移嗎?parcel中的數(shù)據(jù)不就是寫完了name,再寫入strongBinder的嘛,
// 因此這里斷定off開始處,一定是那個要注冊進(jìn)來的binder對象。
unsigned off = bio->data - bio->data0;
/* TODO: be smarter about this? */ // 因為有寫死的嫌疑,所以源碼這里有個todo
for (n = 0; n < bio->offs_avail; n++) {
// 對比offs數(shù)組中是不是存在這樣一個off,存在即說明數(shù)據(jù)傳輸正常,于是把binder對象恢復(fù)出來。
if (bio->offs[n] == off)
return bio_get(bio, sizeof(struct binder_object));
}
bio->data_avail = 0;
bio->flags |= BIO_F_OVERFLOW;
return 0;
}
OK,這個方法返回,則正確恢復(fù)出了要注冊的binder對象,并且強轉(zhuǎn)成了和flat_binder_object結(jié)構(gòu)體類似的結(jié)構(gòu)體binder_object。
回憶一下,parcel中的binder對象在驅(qū)動傳輸過程中,做了以下賦值:
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
可見,union中handle域被賦值為了desc域,即handle句柄,那么強轉(zhuǎn)成binder_object后,binder_object的pointer這個指針域就指向handle句柄這個整型值了。
OK,說清楚了pointer域的含義后,咱們回到svcmgr_handler方法,繼續(xù)接著看do_add_service方法:
int do_add_service(struct binder_state *bs,
uint16_t *s, unsigned len,
void *ptr, unsigned uid, int allow_isolated)
{
// 傳進(jìn)來的s就是服務(wù)的名稱,ptr上面說了,指向handle句柄這個整型值。
struct svcinfo *si;
// ...
si = find_svc(s, len);
if (si) {
// 原來存在,則進(jìn)行覆蓋,被覆蓋的被標(biāo)記為死亡。
if (si->ptr) {
svcinfo_death(bs, si);
}
si->ptr = ptr;
} else {
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
// ...
si->ptr = ptr;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = '\0';
// ...
si->next = svclist; // 可見,smgr管理service是一個鏈表
svclist = si;
}
// ...
return 0;
}
到此,就把整個addService方法的調(diào)用流程都分析完了。
總結(jié)一下:
1、client進(jìn)程拿到的IBinder對象只是一個本地的代理對象,真正起作用的是代理對象C++層的handle句柄,而handle句柄其實就是client進(jìn)程在驅(qū)動中持有的所有的binder_ref的一個序號,驅(qū)動根據(jù)序號找到ref對象,進(jìn)而找到binder_node對象。node對象中記錄了target進(jìn)程以及目標(biāo)binder實體對象的地址,因此可以確切的找到調(diào)用的目的地。
2、parcel中試圖首次傳遞binder實體對象時,驅(qū)動會為其建立binder_node對象,隨后生成一個ref引用,最后把parcel中的傳遞的binder對象類型改為引用類型,這樣目標(biāo)進(jìn)程接收到的IBinder就是本地的代理對象,一樣的,通過handle來和遠(yuǎn)程的Binder實體對象關(guān)聯(lián)。
3、binder線程就是一個普通的用戶進(jìn)程的線程,只不過在驅(qū)動中建立對應(yīng)的binder_thread結(jié)構(gòu)體而已,驅(qū)動只會把work工作項給目標(biāo)進(jìn)程的binder線程處理。對于上面分析的流程,smgr進(jìn)程是主動把主線程注冊成為binder線程的,但是對于一般的應(yīng)用進(jìn)程是怎么處理的呢?這個目前沒研究相關(guān)的代碼,不過可以來看下大佬是怎么說的:
應(yīng)用程序通過INDER_SET_MAX_THREADS告訴驅(qū)動最多可以創(chuàng)建幾個線程,每當(dāng)驅(qū)動接收完數(shù)據(jù)包返回讀Binder的線程時,都要檢查一下是不是已經(jīng)沒有閑置線程了,
如果是,而且線程總數(shù)不會超出線程池最大線程數(shù),就會在當(dāng)前讀出的數(shù)據(jù)包后面再追加一條BR_SPAWN_LOOPER消息,告訴用戶線程即將不夠用了,請再啟動一些,
新線程一啟動又會通過BC_xxx_LOOP告知驅(qū)動更新狀態(tài)。這樣只要線程沒有耗盡,總是有空閑線程在等待隊列中隨時待命,及時處理請求。
根據(jù)目前的理解,以上能成立,需要用戶進(jìn)程至少有一個線程是一直在循環(huán)讀binder的。