Android Framework-interprocess communication - Binder

We know that the root cause of direct calls between two functions in the same program is that they are in the same memory space.
For example, there are the following two functions A and B:

/*Simple.c*/
void A()
{
    
     B(); }
void B()
{
    
     }

Because it is in one memory space, the mapping rules of virtual addresses are completely consistent, so the call relationship between functions A and B is very simple, as shown in the figure.
insert image description here
Two different processes, such as a certain Application1 and ActivityManagerService (the process in which they are located), have no way to directly access the internal functions or variables of each other through the memory address, as shown in the figure.
insert image description here
Since it is impossible to "directly" access the memory space of the other process, is there an "indirect" method? In short, this is what Binder does, as shown in Figure 6-3.
insert image description here
Binder is the most widely used IPC mechanism in Android. Binder includes:
Binder driver;
Service Manager;
Binder Client;
Binder Server.
If you look at the various components in Binder, you will be surprised to find that it has many similarities with the TCP/IP network:
Binder driver→router;
Service Manager→DNS;
Binder Client→client;
Binder Server→server.
A typical service connection process in TCP/IP (such as a client accessing the Google homepage through a browser) is shown in Figure 6-4.
insert image description here
The client queries the DNS for the IP address of Google.com. DNS returns the query result to Client. Client initiates a connection. After the client gets the IP address of Google.com, it can initiate a connection to the Google server based on it. Router is the basis of the entire communication structure, and its responsibility is to deliver data packets to the target IP set by the user.
First of all, in the TCP/IP reference model, for users at the IP layer and above, the IP address is the credential for their communication with each other—the IP identifier of any user in the entire Internet is unique.
Secondly, the Router is the basis for building a communication network, and it can correctly send data packets to the destination according to the destination IP filled in by the user.
Finally, the DNS role is not required, it exists to help people associate complex and difficult-to-remember IP addresses with more readable domain names and provide lookup capabilities. The premise that the client can use DNS is that it has correctly configured the IP address of the DNS server.
The prototype of Binder is shown in Figure 6-5.
insert image description here
The essential goal of Binder is described in one sentence, that is, process 1 (client) wants to communicate with process 2 (server). But because they are cross-process (cross-network), it is necessary to use the Binder driver (router) to correctly deliver the request to the process (network) where the other party is located. The processes participating in the communication need to hold the unique identifier (IP address) "issued" by Binder, as shown in Figure 6-6.
insert image description here
Similar to the TCP/IP network, the "DNS" in Binder is not necessary - the premise is that the client can remember the Binder flag (IP address) of the process it wants to access; and pay special attention to the fact that this flag is "Dynamic IP ", which means that even if the client remembers the unique identifier of the target process in this communication process, the next access still needs to be obtained again, which undoubtedly increases the difficulty of the client. The emergence of "DNS" can perfectly solve this problem, and is used to manage the correspondence between the Binder logo and the more readable "domain name", and provide users with query functions.
In the Binder mechanism, the role of DNS is Service Manager
insert image description here
. Since Service Manager is DNS, what is its "IP address"? The Binder mechanism makes special provisions for this. The only flag of Service Manager in the Binder communication process is always 0.

smart pointer

Smart pointers are widely used in the entire Android project, especially in the source code implementation of Binder.
1.1 The design concept of smart pointers
A major difference between Java and C/C++ is that it does not have the concept of "pointers". This does not mean that Java does not need to use pointers, but that this "super weapon" is hidden.
The common pointer problems in C/C++ projects can be summarized as follows:
the pointer is not initialized, the object is not deleted in time after the new object is created, and the wild pointer
Android smart pointer implementation includes two types: strong pointer and weak pointer:
1.2 Strong pointer sp
frameworks/native /include/utils/StrongPointer.h

template <typename T>
class sp
{
    
    
public:
 inline sp() : m_ptr(0) {
    
     }
 sp(T* other);/*常用构造函数*//*其他构造函数*/sp();/*析构函数*/
 sp& operator = (T* other);// 重载运算符“=”
 …
 inline T& operator* () const {
    
     return *m_ptr; }// 重载运算符“*”
 inline T* operator-> () const {
    
     return m_ptr; }// 重载运算符“->”
 inline T* get() const {
    
     return m_ptr; }private:
 template<typename Y> friend class sp;
 template<typename Y> friend class wp;
 void set_pointer(T* ptr);
 T* m_ptr;
};

The implementation of the operator equals is:

template<typename T>
sp<T>& sp<T>::operator = (T* other)
{
    
    
 if (other) other->incStrong(this);/*增加引用计数*/
 if (m_ptr) m_ptr->decStrong(this);
 m_ptr = other;
 return *this;
}

destructor

template<typename T>
sp<T>::sp()
{
    
    
 if (m_ptr) m_ptr->decStrong(this);/*减小引用计数*/
}

1.3 Weak pointer wp
has a scene, the parent object parent points to the child object child, and then
the child object points to the parent object, which leads to the phenomenon of circular reference.

struct CDad 
{
    
     
 CChild *myChild;
}; 
struct CChild 
{
    
    
 CDad *myDad;
};

Assume that both classes have reference counter functionality.
Because CDad points to CChild, the latter's reference counter is not zero.
And CChild points to CDad, which will also make its counter non-zero.
The memory collector finds that both are in the "needed" state, and of course they cannot be released, thus forming a vicious circle.
An effective way to solve this contradiction is to use "weak references". The specific measures are as follows.
CDad uses a strong pointer to refer to CChild, and CChild only uses a weak reference to point to the parent class. The two parties stipulate that when the strong reference count is 0, no matter whether the weak reference is 0, you can delete yourself (this rule can be adjusted in the Android system). In this way, as long as one party is released, deadlock can be successfully avoided. Will it cause problems with wild pointers? Yes, there are indeed concerns in this regard. For example, because the strong pointer count of CDad has reached 0, according to the rules, the life cycle is over; but at this time, CChild still holds a weak reference of its parent class. Obviously, if CChild uses this pointer to access CDad at this time, it will cause fatal problems. In view of this, we have to specify:
Weak pointers must be upgraded to strong pointers before they can access the target object they point to.

template <typename T>
class wp
{
    
    
public:
 typedef typename RefBase::weakref_type weakref_type;
 inline wp() : m_ptr(0) {
    
     }
 wp(T* other);//构造函数/*其他构造函数省略*/wp(); 
 wp& operator = (T* other);//运算符重载void set_object_and_refs(T* other, weakref_type* refs);
 sp<T> promote() const;/*升级为强指针*//*其他方法省略*/
private:
 template<typename Y> friend class sp;
 template<typename Y> friend class wp;
 T* m_ptr;
 weakref_type* m_refs;
};

Summary
1. Smart pointers are divided into two types: strong pointer sp and weak pointer wp.
2. Usually, the parent class of the target object is RefBase - this base class provides a reference counter of weakref_impl type, which can control strong and weak references at the same time (counting is provided internally by mStrong and mWeak).
3. When incStrong adds strong references, weak references will also be added.
4. Only increase the weak reference count when incWeak.
5. Users can set the rules of the reference counter through extendObjectLifetime, and the timing of deleting the target object is different under different rules.
6. Users can choose the appropriate smart pointer type and counter rules according to the program requirements

Data transfer carrier between processes - Parcel

For data transfer between processes, if it is just an int value, just keep copying until the target process. But what about an object? We can imagine that the transfer of objects between the same process is done by reference, so it is essentially a memory address. This method is powerless in the case of cross-process. Due to the adoption of the virtual memory mechanism, the two processes have their own independent memory address space, so the address value passed across the process is invalid.
Data transfer between processes is an important part of the Binder mechanism, and Parcel is responsible for this important task in the Android system. Parcel is a data carrier, used to carry related information (including data and object references) that you want to send through IBinder. Is it feasible to package the memory-related data occupied by the object in process A and send it to process B, so that B can "reproduce" the object in its own process space? Parcel has this packaging and reorganization capability.
1. Parcel Settings Related
The more data stored, the larger the memory space occupied by Parcel. We can make relevant settings through the following methods.
dataSize(): Get the currently stored data size.

setDataCapacity (int size): Set the space size of Parcel, obviously the stored data cannot be larger than this value.

setDataPosition (int pos): Change the reading and writing position in Parcel, which must be between 0 and dataSize().

dataAvail(): The readable data size in the current Parcel.

dataCapacity(): The storage capacity of the current Parcel.

dataPosition(): The current position value of the data, somewhat similar to a cursor.

dataSize(): The size of the data contained in the current Parcel.
2. Primitives
read and write operations of primitive type data. For example:
writeByte(byte): write a byte.

readByte(): Read a byte.

writeDouble(double): Write a double.

readDouble(): Read a double.
3. The read and write operations of Primitive Arrays
primitive data type arrays usually first write the data size value represented by 4 bytes, and then write the data itself. In addition, the user can choose to read the data into the existing array space, or let Parcel return a new array. Such methods are as follows
writeBooleanArray(boolean[]): write Boolean array.

readBooleanArray(boolean[]): reads a Boolean array.

boolean[]createBooleanArray(): Read and return a Boolean array.

writeByteArray(byte[]): Write byte array.

writeByteArray(byte[], int, int): Different from the above ones, the last two parameters of this function indicate the starting point of the data to be written in the array and how much to write.

readByteArray(byte[]): read byte array.

byte[]createByteArray(): Read and return an array.
If the system finds that the storage capacity of Parcel has been exceeded when writing data, it will automatically apply for the required memory space and expand dataCapacity; and each write starts from dataPosition().
4. Parcelables
Objects that follow the Parcelable protocol can be accessed through Parcel. For example, the bundles that developers often use inherit from Parcelable. Parcel operations related to this type of object include:
writeParcelable(Parcelable, int): Write the name and content of this Parcelable class into Parcel. In fact, it writes data by calling back the writeToParcel() method of this Parcelable.

readParcelable(ClassLoader): Read and return a new Parcelable object.

writeParcelableArray(T[], int): Write an array of Parcelable objects.

readParcelableArray(ClassLoader): Read and return an array of Parcelable objects
5. Bundles
As mentioned above, Bundle inherits from Parcelable and is a special type-safe container. The biggest feature of Bundle is that it uses key-value pairs to store data, and optimizes the reading efficiency to a certain extent. Parcel operations of this type include:
writeBundel(Bundle): Write Bundle to parcel.

readBundle(): Read and return a new Bundle object.

readBundle(ClassLoader): Read and return a new Bundle object, ClassLoader is used for Bundle to obtain the corresponding Parcelable object.
6.
Another powerful weapon of Active Objects Parcel is the ability to read and write Active Objects. What are Active Objects? Usually what we store in Parcel is the content of the object, and what Active Objects writes is their special flag reference. So when reading these objects from Parcel, what you see is not the recreated object instance, but the original written instance. It can be guessed that there are not many objects that can be transferred in this way, and there are mainly two types at present.
(1) Binder. On the one hand, Binder is one of the core mechanisms of IPC communication in the Android system, and on the other hand, it is also an object. Use Parcel to write the Binder object, and you can get the original Binder object when you read it, or its special proxy implementation (the final operation is still the
original Binder object). Operations related to this include:
writeStrongBinder(IBinder)
writeStrongInterface(IInterface)
readStrongBinder()
...
(2) FileDescriptor. FileDescriptor is a file descriptor in Linux, which can be passed through the following methods of Parcel.
writeFileDescriptor(FileDescriptor), readFileDescriptor()
can be considered as a type of Active Object because the passed object will still operate based on the same file stream as the original object.
7. Untyped Containers
It is any type of java container used to read and write standards. Including:
writeArray(Object[]), readArray(ClassLoader), writeList(List), readList(List, ClassLoader), etc.

Parcel supports many types, which is enough to meet the data transfer request of developers. If you want to find an analogy for Parcel, it is more like a container. The reasons are as follows:
1. Cargo irrelevance
means that it does not exclude the type of goods being transported, electronic products are fine, cars are fine, or spare parts are also acceptable.
2. Different goods require different packing and unloading schemes
. For example, the packing and unloading methods for carrying fragile items and hard items will definitely be very different.
3. Long-distance transportation and assembly
Container goods are usually transported across oceans, which is somewhat similar to Parcel's cross-process capability. However, the container shipping company itself is not responsible for the post-assembly of the goods shipped. For example, an automobile manufacturer needs to disassemble a complete vehicle into parts before loading and transporting it; after arriving at the destination, the freight company only needs to hand over the goods to the receiver intact without any responsibility for assembly. Into the obligation of the whole vehicle.
Parcel, on the other hand, is more dedicated, and it will provide the receiver with the service of completely restoring the original data object according to the protocol (the protocol used for packaging and reorganization must be matched) .
The implementation principle of writeString
Combined with an example to explain the implementation principle of writeString in detail, the example is the operation of Parcel in the getService() method of ServiceManagerProxy

Parcel data = Parcel.obtain();
…
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);

The first sentence of code is used to obtain a Parcel object, and finally a local Parcel instance is created and fully initialized.

The writeInterfaceToken in the second sentence is used to write the IBinder interface token, and the parameter is of String type, such as IServiceManager.descriptor="android.os.IServiceManager".

The third sentence writes the Service name that needs to be queried from the ServiceManager in the Parcel through writeString

The internal transfer process of Parcel in the entire IPC is relatively cumbersome, especially when carrying Binder data, multiple conversions are required, so it is easy to lose direction. But no matter how tortuous the process is, one thing remains the same. That is: the protocol used by the writer and the reader must be completely consistent.

Let's take a look at what the writer (ServiceManagerProxy) has "packed" into the "container":

status_t Parcel::writeInterfaceToken(const String16& interface)
{
    
    
 writeInt32(IPCThreadState::self()->getStrictModePolicy()
|STRICT_MODE_PENALTY_GATHER);
 return writeString16(interface);
}

Equivalent to:
writeInterfaceToken→writeInt32(policyvalue)+writeString16(interface)

The interface is "android.os.IServiceManager".

status_t Parcel::writeInt32(int32_t val)
{
    
    
 return writeAligned(val);
}

The implementation of this function is very simple - it only contains one line of code. Judging from the function name, it writes the val value into the storage space of Parcel according to the alignment. In other words, it is to write the data into the mData started by mDataPos (of course, it is necessary to judge whether the current storage capacity meets the requirements internally, whether to apply for new memory, etc.):

status_t Parcel::writeString16(const String16& str)
{
    
    
 return writeString16(str.string(), str.size());
}
status_t Parcel::writeString16(const char16_t* str, size_t len)
{
    
    
 if (str == NULL) return writeInt32 (-1); //str不为空
 status_t err = writeInt32(len); //先写入数据长度
 if (err == NO_ERROR) {
    
    
 len *= sizeof(char16_t); //长度*单位大小=占用的空间
 uint8_t* data =
(uint8_t*)writeInplace(len+sizeof(char16_t));
 if (data) {
    
    
 memcpy(data, str, len);/*将数据复制到data所指向的位置中
*/
 *reinterpret_cast<char16_t*>(data+len) = 0;
 return NO_ERROR;
 }
 err = mError;
 }
 return err;
}

The entire process of writeString16 is not difficult to understand: first fill in the length of the data, occupying 4 bytes; then calculate the space required for the data; finally copy the data to the corresponding location - writeInplace is used to calculate The destination address of the copied data.
Steps to write a String (writeString16):
writeInt32(len);
memcpy;
padding (in some cases padding is not required. And this step is before memcpy in the source code implementation).
Go back to getService in ServiceManagerProxy:

data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);

We decompose the above two statements to get the work of the writer:
WriteInterfaceToken=writeInt32(policyvalue)+writeString16(interface)
writeString16(interface) = writeInt32(len)+write data itself+fill

The reader is Service_manager.c

int svcmgr_handler(struct binder_state *bs, struct binder_txn
*txn,
 struct binder_io *msg, struct binder_io *reply)
{
    
    …
 uint32_t strict_policy;
 …
 strict_policy = bio_get_uint32(msg); //取得policy值
 s = bio_get_string16(msg, &len); //取得一个String16,即上面写入的interface
 if ((len != (sizeof(svcmgr_id) / 2)) ||
 memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
    
    /*判断Interface是否正确?*/
 fprintf(stderr,"invalid id %s\n", str8(s));
 return -1;
 }

The above code segment is used to judge whether the received interface is correct. Where:
uint16_t svcmgr_id[] = { 'a','n','d','r','o','i','d','.','o','s','. ', 'I','S','e','r','v','i','c','e','M','a','n','a', 'g', 'e', ​​'r' }; is the same as the previous "android.os.IServiceManager":



switch(txn->code) {
    
    
 case SVC_MGR_GET_SERVICE:
 case SVC_MGR_CHECK_SERVICE:
 s = bio_get_string16(msg, &len);//获取要查询的service name
 ptr = do_find_service(bs, s, len, txn->sender_euid);
 if (!ptr)
 break;
 bio_put_ref(reply, ptr);
 return 0;

It can be seen that the process of reading data by ServiceManager is exactly the same as the process of writing data.

Binder driver and protocol

The Android system is based on the Linux kernel, so the Binder driver it depends on must also be a standard Linux driver. Specifically, the Binder Driver will register itself as a misc device and provide a /dev/binder node to the upper layer—it is worth mentioning that the Binder node does not correspond to a real hardware device. The Binder driver runs in kernel mode and can provide common file operations such as open(), ioctl(), mmap(), etc.
Character devices in Linux usually go through a series of operations such as alloc_chrdev_region() and cdev_init() to register themselves in the kernel. The misc type driver is relatively simple, and can be easily solved by calling misc_register(). For example, the code related to driver registration in Binder:

/*drivers/staging/android/Binder.c*/
static struct miscdevice binder_miscdev = {
    
    
 .minor = MISC_DYNAMIC_MINOR, /*动态分配次设备号*/
 .name = "binder", /*驱动名称*/
 .fops = &binder_fops /*Binder驱动支持的文件操作*/
};
static int __init binder_init(void)
{
    
     …
 ret = misc_register(&binder_miscdev); /*驱动注册*/}

The Binder driver also needs to fill in the file_operations structure. As follows:

/*drivers/staging/android/Binder.c*/
static const struct file_operations binder_fops = {
    
    
 .owner = THIS_MODULE,
 .poll = binder_poll,
 .unlocked_ioctl = binder_ioctl,
 .mmap = binder_mmap,
 .open = binder_open,
 .flush = binder_flush,
 .release = binder_release,
};

The Binder driver provides a total of 6 interfaces for upper-layer applications - the most used are binder_ioctl, binder_mmap and binder_open.
1.1 Open the Binder driver - binder_open

/*如果没有特别说明,以下的函数都在Binder.c中*/
static int binder_open(struct inode *nodp, struct file *filp)
{
    
    
 struct binder_proc *proc;
 …
 proc = kzalloc(sizeof(*proc), GFP_KERNEL);/*分配空间*/
 if (proc == NULL)
 return -ENOMEM;

The Binder driver has created its own binder_proc entity for the user, and the user's operations on the Binder device will be based on this object.
1.2 binder_mmap
mmap() can directly map the memory block specified by the device to the memory space of the application. For the Binder driver, the mmap() called by the upper-level user eventually corresponds to binder_mmap(). Suppose there are two processes A and B, and process B establishes contact with the Binder driver through open() and mmap(), as shown in Figure 6-13.
insert image description here
1. For the application program, it obtains a memory address (of course, this is a virtual address) through the return value of mmap(), and this address will eventually point to a certain location in the physical memory after virtual memory conversion (segmentation, paging).
2. For the Binder driver, it also has a pointer (binder_proc->buffer) pointing to a virtual memory address. After virtual memory conversion, it is in the same location as the physical memory pointed to by the application.

At this time, Binder and the application program have several shared physical memory blocks. In other words, their operations on their respective memory addresses are actually performed in the same block of memory.
Let's add process A again.
insert image description here
The right half of the Binder driver uses copy_from_user() to copy a certain piece of data in process A to the memory space pointed to by its binder_proc->buffer. Because the location of binder_proc->buffer in physical memory is shared with process B, process B can directly access this data. That is to say, the binder driver realizes the data sharing between process A and process B with only one copy.
1.3 binder_ioctl
This is the one with the largest workload among the Binder interface functions, and it undertakes most of the business driven by the Binder.
insert image description here
insert image description here

"DNS" server - ServiceManager (Binder Server)

The function of ServiceManager (hereinafter referred to as SM) can be compared to the "DNS" server in the Internet, and the "IP address" is 0. In addition, just like DNS itself is also a server, SM is also a standard Binder Server.
1.1 Start of ServiceManager
Since it is DNS, it must be in place before users can browse the web. The same is true for SM, which must ensure that it is in a normal working state before someone uses the Binder mechanism. So, when exactly did it work? We naturally think that it should be started when the init program parses init.rc. And indeed it is. As follows:

/*init.rc*/
service servicemanager /system/bin/servicemanager
 class core
 user system
 group system
 critical
 onrestart restart zygote
 onrestart restart media
 onrestart restart surfaceflinger
 onrestart restart drm

This servicemanager is written in C/C++. The source code path is in the /frameworks/native/cmds/servicemanager directory of the project. You can take a look at its make file first:

LOCAL_PATH:= $(call my-dir)
…
include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := service_manager.c binder.c 
LOCAL_MODULE := servicemanager /*生成的可执行文件名为
servicemanager*/
include $(BUILD_EXECUTABLE)/*编译可执行文件*/

1.2 Construction of ServiceManager
Let's take a look at what work SM has done after startup:

/*frameworks/native/cmds/servicemanager/Service_manager.c*/
int main(int argc, char **argv)
{
    
    
 struct binder_state *bs;
 void *svcmgr = BINDER_SERVICE_MANAGER;
 bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
    
     /*将自己设置为
Binder“大管家”,整个Android 
 系统只允许一个ServiceManager存在,因而如果后面还有人
调用这个函数就会失败*/
 ALOGE("canno t become context manager (%s)\n",
strerror(errno));
 return -1;
 }
 svcmgr_handle = svcmgr;
 binder_loop(bs, svcmgr_handler); //进入循环,等待客户的请求
 return 0;
}

The main function mainly does the following things:
open the Binder device and initialize it;
set itself as the Binder housekeeper;
enter the main loop.
So, what initialization needs to be done specifically?

/*frameworks/native/cmds/servicemanager/Binder.c */
struct binder_state *binder_open(unsigned mapsize)
{
    
    
 struct binder_state *bs; /*这个结构体记录了SM中有关于Binder的所有
信息,
 如fd、map的大小等*/
 bs = malloc(sizeof(*bs));
 …
 bs->fd = open("/dev/binder", O_RDWR); //打开Binder驱动节点
 …
 bs->mapsize = mapsize; //mapsize是SM自己设的,为128*1024,即
128K
 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs-
>fd, 0);return bs;
fail_map:
 close(bs->fd); //关闭file
fail_open:
 free(bs);
 return 0;
}

According to the parameter settings in the above code segment:
1. The starting address of the memory mapped to the process space is determined by the Binder driver;
2. The size of the mapped block is 128KB;
3. The mapped area is read-only;
4. The changed mapped area It is private and does not need to save the file;
5. Start mapping from the starting address of the file

Let's take a look at the second step in the main function, which is to register the servicemanager as the "big steward" of the Binder mechanism:

int binder_become_context_manager(struct binder_state *bs)
{
    
    
 return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

All preparations are ready, SM can start to wait for the client's request - this part of the work is the focus and difficulty of SM. Let's start with binder_loop() to see how SM handles requests (read in sections):

void binder_loop(struct binder_state *bs, binder_handler func)
{
    
    
 int res;
 struct binder_write_read bwr; /*这是执行BINDER_WRITE_READ命令
所需的数据格式*/
 unsigned readbuf[32];/*一次读取容量*/

Before the Binder Server enters the loop, it must first inform the Binder driver of this state change. The following piece of code does the job:

bwr.write_size = 0;//这里只是先初始化为0,下面还会再赋值
 bwr.write_consumed = 0;
 bwr.write_buffer = 0; 
 readbuf[0] = BC_ENTER_LOOPER;/*命令*/
 binder_write(bs, readbuf, sizeof(unsigned));

Then SM goes into the loop. What needs to be done in the loop body?
1. Read the message from the message queue.
2. If the message is "exit command", end the loop immediately; if the message is empty, continue to read or wait for a period of time before reading; if the message is not empty and not an exit command, process it according to the specific situation.
3. Repeat this cycle until exit.
However, there is no message queue in SM, and its "message" (or "command") is obtained from the Binder driver:

for (;;) {
    
    
 bwr.read_size = sizeof(readbuf);/*readbuf的大小为32个
unsigned*/
 bwr.read_consumed = 0;
 bwr.read_buffer = (unsigned) readbuf;/*读取的消息存储到
readbuf中*/
 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); //读取“消
息”
 …
 res = binder_parse(bs, 0, readbuf, bwr.read_consumed,
func);//处理这条消息
 if (res == 0) {
    
    
 ALOGE("binder_loop: unexpected reply?!\n");
 break;
 }
 if (res < 0) {
    
    
 ALOGE("binder_loop: io error %d %s\n", res,
strerror(errno));
 break;
 }
 }
}/*binder_loop结束*/

It can be seen that SM follows the following steps
1. Read messages from the Binder driver
by sending the BINDER_WRITE_READ command—this command can be read or written, depending on bwr.write_size and bwr.read_size. Because the initialization value of write_size is 0, and read_size is sizeof(readbuf), the Binder driver only performs read operations.
2. Process the message
Call binder_parse to parse the message.
3. Keep looping, and will never actively exit (unless a fatal error occurs)
1.3 Obtain ServiceManager service-design thinking
If you want to access the service of SM (Binder Server), what should the process be like?
Nothing more than the following steps:

  1. Open the Binder device;
  2. execute mmap;
  3. Send a request to SM through Binder driver (SM's handle is 0);
  4. get results.

Don't doubt, the core work is really only these. However, some specific details still need to be discussed, such as:

  1. The Binder Client that initiates the request to the SM may be an Android APK application, so the SM must provide a Java layer interface
  2. If each Binder Client has to personally perform the above steps to obtain SM services, it is conceivable that a lot of time will be wasted. Of course, the Android system has also thought of this, so it will provide better packaging to make the entire SM calling process more streamlined and practical.
  3. If the application code uses the SM service (or other Binder Server services) every time, it is necessary to open the Binder driver once and execute mmap, the consequence is that more and more system resources will be consumed until it crashes. An effective solution is that each process is only allowed to open the Binder device once, and only do memory mapping once - all threads that need to use the Binder driver share this resource.

The question is transformed into: If we are to design a BinderClient that meets the above requirements, what should we do?
1. The first thing ProcessState and IPCThreadState
can think of is to create a class to specifically manage the Binder operation in each application process - more importantly, the execution of a series of commands driven by Binder must be "transparent" to the upper user. This class is ProcessState. Only ProcessState is not enough, each thread in the process should have the right to freely communicate with the Binder driver - and the IPC based on Binder is blocked, which can ensure that individual threads will not be stuck when doing inter-process communication application. The actual command communication with the Binder driver is IPCThreadState.
2. Proxy
With the above two classes, the application can now communicate with the Binder driver. In principle, we can still interact with it by sending commands supported by Binder such as BINDER_WRITE_READ, so as to obtain the services provided by SM step by step. It is still necessary to encapsulate the service provided by SM, and name the encapsulation of this SM service ServiceManagerProxy.

/*应用程序获取SM服务示例*/
//Step1. 创建ServiceManagerProxy
ServiceManagerProxy sm = new ServiceManagerProxy(new BpBinder(HANDLE));
//Step2. 通过ServiceManagerProxy获取SM的某项服务
IBinder wms_binder = sm.getService("window");

The application only needs two steps to get the service provided by ServiceManager.
How to achieve this goal:
(1) Interface of ServiceManagerProxy. The service that ServiceManagerProxy can provide must be consistent with the SM of the server, such as getService, addService, etc. Extracting these methods is the interface of ServiceManagerProxy. We
name it IServiceManager, as shown below (you ignore its parameters first):

public interface IServiceManager 
{
    
    
 public IBinder getService(String name) throws
RemoteException;
 public IBinder checkService(String name) throws
RemoteException;
 public void addService(String name, IBinder service,
boolean allowIsolated)throws RemoteException;
 public String[] listServices() throws RemoteException;
}

Obviously, ServiceManagerProxy needs to inherit from IServiceManager, as shown in the figure.
insert image description here
(2) Interface implementation. Taking getService as an example, there are at least two parts to get the service of ServiceManager.
1. Establish a relationship with Binder
Because there are already two classes in the process, ProcessState and IPCThreadState, which specifically communicate with the Binder driver, the Java layer code uses the Binder driver to actually complete based on them. We call it BpBinder.
2. Send commands to Binder to obtain services provided by SM.
Summarized as follows:

  1. Binder architecture
    Its main body includes driver, SM, Binder Client and Binder Server.
  2. Binder driver
  3. Service Manager
    SM is not only the supporter of Binder framework, but also a standard Server.

A graph can be used to summarize the Binder mechanism, as shown in the figure (acquiring SM services (main components)).
insert image description here
The Client in the figure represents the Binder Client, that is, the client that uses the Binder mechanism to obtain services. Its internal structure is
ProcessState/IPCThreadState→BpBinder→Proxy→User from bottom to top. Whether it is Client or Service Manager, their work is done based on Binder Driver.
1.4 ServiceManagerProxy
When thinking about "design intent" in the previous section, we described an implementation of ServiceManagerProxy through a small piece of pseudo-code - the specific implementation in the Android system is basically similar to this, except that it adds one more to ServiceManagerProxy Layer encapsulation, namely ServiceManager.java.
In this way, it is more convenient for the application to use ServiceManager, and even the ServiceManagerProxy object does not need to be created, as shown below:
ServiceManager.getService(name);
The internal implementation of getService:

 public static IBinder getService(String name) {
    
    
 try {
    
    
 IBinder service = sCache.get(name);//查询缓存
 if (service != null) {
    
    
 return service;//从缓存中找到结果,直接返回
 } else {
    
    
 return
getIServiceManager().getService(name);//向SM发起查询
 }
 } catch (RemoteException e) {
    
    
 Log.e(TAG, "error in getService", e);
 }
 return null;
 }
private static IServiceManager getIServiceManager() {
    
    
 if (sServiceManager != null) {
    
    
 return sServiceManager;//返回一个IServiceManager对象
 }
 // Find the service manager
 sServiceManager
=ServiceManagerNative.asInterface(BinderInternal.getContextObjec
t());
 return sServiceManager;
 }

ServiceManagerNative

/*frameworks/base/core/java/android/os/ServiceManagerNative.java
*/
 static public IServiceManager asInterface(IBinder obj)
 {
    
    
 if (obj == null) {
    
    
 return null;
 }
 IServiceManager in =
(IServiceManager)obj.queryLocalInterface(descriptor);
 if (in != null) {
    
    
 return in;
 }
 return new ServiceManagerProxy(obj);
 }

From the comments of this function, it can be found that it is responsible for converting a Binder object into IServiceManager and creating ServiceManagerProxy when necessary.
ServiceManagerProxy must communicate with the Binder driver, so the IBinder object is passed into its constructor

public ServiceManagerProxy(IBinder remote) {
    
    
 mRemote = remote;
}

As you can see, it simply records the IBinder object. It's like ordering food by phone. IBinder is the phone number of the restaurant. Usually, it is written down first, and then the service provided by the restaurant can be obtained through this number when needed. For example, the getService() interface:

public IBinder getService(String name) throws
RemoteException {
    
    
 Parcel data = Parcel.obtain();
 Parcel reply = Parcel.obtain();
 data.writeInterfaceToken(IServiceManager.descriptor);
 data.writeString(name);
 mRemote.transact(GET_SERVICE_TRANSACTION, data, reply,
0);/*利用IBinder对象执行
 
命令*/
 IBinder binder = reply.readStrongBinder();
 reply.recycle();
 data.recycle();
 return binder;
 }

This function implementation is divided into the following three parts.

  1. prepare data
  2. IBinder.transact
    uses IBinder's transact to send the request, regardless of the Binder-driven open, mmap, and a lot of specific commands in the Binder protocol. So this IBinder will definitely use ProcessState and IPCThreadState internally to communicate with the Binder driver
  3. get results

The actual work is only the following sentence:

mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);

It should be noted here that the business code used by the client and the server must be consistent, such as GET_SERVICE_TRANSACTION above. Its definition is:

int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;

According to the definition of IBinder:

int FIRST_CALL_TRANSACTION = 0x00000001;

So this business code is 1. Let's look at the business code description in Service_manager.c:

enum {
    
    
 SVC_MGR_GET_SERVICE = 1, //对应的就是上面的那个业务
 SVC_MGR_CHECK_SERVICE,
 SVC_MGR_ADD_SERVICE,
 SVC_MGR_LIST_SERVICES,
};

In this way, the business codes of the client and the server are consistent.
1.5 When IBinder and BpBinder
create ServiceManagerProxy, they pass in an IBinder object, and then use its transact method to communicate with the Binder driver conveniently. So, how is IBinder implemented internally?
The functions provided by Binder can be uniformly expressed in IBinder, at least the following interface methods are required:

/*frameworks/base/core/java/android/os/IBinder.java*/
public interface IBinder {
    
    
 public IInterface queryLocalInterface(String descriptor);
 public boolean transact(int code, Parcel data, Parcel
reply, int flags)
 throws Remote Exception;}

In addition, there should be a class for obtaining IBinder objects, namely BinderInternal. The corresponding methods provided are:

/*frameworks/base/core/java/com/android/internel/os/BinderIntern
al.java*/
public class BinderInternal {
    
    
 public static final native IBinder getContextObject();}

The corresponding native method:

/*frameworks/base/core/jni/android_util_Binder.cpp*/
static jobject
android_os_BinderInternal_getContextObject(JNIEnv* env, jobject
clazz)
{
    
    
 sp<IBinder> b = ProcessState::self()-
>getContextObject(NULL);
 return javaObjectForIBinder(env, b);
}

It is realized through ProcessState, and the object created in ProcessState is converted into an IBinder object of the Java layer.
IBinder is just an interface class, and obviously there will be concrete implementation classes inheriting from it. In the Native layer, this is BpBinder (BpBinder.cpp); in the Java layer, it is BinderProxy in Binder.java. In fact, what ProcessState::self() ->getContextObject(NULL) returns is a BpBinder object.
insert image description here
BinderProxy and BpBinder inherit from the IBinder interface of the Java and Native layers respectively. Among them, BpBinder is created by ProcessState, and BinderProxy is created by javaObjectForIBinder() function through JNI's NewObject().
Analyze the source code, mRemote->transact, call the transact method of BinderProxy, the real implementation is still in android_util_Binder.cpp, and finally process the user's Binder request through BpBinder.transact:

/*frameworks/native/libs/binder/BpBinder.cpp*/
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;
}

In the end, it is realized through IPCThreadState and ProcessState.
1.6 ProcessState and IPCThreadState
The key point to realize ProcessState is.

  1. Ensure that only one ProcessState instance exists in the same process; and only open the Binder device and do memory mapping when the ProcessState object is created.
  2. Provide IPC services to the upper layer.
  3. Work with IPCThreadState in a division of labor and perform their duties.

Most programs have IPC needs, and inter-process communication itself is very cumbersome, so the Android system uses the Binder mechanism to encapsulate two implementation classes for program processes, namely ProcessState and IPCThreadState. As can be seen from the name, the former is process-
related, while the latter is thread-related. ProcessState is responsible for opening the Binder driver device and performing mmap() and other preparations; and how to communicate with the Binder driver for specific commands is done by IPCThreadState.

Binder client - Binder Client

1. What is Binder?
It is one of many inter-process communication. Inter-process communication is something that every operating system needs to provide.
2. Applications and Binder
The largest "consumer" of Binder is the application program at the Java layer. Although Figure 6-21 is simple, it summarizes the essence of Binder, that is, it is a communication bridge between processes. Next, we
still need to continue digging along this clue. The process 1 shown in the figure is a general reference, so what conditions does a process need to meet, or what preparations need to be done to be eligible to use Binder? From the perspective of application developers, this does not seem to be within their consideration, because in general, they can use a series of interface methods such as bindService, startActivity, and sendBroadcast to achieve interaction with other processes at any point in the program code, such as As shown in Figure 6-22.
insert image description here

With the efforts of Binder Driver, Service Manager, and the powerful package of Binder provided by the Android system for application development, it is possible for applications to communicate smoothly and seamlessly. We can see some clues from the four major components.

  • Activity

The target process can be started by startActivity.

  • Service

Any application can start a specific service through startService or bindService, regardless of whether the latter is cross-process.

  • Broadcast
    Any application can send a broadcast through sendBroadcast, regardless of whether the broadcast handler is in the same process or not.
  • In the above operations of the four major components of intent , in most cases, it does not specifically specify which target application needs to respond to the request—they will first express their "will"
    through an object called "intent", and then the system will
    Find the best matching application process to complete the relevant work. Such a design greatly enhances the flexibility of the system.

The following will take bindService as an example to fully reveal the internal principles of Binder hidden behind these interfaces.
insert image description here
As can be seen from the figure, the entire framework is divided into two parts, which respectively represent the two parts visible and hidden to the application in the Binder mechanism.
In order to allow everyone to see the internal implementation of the entire "hidden part" more clearly, an example is selected for analysis. As shown in Figure 6-25, an Activity in Application1
tries to start a Service service that conforms to the intent description through bindService(intent)——finally, the Service in Application2 will be run.
insert image description here
How can the application rely on bindService to start the Service provided by other processes in the system? The following steps must be required to accomplish this operational goal.

  • Step1. The application fills in the Intent and calls bindService to send the request.
  • Step2. The bindService that received the request (still in the running space of the application at this time) will get in touch with the Activity ManagerService (AMS). In order to obtain the Binder handle value of AMS, ServiceManager.getService must be called in advance, which already involves inter-process communication. After getting the handle value of AMS, the program can actually initiate a request to it
  • Step3. Based on a specific "optimal matching strategy", AMS finds the one that best matches the Intent from the data of all service components in the system stored in its internal storage, and then sends a Service binding request to it (this step is also inter-process communication)
    — - Note that if the target process does not exist, AMS is also responsible for starting it.
  • Step4. The "bound" service process needs to respond to the binding, perform specific operations, and notify AMS after successful completion; then the latter will call back the application that initiated the request (the callback interface is ServiceConnection)

It can be seen that a seemingly simple bindService has a "big world" inside. But why does Application1 only need to call bindService in Activity
without seeing the above tedious process?
The inheritance relationship based on the Activity application program is shown in Figure 6-26.
insert image description here
The "root" of the Activity inheritance relationship is Context. bindService is naturally included in the Context. Specifically, Context just provides an abstract interface, and the functions are implemented in ContextWrapper:

/*frameworks/base/core/java/android/content/ContextWrapper.java*/
 public boolean bindService(Intent service, ServiceConnection
conn,int flags) {
    
    
 return mBase.bindService(service, conn, flags); //mBase是什么?
 }

The above variable mBase is also a Context object, which is implemented by ContextImpl in the latest version (bindService directly calls bindServiceAsUser):

/*frameworks/base/core/java/android/app/ContextImpl.java*/
 public boolean bindServiceAsUser(Intent
service,ServiceConnection conn,int flags,
 UserHandle user) {
    
    int res =
ActivityManagerNative.getDefault().bindService(
 mMainThread.getApplicationThread(),
getActivityToken(),
 service,
service.resolveTypeIfNeeded(getContentResolver()),
 sd, flags, userId); /*ActivityManager出现了,证明了我们猜测的第2步*/}

So, how does the application find and establish a connection with the AMS? Like ServiceManager, AMS also provides ActivityManagerNative and ActivityManagerProxy, as follows:

/*frameworks/base/core/java/android/app/ActivityManagerNative.ja
va*/
 static public IActivityManager getDefault() {
    
    
 return gDefault.get(); /*得到默认的IActivityManager对象*/
 }

What does this gDefault.get() get?

/*frameworks/base/core/java/android/app/ActivityManagerNative.ja
va*/
 private static final Singleton<IActivityManager> gDefault
=new Singleton <IActivity
 Manager>() {
    
    
 /*Singleton,即“单实例”是一种常见的设计模式,它保证某个对象只会被创建
一次。
 当调用gDefault.get()时,会先进行内部判断:如果该对象已经存在,就直接
返回它的现有值;否则才
 需要通过内部create()新建一个对象实例*/
 protected IActivityManager create() {
    
    
 IBinder b = ServiceManager.getService("activity");/*通过
ServiceManager Service
 取得ActivityManagerService的
IBinder对象*/
 …
 IActivityManager am = asInterface(b); /*创建一个可用的
ActivityManagerProxy*/return am;
 }
 };

One of the functions of ActivityManagerNative is to help callers obtain an ActivityManagerProxy conveniently and quickly.
In the single instance of gDefault, obtaining a valid IActivityManager object requires two steps, namely:

  • get IBinder(BpBinder);
  • Convert IBinder to IInterface (in this scenario, IactivityManager).

By the way, another role of ActivityManagerNative is to facilitate the implementation of ActivityManagerService. If you look carefully, you will find that
there are the following methods in ActivityManagerNative:

public boolean onTransact(int code, Parcel data, Parcel reply,
int flags)throws Remote Exception {
    
    
 switch (code) {
    
    
 case START_ACTIVITY_TRANSACTION:
 {
    
    int result = startActivity(app, intent,
resolvedType,
 grantedUriPermissions, grantedMode,
resultTo, resultWho,
 requestCode, onlyIfNeeded, debug,
profileFile, 
 profileFd, autoStopPro filer);}

In this way, as long as it inherits from ActivityManagerNative in AMS, the user's service request code has been connected with its own internal implementation function. Isn't it very convenient? The source code is as follows:

/*frameworks/base/services/java/com/android/server/am/ActivityMa
nagerService.java*/
public final class ActivityManagerService extends
ActivityManagerNative/*果然继承了
 
ActivityManagerNative*/
 implements Watchdog.Monitor,
BatteryStatsImpl.BatteryCallback {
    
    

Therefore, it can be said that ActivityManagerNative (the Native of other services is the same) is not only oriented to the caller, but also oriented to the service implementation itself, but the name of this Native is prone to ambiguity.
After the analysis of the above code, the inter-process communication between Application1 and Application2 should also be supported by ServiceManager and ActivityManagerService,
as shown in Figure 6-27
insert image description here
. When an application needs to query a Binder Server through ServiceManager, it calls the getService method . Directly facing the application is Service
Manager.java, which provides multiple static interfaces for callers to obtain the services provided by ServiceManager, such as Service Manager.getService. These
static functions internally get the ServiceManagerProxy object through getIServiceManager——the latter, as the local agent of SM, will use IBinder to "pass through" the JNI layer to call the corresponding BpBinder, and then use the related interfaces of ProcessState and IPCThreadState, and finally complete the communication with the Binder driver. ServiceManager communication.
insert image description here
bindService call process

Android Interface Description Language - AIDL

AIDL is short for Android Interface Description Language. From the name, it is a language, and it is a language specially used to describe interfaces. To be precise, it is a description language used to define the client/server communication interface.
Through an example to analyze what convenience AIDL can bring to Binder Server and its internal implementation principle. Take WindowManagerService as an example
(1) WMS is started in SystemServer
(2) See how AIDL ensures the consistency of the interface. To use AIDL, you must first write a *.aidl file to describe the Server. for example:

/*IWindowManager.aidl*/
interface IWindowManager
{
    
    …
IWindowSession openSession(in IInputMethodClient client,in
IInputContext inputContext);}

The above code segment only retains an interface method of openSession. After the IWindowManager.aidl file is converted by the tool, it becomes the following:

/*IWindowManager.java*/
public interface IWindowManager extends android.os.IInterface
{
    
    
public static abstract class Stub extends android.os.Binder
//Stub表示一个“桩”
implements android.view.IWindowManager
{
    
    
public static android.view.IWindowManager
asInterface(android.os.IBinder obj)
{
    
    }
@Override public android.os.IBinder asBinder()
{
    
    
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel
data, 
android.os.Parcel reply, int flags) throws
android.os.RemoteException
{
    
    
switch (code)
{
    
    
case TRANSACTION_openSession:
{
    
    }}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements
android.view.IwindowManager
//Proxy就是“代理”,我们已经多次讲解
{
    
    
private android.os.IBinder mRemote;
…
@Override public android.os.IBinder asBinder()
{
    
    
return mRemote;
}
@Override public android.view.IWindowSession
 openSession(com.android.internal.view.IInputMethodClient
client,
 com.android.internal.view.IInputContext inputContext) throws
android.os.RemoteException
{
    
    }
} //Proxy结束
}//Stub结束static final int TRANSACTION_openSession =
(android.os.IBinder.FIRST_CALL_TRANSACTION + 3); 
/*自动分配业务码,大家可以和ServiceManager中的手工分配做下对比*/
}//IWindowManager结束
  • IWindowManager
    generally starts with a capital letter I to represent an Interface. In AIDL, all service interfaces inherit from Iinterface, and then declare methods related to this Server service on this basis. For example, in addition to two nested classes in IWindowManager, the end also contains the prototypes of the interfaces it provides, such as openSession, getRealDisplaySize, and hasSystemNavBar.
  • IWindowManager.Stub
    Remember ServiceManagerNative? The role of Stub is similar to it. It contains a nested class (Stub.Proxy), and various commonly used interface methods (such as asInterface, asBinder, etc.), the most important of which is onTransact. We know that ServiceManagerNative is for both server and client, and so is Stub. In actual use, a Binder Server implementation class usually inherits from Stub. The Stub inherits from Binder and implements the IXX interface of the Server, such as the implementation class WindowManagerService of IWindowManager:
public class WindowManagerService extends IWindowManager.Stub
  • IWindowManager.Stub.Proxy
    Proxy is a proxy, and its function is similar to ServiceManager Proxy. Therefore, this class is oriented to the Binder Client, which allows the caller to easily construct the local proxy object of the Binder Server.
    As shown in the figure (Binder Server based on AIDL),
    insert image description here
    by analyzing the aidl file and the java interface file generated by it, We know that an AIDL interface includes three important classes: IwindowManager, IWindowManager.Stub and IWindowManager.Stub.Proxy. The latter two are respectively oriented to the implementation of the WMS server and the Binder Client local agent, and both inherit from IWindowManager, thus ensuring that the Client and Server communicate on the same service interface.
    (3) How to interact with the Binder driver
    Through analysis, we found that the original system service process was called at the beginning:
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();

They will cause the program to eventually enter a main loop similar to binder_loop. Therefore, the Binder objects in this process do not need to interact with the driver alone
(4) How the Client can accurately access the Binder Server in the target process
can be known by the Binder Server in two ways, one is by registering in the SM , which is the case where WMS belongs. The second is anonymous Server implementation. For the real-name Server, when it uses addService to register itself in the SM, it will "pass" the Binder driver, and the latter will link the Binder object to proc->nodes, and target_proc->refs_by_desc and target_proc as planned -> refs_by_node. In this scenario, proc is the system service process, and target_proc is the SM. In other words, there is a reference to the binder_node describing the WMS in the SM. In this way, when a Binder Client initiates a query to SM through getService, the latter can accurately inform the caller of the location of the WMS node it wants to visit.

Anonymous Binder Server

Register yourself in the Service Manager through addService, so any BinderClient can obtain a reference to it through the SM's getService interface. This type of Binder Server is called a "real name" Server. Not registered in Service Manager, called "anonymous" Binder Server. A direct benefit of anonymity is the improvement of the safety factor. For example, an application provides a certain server service, but it does not want to open it to the public.

Guess you like

Origin blog.csdn.net/jifashihan/article/details/129294282