A simple example of binder

Time:2020-4-8

Binder introduction

Binder is the interprocess communication mechanism (IPC) used in Android. In Android system, 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 run in separate processes. The communication mechanism between these application components and system components running in different processes is binder.
Although Android is built on Linux, in terms of IPC mechanism, it does not use Linux to provide IPC mechanism, but uses a set of lightweight IPC mechanism binder mechanism. Binder is based on openbinder. Openbinder was first developed by be Inc. and then used by palm Inc. Binder is a kind of inter process communication mechanism, which is similar to com and CORBA distributed component architecture. In fact, it provides remote procedure call (RPC) function.

Binder has the following features

  • It has a distributed architecture.
  • System level development oriented. Oriented to the system layer, not the application layer. The goal is process and IPC, not cross network communication.
  • It is implemented in C + + language.
  • Support for multithreaded programming. Various thread models supporting multithreaded programming. Specific thread models are not enforced.
  • It can support multiple operating system platforms. From beos to windows and Palm OS cobalt to Android. Binder 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 allows the binder to support most traditional operating system platforms.
  • Good hardware scalability. The requirement for hardware is very low, which is just in line with the actual situation of Android. Android has a wide range of product lines and the main frequency of chips at all levels. To run smoothly on low-end machines, the performance requirements of the software itself are still very important.
  • High user customizability. As the mechanism implementation of IPC, most of the components in the system are connected in series, and the coupling lines between the components are 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 a client server mode, in which the client requests service, and the server processes the client request after receiving it, or may bring back the result to the client.
A simple example of binder

The inter process communication model of binder mechanism in Android system is summarized as follows:

  1. The client obtains the proxy of the server through the service manager. From the client’s point of view, there is no difference between proxy and its local objects. It can call its methods and access its variables just like any other local object.
  2. The client sends the request information to the server by calling the proxy method.
  3. Proxy sends user request information to Linux kernel space (actually memory sharing) through binder device node (/ dev / binder), which is obtained by binder driver and sent to server.
  4. The server processes the user request and returns the processing result to the client’s proxy through the binder driver of the Linux kernel.
  5. The client received the return result from the server.

Composition of binder mechanism

  • Binder driver: binder is a character driver device in the kernel located at / dev / binder. This device is the core part of the Android system IPC. The service agent of the client is used to send requests to the server through it. The server also returns the processing results to the service agent object of the client through it. In Android, this part encapsulates the operation of binder driver through an ipcthreadstate object.
  • Service Manager: This is mainly used to manage services. The system services provided in Android need to register themselves through service manager, add themselves to the service management chain list, and provide services for 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: we need to emphasize that service here refers to system server, not SDK server, which provides services to clients.
  • Client: generally, it refers to the application on Android system. It can request services from the server.
  • Proxy: get the generated server proxy class object in the client application. From the application point of view, there is no difference between the proxy object and the local object. All methods can be called. All methods are synchronous and return corresponding results.

One of the simplest binder transfers

Here 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 result. The directory structure of the test program is as follows,
A simple example of binder

The class diagram is shown below. The left side is the server side and the right side is the client side.
A simple example of binder

Client code analysis

A simple example of binder
From the client side of the code. If the client wants to communicate with the server, it must first obtain the remote interface of the server, which is implemented by the service manager. The service manager is used to manage the server and provide the client with the function of querying the server remote interface.

1.---> BinderClientRun.cpp
2.
3.int main(int argc, char** argv)
4.{
5.    int sum = 0;
6.    sp<IBinderServiceTest> mBinderServiceTest;
7.
8.    if (mBinderServiceTest.get() == 0) {
9 // get the remote interface of service manager through defaultservicemanager function
10.        sp<IServiceManager> sm = defaultServiceManager(); 
11.        sp<IBinder> binder;
12. 
13 // use SM - > getservice in the loop to continuously try to get the service named "my. Binder. Test" and return the 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);
21. 
22 // after getting the service binder, convert the binder to bpbinderservicetest through interface \cast
23.        mBinderServiceTest = interface_cast<IBinderServiceTest> (binder);
24.        ALOGE_IF(mBinderServiceTest == 0, "no IBinderServiceTest!?");
25.    }
26. 
27. / bpbinderservicetest, you can call the remote server interface
28.    sum = mBinderServiceTest->add(3, 4);
29.    ALOGI("sum = %d", sum);
30.    return 0;
31.}

How does the interface · cast in the code convert the binder to bpbinderservicetest? Android intentionally hides this transformation, making it easy for the app to complete this transformation. Take a look at the definition of interface? Cast

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

Interface? Cast is a template that returns ibinderservicetest:: asinterface. But asinterface is not directly implemented in our source code, so where is it implemented? Let’s take a look at two macro definitions

1.---> IInterface.h
2. 
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();                                            \
10.
11.
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
2. 
3.class IBinderServiceTest: public IInterface {
4.public:                                            
5.    DECLARE_META_INTERFACE(BinderServiceTest);
6.
7.    virtual int add(int a, int b) = 0;             
8.};
9.
10. 
11.---> IBinderServiceTest.cpp
12. 
13.IMPLEMENT_META_INTERFACE(BinderServiceTest, "android.test.IBinderServiceTest");

Hold a meeting between these two grand exhibitions until

1.---> IBinderServiceTest.h
2. 
3.class IBinderServiceTest: public IInterface {
4.public:                                            
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;             
12.};
1.---> IBinderServiceTest.cpp
2.
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 found asinterface. Here we found a bpbinderservicetest and returned it. After the client gets the server-side agent bpbinderservicetest, it can use the server-side interface like a local call. The add () function is invoked in the example program to see its implementation in Proxy.

1.---> IBinderServiceTest.cpp 
2. 
3.class BpBinderServiceTest: public BpInterface<IBinderServiceTest>
4.{
5.public:
6.    BpBinderServiceTest(const sp<IBinder>& impl) :
7.        BpInterface<IBinderServiceTest> (impl) {
8.    }
9.
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);
14. 
15 // write the header of binder's transmission data. Here write the service descriptor "Android. Test. Ibinderservicetest".
16.        data.writeInterfaceToken(IBinderServiceTest::getInterfaceDescriptor());
17. // then write the data to be sent to the server to binder.
18.        data.writeInt32(a);
19.        data.writeInt32(b);
20 // start the remote transmission call.
21.        remote()->transact(TEST_ADD, data, &reply);
22 // read the data returned by the server.
23.        int sum = reply.readInt32();
24.
25.        ALOGI("BpBinderServiceTest sum = %d", sum);
26.        return sum;
27.    }
28.};

In the code, remote () comes from the bprefbase class, which returns a bpbinder pointer, so this calls bpbinder:: transact. Test add is the command code executed by binder, and the server will execute the corresponding command according to this value. The actual binder transmission is completed in the ipcthreadstate. The transmission is synchronous. The call return indicates that the execution of the server has ended. There is no detailed explanation here.

Server side code analysis

A simple example of binder
The client side sends the command test_add to see how the server side executes the command.

1.---> IBinderServiceTest.cpp 
2. 
3.status_t BnBinderServiceTest::onTransact(uint32_t code, const Parcel& data,
4.        Parcel* reply, uint32_t flags) {
5.    switch (code) {
6. // received the command test_add from the client.
7.        case TEST_ADD: {
8. / check whether the data header is a service descriptor.
9.            CHECK_INTERFACE(IBinderServiceTest, data, reply);
10.            
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);
15.            
16.            int sum = 0;
17 // call the function corresponding to the server
18.            sum  = add(a, b);
19.            ALOGI("BnBinderServiceTest sum = %d", sum);
20 // write the returned data to binder.
21.            reply->writeInt32(sum);
22.            return sum;
23.        }   
24.    default:
25.        return BBinder::onTransact(code, data, reply, flags);
26.    }   
27.}

The source of server side is also in ipcthreadstate. Ipcthreadstate uses the processstate class to interact with the binder driver and receive requests from the client. After that, we call the transact function of the BBinder class and pass in the relevant parameters. The transact function of the bbinder class finally calls the ontransact function of the bnbinderservicetest class.
Next, start the analysis from the server side.

1.---> BinderServerRun.cpp 
2. 
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();
9. 
10. // add service "my. Binder. Test" to service manager.
11.    BinderServiceTest::instantiate();
12.    ProcessState::self()->startThreadPool();
13.    IPCThreadState::self()->joinThreadPool();
14.    return 0;
15.}
16. 
17. 
18.---> BinderTestServer.cpp 
19. 
20.void BinderServiceTest::instantiate() {
21.    ALOGI("Enter instantiate");
22.
23.    status_t st = defaultServiceManager()->addService(
24.            String16("my.binder.test"), new BinderServiceTest());
25.    ALOGD("addService ret=%d", st);
26.}
27.
28.BinderServiceTest::BinderServiceTest() {
29.    ALOGD("Constructor");
30.}
31.
32.BinderServiceTest::~BinderServiceTest() {
33.    ALOGD("Destructor");
34.}
35.
36.int BinderServiceTest::add(int a, int b) {
37.    ALOGI("add a = %d, b = %d.", a, b);
38.    return a+b;
39.}

The server side first creates a processstate instance through the processstate:: self() call. 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 
2. 
3.sp<ProcessState> ProcessState::self()
4.{
5.    Mutex::Autolock _l(gProcessMutex);
6.    if (gProcess != NULL) {
7.        return gProcess;
8.    }
9.    gProcess = new ProcessState;
10.    return gProcess;
11.}

There are two main tasks in the process of instantiation of processstate. One is to open the binder device file / dev / binder through the open driver function, and save the open device file descriptor in the member variable mdriverfd; the other is to map the device file / dev / binder to memory through MMAP. There is no detailed explanation here.
Next, analyze what startthreadpool() and jointhreadpool() have done. The implementation of startthreadpool() is shown in the following code:

1.---> ProcessState.cpp 
2.  
3.void ProcessState::startThreadPool()
4.{
5.    AutoMutex _l(mLock);
6.    if (!mThreadPoolStarted) {
7.        mThreadPoolStarted = true;
8.        spawnPooledThread(true);
9.    }
10.}
11....... 
12.void ProcessState::spawnPooledThread(bool isMain)
13.{
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.    }
20.}

Poolthread is a thread subclass defined in ipcthreadstate. Its implementation is as follows:

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

Poolthread inherits the thread class. Its run function calls thread:: run() to create a thread, and finally calls the subclass’s threadloop function. Threadloop() also calls jointhreadpool() to complete the work. The code for jointhreadpool() is as follows.

1.---> IPCThreadState.cpp 
2.  
3.status_t IPCThreadState::getAndExecuteCommand()
4.{
5.    status_t result;
6.    int32_t cmd;
7.
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.        }
17.
18.        result = executeCommand(cmd);
19.
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.    }
29.
30.    return result;
31.}
32.......
33.void IPCThreadState::joinThreadPool(bool isMain)
34.{
35.    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
36.
37.    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
38.    
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);
43.        
44.    status_t result;
45.    do {
46.        processPendingDerefs();
47.        // now get the next command to be processed, waiting if necessary
48.        result = getAndExecuteCommand();
49.
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.        }
55.        
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);
62.
63.    LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",
64.        (void*)pthread_self(), getpid(), (void*)result);
65.    
66.    mOut.writeInt32(BC_EXIT_LOOPER);
67.    talkWithDriver(false);
68.}

This function finally interacts with the binder driver by calling the talkwithdriver function in an infinite loop. In fact, it calls the talkwithdriver function to wait for the client’s request, and then calls the executecommand function to process the request. In the executecommand function, it finally calls the bbinder:: transact function to actually process the client’s request. In the end, bbinder:: transact will call the ontransact function to process, which actually calls bnbinderservicetest:: ontransact in the example.