Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Binder學習筆記(九)—— 服務端如何響應Test()請求 ?

Binder學習筆記(九)—— 服務端如何響應Test()請求 ?

編輯:關於Android編程

從服務端代碼出發,TestServer.cpp

int main() {
    sp < ProcessState > proc(ProcessState::self());
    sp < IServiceManager > sm = defaultServiceManager();
    sm->addService(String16("service.testservice"), new BnTestService());
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    return 0;
}

前三行代碼在之前的Binder學習筆記系列中都分析過了,繼續往下看。

ProcessState::self()->startThreadPool()做了什麼?

frameworks/native/libs/binder/ProcessState.cpp:132

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

繼續spawnPooledThread(true),frameworks/native/libs/binder/ProcessState.cpp:286

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp t = new PoolThread(isMain);
        t->run(name.string());
    }
}

PoolThread是一個線程類,暫時先不去深究,它的run(…)函數最終會落實到線程函數threadLoop()的調用上,這個函數很簡單,frameworks/native/libs/binder/ProcessState.cpp:61

class PoolThread : public Thread
{
......
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    ......
};

它調到了IPCThreadState::joinThreadPool(true);這個函數在main函數中接下來也被調到了,那我們就並案調查吧。

IPCThreadState::self()->joinThreadPool(…)做了什麼?

frameworks/native/libs/binder/IPCThreadState.cpp:477

void IPCThreadState::joinThreadPool(bool isMain)
{
    ......
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    ......

    status_t result;
    do {
        processPendingDerefs();             // 處理上次循環尚未完成的內容
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();    // 重點看這裡
        ......
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    ......

    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}

frameworks/native/libs/binder/IPCThreadState.cpp:414

status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;
    result = talkWithDriver();  // 這裡完成一次對binder的IO
    ......
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();
        ......
        result = executeCommand(cmd);
    ......
    return result;
}

也是一個IO-解析的模式,重點來看解析executeCommand(…),frameworks/native/libs/binder/IPCThreadState.cpp:947

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;

    switch ((uint32_t)cmd) {
    ......

    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            ......

            Parcel buffer;
            buffer.ipcSetDataReference(
                reinterpret_cast(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);

            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            ......

            Parcel reply;
            status_t error;
            ......
            if (tr.target.ptr) {
                sp b((BBinder*)tr.cookie);  // 注意:這裡是重點!!!
                error = b->transact(tr.code, buffer, &reply, tr.flags);

            } else {
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }


            if ((tr.flags & TF_ONE_WAY) == 0) {
                ......
                sendReply(reply, 0);
            } 
            ......

            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;

            ......
        }
        break;

    ......
    }

    ......

    return result;
}

tr.cookie是什麼玩意?我們回到《客戶端如何組織Test()請求 ?》末尾那張圖上,此時服務端收到的tr就應該是客戶端請求test時組織的數據,可是那張圖裡cookie明明是0呀?怎麼可能用空指針來初始化sp呢?而且後面還有對這個指針的調用!

客戶端發出的test請求中tr.cookie是什麼?

為了確認那張圖中cookie的正確性,我用gdb調試到源碼內部,這是能得到最確鑿結論的方法。
* 部署環境
編譯《Binder學習筆記(一)》中的代碼。我將該代碼放到了android源碼的external/testservice下,執行

$ mmm external/testservice
$ emulator&    # 啟動模擬器,把編譯出的可執行文件上傳到模擬器並修改可執行權限
$ adb shell mkdir /data/local/tmp/testservice
$ adb push prebuilts/misc/android-arm/gdbserver/ /data/local/tmp/testservice
$ adb push out/debug/target/product/generic/obj/EXECUTABLES/TestServer_intermediates/LINKED/TestServer /data/local/tmp/testservice
$ adb push out/debug/target/product/generic/obj/EXECUTABLES/TestClient_intermediates/LINKED/TestClient /data/local/tmp/testservice
$ adb shell chmod 755 /data/local/tmp/testservice/*
調試
需要開三個終端:
Target1 在模擬器上啟動server
$ adb shell /data/local/tmp/testservice/TestServer
Target2 在模擬器上通過gdbserver啟動客戶端
$ adb shell gdbserver :1234 /data/local/tmp/testservice/TestClient
Process /data/local/tmp/testservice/TestClient created; pid = 1254
Listening on port 1234
Remote debugging from host 127.0.0.1
Host1 在宿主端啟動gdb
$ ./prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gdb out/debug/target/product/generic/obj/EXECUTABLES/TestClient_intermediates/LINKED/TestClient
......
(gdb) b main
Breakpoint 1 at 0xb6f571fc: file external/testservice/TestClient.cpp, line 14.
(gdb) c
Continuing.
......
(gdb) set solib-absolute-prefix out/debug/target/product/generic/symbols/
Reading symbols from ...... linker...done.
......
Loaded symbols for ......
......
(gdb) b IPCThreadState.cpp:937 # 在我們要查看的位置下斷點
Breakpoint 2 at 0xb6ec89f8: file frameworks/native/libs/binder/IPCThreadState.cpp, line 937.
(gdb) c
Continuing.
......
(gdb) bt  # 注意:一定要通過bt查看是不是由test到達該斷點,如果不是,需要再continue
#0  android::IPCThreadState::writeTransactionData (this=this@entry=0xb6c64000, cmd=cmd@entry=1076388608, binderFlags=binderFlags@entry=16, handle=handle@entry=1, code=code@entry=1, data=..., statusBuffer=statusBuffer@entry=0x0)
    at frameworks/native/libs/binder/IPCThreadState.cpp:937
#1  0xb6ec903c in android::IPCThreadState::transact (this=0xb6c64000, handle=1, code=code@entry=1, data=..., reply=reply@entry=0xbec50ad4, flags=16, flags@entry=0) at frameworks/native/libs/binder/IPCThreadState.cpp:566
#2  0xb6ec408e in android::BpBinder::transact (this=0xb6c490c0, code=1, data=..., reply=0xbec50ad4, flags=0) at frameworks/native/libs/binder/BpBinder.cpp:165
#3  0xb6f5742e in android::BpTestService::test (this=) at external/testservice/TestClient.cpp:10
#4  0xb6f5723c in main () at external/testservice/TestClient.cpp:18
(gdb) p tr
$2 = {target = {handle = 1, ptr = 1}, cookie = 0, code = 1, flags = 16, sender_pid = 0, sender_euid = 0, data_size = 72, offsets_size = 0, data = {ptr = {buffer = 3066360032, offsets = 0}, buf = "\340\360?\000\000\000"}}
(gdb)

非常確認,客戶端發出的數據包中tr.cookie就是0!

那就奇了怪了,客戶端發出的是0,為什麼到了服務端還要用它?

服務端接收到的test請求中tr.cookie是什麼?

繼續用gdb調試服務端,一探究竟。調試服務端也需要三個終端:
1. Target1 在模擬器上通過gdbserver啟動server

$ adb shell gdbserver :1234  /data/local/tmp/testservice/TestServer
Process /data/local/tmp/testservice/TestServer created; pid = 1273
Listening on port 1234
Host1 在宿主機調試server
$ ./prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gdb out/debug/target/product/generic/obj/EXECUTABLES/TestServer_intermediates/LINKED/TestServer
......
(gdb) b main
Breakpoint 1 at 0x19e8: file external/testservice/TestServer.cpp, line 30.
(gdb) c
The program is not being run.
(gdb) target remote :1234
......
0xb6f5c658 in ?? ()
(gdb) c
Continuing.
......
(gdb) set solib-absolute-prefix out/debug/target/product/generic/symbols/
......
(gdb) b IPCThreadState.cpp:1087
Breakpoint 2 at 0xb6eeec52: file frameworks/native/libs/binder/IPCThreadState.cpp, line 1087.
(gdb) c
Continuing.
Target2 在模擬器啟動Client,觸發斷點
$ adb shell /data/local/tmp/testservice/TestClient
BpTestService::test()

然後在Host1上就會看到如下結果:

Breakpoint 2, android::IPCThreadState::executeCommand (this=this@entry=0xb6c64000, cmd=cmd@entry=-2144833022) at frameworks/native/libs/binder/IPCThreadState.cpp:1087
1087                    error = b->transact(tr.code, buffer, &reply, tr.flags);
(gdb) bt  # 打印調用堆棧,確認走到了我們想要斷點
#0  android::IPCThreadState::executeCommand (this=this@entry=0xb6c64000, cmd=cmd@entry=-2144833022) at frameworks/native/libs/binder/IPCThreadState.cpp:1087
#1  0xb6eeedbc in android::IPCThreadState::getAndExecuteCommand (this=this@entry=0xb6c64000) at frameworks/native/libs/binder/IPCThreadState.cpp:433
#2  0xb6eeee20 in android::IPCThreadState::joinThreadPool (this=0xb6c64000, isMain=) at frameworks/native/libs/binder/IPCThreadState.cpp:492
#3  0xb6f7dabc in main () at external/testservice/TestServer.cpp:35
(gdb) p tr  # cookie非0!!
$1 = {target = {handle = 3066421360, ptr = 3066421360}, cookie = 3066323044, code = 1, flags = 16, sender_pid = 1276, sender_euid = 0, data_size = 72, offsets_size = 0, data = {ptr = {buffer = 3065258024, offsets = 3065258096}, buf = "( \264\266p \264\266"}}

見了鬼了,cookie非0!發送端和接收端看到的值不一樣!服務端此時收到的這個cookie是什麼呢?服務端把cookie直接當作地址轉換成了BBinder,能這麼搞說明cookie裡記錄的地址一定是服務端自己地址空間的,接下來又調用b->transact(…)執行具體服務,那這個服務應該就是服務端的BnTestService吧?
BnTestService是在addService時創建,而且還記得嘛,這個地址是被發送給了ServiceManager。參見《binder服務端是如何組織addService數據的?》末尾的圖,服務端調用addService向ServiceManager注冊自己,並把自己的BnTestService對象指針傳給了cookie。在那張圖中有兩個cookie,左邊是向ServiceManager發送的ADD_SERVICE_TRANSACTION命令數據,右邊Parcel是該命令包含的注冊信息數據。

cookie是否就是當初注冊的時候new出來的BnTestService呢?

繼續用gdb驗證!
* 服務端收到的tr.cookie是否就是注冊時呢我出來的BnTestService?
還是調試服務端,很多命令是重復的,如果嫌煩可以寫一個gdb腳本。
Target1和Target2與上一小節沒有任何差別,來看Host1,先寫好gdb腳本,20160515.gdb:

define server_test
    target remote :1234
    b main
    c
    set solib-absolute-prefix out/debug/target/product/generic/symbols/
    b IServiceManager.cpp:161
    b IPCThreadState.cpp:1087
    c
end 

然後執行gdb:

$ ./prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gdb out/debug/target/product/generic/obj/EXECUTABLES/TestServer_intermediates/LINKED/TestServer
......
(gdb) source ../androidex/external/testservice/20160515.gdb
(gdb) server_test
......
Breakpoint 2, android::BpServiceManager::addService (this=0xb6c0e040, name=..., service=..., allowIsolated=) at frameworks/native/libs/binder/IServiceManager.cpp:161
161         data.writeStrongBinder(service);
(gdb) p service  # 查看addService時BnTestService的地址
$1 = (const android::sp &) @0xbeb99b14: {m_ptr = 0xb6c06064}
(gdb) c
Continuing.

Breakpoint 3, android::IPCThreadState::executeCommand (this=this@entry=0xb6c24000, cmd=cmd@entry=-2144833022) at frameworks/native/libs/binder/IPCThreadState.cpp:1087
1087                    error = b->transact(tr.code, buffer, &reply, tr.flags);
(gdb) p tr  # cookie=3066060900=0xb6c06064,正是BnTestService!
$2 = {target = {handle = 3066159216, ptr = 3066159216}, cookie = 3066060900, code = 1, flags = 16, sender_pid = 1297, sender_euid = 0, data_size = 72, offsets_size = 0, data = {ptr = {buffer = 3064995880, offsets = 3064995952}, buf = "( \260\266p \260\266"}}
(gdb)

證實!

服務端接收到test()請求時,tr.cookie就是BnTestService的指針

至於為什麼客戶端發來時組的數據包中cookie為0,服務端收到時自動變成了BnTestService?我們以後再探究。先把test()的流程看完。

BnTestService繼承自BnInterface,後者又繼承自ITestService和BBinder。即BBinder是BnTestService基類的基類,故將cookie轉換成BBinder*是合法的。
在看接下來的調用b->transact(…),frameworks/native/libs/binder/Binder.cpp:97

status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{   // code=TEST
    ......
    switch (code) {
        case PING_TRANSACTION:
            reply->writeInt32(pingBinder());
            break;
        default:
            err = onTransact(code, data, reply, flags); // 走到這裡
            break;
    }
    ......
    return err;
}

BBinder::onTransact(…)是一個虛函數,b實際指向的是BnTestService,因此該虛函數實際應看BnTestService::onTransact(…)版本:

status_t BnTestService::onTransact(uint_t code, const Parcel& data,
        Parcel* reply, uint32_t flags) {
    switch (code) {
    case TEST: {
        printf("BnTestService::onTransact, code: TEST\n");
        CHECK_INTERFACE(ITest, data, reply);
        test();
        reply->writeInt32(100);
        return NO_ERROR;
    }
        break;
    default:
        break;
    }
    return NO_ERROR;
}

終於到達了test()的實現!服務端的test服務被調用,如果有返回值,將被寫入reply,打成包裹發給客戶端,打包和發送過程就前面都有,不再重復分析了。

總結

至此,通過靜態、動態代碼走查,我把Binder的ServiceManager、服務端、客戶端的角色基本梳理清晰了:
* 服務端通過addService向ServiceManager注冊服務,後者緩存下服務的名稱和在服務端的進程內指針,這兩個變量就可以唯一確定一個服務。
* 服務端公開了服務接口,為每一個接口定義一個唯一編碼,並負責實現這些服務接口。
* 客戶端通過getService獲取指定名稱的服務端handle,該handle在客戶端被偽裝成指向服務的指針,通過該指針可以調用服務接口。實際上framework把handle和服務接口打成數據包發送給服務端。
* 服務端在接收到打包請求時,解析接口,並執行對應的實現,將結果返回給客戶端。

當然,Binder的天空下還剩一小片烏雲,就是那個tr.cookie,為什麼客戶端發送的時候填入的是0,而服務端卻接收到了自己的BnTestService指針?現在可以考慮這個問題了。我猜測這是驅動層干的事兒。有沒有點平行宇宙的意思?雙縫干涉之所以出現了干涉條紋,是因為我們所在的世界和另一個平行宇宙世界的粒子發生了疊加!
接下來就繼續穿越到驅動層一探究竟吧。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved