Binder Analysis – Overall overview
Binder Analysis — AMS registration Process
Binder analysis — AMS acquisition process
Binder mechanism is the underlying principle for implementing IPC in Android and is usually only used in system source code
We Android developers usually do application-layer development, and when we want to do IPC, Binder mechanisms are too cumbersome to use directly
So Google came up with AIDL for us
The Android Interface Definition Language (AIDL) is a Binder mechanism that encapsulates the application layer and enables IPC communication
This blog will explain the AIDL invocation process
(The following analysis is Android 6.0 source code)
How does the APP process send data to AMS?
Immediately after the last blog, the APP process AIDL called AMS Service binding Service as an example for analysis
// frameworks/base/core/java/android/app/ContextImpl.java
private boolean bindServiceCommon(Intent service, ServiceConnection conn,
int flags, UserHandle user
){...int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, getOpPackageName(), user.getIdentifier());
if (res < 0)
{
throw new SecurityException(
"Not allowed to bind to service " + service);
}
returnres ! =0; . }Copy the code
We’ve learned through on a blog, eventually ActivityManagerNative. GetDefault () function returns a ActivityManagerProxy object
// frameworks/base/core/java/android/app/ActivityManagerNative$ActivityManagerProxy.java
public int bindService(IApplicationThread caller, IBinder token,
Intent service, String resolvedType, IServiceConnection connection,
int flags, String callingPackage, int userId
){
/ / create the data
Parcel data = Parcel.obtain();
/ / create the reply
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
// Write ApplicationThread to datadata.writeStrongBinder(caller ! =null ? caller.asBinder() : null);
data.writeStrongBinder(token);
service.writeToParcel(data, 0);
data.writeString(resolvedType);
// Write ServiceConnection to data
data.writeStrongBinder(connection.asBinder());
data.writeInt(flags);
data.writeString(callingPackage);
data.writeInt(userId);
// mRemote = BinderProxy, equivalent to calling binderproxy.transact
mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
data.recycle();
reply.recycle();
return res;
}
Copy the code
The next call stack is as follows:
BinderProxy.transact()
–> BpBinder::transact()
–> IPCThreadState::transact()
–> IPCThreadState::waitForResponse()
–> IPCThreadState::talkWithDriver()
–> binder_ioctl_write_read()
–> binder_thread_write()
–> binder_transaction()
–> binder_thread_read()
Most of them were covered in the second blog post and won’t be repeated here
portal
The call flow is the same except for the data in mOut
Eventually the APP thread will be suspended, waiting for the AMS process to return data and wake up
How is the AMS process started?
The AMS service runs in the system_server Process, which is a Java layer Process and is created through the process.start () function
Using the process.start () function, the Zygote Process is sent a socket message to create the Process
Zygote receives the message and calls the zygote.forkandWte () function to fork the new process
Through the Process. The start () function creates a new Process, at the time of initialization is called RuntimeInit. NativeZygoteInit () function, the function through JNI mapping, The onZygoteInit function in app_main.cpp is eventually called
onZygoteInit()
// frameworks/base/cmds/app_process/app_main.cpp
virtual void onZygoteInit(a)
{
sp<ProcessState> proc = ProcessState::self(a);ALOGV("App process: starting thread pool.\n");
proc->startThreadPool(a); }Copy the code
The ProcessState::self() function, described in the previous blog, creates the ProcessState object (singleton), opens the Binder driver, and maps a portion of the kernel’s address space as a buffer through Mmap
ProcessState::startThreadPool()
// frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::startThreadPool(a)
{
AutoMutex _l(mLock); // Concurrent lock, multithreading synchronization
if(! mThreadPoolStarted) { mThreadPoolStarted =true;
spawnPooledThread(true); }}Copy the code
When the Binder device is started, the Binder thread pool is started and mThreadPoolStarted = true is set
The mThreadPoolStarted variable is used to ensure that only one Binder thread pool is started per application process
ProcessState::spawnPooledThread()
// frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted)
{
// Get the Binder thread name in the format Binder_x, where x is an integer;
// The x in each process starts at 1 and increases, so Binder_1 is the main thread
String8 name = makeBinderThreadName(a);ALOGV("Spawning new pooled thread, name=%s\n", name.string());
// Create a Binder thread
sp<Thread> t = new PoolThread(isMain);
t->run(name.string()); }}Copy the code
ProcessState: : spawnPooledThread used to create the Binder thread, through parameter isMain is created to distinguish the main thread or common threads
The Binder main thread (isMain = true) is created. Binder normal threads are created by the Binder driver
On the Binder thread name, only by ProcessState: : spawnPooledThread () function creates a thread to conform to the format, For direct current thread through IPCThreadState: : joinThreadPool () function to join the thread pool threads are not conform to the naming format
Binder:< PID >_x format has been changed since Android 7.0 to Binder:< PID >_x format. Binder thread name pid field can be used to quickly locate the process that the Binder thread belongs to
PoolThread::run()
// frameworks/native/libs/binder/ProcessState.cpp
class PoolThread : public Thread
{
public:
PoolThread(bool isMain)
: mIsMain(isMain)
{
}
protected:
virtual bool threadLoop(a)
{
IPCThreadState::self() - >joinThreadPool(mIsMain);
return false;
}
const bool mIsMain;
};
// system/core/include/utils/Thread.h
class Thread : virtual public RefBase
{
public:...virtual status_t run(const char* name = 0.int32_t priority = PRIORITY_DEFAULT,
size_t stack = 0); . }Copy the code
PoolThread inherits the Thread class, which is essentially a Thread, t->run(name.string()); This line calls the threadLoop() function in PoolThread
IPCThreadState::joinThreadPool()
// frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{...// if isMain = true, write mOut to BC_ENTER_LOOPER
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
// Set the current Binder thread to the foreground thread
set_sched_policy(mMyThreadId, SP_FOREGROUND);
status_t result;
do{...// Handle instructions given by Binder drivers
result = getAndExecuteCommand(a); .// If the thread is no longer needed and it is not the main thread, let it exit the thread pool
if(result == TIMED_OUT && ! isMain) {break; }}while(result ! = -ECONNREFUSED && result ! = -EBADF); . }Copy the code
IPCThreadState::getAndExecuteCommand()
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::getAndExecuteCommand(a)
{
status_t result;
int32_t cmd;
// Communicate with Binder drivers
result = talkWithDriver(a); . }Copy the code
IPCThreadState: : talkWithDriver blog before () function has been introduced, the main is to build binder_write_read structure, through the system call to the Binder driven binder_ioctl () function, into the kernel
It is worth noting that IPCThreadState: : talkWithDriver () function parameter’s default value is true, That is, calling Binder drivers with this function fires both binder_thread_write() and binder_thread_read() by default
binder_thread_write()
// kernel/drivers/staging/android/binder.c
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, // Start address of mOut
size_t size, // The length of mOut
binder_size_t *consumed
){
uint32_t cmd;
struct binder_context *context = proc->context;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
// A pointer to the starting address of mOut
void __user *ptr = buffer + *consumed;
// A pointer to the starting address of mOut
void __user *end = buffer + size;
// Iterate over the mOut memory pointer to fetch the instruction
while (ptr < end && thread->return_error == BR_OK)
{
// Read instructions from mOut, BC_ENTER_LOOPER
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
// Move the pointer back
ptr += sizeof(uint32_t);
switch (cmd)
{
......
case BC_ENTER_LOOPER:
......
// Add BINDER_LOOPER_STATE_ENTERED to the looper flag bit for Binder threads
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
break; . }... }return 0;
}
Copy the code
binder_thread_read()
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed, int non_block
){
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
// a pointer to the starting address of mIn memory
void __user *ptr = buffer + *consumed;
// Pointer to the end address of mIn memory
void __user *end = buffer + size;
int ret = 0;
int wait_for_proc_work;
// Now consumed = 0, if matches
if (*consumed == 0)
{
// Add the directive BR_NOOP to readbuf
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
// Move the pointer back
ptr += sizeof(uint32_t);
}
Thread ->todo = NULL, thread->transaction_stack = NULL,
// wait_for_proc_work = true
wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo); . thread->looper |= BINDER_LOOPER_STATE_WAITING;if (wait_for_proc_work)
// The number of Binder threads available to the current process +1proc->ready_threads++; .if (wait_for_proc_work)
{
......
// Filp ->f_flags & O_NONBLOCK is already assigned
// non_block > 0, else
if (non_block)
{
......
}
else
Thread ->todo = NULL, binder_has_proc_work returns false,
// Suspend the Binder thread
ret = wait_event_freezable_exclusive(proc->wait,
binder_has_proc_work(proc, thread));
}
else{... }... }Copy the code
At this point, the Binder main thread is suspended and waits for another process to communicate with the AMS process to wake it up
The Binder main thread resumes execution of the unfinished binder_thread_read function here
The structure of mIn is as follows
What does the AMS process do when it wakes up?
When the APP process sends data to AMS, the Binder main thread in the AMS process wakes up and continues with the unfinished binder_thread_read function
binder_thread_read()
// kernel\drivers\staging\android\binder.c
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed, int non_block)
{...if (wait_for_proc_work)
// Number of Binder threads available to the current process -1proc->ready_threads--; thread->looper &= ~BINDER_LOOPER_STATE_WAITING; .while (1)
{
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL; .// Add a binder_transaction to the binder_transaction function, proc->todo is not null
else if (!list_empty(&proc->todo) && wait_for_proc_work)
{
// Fetch binder_transaction, but of type binder_work
w = list_first_entry(&proc->todo, struct binder_work, entry); }...W ->type is BINDER_WORK_TRANSACTION
switch (w->type)
{
......
case BINDER_WORK_TRANSACTION:
{
// Type conversion (binder_work -> binder_transaction)
t = container_of(w, struct binder_transaction, work);
} break; . }// t! = null, if not hit
if(! t)continue; .T ->buffer->target_node = AMS, not null, if hit
if (t->buffer->target_node)
{
struct binder_node *target_node =t->buffer->target_node; tr.target.ptr = target_node->ptr; tr.cookie = target_node->cookie; .// The instruction is changed to BR_TRANSACTION
cmd = BR_TRANSACTION;
}
else{... }// t->code = BIND_SERVICE_TRANSACTION
tr.code = t->code;
// t->flags = 0
tr.flags = t->flags;
// if t->from not NULL, if hit
if (t->from)
{
struct task_struct *sender = t->from->proc->tsk;
// Get the PID of APP thread and record it
tr.sender_pid = task_tgid_nr_ns(sender, task_active_pid_ns(current));
}
else{... }// Record the memory address and size of the data sent by the APP process
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
tr.data.ptr.buffer = (binder_uintptr_t) ((uintptr_t)t->buffer->data +
proc->user_buffer_offset);
tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size,
sizeof(void *));
// Copy the instruction BR_TRANSACTION to user space (mIn)
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
// Move the pointer back
ptr += sizeof(uint32_t);
// Copy mOut data to user space (mIn)
if (copy_to_user(ptr, &tr, sizeof(tr)))
return -EFAULT;
// Move the pointer back
ptr += sizeof(tr); .// If hits
if(cmd == BR_TRANSACTION && ! (t->flags & TF_ONE_WAY)) { t->to_parent = thread->transaction_stack; t->to_thread = thread;// Save t in transaction_stack of AMS main thread
thread->transaction_stack = t;
}
else{... }// Break the loop
break; . } done: *consumed = ptr - buffer;Proc ->requested_threads = proc->ready_threads = 0
// proc->requested_threads_started = 0 < proc->max_threads (default: 15),
Thread ->looper = BINDER_LOOPER_STATE_NEED_RETURN + BINDER_LOOPER_STATE_ENTERED,
// If the condition is met, if is hit
if (proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))
{
......
// The number of Binder threads that the current process needs to create +1
proc->requested_threads++;
// Copy the command BR_SPAWN_LOOPER to user space (mIn)
if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
return-EFAULT; . }return 0;
}
Copy the code
When binder_thread_read () function, after the completion of execution binder_ioctl_write_read () function is executed, returns to the IPCThreadState: : getAndExecuteCommand () function, to continue
The structure of mIn is as follows
IPCThreadState::getAndExecuteCommand()
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::getAndExecuteCommand(a)
{
status_t result;
int32_t cmd;
// execute the return
result = talkWithDriver(a);if (result >= NO_ERROR)
{
// The length of data in mIn
size_t IN = mIn.dataAvail(a);if (IN < sizeof(int32_t)) return result;
// Read the instructions in mIn
cmd = mIn.readInt32(a); .// Execute the instructions given by the Binder driver
result = executeCommand(cmd); .// Set the current Binder thread to the foreground thread
set_sched_policy(mMyThreadId, SP_FOREGROUND);
}
return result;
}
Copy the code
IPCThreadState: : getAndExecuteCommand () function performs the IPCThreadState: : joinThreadPool do – while loop () function, only when the Binder threads are no longer needed, The Binder thread is removed from the Binder thread pool
Each loop, will be called IPCThreadState: : talkWithDriver () function and Binder driven communications, IPCThreadState: : talkWithDriver all the data of each time will be mIn () function to return
Inside the Parcel::readInt32() function, the location of an instruction is recorded in the mDataPos field. Each call returns the currently read instruction and increments the mDataPos field
// frameworks/native/libs/binder/Parcel.cpp
int32_t Parcel::readInt32(a) const
{
return readAligned<int32_t> (); }template<class T>
T Parcel::readAligned(a) const
{
T result;
if (readAligned(&result) ! = NO_ERROR) { result =0;
}
return result;
}
template<class T>
status_t Parcel::readAligned(T *pArg) const
{
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(T)) <= mDataSize)
{
const void* data = mData+mDataPos;
mDataPos += sizeof(T);
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
}
else
{
returnNOT_ENOUGH_DATA; }}Copy the code
IPCThreadState::executeCommand()
Order in which instructions are processed in mIn:
- BR_SPAWN_LOOPER
- BR_NOOP
- BR_TRANSACTION
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
switch ((uint32_t)cmd)
{
......
case BR_TRANSACTION: / / 3
{
binder_transaction_data tr;
// Read the data sent by APP process in mIn
result = mIn.read(&tr, sizeof(tr)); . Parcel buffer;// Write data to buffer
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer, this); . Parcel reply;status_terror; .// tr.target. PTR = weak reference to AMS kernel node
if (tr.target.ptr)
{
// Get a strong reference to the AMS kernel node
if (reinterpret_cast<RefBase::weakref_type*>(tr.target.ptr)->
attemptIncStrong(this))
{
// tr.cookie = JavaBBinder (AMS)
// Call JavaBBinder's transact function
error = reinterpret_cast<BBinder*>(tr.cookie)->
transact(tr.code, buffer, &reply, tr.flags);
// Remove strong references to AMS kernel nodes
reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
}
else{ error = UNKNOWN_TRANSACTION; }}else{... }// If oneway is used, then if hits
if ((tr.flags & TF_ONE_WAY) == 0)
{
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
if (error < NO_ERROR)
reply.setError(error);
// Immediately send reply to the APP to wake up the APP process
sendReply(reply, 0);
}
else
{
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); }... }break; .case BR_NOOP: / / 2
break;
case BR_SPAWN_LOOPER: / / 1
// Create a new Binder thread
mProcess->spawnPooledThread(false);
break; . }...return result;
}
Copy the code
-
The main thread handles this instruction (BR_SPAWN_LOOPER)
By ProcessState: : spawnPooledThread () function to create a new Binder threads
The new thread starts, will perform IPCThreadState: : joinThreadPool () function to add a new thread to Binder thread pool
In IPCThreadState: : joinThreadPool () function, will set a new thread to the foreground thread
Since The Android system uses preemptive multitasking CPU scheduling, new threads will preempt the CPU slice of the main thread to execute
-
The new thread processes the instruction (BR_NOOP)
No processing, return directly
-
A new thread processes the instruction (BR_TRANSACTION)
Receives data from the APP process
JavaBBinder inherits from BBinder, which is equivalent to calling the parent BBinder::transact() function
BBinder::transact()
// frameworks/native/libs/binder/Binder.cpp
status_t BBinder::transact(
uint32_t code, // code = BIND_SERVICE_TRANSACTION
const Parcel& data, // data = data sent by the APP process
Parcel* reply,
uint32_t flags
){
data.setDataPosition(0);
status_t err = NO_ERROR;
// code = BIND_SERVICE_TRANSACTION
switch (code)
{
case PING_TRANSACTION:
reply->writeInt32(pingBinder());
break;
default:
// Call JavaBBinder's onTransact function
err = onTransact(code, data, reply, flags);
break;
}
if(reply ! =NULL)
{
reply->setDataPosition(0);
}
return err;
}
Copy the code
JavaBBinder::onTransact()
// frameworks/base/core/jni/android_util_Binder.cpp
const char* const kBinderPathName = "android/os/Binder";
static int int_register_android_os_Binder(JNIEnv* env)
{... gBinderOffsets.mExecTransact =GetMethodIDOrDie(env, clazz, "execTransact"."(IJJI)Z"); . }virtual status_t onTransact(
uint32_t code, // code = BIND_SERVICE_TRANSACTION
const Parcel& data, // data = data sent by the APP process
Parcel* reply,
uint32_t flags = 0
){...// mObject = AMS object
. / / gBinderOffsets mExecTransact = android/OS/Binder execTransact function
// code = BIND_SERVICE_TRANSACTION
// Call the AMS execTransact function
jboolean res = env->CallBooleanMethod(mObject,
gBinderOffsets.mExecTransact,
code, reinterpret_cast<jlong>(&data),
reinterpret_cast<jlong>(reply), flags); .returnres ! = JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION; }Copy the code
AMS inherits from ActivityManagerNative, which inherits from Binder and is equivalent to calling the binder.exectransact () function of the superparent class
Binder.execTransact()
// frameworks/base/core/java/android/os/Binder.java
private boolean execTransact(int code, long dataObj, long replyObj, int flags)
{
/ / create the data
Parcel data = Parcel.obtain(dataObj);
/ / create the reply
Parcel reply = Parcel.obtain(replyObj);
booleanres; .// Call ActivityManagerNative's onTransact functionres = onTransact(code, data, reply, flags); . reply.recycle(); data.recycle(); .return res;
}
Copy the code
ActivityManagerNative.onTransact()
// frameworks/base/core/java/android/app/ActivityManagerNative.java
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
{
// code = BIND_SERVICE_TRANSACTION
switch (code)
{
......
case BIND_SERVICE_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
IBinder token = data.readStrongBinder();
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
b = data.readStrongBinder();
int fl = data.readInt();
String callingPackage = data.readString();
int userId = data.readInt();
IServiceConnection conn = IServiceConnection.Stub.asInterface(b);
// call AMS's bindService function
int res = bindService(app, token, service, resolvedType, conn,
fl, callingPackage, userId);
reply.writeNoException();
reply.writeInt(res);
return true; }... }return super.onTransact(code, data, reply, flags);
}
Copy the code
After ActivityServiceManager. BindService () function is binding Service process, here will not continue to expand
IPCThreadState::sendReply()
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{
status_t err;
status_t statusBuffer;
// Collate the data and write the instructions BC_REPLY and reply together to mOut
err = writeTransactionData(BC_REPLY, flags, - 1.0, reply, &statusBuffer);
if (err < NO_ERROR)
return err;
// Communicate with Binder drivers
return waitForResponse(NULL.NULL);
}
Copy the code
IPCThreadState: : waitForResponse blog before () function has been introduced, the main is to call IPCThreadState: : talkWithDriver communicate with BInder drive () function
When BC_REPLY is sent to the Binder driver, the Binder driver’s binder_transaction() function is called to wake up the APP process and send reply to the APP process
This part of the process is similar to the execution of the binder_send_reply() function described in the second blog post for reference
What does the APP process do when it wakes up?
When the binder_transaction function completes, the APP process wakes up and continues with the unfinished binder_thread_read function
What does the process do when it wakes up compared to the AMS process described in the second blog? This part is pretty much the same
Because our analysis is the process of APP AIDL call AMS Service binding Service process, thus finally returned to ContextImpl. BindServiceCommon () function
// frameworks/base/core/java/android/app/ContextImpl.java
private boolean bindServiceCommon(Intent service, ServiceConnection conn,
int flags, UserHandle user
){...// execute the return
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, getOpPackageName(), user.getIdentifier());
if (res < 0)
{
throw new SecurityException(
"Not allowed to bind to service " + service);
}
returnres ! =0; . }Copy the code
At this point, the entire AIDL call process ends
conclusion
(The overall flow chart of AIDL calls will be added later)