Binder drive

Binder drivers are at the heart of the Binder framework, and this section covers the details of messaging protocols, memory sharing mechanisms, and object passing

Application layer and driver messaging protocols

The Binder application layer’s IPCThreadState and Binder drivers pass data through ioctl. Therefore, some IOCtl commands are defined:

The command instructions The data format
BIDNER_WRITE_READ Data is read and written to the driver, either individually or simultaneously. Control by whether there is read or write data in the incoming data struct binder_write_read
BINDER_SET_IDLE_TIMEOUT There are definitions not used int64_t
BINDER_SET_MAX_THREADS Set the maximum number of threads in the thread pool, after which the driver will not notify the application layer to start new threads size_t
BINDER_SET_IDLE_PRIORITY There are definitions not used int
BINDER_SET_CONTEXT_MGR Set this process toThe Binder systemtheManagement process, onlyServiceManagerThe process will use this command int
BINDER_THREAD_EXIT Notifies the driver that the current thread is about to exit so that the driver can clean up data related to the thread int
BINDER_VERSION To obtainBinderThe version number of the struct binder_verison

Commands in the table. The most common command is BIDNER_WRITE_READ, and the rest are ancillary commands. BIDNER_WRITE_READ uses the following data format definition:

struct binder_write_read {
	binder_size_t		write_size;	// The length of bytes planned to write to the driver
	binder_size_t		write_consumed;	// The length of the actual write driver
	binder_uintptr_t	write_buffer;// The buffer pointer passed to the driver, that is, the actual data to be written
	binder_size_t		read_size;	// The length of bytes planned to be read from the driver
	binder_size_t		read_consumed;	// The length of bytes actually read from the driver
	binder_uintptr_t	read_buffer; // Receive a pointer to the read data
};
Copy the code

The data stored in read_buffer and write_buffer is also formatted as the message ID plus binder_transaction_data, which is defined as follows:

struct binder_transaction_data {
	union {
		__u32	handle;
		binder_uintptr_t ptr;
	} target;               // Handle for BpBinder objects and PTR for BBinder objects
	binder_uintptr_t	cookie;	// For BBinder objects, here is a pointer to BBinder
	__u32		code;		// Binder service function number
	__u32	        flags;
	pid_t		sender_pid; // Process ID of the sender
	uid_t		sender_euid; // The sender's EUID
	binder_size_t	data_size; // The size of the entire data area
	binder_size_t	offsets_size; // Size of the IPC object area
	union {
		struct {
			binder_uintptr_t	buffer; // A pointer to the beginning of the data area
			binder_uintptr_t	offsets;// Pointer to IPC to object in data area
		} ptr;
		__u8	buf[8];
	} data;
};
Copy the code

In binder_transaction_data:

  • data.ptrPoint to theThe area of data passed to the driverStart address of
    • The area of data passed to the driverIt’s passed down from the application layerParcel objectThe data area
  • inParcel objectIf packedThe IPC object(i.e.Binder object), orFile descriptor, there will be a space in the data area dedicated to this part of the data.
    • So usingdata.ptr.offsetsPresentation data areaThe IPC objectThe starting position of
    • withoffsets_sizePresentation data areaThe IPC objectThe size of the

Binder messages are divided into two sets according to the sent and received, and are easily distinguished by their names:

  • Send typeRefers to the message fromThe application layertodriveThe process, usually byBC_At the beginning
  • Receive typeRefers to the message fromdrivetoThe application layerThe process, usually byBR_At the beginning

List of Binder commands sent to the driver

The command instructions
BC_TRANSACTION sendBinder callThe data of
BC_REPLY returnBinder callThe return value of the
BC_ACQUIRE_RESULT In response toBR_ATTEMPT_ACQUIREThe command
BC_FREE_BUFFER Released bymmapAllocated memory block
BC_INCREFS increaseBinder objectWeak reference count of
BC_ACQUIRE increaseBinder objectStrong reference count of
BC_DECREFS To reduceBinder objectWeak reference count of
BC_RELEASE To reduceBinder objectStrong reference count of
BC_INCREFS_DONE In response toBC_INCREFSinstruction
BC_ACQUIRE_DONE In response toBC_ACQUIREinstruction
BC_ATTEMPT_ACQUIRE Upgrade weak references to Binder objects to strong references
BC_REGISTER_LOOPER Registers the current thread as a thread poolThe main thread
BC_ENTER_LOOPER Notification drivers, threads can enter the sending and receiving of data
BC_EXIT_LOOPER Notification driver, the current thread exits sending and receiving data
BC_REQUEST_DEATH_NOTYFICATION Notifies the driver to receive aBinder serviceDeath notice
BC_CLEAR_DEATH_NOTYFICATION Notification driver no longer receives aBinder serviceDeath notice
BC_DEAD_BINDER_DONE In response toBR_DEAD_BINDERThe command

The list of commands returned by the driver

The command instructions
BR_ERROR There was an internal error in the drive
BR_OK Command successful
BR_TRANSACTION Binder call command
BR_REPLY Returns the result of the Binder call
BR_ACQUIRE_RESULT Don’t use
BR_DEAD_REPLY Send to driverBinder callIf the other party is dead, the driver responds to this command
BR_TRANSACTION_COMPLETE In response toBR_TRANSACTIONThe command
BR_INCREFS Ask for an increase inBinder objectWeak reference count of
BR_ACQUIRE Ask for an increase inBinder objectStrong reference count of
BR_DECREFS To reduceBinder objectWeak reference count of
BR_RELEASE To reduceBinder objectStrong reference count of
BR_ATTEMPT_ACQUIRE Requirements will beBinder objectBecomes a strong reference
BR_NOOP Command successful
BR_SPAWN_LOOPER Notice to createBinder thread
BR_FINISHED Don’t use
BR_DEAD_BINDER Notice concernedBinderHave died
BR_CLEAR_DEATH_NOTYFICATION_DONE In response toBC_CLEAR_DEATH_NOTYFICATION
BR_FAILED_REPLY ifBinder calltheFunction,Incorrect reply to this message

Binder message sequence diagrams

The two commands listed above seem quite large, but can actually be divided into four categories:

  • Binder threadManagement related
  • Binder method callrelated
  • Binder object reference count managementrelated
  • Binder object death notificationrelated

Take a look at the following two sequence diagrams:

Binder callMessage sequence diagram of

Binder object reference count managementMessage sequence diagram of

Binder driveAnalysis of the

The operator functions defined in Binder drivers are as follows:

static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};
Copy the code

Common read and write operations are not implemented in Binder drivers. Data transfer is done through iocTL operations.

Binder drivers define a number of data structures. Some of the main data structures are as follows:

The data structure instructions
binder_proc Each useopenOpen theBinder Equipment documentationThe process creates one in the driverThe biner_procStructure to record various information and states about the process. Such as:Binder node table,Node reference tableEtc.
binder_thread eachBinder threadinBinder driveOne of thembinder_threadStructure that records thread-specific information, such as tasks to be completed, etc
binder_node binder_procThere are aBinder Object list, the table isbinder_nodeStructure. Represents the BBinder object in the process, records the BBinder object pointer, reference count and other data
binder_ref binder_procAnd there’s another oneNode reference table, the table isbinder_refStructure. On behalf of processBpBinder objectTo save the referenced objectbinder_nodePointer.BpBinderIn themHandlerValue is its position in the index table
binder_buffer Driven bymmapWay to create a large cache each timeBinderWhen transmitting data, one is allocated in the cachebinder_bufferTo store data

In Binder drivers, red-black trees are used extensively to manage data. Binder drivers are implementations provided by the kernel that represent a global variable of life binder_procs:

static HLIST_HEAD(binder_procs);
Copy the code

The binder_PROC structure for all processes will be inserted into the red-black tree represented by this variable. Take the code for binder_open:

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);// Allocate space to the binder_proc structure
	/ /...
	get_task_struct(current->group_leader);// Get the task structure of the current process
	proc->tsk = current->group_leader;
	/ /...
	INIT_LIST_HEAD(&proc->todo);// Initialize the toDO queue in binder_porc
	/ /...
	filp->private_data = proc;// Saves the proc to a file structure for the next call
	mutex_lock(&binder_procs_lock);
	// Insert proc into the global variable binder_procs
	hlist_add_head(&proc->proc_node, &binder_procs);
	mutex_unlock(&binder_procs_lock);
	/ /...
	return 0;
}
Copy the code

The binder_open function does the following:

  • Open theBinder driveDevice file of
  • Creates and initializes the current processbinder_procThe structure of the bodyproc
  • willprocInsert into the red black treebinder_procsIn the
  • willprocInto thefileThe structure of theprivate_dataIn the field
    • Other operations of the driver can be called fromfileStructure that represents the current processbinder_proc

Binder’s memory sharing mechanism

As described earlier, when a user process opens a Binder device, mmap is called to create a memory space in the driver to receive the Binder data passed to the user process. Let’s take a look at the mmap code (this part is 9.0 source code, and the structure is a little different from the book) :

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	if(proc->tsk ! = current->group_leader)return -EINVAL;
	// Check whether the required space allocation is greater than SZ_4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
    // Check the mmap flags, do not have FORBIDDEN_MMAP_FLAGS
	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
		ret = -EPERM;
		failure_string = "bad vm_flags";
		goto err_bad_arg;
	}
	// the child process of fork cannot copy the mapping space and is not allowed to modify attributes
	vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
	vma->vm_flags &= ~VM_MAYWRITE;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;
	// The actual memory allocation logic is in binder_alloc_mmap_handler
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
	/ /...
}
int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma)
{
	int ret;
	struct vm_struct *area;
	const char *failure_string;
	struct binder_buffer *buffer;

	mutex_lock(&binder_alloc_mmap_lock);
	// Goto error if cache has been allocated
	if (alloc->buffer) {
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}
	// Allocate a buffer to the user process
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_ALLOC);
    // If memory allocation fails, goto corresponds to an error
	if (area == NULL) {
		ret = -ENOMEM;
		failure_string = "get_vm_area";
		goto err_get_vm_area_failed;
	}
	// Place the allocated buffer pointer in binder_proc's buffer field
	alloc->buffer = area->addr;
	// Reconfigure the memory start address and offset
	alloc->user_buffer_offset =
		vma->vm_start - (uintptr_t)alloc->buffer;
	mutex_unlock(&binder_alloc_mmap_lock);
	// Create a physical page structure
	alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
				   ((vma->vm_end - vma->vm_start) / PAGE_SIZE),
			       GFP_KERNEL);
			       
	alloc->buffer_size = vma->vm_end - vma->vm_start;
	// Allocate memory space for struct buffers in the kernel
	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);

	// This part is quite different from the book
	// Only the memory address is associated
	buffer->data = alloc->buffer;
	list_add(&buffer->entry, &alloc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(alloc, buffer);
	// Asynchronous transfer sets the size of the available space to half the size of the map
	alloc->free_async_space = alloc->buffer_size / 2;
	barrier(a); alloc->vma = vma; alloc->vma_vm_mm = vma->vm_mm;/* Same as mmgrab() in later kernel versions */
	atomic_inc(&alloc->vma_vm_mm->mm_count);

	return 0;
/ /... Corresponding error message
}
Copy the code

Memory allocation:

  • First callget_vm_areainThe user processAllocate an address space
  • Then, inThe kernelThe same amount of page space is allocated in
  • And then put themPhysical memoryAddresses are bound together
  • suchapplicationandThe kernelWe’ll be able to share a space

When a Binder call occurs:

  • The data fromThe calling processCopied to theThe kernel spaceIn the
  • Driving inService processtheThe bufferTo find a space of the right size to store data
    • becauseService processtheThe user spacetheThe bufferandThe kernel spacetheThe bufferIs Shared
    • soService processThere is no need to reconvert data fromThe kernel spaceCopied to theThe user space
    • Saves a copy process
    • To improve theBinder communicationThe efficiency of

drivenioctloperation

Ioctl implementation is in the driver binder_ioctl function, which is used to handle ioctl operation command, the command is the most common BINDER_WRITE_READ, used in the Binder calls, first look at the command processes:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	
	// Check if binder_stop_on_user_error is less than 2, which represents the number of errors in Binder
	// If the value is greater than 2, suspend the current process to the binder_user_error_wait wait queue
	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret)
		goto err_unlocked;
		
	// Get the data structure of the current thread
	thread = binder_get_thread(proc);
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_WRITE_READ:
	    // Call the binder_ioctl_write_read method
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		break;
}
static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
	struct binder_proc *proc = filp->private_data;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	struct binder_write_read bwr;

	if(size ! =sizeof(struct binder_write_read)) {
		ret = -EINVAL;
		goto out;
	}
	// Copy the data into the kernel
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	if (bwr.write_size > 0) {
	    // Call binder_thread_write if the command is passed to the driver
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		// Omit exception handling and log printing
	}
	if (bwr.read_size > 0) {
	    // If the command reads the driver's data, call binder_thread_read
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		trace_binder_read_done(ret);
		binder_inner_proc_lock(proc);
		// If the toDO queue of the process is not empty, wake up the user process
		if (!binder_worklist_empty_ilocked(&proc->todo))
			binder_wakeup_proc_ilocked(proc);
		binder_inner_proc_unlock(proc);
		// Omit exception handling and log printing
	}
// Exception handling
}
Copy the code

The BINDER_WRITE_READ command may write data to or read data from the driver

  • Write data, passbinder_thread_writemethods
  • Read data, passbinder_thread_readmethods
  • In the next sectionCall the processWe’ll talk about these two methods in detail

In addition to the BINDER_WRITE_READ command, there are other communication AIDS such as BINDER_SET_MAX_THREADS and BINDER_SET_CONTEXT_MGR.

The focus is on the data read and write part of the call

Binder callprocess

Binder invocation is the most central activity in Binder drives, involving almost all data structures driven by Binder. Let’s first look at the overall flow of the call:

  • Binder callfromThe client processIn a
    • throughioctlOperation to rundrivethebinder_ioctl
  • driveReceived commandBC_TRANSACTIONafter
    • Find a representativeService processthebinder_procThe structure of the body
    • inService processThe buffer allocates a block of memory to hold data fromThe client processData passed
    • ioctlA reply messageBR_TRANSACTION_COMPLETED
  • ——— to here, this timeioctlEnd ———
  • The client processAfter receiving the reply message
    • Call againioctlWait, ready to read the data, until the call results return

The server process has no way of knowing when a Binder call will arrive, so it has at least one thread waiting on the IOCtl.

  • driveWakes up the waiting thread when a call arrives and passes the dataService process
  • There may be more than one at a timeBinder callarrive
    • driveCreate one for each invocationbinder_transactionThe structure of the body
    • willThe structure of the bodyIn theThe work fieldInserted into theService processtheTodo queueIn the
      • Todo queueIs abinder_workList of structures
      • Each process will have oneTodo queueTo receive the work that needs to be done
      • whenThe service sidetheThe user processrightioctlWhen performing read operations
        • It will run in a loopTodo queueTasks that need to be completed in
        • Wait until the queue is empty

The call flow of the client process

Binder calls to binder_thread_write (binder_thread_write, binder_thread_write, binder_thread_write, binder_thread_write, binder_thread_write, binder_thread_write, binder_thread_write, binder_thread_write)

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed)
{
/ /...
        case BC_TRANSACTION:
		case BC_REPLY: {
			struct binder_transaction_data tr;
			// Copy data from the package structure tr to the kernel
			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			ptr += sizeof(tr);
			// Call the binder_transaction function
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);
			break;
		}
/ /...
}
Copy the code

The BC_TRANSACTION command is processed by calling the binder_transaction function, which processes both BC_TRANSACTION and BC_REPLY messages. Reply =false:

  1. Find the node that represents the target process:
        if (tr->target.handle) {
			struct binder_ref *ref;
			// Find the reference to handle
			ref = binder_get_ref_olocked(proc, tr->target.handle,
						     true);
			if (ref) {
			    // The node field of the reference holds Pointers to the Binder object nodes
			    // Copy to target_node
				target_node = binder_get_node_refs_for_txn(
						ref->node, &target_proc,
						&return_error);
			} else {
                / /...
			}
		/ /...
		} else {
		    // Put the admin process node name in target_node
			target_node = context->binder_context_mgr_node;
			/ /...
		}
Copy the code
  1. Search target thread
		if(! (tr->flags & TF_ONE_WAY) && thread->transaction_stack) {struct binder_transaction *tmp;
			tmp = thread->transaction_stack;
			/ /...
			while (tmp) {
				struct binder_thread *from;
				spin_lock(&tmp->lock);
				from = tmp->from;
				if (from && from->proc == target_proc) {
					atomic_inc(&from->tmp_ref);
					target_thread = from;
					spin_unlock(&tmp->lock);
					break;
				}
				spin_unlock(&tmp->lock); tmp = tmp->from_parent; }}Copy the code

The logic of this code is as follows:

  • If the call is not asynchronous and the caller is in the threadtransaction_stackDon’t forNULL, looks for the process that has the same target as this calltransaction_stackIf found, its target thread is set to the target thread of this call.
  • transaction_stackBinder is a list of all Binder calls that are being made by the threadbinder_transactionStructure.
  • Binder driveusingbinder_transactionTo save it onceBinder callAll data, including transmitted data, communication process, thread information, etc. Because Binder calls involve two processes, return values are passed to the caller.
  • So, drive with structurebinder_transactionIt’s not over yetBinder call.
  • Usually executedBinder callThe same process does not existBinder call, sotarget_threadThe values of are mostlyNULL
  1. If there is a target threadThe target threadIn theTodo queue, otherwise useThe target processtheTodo queue
	if (target_thread)
		e->to_thread = target_thread->pid;
	e->to_proc = target_proc->pid;
Copy the code
  1. For the currentBinder callcreatebinder_transactionStructure and populate it with the data from the call.
	t = kzalloc(sizeof(*t), GFP_KERNEL);
	/ /...
	tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
	/ /...
	if(! reply && ! (tr->flags & TF_ONE_WAY)) t->from = thread;else
		t->from = NULL;
	t->sender_euid = task_euid(proc->tsk);
	t->to_proc = target_proc;
	t->to_thread = target_thread;
	t->code = tr->code;
	t->flags = tr->flags;
	if(! (t->flags & TF_ONE_WAY) &&binder_supported_policy(current->policy)) {
		/* Inherit supported policies for synchronous transactions */
		t->priority.sched_policy = current->policy;
		t->priority.prio = current->normal_prio;
	} else {
		/* Otherwise, fall back to the default priority */
		t->priority = target_proc->default_priority;
	}
Copy the code
  1. inThe target process(i.e.The service side) buffer allocation space, copyThe user processThe data to theThe kernel
    t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, tr->offsets_size, extra_buffers_size, ! reply && (t->flags & TF_ONE_WAY));/ /...
	if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
			   tr->data.ptr.buffer, tr->data_size)) {
		/ /...
	}
	if (copy_from_user(offp, (const void __user *)(uintptr_t)
			   tr->data.ptr.offsets, tr->offsets_size)) {
		/ /...
	}
Copy the code
  • The data copied here isBinder callThe parameter data is also passed toService processThe data is therefore buffered. This buffer is in theThe target processIs allocated in a large buffer
  1. Processing in transitBinder object
off_end = (void *)off_start + tr->offsets_size;
	sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
	sg_buf_end = sg_bufp + extra_buffers_size;
	off_min = 0;
	for (; offp < off_end; offp++) {
		/ /...
	}
Copy the code
  • Passed by parameterBinder objectNeed to beconversionHere,The for loopIs to do the conversion operation, more content, will be belowHandle the passed Binder objectsChapter to explain
  1. Will this callbinder_transactionThe structure links to the thread’sbinder_stackIn the list
if(! (t->flags & TF_ONE_WAY)) {/ /...
		t->need_reply = 1;
		t->from_parent = thread->transaction_stack;
		thread->transaction_stack = t;
		// The code in the comments below is already integrated into the binder_proc_transaction function
		// First, place binder_work in the structure binder_transaction on the toDO queue of the target target process or thread
		// Then create a new structure for binder_work and place it on the toDO queue of the sending thread
		if (!binder_proc_transaction(t, target_proc, target_thread)) {
		    / /... Failure to deal with}}Copy the code
  • binder_transactionThe structure contains onebinder_workSo it can be put intoTodo queueIn the
  • When called locallybinder_workthetypeIs set toBINDER_WORK_TRANSACTIONAfter, insertThe target processorThe target threadtheTodo queueIn the
  • In order to make theThe client processIf I get a reply, I’ll create a new one herebinder_workOf the structure and put ittypeSet up aBINDER_WORK_TRANSAXTION_COMPLETETAnd inserts it into the current thread’sTodo queueIn the

As mentioned earlier, the binder_thread_read function is also checked after the execution of the binder_thread_write function:

  • Because the calling end is doneBC_TRANSACTIONAfter, it will be executed immediatelyioctlthereadinstruction
  • So, on the call operation,binder_thread_readThe delta function sort of followsbinder_thread_writeFunction is executed after

The new binder_work structure has been inserted into the toDO queue. Let’s see what the binder_thread_read function does with the toDO queue:

while (1) {
        / /...
        // Obtain the TODO queue, and if it fails, goto retry
		if (!binder_worklist_empty_ilocked(&thread->todo))
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			list = &proc->todo;
		else {
			binder_inner_proc_unlock(proc);
			/* no data added */
			if (ptr - buffer == 4 && !thread->looper_need_return)
				goto retry;
			break;
		}
		// Fetch the element from the todo queue
		w = binder_dequeue_work_head_ilocked(list);
		/ /...
		switch (w->type) {
		    / /... Omit a lot of case statements
		    case BINDER_WORK_TRANSACTION_COMPLETE: {
			    binder_inner_proc_unlock(proc);
			    cmd = BR_TRANSACTION_COMPLETE;
			    Put_user puts the return message in a pointer to user space
			    if (put_user(cmd, (uint32_t __user *)ptr)) 
				    return -EFAULT;
			    ptr += sizeof(uint32_t);

			    binder_stat_br(proc, thread, cmd);

			    kfree(w);
			    binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
		    } break;
		    / /... Omit a lot of case statements
	    }
		/ /...
}
Copy the code
  • binder_thread_readAll the function does is send the reply messageBR_TRANSACTION_COMPLETECopy to user space
  • suchThe client processYou can receive a reply message

At this point, the call to the client process is complete. But Binder calls are only halfway through, so let’s look at how the server process calls the data.

Service processCall flow of

The server process has at least one thread waiting on the IOCtl for the call to arrive. The binder_thread_read function is also called when the server process calls ioctl, so the binder_thread_read function is also called.

  1. If there is no data in the buffer holding the returned result, write firstBR_NOOPMessage:
    if (*consumed == 0) {
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
	}
Copy the code
  1. Enter the loop to process allTodo queueIn the work
while (1) {
    / /...
}
Copy the code
  1. Reads the thread or processTodo queueNeeds to be completed inwork
        struct binder_work *w = NULL;
        / /...
		if (!binder_worklist_empty_ilocked(&thread->todo))
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			list = &proc->todo;
		/ /...
		w = binder_dequeue_work_head_ilocked(list);
Copy the code
  1. withA switch statementHandles all types ofwork
    switch (w->type) {
		case BINDER_WORK_TRANSACTION: {
			binder_inner_proc_unlock(proc);
			t = container_of(w, struct binder_transaction, work);
		} break;
    }
Copy the code
  • afterThe call flow of the client processAfter, at this timeService processThere already exists a type ofBINDER_WORK_TRANSACTIONWork needs to be done
  • I’m just taking out the sumbinder_workThe associatedbinder_transactionStructure pointer
  • And save to a variabletIn the
  1. Adjust the priority of the thread
        if(! t)continue;

		if (t->buffer->target_node) {
			struct binder_node *target_node = t->buffer->target_node;
			struct binder_priority node_prio;

			tr.target.ptr = target_node->ptr;
			tr.cookie =  target_node->cookie;
			node_prio.sched_policy = target_node->sched_policy;
			node_prio.prio = target_node->min_priority;
			binder_transaction_priority(current, t, node_prio,
						    target_node->inherit_rt);
			cmd = BR_TRANSACTION;
		}
Copy the code
  • If t is NULL, continue the loop
  • Otherwise, start preparing the returned messageBR_TRANSACTION
  • Also, set the priority of the thread
    • ifThe calling threadThe priority of theThe current threadThe specifiedLowest priority, then theThe current threadSet the priority ofThe calling threadThe priority of the
    • Otherwise, theThe current threadSet to the specifiedLowest priority
    • This means thatBinder threadRuns at the lowest possible priority
  1. Prepare the returned data
        tr.code = t->code;
		tr.flags = t->flags;
		tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);
		/ /...
		// Let the data pointer in tr point to the data buffer stored in the kernel
		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 +
			binder_alloc_get_user_buffer_offset(&proc->alloc));
		tr.data.ptr.offsets = tr.data.ptr.buffer +
					ALIGN(t->buffer->data_size,
					    sizeof(void *));
		// Copy the BR_TRANSACTION message to user space
		if (put_user(cmd, (uint32_t __user *)ptr)) {
			/ /...
			return -EFAULT;
		}
		ptr += sizeof(uint32_t);
		if (copy_to_user(ptr, &tr, sizeof(tr))) {
		    // Copy structure tr data to user space
			/ /...
			return -EFAULT;
		}
		ptr += sizeof(tr);

		/ /...
		break;// Break the while loop
Copy the code

This code prepares the return data for the message BR_TRANSACTION. Note that:

  • callcopy_to_userCopied to theThe user spaceThe onlyStructure the trThe data of
  • Service processSo once I get this structure, I’ll just read what’s inside itData pointerThe data of
  • After the data is ready, use itBreak statementJump out of theThe while loop
  1. Start a new thread
    if (proc->requested_threads == 0 &&
	    list_empty(&thread->proc->waiting_threads) &&
	    proc->requested_threads_started < proc->max_threads &&
	    (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
	     BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
	     /*spawn a new thread if we leave this out */) {
		proc->requested_threads++;
		/ /...
		if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
			return -EFAULT;
	}
Copy the code

Binder calls are performed by the current thread, and incoming calls are handled by the thread, so:

  • The number of threads available in the process is checked before the function terminates
    • If a new thread needs to be created, theThe buffer dataaddBR_SPAWN_LOOPERThe message,Service processReceiving this message will start a new thread

The full Binder call process also needs to pass the reply message to the client process. This process uses the same functions as above, which will not be analyzed for the moment. Let’s just digest it

Handle the passed Binder objects

The principle of Binder object passing and the implementation of the user layer were introduced. Let’s take a look at how Binder drivers deliver Binder objects.

  • Binder driveRepresents the structure of each processbinde_procThere are two fields:nodesandrefs_by_node
    • These two fields each point to the head of two red-black trees
    • nodes: Points to yesBinder Object list, stored in this processBinder entity objectRelevant data
    • refs_by_node: Points to yesBinder references the object table, which stores the processBinder reference objectAnd the corresponding dataA node pointer to an entity object

Let’s draw an abstract point:

The code that handles object transformation in Binder drivers is in the function binder_TRANSACTION

        case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {
			struct flat_binder_object *fp;
			fp = to_flat_binder_object(hdr);
			ret = binder_translate_binder(fp, t, thread);
		} break;
static int binder_translate_binder(struct flat_binder_object *fp, struct binder_transaction *t, struct binder_thread *thread)
{
	struct binder_node *node;
	struct binder_proc *proc = thread->proc;
	struct binder_proc *target_proc = t->to_proc;
	struct binder_ref_data rdata;
	int ret = 0;
	// Find nodes in the binder object table based on the binder value
	node = binder_get_node(proc, fp->binder);
	if(! node) {// If no, create a new node
		node = binder_new_node(proc, fp);
		if(! node)return -ENOMEM;
	}
	/ /...
	if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
		ret = -EPERM;
		goto done;
	}
	// Look for a reference to the node in the receiving process, but a new reference cannot be created
	ret = binder_inc_ref_for_node(target_proc, node,
			fp->hdr.type == BINDER_TYPE_BINDER,
			&thread->todo, &rdata);
	if (ret)
		goto done;
	// Change the type value of the binder data structure passed to BINDER_TYPE_HANDLE
	if (fp->hdr.type == BINDER_TYPE_BINDER)
		fp->hdr.type = BINDER_TYPE_HANDLE;
	else
		fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
	fp->binder = 0;
	// rdata.desc stores the id of the node reference table and assigns it to handle
	fp->handle = rdata.desc;
	fp->cookie = 0;
	/ /...
}
Copy the code

The above process is

  • throughbinder_get_nodeThe function sends the process’sBinder Object node tableFind node in
    • The lookup is done by comparing the data inBinder fieldAnd the corresponding field in the node
    • Binder fieldIs stored inBinder objectWeak reference pointer to
    • If you don’t find it
      • The new node
      • theBinder field,Cookie fieldTo the new node
  • throughbinder_inc_ref_for_nodeFunction is found in the target processBinder nodethereference.
    • If you don’t find one, you’ll create another
    • referenceRefers to theNode reference tableIn therefs_by_nodeNode that contains a pointerBinder nodeA pointer to the
  • The data oftypeChange the value of the field toBINDER_TYPE_HANDLEorBINDER_TYPE_WEAK_HANDLE
  • thehandleThe value of the field is set toNode reference tableThis is also the serial number ofBinder reference objectIn theThe handle valueThe origin of

BINDER_TYPE_HANDLE or BINDER_TYPE_WEAK_HANDLE

        case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct flat_binder_object *fp;

			fp = to_flat_binder_object(hdr);
			ret = binder_translate_handle(fp, t, thread);
		} break;
static int binder_translate_handle(struct flat_binder_object *fp, struct binder_transaction *t, struct binder_thread *thread)
{
    / /...
    // Look up node references by the handle value
	node = binder_get_node_from_ref(proc, fp->handle,
			fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
	if(! node) {/ /...
		return -EINVAL;
	}
	/ /...
	if (node->proc == target_proc) {
	    // If the target process is the process of the Binder object, start the transformation
		if (fp->hdr.type == BINDER_TYPE_HANDLE)
			fp->hdr.type = BINDER_TYPE_BINDER;
		else
			fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
		fp->binder = node->ptr;
		fp->cookie = node->cookie;
		if (node->proc)
			binder_inner_proc_lock(node->proc);
		binder_inc_node_nilocked(node,
					 fp->hdr.type == BINDER_TYPE_BINDER,
					 0.NULL);
		/ /...
	} else {
		/ /...
		// If not, create a new reference to the Binder node in the target process
		ret = binder_inc_ref_for_node(target_proc, node,
				fp->hdr.type == BINDER_TYPE_HANDLE,
				NULL, &dest_rdata);

		/ /...
		fp->handle = dest_rdata.desc;
		fp->cookie = 0;
		trace_binder_transaction_ref_to_ref(t, node, &src_rdata,
		/ /...}}Copy the code

The flow of this code is as follows:

  • By transmitting dataHandle fieldsIn the sending processNode reference tableLook for
    • Under normal circumstances it can be found, cannot return on the error
  • If the target process is the same process as the Binder object
    • Start to transform the dataThe type fieldtoBINDER_TYPE_BINDERorBINDER_TYPE_WEAK_BINDERBINDER_WORK_TRANSACTION
    • thebinderandcookieField is set to the value saved in the node
  • If the target process is not the same process as the Binder object
    • Creates a reference to a node object in the target object

Here, Binder principle part is almost complete, including:

  • The clientcall,receiveMessage process
  • The service sideListening to the,replyNews of
  • driveTo transmit data,Thread schedulingThe operation of the

Now, let’s look at the last little piece: the ServiceManager’s role

ServiceManagerThe role of

The ServiceManager is described briefly:

  • ServiceManagerisBinder architectureIs used to parseThe name of the BinderThe module
  • ServiceManagerIt’s one in itselfBinder service
  • ServiceManagerIt’s not usedlibbinderTo buildBinder service
    • 2020-09-04 synchronized the project code, found also replaced bylibbinderThat one…
    • I’m not sure if they changed the storage
    • If this is not interesting, the easy version is for deepeningbinderIt helps to understand
    • Let’s stick to the old version for a moment
  • ServiceManagerSelf implemented a simpleBinder frameworkTo communicate directly with the driver…

ServiceManager source path in: frameworks/native/CMDS/ServiceManager, mainly contains two files:

  • Binder.c: For simple binder communication

    • Simple versionBinder agreementimplementation
    • directioctlOperation andBinder drivecommunication
    • Not the focus of this section, interested can refer to the source code to learn
  • Service_manager. c: Implements the service logic of the ServiceManager

    • The point is to understandServiceManagerHow to respond toBinder serviceRegister and query
  • When should such an important service be launched?

    • This part is insystem/core/rootdir/init.rcIn the configuration
    on post-fs
        ......
        # start essential services
        start logd
        start servicemanager
        start hwservicemanager
        start vndservicemanager
    Copy the code
    • hwservicemanagerUsed to supportHIDL
    • vndservicemanagerThird-party vendors should be used fromTreble architectureIn the

ServiceManagerThe architecture of the

Start with the ServiceManager’s main function:

int main(int argc, char** argv)
{
    struct binder_state *bs;
    union selinux_callback cb;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }

    bs = binder_open(driver, 128*1024);
    if(! bs) {/ /...
        // Omit some macro judgments
        return - 1;
    }

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n".strerror(errno));
        return - 1;
    }
    
    // Set selinux callback
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
    cb.func_log = selinux_log_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    
    / /...
    // Omit some macro judgments, all for seHandle (check SELinux permissions)
    sehandle = selinux_android_service_context_handle(a);selinux_status_open(true);
    if (sehandle == NULL) {
        ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
        abort(a); }if (getcon(&service_manager_context) ! =0) {
        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
        abort(a); }binder_loop(bs, svcmgr_handler);

    return 0;
}
Copy the code

The flow of main is as follows:

  • First callbinder_openTo open theBinder equipmentAnd initialize the system
    • Create a block128x1024Size of memory space
  • Then callbinder_become_context_managerSet this process toBinder frameworkManagement process of
    • binder_become_context_managerThe function code is as follows:
    int binder_become_context_manager(struct binder_state *bs)
    {
        return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
    }
    Copy the code
    • The function is usually simple and passes directlyioctlControl commandBINDER_SET_CONTEXT_MGRSent to drive
  • The last executionbinder_loop(bs, svcmgr_handler)After simple processing, the loop waits for messages

Binder_loop (bs, svcmgr_handler);

  1. binder_loopThe function definition of is:
void binder_loop(struct binder_state *bs, binder_handler func)
Copy the code
  • The second argument is passed in abinder_handlertype
  1. Look at thebinder_handlerType definition:
typedef int (*binder_handler)(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply);
Copy the code
  • This is the definition of aFunction pointer type
  • The return value isint
  1. Look at thebinder_loop(bs, svcmgr_handler)In thesvcmgr_handlerDeclaration of functions
int svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply)
{
    / /...
    // Omit some switch statements, more on that later
    return 0;
}
Copy the code
  • The pointer passed in issvcmgr_handlerA function pointer
  • It should automatically transition tobinder_handlerPointer types
  • After all, the function arguments and return values are the same
  • This is the part I find most amazing…
    • I wrote a C code to try it out,
    • If you define the function ofparameter,The return valuewithtypedefThe definition of theA function pointerIf not, strong compilation will fail
    • But t the operation still feels very indecent. (Java is more rigorous)
  1. I think the parameters are pretty well understood, so let’s take a closer lookbinder_loopThe implementation of these two parameters passed in to do what.

    PS: The simplified version is easier to understand…

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];
    
    // As usual, used to record some parameters written to the driver
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    // This property should inform the driver that it is ready to receive data
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        // Infinite loop
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // Start reading data
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < 0) {
            // Abnormal communication, exit loop
            ALOGE("binder_loop: ioctl failed (%s)\n".strerror(errno));
            break;
        }
        // After reading the data
        // Call binder_parse for data parsing
        // Pass the pointer to the binder_handler function as well
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        
        // Exit the loop
        if (res == 0 || res < 0) {
            / /...
            break; }}}Copy the code
  • There are comments, very concise logic
  • And finally it’s donebinder_parsefunction
  1. We will follow upbinder_parsefunction
int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        / / instruction fetch
        uint32_t cmd = *(uint32_t *) ptr;
        switch(cmd) {
        / /...
        // Since binder_handler is executed here
        // So we'll focus on this first, when Binder calls
        case BR_TRANSACTION: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            // Check data
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small! \n");
                return - 1;
            }
            binder_dump_txn(txn);
            // If the function pointer exists
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
                // Some initialization operations
                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, txn);
                // Execute the function pointed to by the binder_handler function pointer
                res = func(bs, txn, &msg, &reply);
                
                if (txn->flags & TF_ONE_WAY) {
                    // No result is returned in this case
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {
                    // Return the result data
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
            ptr += sizeof(*txn);
            break;
        }
        / /...
    }
    return r;
}
Copy the code
  • Or look at the comments, the code is very concise, the comments are very detailed, ha ha ha
  • Here we can see the real dealCall the serviceisbinder_handlerThe function pointer
  • That is to say,The remote invocationThe processing logic insvcmgr_handlerThis function
    • 666!
  1. Let’s seesvcmgr_handlerfunction
int svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply)
{
    / /...
    // If the requested target service is not a ServiceManager, return it directly
    if(txn->target.ptr ! = BINDER_SERVICE_MANAGER)return - 1;
        
    If the request message content is a simple test path, no further execution is required, return 0
    if (txn->code == PING_TRANSACTION)
        return 0;

    // Check the received message id string
    strict_policy = bio_get_uint32(msg);
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return - 1;
    }
    / /...
    // Check SELinux permissions
    if (sehandle && selinux_status_updated(a) >0) {
        struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle(a);if (tmp_sehandle) {
            selabel_close(sehandle); sehandle = tmp_sehandle; }}switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        // Process queries or commands to obtain services
        / /...
        break;
    case SVC_MGR_ADD_SERVICE:
        // Process the directive to register the service
        / /...
        break;

    case SVC_MGR_LIST_SERVICES: {
        / /...
        // Handle the instruction to get the list of services
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return - 1;
    }
    // Send a return message
    bio_put_uint32(reply, 0);
    return 0;
}
Copy the code
  • ServiceManagerThe architecture is very simple and efficient, with only one loop to sumBinder drivecommunicate

ServiceManagerServices provided

As you can see from the svcmgr_handler function, the ServiceManager provides three service functions:

  • registeredBinder service
  • The queryBinder service
  • To obtainBinder serviceThe list of

registeredBinder service

Binder service do_add_service (case SVC_MGR_ADD_SERVICE)

int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,
                   uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {
    struct svcinfo *si;

    // Some basic information judgment
    if(! handle || (len ==0) || (len > 127))
        return - 1;
    // Check whether the calling process has permission to register the service
    if (!svc_can_register(s, len, spid, uid)) {
        / /... Omit log print
        return - 1;
    }
    // Check whether the service to be registered already exists
    si = find_svc(s, len);
    if (si) {
    // If present, reduce the reference count for previous Binder objects by one
        if (si->handle) {
            svcinfo_death(bs, si);
        }
        // Replace the handle in the original node with the new handle
        si->handle = handle;
    } else {
        // If the service does not exist, a new list item will be generated and added to the list after initialization
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if(! si) {// Failed to apply for memory
            return - 1;
        }
        // Some initialization operations
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\ 0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
        si->next = svclist;
        svclist = si;
    }
    // Add a reference count for Binder services
    binder_acquire(bs, handle);
    // Register death notifications for the Binder service
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}
Copy the code

The do_add_service function flows as follows:

  • First check whether the calling process has permission to register the service
    • This part is throughSELinuxTo control, as we’ll see later
  • Next check to see if the service you want to register already exists
    • Existence, put the originalBinder serviceThe reference count in the driverMinus one
    • There is no
      • Create a new onescvinfostructure
      • Populate the structure with information about services that need to be registered
      • Add structure to the list of servicessvclist
  • Tell the kernel toBinder serviceReference count ofGal.
  • Sign up for death notifications for the service

Do you find conditions that already exist for the service:

  • On the firstMinus oneagainGal.The operation of the
  • Is it possible to count without operation?

Think about it

The queryBinder service

In case SVC_MGR_CHECK_SERVICE processing query service function, the specific implementation interface is do_find_service function, the code is as follows:

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
    struct svcinfo *si = find_svc(s, len);
    if(! si || ! si->handle) {return 0;
    }
    if(! si->allow_isolated) {// If this service doesn't allow access from isolated processes,
        // then check the uid to see if it is isolated.
        uid_t appid = uid % AID_USER;
        if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
            return 0; }}if (!svc_can_find(s, len, spid, uid)) {
        return 0;
    }
    return si->handle;
}
Copy the code

The main job of the do_find_service function is to search the list and return the found service. Note the code that determines uid:

  • In the callServiceManagertheaddBinderThere is a parameter in serviceallowed_isolatedTo specify whether the service is allowed to be accessed from the sandbox
  • The code here should be to determine if the calling process is oneSeparation process
    • ifappidinAID_ISOLATED_START(99000)andAID_ISOLATED_END(99999)between
    • Indicates that the service can passallowed_isolatedTo control whether normal user processes are allowed to use their services

conclusion

Finally, finally finished.

Ashmem anonymous shared memory (Binder based memory sharing) is also a part of the book.

I think IT will not be used as the notes for Binder for the time being. Binder is already very complicated, but I have gained a lot after reading it.

It seems very necessary to write the introduction (summary), ha ha