1 Introduction to Binder mechanism
To ensure system security and stability, the Linux operating system has process isolation. Two different processes, such as the App process and the System_server process of ActivityManagerService, cannot directly access each other’s internal functions or variables through the memory address. Therefore, if two processes need to access each other, a concept of inter-process communication is involved, namely IPC(inter-process communication). In essence, by virtue of the principle that the kernel space of different processes is shared, two different processes access the kernel space, so as to achieve the purpose of “indirect” access to each other. Binder is a widely used IPC mechanism in Android system. It is used for both application requests to system services (for example, application calls the startActivity interface of AMS, the core service of the system, to start applications) and external services provided by applications themselves. Therefore, Binder mechanism plays a very important role in Android system. It can be said that understanding Binder is a prerequisite for understanding Android system. This article provides a comprehensive look at the Binder mechanism, based on the latest Android 11 code.
In Unix/Linux, there are many traditional IPC mechanisms. Such as pipes, message queues, shared memory, sockets, semaphores, etc. Android, though linux-based, rarely uses these traditional IPC mechanisms, using binders for the most part. Binder has three advantages over traditional IPC mechanisms:
-
Performance advantages: Pipes, message queues, and sockets all require two copies of data, whereas binders use memory mapping and require only one copy. It should be noted that for the underlying IPC form of the system, one less copy of data has a very large impact on overall performance.
-
Advantages in stability: Binder itself is based on C/S architecture, which leaves any needs of the Client to the Server. With clear architecture, clear responsibilities and mutual independence, Binder naturally has better stability. Shared memory does not require copying, but is controlled and difficult to use. Binder mechanisms are superior to memory sharing from a stability perspective.
-
Security advantage: With Binder IPC, ids (UID/PID) are passed automatically along with the invocation. The Server can easily know the identity of the Client, facilitating security checks, which is very important for mobile operating systems.
2 Binder overall architecture
A classic architecture diagram is used to describe binder’s overall architecture as follows:As you can see from the picture. The Binder mechanism can be architecturally divided into three layers:
- The Binder driver layer, which we know is based on the Linux kernel, is in the Linux kernel. Binder drivers register themselves as misC devices and provide the upper layer with a dev/ Binder node that does not correspond to real hardware devices. Binder drivers run in kernel mode and provide low-level functions such as data transfer, object identification, thread management, and call process control, which are the core of Binder’s cross-process communication.
- Framework C++ layer, based on the driver layer, Binder mechanism C++ encapsulation implementation.
- Framework Java layer, Binder mechanism of Java layer encapsulation implementation, using JNI call reuse C++ layer implementation.
The Binder communication architecture is a typical C/S architecture consisting of Client, Server, ServiceManager, and Binder Driver. Client, Server, ServiceManager, Binder The roles of drivers in communication are similar to those of Servers, clients, ServiceManager, and routers (Binder drivers) on the Internet. The general process of Binder communication is as follows:
- At system startup, a process registers itself as a ServiceManager with a Binder driver whose address is fixed at 0.
- A Server registers a Binder (a Binder entity in a Server) with a ServiceManager to provide services. The driver creates entity nodes in the kernel for the Binder and references to the entity by the ServiceManager. The driver packages the name and the new reference to the ServiceManager, which the ServiceManger fills in the lookup table.
- With the help of Binder drivers, clients get references to Binder entities from ServiceManager by name and communicate with Server processes through this reference.
The whole process is shown below:
3 Binder drivers and protocols
This part of the source is located in the Linux kernel:
/kernel/drivers/android/binder.c
/kernel/include/uapi/linux/android/binder.h
3.1 Binder driver device initialization
Binder drivers do several things during initialization:
- Register yourself with misc_register() as a driver of type MISC Device;
- Fill in the file_operations function pointer structure to identify the file operations supported by user space with Binder drivers;
- Specify the Binder device name as “Binder” so that user space can use Binder by operating on the /dev/binder file. The following code looks like this:
/*kernel/drivers/android/binder.c*/
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
if(! binder_device)return -ENOMEM;
binder_device->miscdev.fops = &binder_fops;// 1. File operations supported by Binder drivers
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;// Dynamically allocate the device number
binder_device->miscdev.name = name;//2. Driver name, usually "binder"
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
mutex_init(&binder_device->context.context_mgr_node_lock);
ret = misc_register(&binder_device->miscdev);// 3. Register Binder drivers
if (ret < 0) {
kfree(binder_device);
return ret;
}
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,// binder_ioctl
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,// binder_mmap
.open = binder_open,// binder_open
.flush = binder_flush,
.release = binder_release,
};
Copy the code
Binder drivers provide six interfaces for upper-layer applications — the most used are binder_ioctl, binder_mmap, and binder_open. This is because processes that need Binder almost always open Binder devices through binder_open, do memory mapping through binder_mmap, and then do actual operations through binder_ioctl. The Client requests to the Server and the Server returns the request results to the Client through iocTL.
3.2 Opening Binder driver – Binder Open
To access Binder drivers, upper user-space processes first need to open Binder driver devices through open(“dev/ Binder “), which is eventually implemented in binder_open(). As follows:
/*kernel/drivers/android/binder.c*/
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
struct binder_device *binder_dev;
binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
current->group_leader->pid, current->pid);
proc = kzalloc(sizeof(*proc), GFP_KERNEL);//1. Create a binder_proc object corresponding to the process
if (proc == NULL)
return -ENOMEM;
spin_lock_init(&proc->inner_lock);
spin_lock_init(&proc->outer_lock);
get_task_struct(current->group_leader);
proc->tsk = current->group_leader;
INIT_LIST_HEAD(&proc->todo);
proc->default_priority = task_nice(current);
/* binderfs stashes devices in i_private */
if (is_binderfs_device(nodp))
binder_dev = nodp->i_private;
else
binder_dev = container_of(filp->private_data,
struct binder_device, miscdev);
proc->context = &binder_dev->context;
binder_alloc_init(&proc->alloc);
binder_stats_created(BINDER_STAT_PROC);
proc->pid = current->group_leader->pid;
// 2. Initialize binder_proc
INIT_LIST_HEAD(&proc->delivered_death);
INIT_LIST_HEAD(&proc->waiting_threads);
filp->private_data = proc;
mutex_lock(&binder_procs_lock);/ / acquiring a lock
hlist_add_head(&proc->proc_node, &binder_procs);// 3. Add to the global list binder_procs
mutex_unlock(&binder_procs_lock);
if (binder_debugfs_dir_entry_proc) {
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/* * proc debug entries are shared between contexts, so * this will fail if the process tries to open the driver * again with a different context. The priting code will * anyway print all contexts that a given PID has, so this * is not a problem. */
proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
binder_debugfs_dir_entry_proc,
(void *)(unsigned long)proc->pid,
&proc_fops);
}
return 0;
}
Copy the code
In Binder drivers, all processes using Binder are logged through the binder_procs collection. Each process that opens a Binder device for the first time creates a binder_proc struct object that describes the process using Binder and is then added to the list.
3.3 Memory Mapping — binder_mmap
After the Binder device is turned on, the upper layer processes are memory-mapped through Mmap. Mmap has the following two functions:
- Apply for a memory space to receive data during communication with the Binder
- Address map this block of memory for future access
The kernel counterpart of mmap is the binder_mmap() function: in this function, a piece of physical memory is allocated and the user space and kernel space virtual address are mapped to this piece of physical memory. After that, when a Client wants to send data to the Server,A copy of copy_from_user action is required to copy the data sent from Client to the memory address specified by the Server kernel space (Binder communication mechanism mentioned above only needs one memory copy).Since the memory address has been mapped to user space on the Server side, the Server can directly access the memory address without another replication, as shown in the following figure:The illustration is as follows:
- Server invokes mmap for /dev/binder devices after startup;
- The binder_mmap function in the kernel does the corresponding processing: allocates a block of physical memory, and then maps both user space and kernel space.
- The Client sends a request using the BINDER_WRITE_READ command. The request is sent to the driver first, and data needs to be copied from the Client’s user space to the kernel space.
- The driver notifies the Server of a request via BR_TRANSACTION, and the Server processes it. Because this memory is also mapped in user space, the Server process’s code is directly accessible.
3.4 Binder Driver control protocol – binder_ioctl
Binder_ioctl (), which implements commands for upper-layer applications and Binder drivers, is responsible for most of the Binder drivers’ business and is the most important part of our study. The following table lists the commands supported by binder_ioctl:
The command | instructions |
---|---|
BINDER_WRITE_READ | Read and write operations that can be used to read or write data to Binder |
BINDER_SET_MAX_THREADS | Set the maximum number of threads supported. Because the client can send requests concurrently to the Server, the Binder driver tells Binder Server to stop starting new threads if it finds that the current thread count has exceeded the set point |
BINDER_SET_CONTEXT_MGR | For Service Manager, set yourself as “Binder Manager”. Only one SM can exist in the system |
BINDER_THREAD_EXIT | Notify the Binder thread to exit. Binder drivers should be told when each thread exits so that resources can be released. Otherwise, memory leaks will occur |
BINDER_VERSION | Obtain the Binder version number |
The BINDER_WRITE_READ command is the most important one, which is divided into several sub-commands, as shown in the following table:
The command | instructions |
---|---|
BC_TRANSACTION | Binder transactions: Client requests to the Server |
BC_REPLY | A transaction reply, that is, a reply from the Server to the Client |
BC_ENTER_LOOPER | Notify the driver main thread ready |
BC_REGISTER_LOOPER | Notify the driver child thread ready |
BR_REPLY | Notification process receives response to Binder request (Client) |
BR_TRANSACTION_COMPLETE | Drive an acknowledgement reply to accept the request |
BR_TRANSACTION | Notification process receives a Binder request (Server side) |
BR_DEAD_BINDER | Send death notification |
BR_SPAWN_LOOPER | Notify the Binder process to create a new thread |
BC_TRANSACTION and BC_REPLAY are the two most critical commands that Binder mechanisms rely on for client-server interaction.
It may be difficult to understand the protocol in isolation, but let’s take a closer look at how the Binder protocol communicates using a Binder request process.The illustration is as follows:
- Binder is C/S architecture, communication process involves three roles: Client, Server and Binder driver
- Binder drivers are used to transfer data between Client and Server requests and between Server and Client replies
- The BC_XXX command is a command that a process sends to a driver
- The BR_XXX command is the command that the driver sends to the process
- The whole communication process is controlled by Binder drive
4 Binder Framework C++ layer
The C++ part of the Binder Framework: the main function of the Binder Framework is to realize the docking interaction with the Binder drivers, encapsulate the complex internal implementation, and provide the external use interface. Header files are defined in: / frameworks/native/include/binder, implementation in this path: / frameworks/native/libs/binder. Binder libraries are eventually compiled into a dynamic link library, libbinder.so, which can be linked by other processes. For purposes of illustration, the C++ portion of the Binder Framework is referred to as libbinder.
4.1 Main class structure
A class diagram is used to describe the relationships between the main class structures in Libbinder:
In libbinder’s design class diagram above, let’s clarify the functions and responsibilities of each core class:
- The base class
- IBinder: base class for Binder objects. This class describes all objects passed to Binder. It is the parent of both the Binder server object BBinder and the Binder client object BpBinder. The main methods defined are: A. ransact — perform a Binder operation; B. cueryLocalInterface — Try to get local Binder objects; C. GetInterfaceDescriptor — Get a unique description of a Binder’s service interface; D. isbinderAlive — check whether Binder services are alive, etc.
- IInterface: Base class for Binder service interfaces. Binder services are usually required to provide both client and server interfaces. Each Binder service is implemented for a function, so it defines a set of interfaces to describe all the functions it provides. Binder services, on the other hand, have classes that both implement the service themselves and call the client process. For ease of development, the service interfaces in the two classes should be consistent. So for implementation convenience, the local implementation class and the remote interface class need to inherit from a common base class that describes the service interface (IXXXService in the figure above). This base class is usually a subclass of IInterface.
- The client class
- BpBinder: Instances of BpBinder represent client Binders, objects of which are invoked by clients. Most importantly, this class provides transact methods that encapsulate the parameters of the client call and send them to Binder drivers wrapped in IPCThreadState logic.
- BpInterface: The base class of the client interface. The remote interface is the set of interfaces to be invoked by the client. BpInterface is a template class that, in addition to INTERFACE, inherits BpRefBase, through which the remote method obtains a handle to the service implementer.
- The server class
- BBinder: The BBinder instance represents the server Binder, which describes the provider of the service. All implementers of Binder services inherit from this class. Of the inherited classes, the most important is to implement the onTransact method, which is the entry point for all requests. Therefore, this method corresponds to the Transact method in BpBinder, which also has a uint32_t code parameter (unified in IBinder definition). In the implementation of this method, the service provider distinguishes the interface of the request by code. The method that implements the service is then invoked.
- BnInterface: The base class for the server interface, which is the set of interfaces that need to be implemented in the server service. BnInterface is a template class that inherits BBinder from INTERFACE(the base class for Binder service interfaces, derived from IInterface), and thus can be implemented by copying the onTransact method.
- The class that communicates with the driver
- ProcessState: Represents processes that use Binder. Binder drivers must be open and mmap with /dev/binder devices. This logic is common to all Binder drivers. Encapsulating this common logic is one of the responsibilities of the Framework layer. In Libbinder, the ProcessState class encapsulates this logic and is responsible for the initialization of the process Binder.
- IPCThreadState: Represents threads in the process that use Binder and encapsulates the implementation logic that communicates with Binder drivers.
In the implementation class of the client interface, each interface, after the parameters are assembled, calls remote()-> Transact to send the request, which in this case is BpBinder’s Transact method called, and the request arrives in the service implementer’s onTransact through Binder. This process is shown below:
4.2 process initialization with Binder (ProcessState)
Binder drivers require binder_open, binder_mmap, and binder_mmap. Because this logic is common to all Binder processes, libbinder uses the ProcessState class to encapsulate the logic as follows:
/*framework/native/libs/binder/ProcessState.cpp*/
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver))// 1. Call open_driver to open the binder driver
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mStarvationStartTimeMs(0)
, mBinderContextCheckFunc(nullptr)
, mBinderContextUserData(nullptr)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1),mCallRestriction(CallRestriction::NONE)
{
// TODO(b/139016109): enforce in build system
#if defined(__ANDROID_APEX__)
LOG_ALWAYS_FATAL("Cannot use libbinder in APEX (only system.img libbinder) since it is not stable.");
#endif
if (mDriverFD >= 0) {
// 2. Mmap performs memory mapping
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
close(mDriverFD);
mDriverFD = -1; mDriverName.clear(); }}}Copy the code
This is the ProcessState constructor, where Binder initialization is done. In this function, the Binder device is opened by calling the Open_driver method when mDriverFD is initialized, and then the mMAP memory is mapped in the function body. BINDER_VM_SIZE (BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE, BINDER_VM_SIZE) This also explains the actually we usually encountered in the development process of a problem, too much is delivered via Intent object will quote TransactionTooLargeException anomalies. The open_driver action code is as follows:
/*framework/native/libs/binder/ProcessState.cpp*/
#define DEFAULT_MAX_BINDER_THREADS 15
static int open_driver(const char *driver)
{
int fd = open(driver, O_RDWR | O_CLOEXEC);// 1. The dev/binder device is opened with the open system call
if (fd >= 0) {
int vers = 0;
// 2. Obtain the version number of Binder implementation through IOCtl and check whether it matches
status_t result = ioctl(fd, BINDER_VERSION, &vers);
if (result == -1) {
ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
close(fd);
fd = -1;
}
if(result ! =0|| vers ! = BINDER_CURRENT_PROTOCOL_VERSION) { ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)! ioctl() return value: %d",
vers, BINDER_CURRENT_PROTOCOL_VERSION, result);
close(fd);
fd = -1;
}
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
// 3. Use ioctl to set the maximum number of Binder threads supported by the process
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
if (result == -1) {
ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); }}else {
ALOGW("Opening '%s' failed: %s\n", driver, strerror(errno));
}
return fd;
}
Copy the code
In addition to the implementation of the open_driver function, the dev/binder device is opened through the open system call. The BINDER_SET_MAX_THREADS command called by IOCtl is also used to set the maximum number of Binder threads supported by the process. Currently, the default maximum number of Binder threads is 15. This also explains the performance problem we encountered in the development process: The process ran out of Binder threads. At this time, 15 Binder threads had been started in the process to handle Binder requests, all of which were in working or blocking state. At this time, new Binder requests could not be processed, thus blocking the card master.
4.3 Communication with Binder Drivers (IPCThreadState)
IPCThreadState is a singleton class, with one instance of each Binder thread in the process that takes care of the details of communicating with the driver. The key methods in this class are described as follows:
The most important of these is the Transact method, which performs a Binder data transfer as follows:
/*framework/native/libs/binder/IPCThreadState.cpp*/
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags){...// 1. Use writeTransactionData to assemble and write dataerr = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr); .// 2. Check whether it is a TF_ONE_WAY asynchronous one-way request. If the thread does not need to block waiting for the return value, it can directly return it
if ((flags & TF_ONE_WAY) == 0) {...// 3. Pass the data to the driver via waitForResponse and wait for the result to return
if (reply) {
err = waitForResponse(reply);
} else{ Parcel fakeReply; err = waitForResponse(&fakeReply); }... }else {
err = waitForResponse(nullptr, nullptr);
}
return err;
}
Copy the code
As can be seen from the code, the main process of completing a Binder data transfer through Binder driver is as follows:
- First, the writeTransactionData function is used to complete the assembly and filling of binder_transaction_data.
- Through the waitForResponse function to complete the transmission data write driver and wait for the return data, the specific implementation will call talkWithDriver function, through the IOCtl command BINDER_WRITE_READ communication with the Binder driver, complete the data write transmission, and read the returned data.
The detailed code is as follows:
/*framework/native/libs/binder/IPCThreadState.cpp*/
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
/ / assembly binder_transaction_data
binder_transaction_data tr;
tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
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(binder_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 = reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
while (1) {
// Call talkWithDriver to actually transfer the data and write the driver and wait for the data to return
if ((err=talkWithDriver()) < NO_ERROR) break;
}
}
status_t IPCThreadState::talkWithDriver(bool doReceive){...do {
IF_LOG_COMMANDS() {
alog << "About to read/write, write size = " << mOut.dataSize() << endl;
}
#if defined(__ANDROID__)
// Communicates with Binder drivers through the BINDER_WRITE_READ command
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
elseerr = -errno; . }while(err == -EINTR); . }Copy the code
4.4 C++ Binder service implementation examples
The above theoretical knowledge may be boring and abstract. Binder services in the Android system are taken as an example to make a specific analysis based on the above theories. We take SurfaceFlinger as an example to analyze the implementation of Binder service in C++. SurfaceFlinger C++ Binder service implementation class
ISurfaceComposer defines the functional interfaces provided by SurfaceFlinger. SurfaceFlinger subclasses inherit these interfaces.
- The BpSurfaceComposer is a remote interface provided to the client to invoke, retrieving a handle to the service implementer via the remote method and then sending a specific request via the Transact method.
- BnSurfaceComposer has only one onTransact method, which connects to each request based on the requested code and directly calls the corresponding method in SurfaceFlinger.
- SurfaceFlinger is the real implementation of the service interface functionality.
4.4.1 Server implementation
SurfaceFlinger is a subclass of BnSurfaceComposer. SurfaceFlinger is a subclass of BnSurfaceComposer. So any virtual methods that call themselves in BnSurfaceComposer are actually implemented in the SurfaceFlinger subclass. All the BnSurfaceComposer class does is override the onTransact method, which does its job: it identifies which interface is being called based on the requested code, reads the packed arguments from the Parcel in order, and then calls the virtual functions left to the subclasses. We have a look at BnSurfaceComposer: : onTransact the code snippet:
/*framework/native/libs/gui/ISurfaceComposer.cpp*/
status_t BnSurfaceComposer::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
...
case CREATE_DISPLAY: {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
String8 displayName = data.readString8();
bool secure = bool(data.readInt32());
sp<IBinder> display(createDisplay(displayName, secure));
reply->writeStrongBinder(display);
returnNO_ERROR; }... }}Copy the code
In this code, we see how the implementation differentiates interfaces by code, reads the call parameters through a Parcel, and then invokes the specific service party. SurfaceFlinger createDisplay (); SurfaceFlinger createDisplay ();
/*framework/native/services/surfaceflinger/SurfaceFlinger.cpp*/
sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure) {
class DisplayToken : public BBinder {
sp<SurfaceFlinger> flinger;
virtual ~DisplayToken() {
// no more references, this display must be terminated
Mutex::Autolock _l(flinger->mStateLock);
flinger->mCurrentState.displays.removeItem(this);
flinger->setTransactionFlags(eDisplayTransactionNeeded);
}
public:
explicit DisplayToken(const sp<SurfaceFlinger>& flinger)
: flinger(flinger){}}; sp<BBinder> token =new DisplayToken(this);
Mutex::Autolock _l(mStateLock);
// Display ID is assigned when virtual display is allocated by HWC.
DisplayDeviceState state;
state.isSecure = secure;
state.displayName = displayName;
mCurrentState.displays.add(token, state);
mInterceptor->saveDisplayCreation(state);
return token;
}
Copy the code
4.4.2 Register services
Once a service is implemented, it is not immediately available for use by others. Binder drivers register services with ServiceManager to provide services externally. Here we look at the SurfaceFlinger service registration published specific code implementation:
/*frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp*/
int main(int, char**){...// When SF is launched in its own process, limit the number of
// binder threads to 4.
ProcessState::self()->setThreadPoolMaxThreadCount(4);
// 1. Start Binder thread pools
// start the thread pool
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();
setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);
set_sched_policy(0, SP_FOREGROUND);
// initialize before clients can connect
flinger->init();
/ / 2. Through IServiceManager: : in the addService ServiceManager registration service, the service for the name of "SurfaceFlinger"
// publish surface flinger
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false.IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
// run surface flinger in this thread
flinger->run();
return 0;
}
Copy the code
Thus, release Binder service need to start the Binder thread pool, and then through IServiceManager: : the addService in ServiceManager service registration.
4.4.3 Client implementation
The BpSurfaceComposer needs to implement all interfaces in ISurfaceComposer. We mentioned above createDisplay interface, for example, to see BpSurfaceComposer: : createDisplay method code is how to implement:
/*frameworks/native/libs/gui/ISurfaceComposer.cpp*/
virtual sp<IBinder> createDisplay(const String8& displayName, bool secure)
{
Parcel data, reply;
// 1. Enter the unique description of the service interface
data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
// 2. Use Parcel to write send parameters
data.writeString8(displayName);
data.writeInt32(secure ? 1 : 0);
// 3. Call remote()->transact to send the request
remote()->transact(BnSurfaceComposer::CREATE_DISPLAY, data, &reply);
return reply.readStrongBinder();
}
Copy the code
The code is simple and the logic is: package it by writing the call argument via Parcel and then call remote()-> Transact to send the request.
4.4.4 Obtaining Services
Before using the service, the client needs to obtain the handle to access the service from the ServiceManager based on the service name. To pass BpSurfaceComposer: : remote () function to get the remote agent service call transact method after completing a Binder to send data. The detailed code is as follows:
/*framworks/native/libs/gui/SurfaceComposerClient.cpp*/
void ComposerService::connectLocked() {
// 1. The remote Binder service name is "SurfaceFlinger"
const String16 name("SurfaceFlinger");
/ / 2. Call IServiceManager: : getService encapsulate interface to get the remote service agent and encapsulated into the BpSurfaceComposer
while(getService(name, &mComposerService) ! = NO_ERROR) { usleep(250000); }... }/*frameworks/native/include/binder/IServiceManager.h*/
template<typename INTERFACE>
status_t getService(const String16& name, sp<INTERFACE>* outService)
{ // 1. Obtain the proxy of the ServiceManager to access the ServiceManager
const sp<IServiceManager> sm = defaultServiceManager();
if(sm ! = nullptr) {// 2. Use getService to get the specific service handle, and use the Interface_cast conversion to get the BpSurfaceComposer object
*outService = interface_cast<INTERFACE>(sm->getService(name));
if((*outService) ! = nullptr)return NO_ERROR;
}
return NAME_NOT_FOUND;
}
/*frameworks/native/libs/binder/IServiceManager.cpp*/
sp<IServiceManager> defaultServiceManager()
{
std::call_once(gSmOnce, []() {
sp<AidlServiceManager> sm = nullptr;
while (sm == nullptr) {
/ / ProcessState: : getContextObject (nullptr) get access ServiceManager handlesm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr)); . }}});return gDefaultServiceManager;
}
/*frameworks/native/libs/binder/ProcessState.cpp*/
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
// The "0" parameter indicates that the ServiceManager service is searched. This is a special address used to identify the ServiceManager service
sp<IBinder> context = getStrongProxyForHandle(0); .return context;
}
Copy the code
The interface_cast method is used to retrieve the interface object of the service. The method itself automatically determines whether a local or remote Binder is returned based on whether it is in the same process. Interface_cast is a template method with the following source code:
/*frameworks/native/libs/binder/include/binder/IInterface.h*/
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
Copy the code
For ISurfaceComposer is ISurfaceComposer: : asInterface (obj), which are defined as follows:
/*frameworks/native/libs/binder/include/binder/IInterface.h*/
::android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const ::android::sp<::android::IBinder>& obj) \
{ \
::android::sp<I##INTERFACE> intr; \
if(obj ! = nullptr) { \//1. Use queryLocalInterface to retrieve local Binder objects in the same process
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
//2. Create and return the remote Binder object encapsulated as BpSurfaceComposer on failure
if (intr == nullptr) { \
intr = newBp##INTERFACE(obj); \} \} \returnintr; The \}Copy the code
Use ## instead of SurfaceComposer when you understand these template definitions.
5 ServiceManager – Binder ServiceManager
The function of ServerManager(SM for short) is similar to the DNS server on the Internet. The VALUE of IP address is 0. Each Binder service requires a unique name, and SM manages the registration and lookup of these services. In addition, SM is a standard Binder Server, just as DNS servers themselves are servers, and the ServiceManager is accessed through a specific handle = 0 location in Binder drivers. Therefore, by analyzing SM, we can fully see the process of how a Binder Server is built by an upper application driven by Binder. Next we analyze the concrete implementation of SM module.
5.1 Starting the ServiceManager process
SM, as the “master steward” of Binder services, must ensure that the system is started and in working order before all Binder services are started. So SM is started directly at boot time by init program parsing init.rc load. As follows:
/*framework/native/cmds/servicemanager/servicemanager.rc*/
service servicemanager /system/bin/servicemanager// 1. The Servicemanager is an independent executable file
class core animation
user system
group system readproc
critical
onrestart restart healthd
onrestart restart zygote/ / 2.servicemanagerThe result may be caused after the restartzygoterestartonrestart restart audioserver
onrestart restart media
onrestart restart surfaceflinger/ / 3.servicemanagerThe result may be caused after the restartsurfaceflingerrestartonrestart restart inputflinger
onrestart restart drm
onrestart restart cameraserver
onrestart restart keystore
onrestart restart gatekeeperd
onrestart restart thermalservice
writepid /dev/cpuset/system-background/tasks
shutdown critical
Copy the code
As can be seen from above, servicemanager is an independent executable file written by C++. It is a native process that runs independently. When the system is started, the init process is pulled directly. The source path is as follows:
/framework/native/cmds/servicemanager/*
The main function has the following logic:
/*framework/native/cmds/servicemanager/main.cpp*/
int main(int argc, char** argv) {
if (argc > 2) {
LOG(FATAL) << "usage: " << argv[0] < <" [binder driver]";
}
const char* driver = argc == 2 ? argv[1] : "/dev/binder";
//1. Bind /dev/binder driver and Mmap with initWithDriver
sp<ProcessState> ps = ProcessState::initWithDriver(driver);
ps->setThreadPoolMaxThreadCount(0);
ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
//2. Create a ServiceManager object and register itself as a Binder Server named Manager through the addService interface
sp<ServiceManager> manager = new ServiceManager(std::make_unique<Access>());
if(! manager->addService("manager", manager, false /*allowIsolated*/.IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
LOG(ERROR) << "Could not self register servicemanager";
}
IPCThreadState::self()->setTheContextObject(manager);
ps->becomeContextManager(nullptr, nullptr);// 3. Notify binder drivers to register themselves as binder service managers
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
BinderCallback::setupTo(looper);
ClientCallbackCallback::setupTo(looper, manager);
while(true) {
looper->pollAll(-1);// 4. Loop waiting for other modules to request service
}
// should not be reached
return EXIT_FAILURE;
}
Copy the code
The following four things have been done:
- By calling the initWithDriver static interface of ProcessState, which creates the ProcessState object for the SM process and completes the process’s Binder initialization: The Open_driver action is called to open the Binder driver, and the Mmap action is called to inform the Binder driver to complete binder_mmap physical memory allocation and virtual memory mapping operations.
- Create ServiceManager object and register SM itself as Binder Server service named “manager” through addService interface, which further shows that SM itself is a C++ written Binder Server service process in essence.
- Call the becomeContextManager interface of ProcessState and notify the Binder device driver to register SM as the Binder service “big manager” through the BINDER_SET_CONTEXT_MGR command of binder_IOCtl protocol.
- Enter a loop to wait for Client access requests from the Binder Client process.
5.2 ServiceManager Binder service interfaces
Let’s look at the code definition of the Servicemanager.h interface file:
/*framework/native/cmds/servicemanager/ServiceManager.h*/
class ServiceManager : public os: :BnServiceManager.public IBinder: :DeathRecipient {
public: ServiceManager(std::unique_ptr<Access>&& access); ~ServiceManager(); .// getService will try to start any services it cannot find
// 1. Query Binder Server services by name
binder::Status getService(const std::string& name, sp<IBinder>* outBinder) override;
binder::Status checkService(const std::string& name, sp<IBinder>* outBinder) override;
// 2. Register Binder Server services
binder::Status addService(const std::string& name, const sp<IBinder>& binder,
bool allowIsolated, int32_t dumpPriority) override;
// 3. Iterate through and list all registered Binder Server services
binder::Status listServices(int32_t dumpPriority, std::vector<std::string>* outList) override; . };Copy the code
As seen in the Binder framework C++ layer analysis section above, ServiceManager inherits from BnServiceManager and is a Binder Server implementation. The client provides getService to query Service, addService to register Service, and listServices to traverse all registered services. The internal implementation maintains a mapping between the service name and the specific service agent through a global variable record of ServiceMap type named mNameToService (essentially a collection of map types). Take the addService registration service implementation code as an example:
/*framework/native/cmds/servicemanager/ServiceManager.cpp*/
Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority){... auto entry = mNameToService.emplace(name, Service { .binder = binder, .allowIsolated = allowIsolated, .dumpPriority = dumpPriority, .debugPid = ctx.debugPid, }); .return Status::ok();
}
Copy the code
5.3 Encapsulation for accessing ServiceManager Interfaces in Libbinder
Source path:
frameworks/native/include/binder/IServiceManager.h frameworks/native/libs/binder/IServiceManager.cpp
This logic is equivalent to the Binder framework C++ layer libbinder library as a Binder client to access the Binder server interface provided by the ServiceManager process.
IServiceManager’s C++ interface is defined as follows:
/*frameworks/native/include/binder/IServiceManager.h*/
class IServiceManager : public IInterface
{
public:...// 1. Query Binder Server services by name
/** * Retrieve an existing service, blocking for a few seconds * if it doesn't yet exist. */
virtual sp<IBinder> getService( const String16& name) const = 0;
/** * Retrieve an existing service, non-blocking. */
virtual sp<IBinder> checkService( const String16& name) const = 0;
// 2. Register Binder Server services
/** * Register a service. */
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated = false,
int dumpsysFlags = DUMP_FLAG_PRIORITY_DEFAULT) = 0;
// 3. Iterate through and list all registered Binder Server services
/** * Return list of all existing services. */
// NOLINTNEXTLINE(google-default-arguments)
virtual Vector<String16> listServices(int dumpsysFlags = DUMP_FLAG_PRIORITY_ALL) = 0;
/** * Efficiently wait for a service. * * Returns nullptr only for permission problem or fatal error. */
virtual sp<IBinder> waitForService(const String16& name) = 0; . };Copy the code
Common Binder services need to be called by ServiceManager to obtain interfaces. How to obtain interfaces for ServiceManager? In libbinder, a defaultServiceManager method is provided to get the proxy for the ServiceManager, and this method requires no arguments. The reason is explained in the drivers section: Binder implementations provide a special location for ServiceManager that does not need to be identified as a normal service. The defaultServiceManager code is as follows:
/*frameworks/native/libs/binder/IServiceManager.cpp*/
sp<IServiceManager> defaultServiceManager()
{
std::call_once(gSmOnce, []() {
sp<AidlServiceManager> sm = nullptr;
while (sm == nullptr) {
sm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr));
if (sm == nullptr) {
ALOGE("Waiting 1s on context object on %s.".ProcessState::self()->getDriverName().c_str());
sleep(1);
}
}
gDefaultServiceManager = new ServiceManagerShim(sm);
});
return gDefaultServiceManager;
}
Copy the code
6 Binder Framework Java layer
6.1 Main Structure
Android applications are developed using the Java language, and the Binder Framework provides Java interfaces. We have examined the complete implementation of the Binder Framework C++ layer. Therefore, the Java layer does not need to be implemented repeatedly at all, but instead directly calls and reuses the C++ layer implementation through the JNI mechanism provided by the virtual machine. The following diagram illustrates the invocation relationship between the Binder Framework Java layer and the C++ layer.
6.2 JNI connection Invocation
JNI stands for Java Native Interface, which is a mechanism provided by Java Virtual machines. This mechanism allows native code to communicate with Java code. To put it simply: We can call Java code on the C/C++ side and C/C++ code on the Java side. In fact, many of the services or mechanisms in Android are implemented in the C/C++ layer, and if you want to reuse these implementations to the Java layer, you must connect them through JNI. In fact, there are two directions to solve the call problem:
- How does Java side code call C++ methods in libbinder? Take the Transact method in Binderproxy. Java as an example, its function is to realize the Binder client of the Java layer to complete a Binder data transmission using the remote proxy of the server. The simplified code is as follows:
/*frameworks/base/core/java/android/os/BinderProxy.java*/
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
..
try {
return transactNative(code, data, reply, flags);
} finally{... }}/** * Native implementation of transact() for proxies */
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
Copy the code
As you can see, the method transactNative is modified with the native keyword, and there is no method implementation body. These methods are actually implemented in C++. The following code in the android_util_binder.cpp file defines the mapping between Java and C++ methods and the actual implementation. The simplified code is as follows:
/*frameworks/base/core/jni/android_util_Binder.cpp*/
// this collection defines the mapping between Java methods and C++ methods
static const JNINativeMethod gBinderProxyMethods[] = {
/* name, signature, funcPtr */. {"transactNative"."(ILandroid/os/Parcel; Landroid/os/Parcel; I)Z", (void*)android_os_BinderProxy_transact},
...
};
// Real method implementation
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException{...// 1. Obtain the BpBinder objectIBinder* target = getBPNativeData(env, obj)->mObject.get(); .// 2. Call the Transact interface of BpBinder in Libbinder for real Binder transportstatus_t err = target->transact(code, *data, reply, flags); .return JNI_FALSE;
}
Copy the code
- How can Libbinder notify calls to the Java layer? Binder::onTransact in Libbinder ::onTransact in Binder::onTransact in Java Android_util_binder.cpp JavaBBinder::onTransact (JavaBBinder is aBBinder subclass) uses the CallBooleanMethod interface provided by the VIRTUAL machine to invoke native Java methods Method implementation on Object. The simplified code implementation is as follows:
/*frameworks/base/core/jni/android_util_Binder.cpp*/
// JavaBBinder is a subclass of BBinder
class JavaBBinder : public BBinder
{
protected:... status_t onTransact( uint32_t code,const Parcel& data, Parcel* reply, uint32_t flags = 0) override
{
...
// 1. CallBooleanMethod This method is provided by the virtual machine to implement the native method to call a Java Object method. This line of code is actually calling a method whose offset is mExecTransact on mObject,jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); . }... };const char* const kBinderPathName = "android/os/Binder";
static int int_register_android_os_Binder(JNIEnv* env){... jclass clazz = FindClassOrDie(env, kBinderPathName);// 2. Find the execTransact method in Android/OS/binder.java
gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact"."(IJJI)Z"); .return RegisterMethodsOrDie(
env, kBinderPathName,
gBinderMethods, NELEM(gBinderMethods));
}
/*frameworks/base/core/java/android/os/Binder.java*/
// Entry point from android_util_Binder.cpp's onTransact
@UnsupportedAppUsage
private boolean execTransact(int code, long dataObj, long replyObj, int flags){...try {
return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
}
}
private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags, int callingUid){...try{...if((flags & FLAG_COLLECT_NOTED_APP_OPS) ! =0) {... }else {
// call the onTransact implementation of the Java layerres = onTransact(code, data, reply, flags); }}catch (RemoteException|RuntimeException e) {
...
} finally{... }... }Copy the code
6.3 Java Binder Service Implementation Examples
As with the C++ layer, let’s look at a concrete example of how Binder services are implemented for the Java layer. Taking ActivityManager, the core service of the system_server process as an example, the following is the structure of its class diagram:
The IActivityManager interface defines the functional interfaces provided by ActivityManager, which are inherited by subclasses.
- IActivityManager. Stub. The Proxy is provided to the client calls the remote interface, handle to get to the point to the service implementation party BinderProxy, then sent via transact method specific request.
- The iActivityManager. Stub is a server-side implementation with only one onTransact method that connects to each request based on the requested code and directly calls the corresponding method in ActivityManagerService.
- ActivityManagerService is the true implementation of the server-side interface functionality.
As you can see, this structure is basically the same as the Binder C++ layer SurfaceFlinger service in 4.4.4 summary. For Android application developers, system of these classes is enclosed, so we will not contact directly to the above a few classes, but using Android. The app. The ActivityManager interface. How does the interface in ActivityManager relate to the above implementation? Let’s take a look at one of these methods:
/*frameworks/base/core/java/android/app/ActivityManager.java*/
public List<RunningAppProcessInfo> getRunningAppProcesses() {
try {
return getService().getRunningAppProcesses();
} catch (RemoteException e) {
throwe.rethrowFromSystemServer(); }}Copy the code
The implementation of this method calls the method in getService(), so let’s see what getService() returns.
/*frameworks/base/core/java/android/app/ActivityManager.java*/
@UnsupportedAppUsage
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
@UnsupportedAppUsage
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
// 1. Access the ServiceManager and query the IBinder object of the remote service based on the service name Activity
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
// 2. Call asInterface to convert the IBinder object into the IActivityManager interface object
final IActivityManager am = IActivityManager.Stub.asInterface(b);
returnam; }};Copy the code
Here is actually the first through the IBinder b = ServiceManager. GetService (” activity “); Use ServiceManager to query the remote Service agent Binder objects that have been obtained from ActivityManager. (This AMS core service is registered with ServiceManager Binder during system startup. And the service name is “activity”); Let’s look at the implementation of asInterface(b) :
/*gen/android/app/IActivityManager.java*/
public static android.app.IActivityManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin! =null)&&(iin instanceof android.app.IActivityManager))) {
return ((android.app.IActivityManager)iin);
}
return new android.app.IActivityManager.Stub.Proxy(obj);
}
Copy the code
First determined by any local queryLocalInterface Binder, if any direct return, or to create an android. The app. IActivityManager. Stub. The Proxy client Proxy objects.
6.4 AIDL mechanism
Android app developers should be familiar with AIDL. AIDL stands for Android Interface Definition Language and is a mechanism provided by the Android SDK. With this mechanism, applications can provide cross-process services for other applications to use. Aidl files are defined using the syntax of the Java language. Each. Aidl file can contain only one interface and all of its method declarations. Here is an example of an AIDL file:
// IMyAidlInterface.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
void add(int a, long b);
void sum(in int[] numbers);
}
Copy the code
This file contains the two interfaces add and sum. For projects that contain. Aidl files, the Android IDE generates Java files for the AIDL files when compiling the project. The structure contained in the Java file compiled against the above AIDL file is shown below:
In the generated Java file, there are:
- Public interface definition: an interface named IMyAildInterface that inherits from Android.os. IInterface and includes the interface methods add and sum that we declared in aiDL.
- Binder server implementation encapsulation: IMyAildInterface contains a static inner class named Stub, which is an abstract class that inherits from Android.os. Binder and implements the IMyAildInterface interface. This class contains an onTransact method.
- Binder client implements encapsulation: Stub contains a static internal class named Proxy, which also implements IMyAildInterface. The Proxy class holds the mRemote remote service Proxy object of android.os.IBinder type. Call its Transact method for Binder access.
It can also be seen from this process that AIDL is the embodiment of the typical agent design mode. In essence, THE Binder logic of Java layer is automatically simplified and encapsulated during compilation, saving application developers from writing common and tedious code related to the implementation of the agent mode.
7 summary
At this point, we have basically analyzed the entire communication mechanism between Binder processes from the bottom up, which is one of the most complex modules in Android and the cornerstone of the entire system. It is recommended that every reader aspiring to become an Android development expert take a good look at this chapter. Finally, the chapter ends with a data transfer architecture diagram drawn by industry leaders for the Binder IPC mechanism.
Refer to the article
Understanding Android Binder mechanisms (1/3) : Binder Drivers Paul.pub/Android-bin…
Binder: Why Did Android use Binder for IPC? zhuanlan.zhihu.com/p/116440847
Thoroughly understand the Android Binder communication architecture gityuan.com/2016/09/04/…