Why understand Binder? Generally, inter-process communication (IPC) is rarely used directly in Android application development, but if you want to know:
You must have some understanding of Binder, whether it is the four major components or various system Services, such as ActivityManagerService and PackageManagerService, their implementation depends on Binder's communication mechanism. It can be seen that Binder is important in the Android system. It can be said that Binder is the first step towards becoming a senior engineer.
The Binder mechanism is very complex and it is difficult to fully understand it. In addition to understanding various knowledge in the operating system, you also need to understand the code implementation of the Binder driver layer. I have read a lot of articles about Binder recently, most of which are too abstract or too in-depth in source code details, and there are few articles that are really easy to understand. This article mainly explains the various concepts and basic communication processes in Binder from a macro perspective, focusing only on the implementation of the Java layer, and not introducing the underlying implementation. For application developers, understanding the basic design principles and communication processes of Binder is enough. If you want to understand Binder in depth, you need to read the source code yourself. This article mainly analyzes from three aspects: 1. Why Binder?
2. The basic principle of Binder
3. Understanding Binder through code
1. Why Binder? 1.1 Disadvantages of Traditional IPC Mechanism As we all know, the Android system is implemented based on the Linux kernel. Linux has provided a variety of inter-process communication mechanisms, such as pipes, message queues, shared memory, and sockets. Why do we need to implement another IPC mechanism? There are two main reasons: 1.1.1 Performance perspective Pipes, message queues, and sockets all require two memory copies to achieve one process communication, which is too inefficient; although shared memory does not require memory copying, it is complex to manage; Binder only requires one memory copy, which is lower than shared memory and better than other methods from a performance perspective. 1.1.2 Security Considerations The traditional IPC mechanism has no security measures. The receiver cannot obtain the reliable process ID or user ID of the other party. It is completely protected by the upper-layer protocol. For example, the IP address of Socket communication is filled in by the client and is likely to be tampered by malicious programs. As an open source platform for end users, Android has a large number of applications for users to choose from in the application market, so security is extremely important. The Android system assigns a user ID (UID) to each installed App. The UID is an important identifier for identifying the identity of the process. A series of permission checks can be performed through the UID. On the other hand, the access point of the traditional IPC is open, and any program can access it according to the protocol. It is impossible to prevent malicious programs from accessing it. Android needs an IPC mechanism based on the C/S architecture. The server side needs to be able to verify the identity of the client's request to ensure data security. 1.2 Some basic knowledge of Linux To understand how Binder implements cross-process communication with only one memory copy, we first need to understand why the traditional IPC mechanism requires two memory copies. This requires understanding some basic knowledge of operating systems. 1.2.1 Process Isolation Let's first look at Wikipedia's definition of "process isolation": Process isolation is a set of different hardware and software technologies designed to protect processes in the operating system from interfering with each other. This technology is to prevent process A from writing to process B. Process isolation is implemented using virtual address space. The virtual address of process A is different from the virtual address of process B, which prevents process A from writing data information to process B. In other words, data between processes is not shared, and process A cannot directly access the data of process B, thereby ensuring data security. In an operating system with process isolation, the interaction between processes must be through the IPC mechanism. The implementation of process isolation uses virtual address space. What is virtual address space? First of all, you need to understand the concept of virtual memory in the operating system. It is a technology that improves programming efficiency and physical memory utilization efficiency. In simple terms, the application sees a continuous and complete memory address space, but in fact these spaces are mapped to fragmented physical memory. This mapping process is transparent to the application. This concept is very important. For a deeper understanding of virtual memory, you can refer to this article: Understanding Linux Virtual Memory and Physical Memory 1.2.2 Process space: user space/kernel space Today's operating systems all use virtual memory. For a 32-bit operating system, the addressing space is 2 to the power of 32, or 4G. The core of the operating system is the kernel, which has all access rights to the underlying devices, so it needs to be independent of ordinary applications, and user processes cannot directly access kernel processes. The operating system logically divides the virtual address space into user space and kernel space. In a 32-bit Linux operating system, the high 1GB bytes are used by the kernel, called the kernel space; the remaining 3GB bytes are used by user processes, called the user space. 1.2.3 System calls: user mode/kernel mode Because the permissions of user space are lower than those of kernel space, it is inevitable that user space needs to access the resources of kernel space, such as reading and writing files and network access. How to achieve this? The only way is through the system call interface provided by the operating system. Through the system call interface, user programs can achieve limited access to kernel resources under the control of the kernel, which can not only meet the resource requests of applications, but also ensure system security and stability. When a user process executes its own code, the process is currently in the user running state (user state). At this time, the processor executes user code with lower permissions. When a user process executes kernel code through a system call, the process temporarily enters the kernel running state (kernel state). At this time, the processor has maximum permissions and can execute privileged instructions. 1.2.4 Kernel modules/drivers As mentioned earlier, user space can access kernel space through system calls, so how do user spaces (processes) communicate? Traditional IPC mechanisms are supported by the kernel, and the same is true for Binder. There is a Binder driver running in the kernel that is responsible for Binder communication between processes. Driver generally refers to device driver, which is a special program that enables computers and devices to communicate. It is equivalent to the interface of hardware, and the operating system can only use this interface. The Binder driver is a virtual character device registered in /dev/binder. It defines a set of Binder communication protocols, is responsible for establishing Binder communication between processes, and provides a series of underlying support for data packets to be transmitted between processes. Application processes also access the Binder driver through system calls. 1.3 Communication Principles of Traditional IPC Mechanism After understanding the basics above, let's take a look at how the traditional IPC mechanism is implemented, which is usually the following two steps (except for the shared memory mechanism):
This traditional IPC mechanism has two problems:
2. The basic principle of Binder 2.1 Binder underlying principle The traditional IPC mechanism needs to copy the memory twice. How does Binder realize inter-process communication with only one memory copy? We have learned that Linux uses virtual memory addressing. The virtual memory address of the user space is mapped to the physical memory. The reading and writing of the virtual memory is actually the reading and writing of the physical memory. This process is memory mapping, and this memory mapping process is implemented through the system call mmap(). Binder uses memory mapping to create a memory mapping layer between the kernel space and the data buffer of the receiver's user space. In this way, the data copied from the sender's user space to the kernel space buffer is equivalent to being directly copied to the receiver's user space data buffer, thus reducing one data copy. 2.2 Binder Communication Model Binder is based on the C/S architecture. For both communicating parties, the process that initiates the request belongs to the Client, and the process that receives the request belongs to the Server. Due to process isolation, the two parties cannot communicate directly. How is Binder implemented? The network communication example in the Binder principle analysis written for Android application engineers is very appropriate. The communication process of Binder is similar to that of network requests. The network communication process can be simplified into four roles: Client, Server, DNS server and router. The general process of a complete network communication is as follows: a. Client enters the domain name of the Server b. DNS domain name resolution The corresponding server cannot be found directly through the domain name. The server's domain name must first be converted into a specific IP address through a DNS server. c. Send the request to the Server through the router After the client resolves the server's IP address through the DNS server, it cannot directly initiate a request to the server. It needs to go through layers of routers before reaching the server. d. Server returns data After the server receives and processes the request, it returns the data to the client through the router. In the Binder mechanism, four roles are also defined: Client, Server, Binder driver and ServiceManager. Binder driver: Similar to a router in network communication, it is responsible for forwarding the client's request to a specific server for execution, and transmitting the data returned by the server back to the client. ServiceManager: Similar to the DNS server in network communication, it is responsible for converting the Binder descriptor requested by the Client into a specific Server address so that the Binder driver can forward it to the specific Server. If the Server needs to provide Binder services, it needs to register with the ServiceManager. The specific communication process is as follows: a. Server registers with ServiceManager The server registers with the ServiceManager through the Binder driver, declaring that it can provide services to the outside world. The ServiceManager will keep a mapping table: the Binder reference corresponding to the server named zhangsan is 0x12345. b. Client requests the Server's Binder reference from ServiceManager When the Client wants to request data from the Server, it needs to first request the Server's Binder reference from the ServiceManager through the Binder driver: I want to communicate with the Server named zhangsan, please tell me the Server's Binder reference. c. Send a request to a specific Server After the Client obtains the Binder reference, it can communicate with the Server through the Binder driver. d. Server returns the result After the server responds to the request, it needs to return the result to the client again through the Binder driver. As you can see, the communication between Client, Server and ServiceManager is all done through Binder driver as a bridge, which shows the importance of Binder driver. Maybe you still have some doubts, ServiceManager and Binder driver belong to two different processes, they are for the inter-process communication between Client and Server, that is to say, the inter-process communication between Client and Server depends on the inter-process communication between ServiceManager and Binder driver, which is like: "Eggs lay chickens, chickens lay eggs, but the first egg must be hatched by a chicken". How does the Binder mechanism create the first chicken to lay eggs?
Android Binder Design and Implementation - The design chapter provides a more detailed introduction to Client, Server, Binder driver and ServiceManager. 2.3 Binder's Proxy Mechanism Through the above analysis, we have learned the basic communication process of Binder: Client obtains the Binder reference of Server from ServiceManager, and Client initiates specific requests to Server through Binder reference. How does Client call Server method through this Binder reference? For example, if a Server provides an add method, the process for a Client to actually request add is as follows: the Client first obtains the Server's Binder reference from the ServiceManager through the Binder driver. This reference is a Java Object, and this Object has an add method. After obtaining this Object, the Client can directly request the add method. In fact, the Object obtained by the Client is not the real Binder entity of the Server. The Binder driver performs an object conversion and packages the Object into a proxy object ProxyObject. The ProxyObject has the same method signature as the real Binder entity. When the Client requests the add method through the ProxyObject, the Binder driver will automatically forward the request to the specific Binder entity for execution. This is the Binder proxy mechanism. Since the ProxyObject has the same method signature as the real Binder entity, the Client does not need to care whether it is a ProxyObject or a real Object. For the convenience of description, the real Binder entity of the Server is called the Binder local object; the Binder reference in the Client, that is, ProxyObject, is called the Binder proxy object. 2.4 Re-understanding of the Binder Concept After the above analysis, we have roughly understood the basic communication principle of the Binder mechanism. Now let's go back and re-sort out our understanding of the Binder mechanism: Generally speaking, Binder is an object-oriented IPC mechanism based on C/S structure. It includes four major components: Client, Server, Binder driver and ServiceManager. The meaning of Binder in each component is different:
Binder is a reference to the local object of the Server. This reference is actually a proxy object. The Client indirectly accesses the local object of the Server through this proxy object.
Binder is a local object that provides a specific implementation and needs to be registered with the ServiceManager;
It is the bridge connecting the Client to the Server, responsible for converting the proxy object into a local object and returning the execution result of the Server to the Client.
It saves the mapping between the Server Binder character name and the Binder reference, and the Client uses it to find the Server's Binder reference. The Binder driver retains the specific structure of the Binder proxy object and the Binder local object. Since we are only concerned with the basic communication mechanism of Binder, we will not introduce the underlying implementation in detail. Students who want to know more can refer to Android Binder Design and Implementation - Design. 3. Understanding Binder through code The above introduction is relatively abstract. Now let's understand Binder through specific examples.
3.1 Understand the usage of Binder through AIDL examples The most common way to implement Binder communication is through aidl. The aidl interface defines the interface for communication between Client and Server. If you are not familiar with aidl, please refer to the official document Android Interface Definition Language (AIDL). 3.1.1 Responsibilities of Several Classes Related to Binder Before the specific analysis, we need to understand the responsibilities of several classes related to Binder:
The Base interface for cross-process communication declares a series of abstract methods that need to be implemented for cross-process communication. Implementing this interface means that cross-process communication is possible. Both the Client and the Server must implement this interface.
This is also a Base interface, which is used to indicate what capabilities the Server provides and is the protocol for communication between the Client and the Server.
The base class for local objects that provide Binder services. It implements the IBinder interface. All local objects must inherit this class.
In the Binder.java file, a BinderProxy class is also defined. This class represents the Binder proxy object. It also implements the IBinder interface, but many of its implementations are handled by the native layer. What the Client actually gets is this proxy object.
This class is automatically generated after compiling the aidl file. It inherits from Binder, indicating that it is a Binder local object; it is an abstract class that implements the IInterface interface, indicating that its subclasses need to implement the specific capabilities that the Server will provide (that is, the methods declared in the aidl file).
It implements the IInterface interface, which means it is part of the Binder communication process; it implements the methods declared in aidl, but is ultimately handled by the mRemote member, which means it is a proxy object. The mRemote member is actually BinderProxy. 3.1.2 AIDL Examples First, define an aidl file. This interface declares a getPid method:
The following is the Java class generated after compiling IRemoteService.aild:
There are 3 classes in this file: a. IRemoteService Inherited from IInterface interface, declared the getPid method declared in IRemoteService.aidl, which is the interface for communication between Client and Service. b. IRemoteService.Stub The static abstract inner class of IRemoteService inherits from Binder. Its subclass needs to implement the IRemoteService interface, indicating that it is the Binder local object of the Server and needs to implement the getPid interface. c. IRemoteService.Stub.Proxy The static inner class of IRemoteService.Stub does not inherit from Binder, but contains an IBinder object, which is actually BinderProxy, indicating that it is the local proxy object of Server in Client. Proxy implements the getPid interface, serializes the parameters and passes them to mRemote (BinderProxy) for processing, which is actually to pass it to the Binder driver to complete the communication with the remote Stub. Let's first look at the asInterface method in Stub. This method is usually called by the Client after bindService succeeds. Its function is to convert the IBinder object returned after the binding is successful into a specific IInterface interface. After the Client obtains this IInterface interface, it can freely call the methods provided by the Server.
The asInterface method may return the IRemoteService object of the Stub itself, or create a Proxy object. Why is this? Because although Binder is a cross-process communication mechanism, it can also serve the process itself, that is, the Client and Server may be in the same process. In the same process, there is no need to transfer through the Binder driver, and direct access is sufficient; if the Client and Server are in different processes, they need to be transferred through the Binder proxy object. In other words:
How does obj.queryLocalInterface find out whether there is a local IInterface? From the Binder code, we can see that it simply compares the Binder descriptor with the descriptor to be searched. If they match, it directly returns mOwner, which is the this parameter passed in when calling the attachInterface method in the Stub construction method. The queryLocalInterface method of BinderProxy directly returns null. There are two scenarios in which the Client calls the Server method through Binder: 1. Client and Server are in the same process The Stub.asInterface method returns a Stub object, which is a Binder local object. This means that it has nothing to do with Binder cross-process communication and can be called directly. At this time, the Client caller and the Server responder are in the same thread. 2. Client and Server are in different processes The Stub.asInterface method returns a Binder proxy object, which needs to be driven by the Binder to complete cross-process communication. In this scenario, the Client caller thread will be suspended (Binder also provides an asynchronous method, which is not discussed here) and wait for the Server to respond before returning data. It should be noted that the Server's response is processed in the Binder thread pool of the Server process, not the main thread. Next, we analyze the specific process of Client calling the getPid method in the cross-process scenario: 1. Client calls the Binder proxy object, and the Client thread hangs The IRemoteService reference obtained by the Client is actually a Proxy. Calling the getPid method actually calls the getPid method of the Proxy. This method simply serializes the parameters and calls the transact method of the mRemote member. The Stub class defines a method number for each method in IRemoteService, and the number of the getPid method is passed into the transact method. At this time, the Client caller thread is suspended, waiting for the Server to respond with data.
2. The Binder proxy object dispatches the request to the Binder driver The mRemote member in Proxy is actually BinderProxy, and the transact method in BinderProxy is ultimately called into the transactNative method, which means that the Client's request is dispatched to the Binder driver for processing. 3. Binder driver dispatches the request to Server After a series of processing, the Binder driver dispatches the request to the Server, that is, calling the onTransact method of the local Binder object (Stub) of the Server, and finally completing the specific call of the getPid method in this method. In the onTransact method, the specific method to be processed is distinguished according to the method number passed in when calling transact in the Proxy.
4. Wake up the Client thread and return the result After onTransact is completed, the result is written to reply and returned to the Binder driver, which wakes up the suspended Client thread and returns the result. At this point, a cross-process communication is completed. 3.2 Manual coding to implement ActivityManagerService From the previous examples, we already know that the aidl file is only used to define the interface for C/S interaction. Android will automatically generate the corresponding Java class during compilation. The generated class contains Stub and Proxy static inner classes to encapsulate the data conversion process. In actual use, you only need to care about the specific Java interface class. Why are Stub and Proxy static inner classes? This is actually just to put the three classes in one file to improve the aggregation of the code. Through the above analysis, we can actually implement Binder communication without aidl and manually code. Let's implement ActivityManagerService through coding. First define the IActivityManager interface:
Secondly, implement the local Binder object base class on the ActivityManagerService side:
Again, implement the proxy object on the Client side:
***, implement the Binder local object (IActivityManager interface):
The simplified version of ActivityManagerService has been implemented here. The remaining thing is that the Client needs to obtain the proxy object IActivityManager of AMS to communicate. In the actual development process, the intermediate code can be automatically compiled through the aidl file, and we do not need to implement it manually, but manual coding can deepen the understanding of the Binder mechanism. We will not use AMS directly during the development process, but understanding the implementation principle of AMS is essential for familiarity with the Framework. I will analyze the specific implementation principle of AMS in subsequent articles. So far, the basic communication process of the Binder mechanism has been introduced. Since the Binder mechanism is too complicated and my level is limited, errors or shortcomings are inevitable in the article. You are welcome to correct me. |
<<: iPhone XS signal is worse than previous generation? We tested it to see if it works.
One of the important qualities of scientists is t...
The most important thing in mobile phone evaluati...
Sustainable road development balances economic gr...
Recently, a message has been circulating in many ...
From last year to now, domestic home appliance ma...
1000+ complete set of BP and research report docu...
The emergence of smartphones has overturned tradi...
On March 29, according to a report by Southern Me...
The project that I will be dissecting for you tod...
Course Catalog: Part I: Chapter 01 AE Interface a...
4 years ago today January 16, 2019 Yu Min, the he...
Dragons are animals from mythology, and it is pro...
Contributing author: Su Hao (Professor at North C...
Today I’m going to join in the fun and talk about...
Mixed Knowledge Specially designed to cure confus...