Analyze the causes and consequences of the Binder Mechanism in Android from the source code perspective
I have also mentioned an article "taking you through socket programming in linux from scratch", mainly starting from the perspective of Process Communication and then extending to socket development in linux. This article still analyzes the process Communication Mechanism in Android from the perspective of Process Communication.
Why is the binder communication mechanism used in Android?
As we all know, there are many methods for Process Communication in linux, such as pipelines, message queues, and socket mechanisms. We are not familiar with socket anymore. However, as a common interface, it has high communication overhead and low data transmission efficiency, it is mainly used for inter-network process Communication and local low-speed communication. Message queues and pipelines both adopt the storage-forwarding mode, and the efficiency is also a little low, because data transmission in this mode requires two memory copies, first, copy from the sender's cache area to the cache area opened by the kernel, and then copy from the kernel to the receiver's cache area. The traditional ipc does not have any security measures, and there is no way between two processes to identify each other. In Android, each application is assigned a uid after installation, therefore, this identity is guaranteed and more secure. To ensure security and efficiency, Android provides a new ipc communication mechanism, namely, binder.
Binder Communication Model
Inter-process communication can be simply understood as the Client-server mode. A model of the binder Mechanism in the Android system is as follows:
The Client obtains the proxy object on the server. The Client sends a request to the server by calling the proxy object. The proxy object sends the Client request information to the Linux kernel space through the binder device node, which is obtained by the binder driver and sent to the service process. The service process processes Client requests and returns the results to the proxy object through the binder driver of the Linux kernel. The client receives the proxy response.
After the client thread enters the binder driver, the kernel suspends the client thread and then enters the service process until the result is returned, so this process is similar to "thread migration", that is, a thread enters another process and returns results with it.
Binder composition structure: Binder driver
The binder is a character driver in the kernel located in/dev/binder. This device is the core part of the Android system IPC. The client's Service proxy is used to send requests to the server. The server also returns the processing results to the client's Service proxy object. In Android, an IPCThreadState object encapsulates operations on the Binder driver. Service Manager
This is mainly used to manage services. All system services provided by Android must register themselves through Service Manager and add themselves to the Service management linked list to provide services for the client. To communicate with a specific system server, the client needs to query and obtain the required services from the Service Manager. Service Manager is the Management Center of system Service objects. Service (Server)
It should be emphasized that the service here refers to the System Server, rather than the SDK server, to provide services to the client. Client
Generally, it refers to applications on the Android system. It can request services in the Server. Proxy object
Obtain the generated proxy class object in the client application. From the application perspective, there is no difference between the proxy object and the local object. You can call its method. The methods are both synchronous and the corresponding results are returned. Analyze MediaPlayerService from source code
Like other blog articles about the binder Mechanism, I will introduce the bidner from the perspective of MediaPlayerService (we suggest you read this book to understand Android in depth). The source code is in frameworkaseMediaMediaServerMain_mediaserver.cpp. MPS is selected because this service involves many important services, such as audio and video.
Int main (int argc, char ** argv) {// open the driver device and map the device file descriptor to the memory. This fast memory serves as the place for Data Interaction sp
Proc (ProcessState: self (); sp
Sm = defaservicservicemanager (); LOGI (ServiceManager: % p, sm. get (); waitBeforeAdding (String16 (media. audio_flinger); AudioFlinger: instantiate (); waitBeforeAdding (String16 (media. player); // register MediaPlayerService, MediaPlayerService: instantiate (); waitBeforeAdding (String16 (media. camera); CameraService: instantiate (); waitBeforeAdding (String16 (media. audio_policy); AudioPolicyService: instantiate (); ProcessState: self ()-> startThreadPool (); IPCThreadState: self ()-> joinThreadPool ();}
First, let's take a look at the constructor of ProcessState.
ProcessState: ProcessState (): mDriverFD (open_driver () // start address for enabling binder, mVMStart (MAP_FAILED) // memory ing, mManagesContexts (false), mBinderContextCheckFunc (NULL ), mBinderContextUserData (NULL), mThreadPoolStarted (false), mThreadPoolSeq (1 ){...... mVMStart = mmap (0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0 );.....}
The above ProcessState constructor code first calls the open_driver () method and goes to the following:
Static int open_driver () {if (gSingleProcess) {return-1;} // open the binder device, if the device is successfully opened, the returned file descriptor int fd = open (/dev/binder, O_RDWR); if (fd> = 0) {// changes the nature of the opened file. If the FD_CLOEXEC bit is 0, the file remains open during execve. Otherwise, it is disabled. Fcntl (fd, F_SETFD, FD_CLOEXEC); int vers; # if defined (HAVE_ANDROID_ OS) status_t result = ioctl (fd, BINDER_VERSION, & vers); # else status_t result =-1; errno = EPERM; # endif if (result =-1) {LOGE (Binder ioctl to obtain version failed: % s, strerror (errno); close (fd ); fd =-1;} if (result! = 0 | vers! = BINDER_CURRENT_PROTOCOL_VERSION) {LOGE (Binder driver protocol does not match user space protocol !); Close (fd); fd =-1 ;}# if defined (HAVE_ANDROID_ OS) size_t maxThreads = 15; result = ioctl (fd, BINDER_SET_MAX_THREADS, & maxThreads ); if (result =-1) {LOGE (Binder ioctl to set max threads failed: % s, strerror (errno ));} # endif} else {LOGW (Opening '/dev/binder' failed: % s, strerror (errno);} return fd ;}
Open_driver is used to open the/dev/binder file of the binder device. After the file is successfully opened, a file descriptor is returned. Then, the fcntl function is called to change the nature of the opened device file. Next, call the ioctl function. ioctl is the function used to manage the I/O channels of the device in the device driver. The management of the I/O channel is to control some features of the device, such as the Transmission baud rate of the serial port and the speed of the motor. BINDER_SET_MAX_THREADS is the control command of the user program on the device, maxThreads is a supplementary parameter, which means to tell the binder driver the maximum number of threads supported by this file descriptor. Use the mmap function in the constructor to map the file descriptor to the memory to use this memory to receive data. The constructor does two important tasks:
Open the/dev/binder device file and obtain a file descriptor associated with the device file. This is equivalent to obtaining a channel for interaction with the kernel's binder driver. Use the mmap function to map the file descriptor to the memory to use this memory to accept data.
Then return to ProcessState: self () to see:
sp
ProcessState::self(){ if (gProcess != NULL) return gProcess; AutoMutex _l(gProcessMutex); if (gProcess == NULL) gProcess = new ProcessState; return gProcess;}
This self only returns the ProcessState object. ProcessState is designed in singleton mode and is mainly used to maintain a state in which the current process communicates with the binder device.
What can I do next after I have an interactive channel with the binder driver? Let's take a look at the following code:
sp
sm = defaultServiceManager();
The business Implementation of defaservicservicemanager is in IServiceManager. cpp. The prototype is as follows:
sp
defaultServiceManager(){ if (gDefaultServiceManager != NULL)return gDefaultServiceManager; AutoMutex _l(gDefaultServiceManagerLock); if (gDefaultServiceManager == NULL) { gDefaultServiceManager = interface_cast
(ProcessState::self()->getContextObject(NULL)); } return gDefaultServiceManager;}
The most important thing is the code:
gDefaultServiceManager = interface_cast
(ProcessState::self()->getContextObject(NULL))
Go back to ProcessState and check the implementation of getContextObject.
sp
ProcessState::getContextObject(const sp
& caller){ if (supportsProcesses()) { return getStrongProxyForHandle(0); } else { return getContextObject(String16(default), caller); }}
SupportsProcesses () indicates whether the current device supports processes. There is no doubt that the real device must support the process. Therefore, you must follow the getStrongProxyForHandle (0) method.
Sp
ProcessState: getStrongProxyForHandle (int32_t handle) {sp
Result; AutoMutex _ l (mLock); handle_entry * e = lookupHandleLocked (handle); if (e! = NULL) {IBinder * B = e-> binder; if (B = NULL |! E-> refs-> attemptIncWeak (this) {B = new BpBinder (handle); // create a BpBinder e-> binder = B; // fill the resource item with this binder if (B) e-> refs = B-> getWeakRefs (); result = B ;} else {// This little bit of nastyness is to allow us to add a primary // reference to the remote proxy when this team doesn't have one // but another team is sending handle to us. result. force_set (B); e-> refs-> decWeak (this) ;}/// new BpBinder (0) is returned;/return result ;}
The lookupHandleLocked function is used to find the corresponding resources. It is definitely not available at the beginning, so the B = null branch is used. In this branch, a BpBinder is created using handler = 0 as the parameter, then, assign the binder value to the binder attribute of handle_entry, and finally return the BpBinder object, that is, new BpBinder (0) is returned ). I think the handle_entry resource item should be linked to the current process. Otherwise, why should BpBinder be bound to this resource item.
Here there are two important things: BpBinder and BBinder. They are paired. BpBinder is the proxy class of the client and the server, and BBinder is the opposite end of BpBinder, represents the target end of the client to interact. In other words, if BpBinder is a client, then BBinder is a server.
Next, gdefaservicservicemanager = interface_cast (ProcessState: self ()-> getContextObject (NULL) should be converted to the following code:
gDefaultServiceManager = interface_cast
(BpBinder)
The interface_cast method is described as follows:
template
inline sp
interface_cast(const sp
& obj){ return INTERFACE::asInterface(obj);}
Interface_cast is a template method. INTERFACE represents IserviceManager, which calls IserviceManager. asInterface method. In this method, a BpManagerService object is constructed using the BpBinder object. We know that BpBinder and BBinder are in a communication relationship, why is a BpManagerService coming up here? BpManagerService implements the business functions in IServiceManager, and a mRemote object in BpManagerService points to the BpBinder object. In this way, BpManagerService implements business functions and communicates with the server. Next, we will analyze the service registration.
In MediaPlayerService. cpp:
Void MediaPlayerService: instantiate () {// defaservicservicemanager actually returns BpManagerService, which is the descendant of IServiceManager defaultServiceManager ()-> addService (String16 (media. player), new MediaPlayerService ());}
DefaultServiceManager () returns BpManagerService, which implements the IServiceManager business method. Therefore, we need to go to BpManagerService to see the implementation of addService:
Virtual status_t addService (const String16 & name, const sp
& Service) {Parcel data, reply; // data packet. writeInterfaceToken (IServiceManager: getInterfaceDescriptor (); data. writeString16 (name); data. writeStrongBinder (service); // after the data is packaged, the communication work is handed over to BpServiceManager. // This remote is actually called mRemote, that is, after BpBinder object // data is packaged, the communication work is handed over to the BpBinder object to complete status_t err = remote ()-> transact (ADD_SERVICE_TRANSACTION, data, & reply ); // Therefore, the addService function is actually The function at the business layer is very simple. It is to package the data and hand it over to the communication layer to process return err = NO_ERROR? Reply. readExceptionCode (): err ;}
Parcel can be understood as a data packet transmitted between processes. data is the data packet we want to send, while reply is the data packet returned by the Service. After the data is packaged, it is sent to remote () when the function's transact method is executed, the remote () method returns the mRemote object in BpManagerService. This object points to the previous BpBinder object, therefore, the work of communicating with the service is handed over to the BpBinder object. Next we will go to BpBinder to see the implementation of this method:
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;}
BpBinder does not actually have much practical work to do, that is, a representation. This real work is handed over to the IPCThreadState statement. IPCThreadState is the real working guy in the process. Go to IPCThreadState. cpp to see:
Status_t IPCThreadState: transact (int32_t handle, uint32_t code, const Parcel & data, Parcel * reply, uint32_t flags ){...... if (err = NO_ERROR) {LOG_ONEWAY (>>>> SEND from pid % d uid % d % s, getpid (), getuid (), (flags & TF_ONE_WAY) = 0? Read reply: one way); // The value of handle is 0, which indicates the target end of the communication. // BC_TRANSACTION parameter: the message code sent by the application to the binder device. // The binder device returns a message code starting with "BR _" to the application. // Start the data and wait for the result to be returned. // In fact, this method only writes the data to mOut and does not send the data out of err = writeTransactionData (BC_TRANSACTION, flags, handle, code, data, NULL );}........ if (flags & TF_ONE_WAY) = 0 ){............ # endif if (reply) {err = waitForResponse (reply);} else {Parcel fakeReply; err = waitForResponse (& fakeReply );} # if 0 .........} else {err = waitForResponse (NULL, NULL);} return err ;}
WriteTransactionData is just a place where data is written. The prototype is as follows:
Status_t IPCThreadState: Counter (int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel & data, status_t * statusBuffer) {handle tr; // handle is 0, indicates the destination tr.tar get. handle = handle; // message code 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 (); tr. offsets_size = data. ipcObjectsCount () * sizeof (size_t); tr. data. ptr. offsets = data. ipcObjects ();} else if (statusBuffer) {tr. flags | = TF_STATUS_CODE; * statusBuffer = err; tr. data_size = sizeof (status_t); tr. data. ptr. buffer = statusBuffer; tr. offsets_size = 0; tr. data. ptr. offsets = NULL;} else {return (mLastError = err);} mOut. writeInt32 (cmd); mOut. write (& tr, sizeof (tr); return NO_ERROR ;}
This method only writes the command directly to the out and does not send the message. So proceed to the waitForResponse method, the task of sending and receiving data must be shown in the following figure:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){int32_t cmd;int32_t err;while (1) { if ((err=talkWithDriver()) < NO_ERROR) break; err = mIn.errorCheck(); if (err < NO_ERROR) break; if (mIn.dataAvail() == 0) continue; cmd = mIn.readInt32(); IF_LOG_COMMANDS() { alog << Processing waitForResponse Command: << getReturnString(cmd) << endl; } switch (cmd) { case BR_TRANSACTION_COMPLETE: if (!reply && !acquireResult) goto finish; break; case BR_DEAD_REPLY: err = DEAD_OBJECT; goto finish; case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; case BR_ACQUIRE_RESULT: { LOG_ASSERT(acquireResult != NULL, Unexpected brACQUIRE_RESULT); const int32_t result = mIn.readInt32(); if (!acquireResult) continue; *acquireResult = result ? NO_ERROR : INVALID_OPERATION; } goto finish; case BR_REPLY: { binder_transaction_data tr; err = mIn.read(&tr, sizeof(tr)); LOG_ASSERT(err == NO_ERROR, Not enough command data for brREPLY); if (err != NO_ERROR) goto finish; if (reply) { if ((tr.flags & TF_STATUS_CODE) == 0) { reply->ipcSetDataReference( reinterpret_cast
(tr.data.ptr.buffer), tr.data_size, reinterpret_cast
(tr.data.ptr.offsets), tr.offsets_size/sizeof(size_t), freeBuffer, this); } else { err = *static_cast
(tr.data.ptr.buffer); freeBuffer(NULL, reinterpret_cast
(tr.data.ptr.buffer), tr.data_size, reinterpret_cast
(tr.data.ptr.offsets), tr.offsets_size/sizeof(size_t), this); } } else { freeBuffer(NULL, reinterpret_cast
(tr.data.ptr.buffer), tr.data_size, reinterpret_cast
(tr.data.ptr.offsets), tr.offsets_size/sizeof(size_t), this); continue; } } goto finish; default: err = executeCommand(cmd); if (err != NO_ERROR) goto finish; break; }}finish:if (err != NO_ERROR) { if (acquireResult) *acquireResult = err; if (reply) reply->setError(err); mLastError = err;}return err;}
WaitForResponse is used to send and receive data, so how does one communicate with the binder driver? Let's take a look at the talkWithDriver function:
Status_t IPCThreadState: talkWithDriver (bool doReceive) {LOG_ASSERT (mProcess-> mDriverFD> = 0, Binder driver is not opened ); // binder_write_read Is the structure of data exchange with the binder driver binder_write_read bwr; // Is the read buffer empty? 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; // fill bwr with the Request command. write_size = outAvail; bwr. write_buffer = (long unsigned int) mOut. data (); // This is what we'll read. if (doReceive & needRead) {// accept the data buffer information filling. if the data is received later, enter bwr in mIn directly. read_size = mIn. dataCapacity (); bwr. read_buffer = (long unsigned int) mIn. data ();} else {bwr. read_size = 0;} IF_LOG_COMMANDS () {TextOutput: Bundle _ B (alog); if (outAvail! = 0) {alog <Sending commands to driver: <indent; const void * cmds = (const void *) bwr. write_buffer; const void * end = (const uint8_t *) cmds) + bwr. write_size; alog <HexDump (cmds, bwr. write_size) <endl; while (cmds <end) cmds = printCommand (alog, cmds); alog <dedent ;}alog <Size of receive buffer: <bwr. read_size <, needRead: <needRead <, doReceive: <doReceive <endl ;}// Return immediately if there is nothing to do. // if no data is exchanged, 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_LOG_COMMANDS () {alog <About to read/write, write size = <mOut. dataSize () <endl ;}# if defined (HAVE_ANDROID_ OS) if (ioctl (mProcess-> mDriverFD, BINDER_WRITE_READ, & bwr)> = 0) err = NO_ERROR; else err =-errno; # else err = INVALID_OPERATION; # endif IF_LOG_COMMANDS () {alog <Finished read/write, write size = <mOut. dataSize () <endl ;}while (err ==-EINTR); IF_LOG_COMMANDS () {alog <Our err: <(void *) err <, write consumed: <bwr. write_consumed <(of <mOut. dataSize () <), read consumed: <bwr. read_consumed <endl;} 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);} IF_LOG_COMMANDS () {TextOutput: Bundle _ B (alog); alog <Remaining data size: <mOut. dataSize () <endl; alog <existing ed commands from driver: <indent; const void * cmds = mIn. data (); const void * end = mIn. data () + mIn. dataSize (); alog <HexDump (cmds, mIn. dataSize () <endl; while (cmds <end) cmds = printReturnCommand (alog, cmds); alog <dedent;} return NO_ERROR;} return err ;}
It seems that communication with the binder driver is not in read/write mode, but in ioctl mode. This completes the binder communication.
Let's analyze it here. In-depth research at the moment