A Simple Example of Binder

Keywords: Android Linux Programming network

Binder introduction

Binder is an interprocess communication mechanism (IPC) used in Android. In Android systems, applications are composed of four types of components: Activity, Service, Broadcast Receiver and Content Provider. They may run in the same process or in different processes. In addition, various system components are also running in independent processes. The communication mechanism between application components and system components running in different processes is Binder.
Android is built on Linux, but in terms of IPC mechanism, it does not use Linux to provide IPC mechanism, but uses a lightweight IPC mechanism - Binder mechanism. Binder is based on OpenBinder. OpenBinder was first developed by Be Inc., and then Palm Inc. Binder is an inter-process communication mechanism. It is a distributed component architecture similar to COM and CORBA. Popular, it actually provides remote procedure call (RPC) functions.

Binder has the following characteristics

  • It has a distributed architecture.
  • System-level development. It's system-oriented, not application-oriented. The goal is process and IPC, not cross-network communication.
  • It is implemented in C++ language.
  • Support for multithreaded programming. Various threading models that support multi-threaded programming. Specific threading models are not mandatory.
  • It can support multiple operating system platforms. BeOS, Windows and Palm OS Cobalt, and Android. Binders can be implemented and used on these different platforms. Binder uses components and objects to present basic system services, such as processes and shared memory. Instead of trying to replace and hide the traditional concepts in the operating system, it embraces and transcends them. This enables binder to support most traditional operating system platforms.
  • Good hardware scalability. The requirement for hardware is very low, which is in line with the actual situation of android. android has a wide range of product lines and chips at all levels. In order to run smoothly on low-end machines, the performance requirements of the software itself are still very important.
  • User customization is high. As the mechanism implementation of IPC, most components in the system are connected in series, and the coupling between components is minimized. Each component can be modified and replaced freely, which improves the customizability of users.

Binder Application Model
An IPC communication can be simply understood as Client-Server mode. Client requests Service. Server receives a Client request and processes it accordingly, or may bring back the result to Client.

The interprocess communication model of Binder mechanism in Android system is summarized as follows:

  1. Client obtains Server's Proxy through Service Manager. From Client's point of view, Proxy is no different from his native objects. It can call its methods and access its variables like other local objects.
  2. Client sends request information to Server by calling Proxy.
  3. Proxy sends user request information to Linux kernel space (actually memory sharing) via Binder device node (/dev/binder), which is acquired by Binder driver and sent to Server.
  4. Server processes user requests and returns the processing results to Client's Proxy through the Binder driver of the Linux kernel.
  5. Client receives the return result from Server.

Composition of Binder Mechanism

  • Binder Driver: Binder is a character driver in the kernel located at / dev/binder. This device is the core part of IPC in Android system. Client's service proxy is used to send requests to Server through it, and the Server is also used to return the processing results to client's service proxy object. This part encapsulates Binder-driven operations in Android through an IPCThreadState object.
  • Service Manager: This is mainly used to manage services. The system services provided in Android are registered by Service Manager and added to the service management list to provide services to clients. If the client wants to communicate with a specific system server, it needs to query and obtain the required services from the Service Manager. You can see that service manager is the management center of system service objects.
  • Server: It's important to emphasize that the service here refers to System Server, not SDK server, which provides services to clients.
  • Client: Usually refers to the application on Android system. It can request services in Server.
  • Proxy: It refers to the acquisition of generated Server Agent (proxy) class objects in client applications. From the application point of view, there is no difference between the proxy object and the local object. They can call their methods, which are synchronous and return the corresponding results.

A Simplest Binder Transport

The following is a simple example to analyze Binder transport. The test program calls the add() function from the client side, completes the addition operation on the server side, and returns the results. The directory structure of the test program is as follows.

The class diagram is shown below. server is on the left and client is on the right.

Client-side code parsing

Look at the client-side code. If Client wants to communicate with Server, it must first obtain the remote interface of Server, which is implemented by Service Manager. Service Manager is used to manage Server and provide Client with the function of querying Server remote interface.

1.---> BinderClientRun.cpp
3.int main(int argc, char** argv)
5.    int sum = 0;
6.    sp<IBinderServiceTest> mBinderServiceTest;
8.    if (mBinderServiceTest.get() == 0) {
9.        // Get the remote interface of Service Manager through defaultService Manager function
10.        sp<IServiceManager> sm = defaultServiceManager(); 
11.        sp<IBinder> binder;
13.        // Use SM - > getService in the loop to constantly try to get a Service named "my.binder.test" and return it to binder
14.        do {
15.            binder = sm->getService(String16("my.binder.test"));
16.            if (binder != 0)
17.                break;
18.                ALOGI("getService fail");
19.            usleep(500000); // 0.5 s
20.        } while (true);
22.        // After getting the Service binder, the binder is converted to BpBinderService Test by interface_cast
23.        mBinderServiceTest = interface_cast<IBinderServiceTest> (binder);
24.        ALOGE_IF(mBinderServiceTest == 0, "no IBinderServiceTest!?");
25.    }
27.    // With BpBinderService Test, you can call the remote server interface
28.    sum = mBinderServiceTest->add(3, 4);
29.    ALOGI("sum = %d", sum);
30.    return 0;

How does interface_case in the code translate binder into BpBinderService Test? Android intentionally hides this transformation and makes it easy for applications to complete it. Look at the definition of interface_case

1.---> IInterface.h
3.template<typename INTERFACE>
4.inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
6.    return INTERFACE::asInterface(obj);

Interface_case is a template that returns IBinderService Test:: asInterface. But asInterface is not directly implemented in our source code, so where is it implemented? Let's first look at two macro definitions

1.---> IInterface.h
3.#define DECLARE_META_INTERFACE(INTERFACE)                               \
4.    static const android::String16 descriptor;                          \
5.    static android::sp<I##INTERFACE> asInterface(                       \
6.            const android::sp<android::IBinder>& obj);                  \
7.    virtual const android::String16& getInterfaceDescriptor() const;    \
8.    I##INTERFACE();                                                     \
9.    virtual ~I##INTERFACE();                                            \
12.#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
13.    const android::String16 I##INTERFACE::descriptor(NAME);             \
14.    const android::String16&                                            \
15.            I##INTERFACE::getInterfaceDescriptor() const {              \
16.        return I##INTERFACE::descriptor;                                \
17.    }                                                                   \
18.    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
19.            const android::sp<android::IBinder>& obj)                   \
20.    {                                                                   \
21.        android::sp<I##INTERFACE> intr;                                 \
22.        if (obj != NULL) {                                              \
23.            intr = static_cast<I##INTERFACE*>(                          \
24.                obj->queryLocalInterface(                               \
25.                        I##INTERFACE::descriptor).get());               \
26.            if (intr == NULL) {                                         \
27.                intr = new Bp##INTERFACE(obj);                          \
28.            }                                                           \
29.        }                                                               \
30.        return intr;                                                    \
31.    }                                                                   \
32.    I##INTERFACE::I##INTERFACE() { }                                    \
33.    I##INTERFACE::~I##INTERFACE() { }

These two macros will be used in the program

1.---> IBinderServiceTest.h
3.class IBinderServiceTest: public IInterface {
5.    DECLARE_META_INTERFACE(BinderServiceTest);
7.    virtual int add(int a, int b) = 0;             
11.---> IBinderServiceTest.cpp
13.IMPLEMENT_META_INTERFACE(BinderServiceTest, "android.test.IBinderServiceTest");

Expanding these two macros will wait

1.---> IBinderServiceTest.h
3.class IBinderServiceTest: public IInterface {
5.    static const android::String16 descriptor; 
6.    static android::sp<IBinderServiceTest > asInterface
7.                     (const android::sp<android::IBinder>& obj);
8.    virtual const android::String16& getInterfaceDescriptor() const; 
9.    IBinderServiceTest (); 
10.    virtual ~IBinderServiceTest (); 
11.    virtual int add(int a, int b) = 0;             
1.---> IBinderServiceTest.cpp
3.    const android::String16 IBinderServiceTest ::descriptor("android.test.IBinderServiceTest"); 
4.    const android::String16& IBinderServiceTest ::getInterfaceDescriptor() const { 
5.        return IBinderServiceTest ::descriptor;
6.    }
7.    android::sp<IBinderServiceTest > IBinderServiceTest ::asInterface
8.                              (const android::sp<android::IBinder>& obj)
9.    {
10.        android::sp<IBinderServiceTest > intr;
11.        if (obj != NULL) {
12.            intr = static_cast<IBinderServiceTest *>
13.                         (obj->queryLocalInterface(IBinderServiceTest ::descriptor).get());
14.            if (intr == NULL) {
15.                intr = new BpBinderServiceTest (obj);
16.            }
17.        }
18.        return intr;
19.    }
20.    IBinderServiceTest ::IBinderServiceTest () { }
21.    IBinderServiceTest ::~IBinderServiceTest () { }

In the expanded macro, we find the asInterface, where new a BpBinderService Test is returned. After the Client side gets the proxy BpBinderService Test of the Server side, it can use the interface of the Server side as a local call. The add() function is called in the sample program to see its implementation in earlier Proxy.

1.---> IBinderServiceTest.cpp 
3.class BpBinderServiceTest: public BpInterface<IBinderServiceTest>
6.    BpBinderServiceTest(const sp<IBinder>& impl) :
7.        BpInterface<IBinderServiceTest> (impl) {
8.    }
10.    int add(int a, int b) {
11.        // The Parcel class is used to serialize inter-process communication data.
12.        Parcel data, reply;
13.        ALOGI("BpBinderServiceTest add, a = %d, b = %d", a, b);
15.        // Write the header of the Binder transport data, where the Service descriptor "android. test. IBinderService Test" is written.
16.        data.writeInterfaceToken(IBinderServiceTest::getInterfaceDescriptor());
17.        // The data to be sent to Server is then written to Binder.
18.        data.writeInt32(a);
19.        data.writeInt32(b);
20.        // Start making remote transport calls.
21.        remote()->transact(TEST_ADD, data, &reply);
22.        // Read the data returned by Server.
23.        int sum = reply.readInt32();
25.        ALOGI("BpBinderServiceTest sum = %d", sum);
26.        return sum;
27.    }

In the code, remote() comes from the BpRefBase class, which returns a BpBinder pointer, so this will call BpBinder::transact. TEST_ADD is the command code executed by Binder, and Server executes the corresponding command based on this value. The actual Binder transmission is completed in IPCThreadState, which is synchronized and the call return indicates that the Server end has finished executing. I will not elaborate here.

Server-side code parsing

The Client side sends the command TEST_ADD to see how the Server side executes the command.

1.---> IBinderServiceTest.cpp 
3.status_t BnBinderServiceTest::onTransact(uint32_t code, const Parcel& data,
4.        Parcel* reply, uint32_t flags) {
5.    switch (code) {
6.        // The command TEST_ADD on the Client side is received.
7.        case TEST_ADD: {
8.            // Check whether the header is a Service descriptor.
9.            CHECK_INTERFACE(IBinderServiceTest, data, reply);
11.            // Read the input data.
12.            int a = data.readInt32();
13.            int b = data.readInt32();
14.            ALOGI("BnBinderServiceTest add, a = %d  b = %d", a, b);
16.            int sum = 0;
17.            // Call the corresponding function of Server
18.            sum  = add(a, b);
19.            ALOGI("BnBinderServiceTest sum = %d", sum);
20.            // Write the return data to Binder.
21.            reply->writeInt32(sum);
22.            return sum;
23.        }   
24.    default:
25.        return BBinder::onTransact(code, data, reply, flags);
26.    }   

The source of the Server end is also in IPC ThreadState. IPCThreadState uses the ProcessState class to interact with the Binder driver and receive requests from Client. The BBinder class's transact function is then called and the relevant parameters are passed in. The transact function of the BBinder class finally calls the onTransact function of the BnBinderServiceTest class.
Next, start with the Server startup.

1.---> BinderServerRun.cpp 
3.int main(int argc, char** argv)
4. {
5.    // Create a ProcessState instance.
6.    sp<ProcessState> proc(ProcessState::self());
7.    // Get the remote interface of Service Manager.
8.    sp<IServiceManager> sm = defaultServiceManager();
10.    // Add Service "my.binder.test" to Service Manager.
11.    BinderServiceTest::instantiate();
12.    ProcessState::self()->startThreadPool();
13.    IPCThreadState::self()->joinThreadPool();
14.    return 0;
18.---> BinderTestServer.cpp 
20.void BinderServiceTest::instantiate() {
21.    ALOGI("Enter instantiate");
23.    status_t st = defaultServiceManager()->addService(
24.            String16("my.binder.test"), new BinderServiceTest());
25.    ALOGD("addService ret=%d", st);
28.BinderServiceTest::BinderServiceTest() {
29.    ALOGD("Constructor");
32.BinderServiceTest::~BinderServiceTest() {
33.    ALOGD("Destructor");
36.int BinderServiceTest::add(int a, int b) {
37.    ALOGI("add a = %d, b = %d.", a, b);
38.    return a+b;

The Server side first creates a ProcessState instance by calling ProcessState::self(). ProcessState::self() is a static member variable of the ProcessState class, which returns a globally unique ProcessState instance gProcess. The code is as follows

1.---> ProcessState.cpp 
3.sp<ProcessState> ProcessState::self()
5.    Mutex::Autolock _l(gProcessMutex);
6.    if (gProcess != NULL) {
7.        return gProcess;
8.    }
9.    gProcess = new ProcessState;
10.    return gProcess;

There are two main tasks in the process of process state instantiation. First, the Binder device file / dev/binder is opened by the open_driver function, and the device file descriptor is saved in the member variable mDriverFD; second, the device file / dev/binder is mapped to memory by mmap. I will not elaborate here.
Next, we analyze what startThreadPool() and joinThreadPool() did. The implementation of startThreadPool() is shown in the following code:

1.---> ProcessState.cpp 
3.void ProcessState::startThreadPool()
5.    AutoMutex _l(mLock);
6.    if (!mThreadPoolStarted) {
7.        mThreadPoolStarted = true;
8.        spawnPooledThread(true);
9.    }
12.void ProcessState::spawnPooledThread(bool isMain)
14.    if (mThreadPoolStarted) {
15.        String8 name = makeBinderThreadName();
16.        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
17.        sp<Thread> t = new PoolThread(isMain);
18.        t->run(name.string());
19.    }

PoolThread is a Thread subclass defined in IPCThreadState. Its implementation is as follows:

1.---> ProcessState.cpp 
3.class PoolThread : public Thread
6.    PoolThread(bool isMain)
7.        : mIsMain(isMain)
8.    {
9.    }
12.    virtual bool threadLoop()
13.    {
14.        IPCThreadState::self()->joinThreadPool(mIsMain);
15.        return false;
16.    }
18.    const bool mIsMain;

PoolThread inherits the Thread class, whose run function calls Thread::run() to create a thread, and finally calls the threadLoop function of the subclass. threadLoop() also calls joinThreadPool() to complete the work. The code for joinThreadPool() is as follows.

1.---> IPCThreadState.cpp 
3.status_t IPCThreadState::getAndExecuteCommand()
5.    status_t result;
6.    int32_t cmd;
8.    result = talkWithDriver();
9.    if (result >= NO_ERROR) {
10.        size_t IN = mIn.dataAvail();
11.        if (IN < sizeof(int32_t)) return result;
12.        cmd = mIn.readInt32();
13.        IF_LOG_COMMANDS() {
14.            alog << "Processing top-level Command: "
15.                 << getReturnString(cmd) << endl;
16.        }
18.        result = executeCommand(cmd);
20.        // After executing the command, ensure that the thread is returned to the
21.        // foreground cgroup before rejoining the pool.  The driver takes care of
22.        // restoring the priority, but doesn't do anything with cgroups so we
23.        // need to take care of that here in userspace.  Note that we do make
24.        // sure to go in the foreground after executing a transaction, but
25.        // there are other callbacks into user code that could have changed
26.        // our group so we want to make absolutely sure it is put back.
27.        set_sched_policy(mMyThreadId, SP_FOREGROUND);
28.    }
30.    return result;
33.void IPCThreadState::joinThreadPool(bool isMain)
35.    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
37.    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
39.    // This thread may have been spawned by a thread that was in the background
40.    // scheduling group, so first we will make sure it is in the foreground
41.    // one to avoid performing an initial transaction in the background.
42.    set_sched_policy(mMyThreadId, SP_FOREGROUND);
44.    status_t result;
45.    do {
46.        processPendingDerefs();
47.        // now get the next command to be processed, waiting if necessary
48.        result = getAndExecuteCommand();
50.        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
51.            ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
52.                  mProcess->mDriverFD, result);
53.            abort();
54.        }
56.        // Let this thread exit the thread pool if it is no longer
57.        // needed and it is not the main process thread.
58.        if(result == TIMED_OUT && !isMain) {
59.            break;
60.        }
61.    } while (result != -ECONNREFUSED && result != -EBADF);
64.        (void*)pthread_self(), getpid(), (void*)result);
66.    mOut.writeInt32(BC_EXIT_LOOPER);
67.    talkWithDriver(false);

This function eventually interacts with the Binder driver by calling the talkWithDriver function in an infinite loop. In fact, it calls the talkWithDriver to wait for the client's request and then calls the executeCommand to process the request. In the executeCommand function, BBinder::transact is finally called to be true. Client's request is being processed. BBinder:: transact eventually calls the onTransact function to handle it, which actually calls the BnBinderServiceTest::onTransact in the example.

Posted by Shaun on Wed, 25 Sep 2019 23:19:52 -0700