basis

  1. For the purposes of this description, the process that performs the system call is referred to as the source or current process, and the process to be invoked is referred to as the target process
  2. Binder online source code

ONE_WAY

Binder is invoked in two modes, one way mode and non-One way mode

  1. ONE_WAY: asynchronous call. The caller does not block, and the source process finishes sending data. One way means one-way. Only data is transmitted and no result is returned
  2. Non-one_way: blocks and waits for the target process to return a result

Steps to interact with the kernel

A process interacts with a binder driver in the following steps:

  1. Binder_open to enable communication with binder
  2. Memory mapping is performed via binder_mmap, in this case between the receiving process space and the kernel space
  3. Data is exchanged through binder_ioctl. The process is also divided into four steps
    • Use copy_from_user to copy information from the calling process, including the amount of data to write, amount of data to read, and so on. handshake
    • Read from the calling process space using binder_thread_write
    • Write to the calling process space using binder_thread_read
    • Copy the handshake information from the first step back into the calling process space
  4. When user processes communicate with the kernel through binder, their data formats are agreed upon. That is, the number of bytes and the value, are fixed. So that the kernel can parse out the specific value

Common structure

link

To sum up a few important ones:

The name content Initialization time
binder_write_read Recording read and write data Fill in the first time copy_from_user
binder_transaction_data Transaction data that binder communicates Records the caller’s PID/UID, target process and other information
vm_area_struct Description of the virtual address of the calling process Binder_mmap will be used as a parameter
binder_proc The structure of an application process in binder drivers, which stores information about the process binder_open
binder_alloc The structure in binder_proc used to manipulate binder_buffer binder_open
binder_buffer The virtual address of a memory map in kernel space Mmap () creates a cache for Binder to transfer data

In older versions, binder_buffer was stored directly in binder_proc, without binder_alloc.

There are several quotes:

Vm_area_struct ->vm_private_data = binder_proc Binder_proc ->binder_alloc->buffer = vm_area_struct->vm_start // The maximum value is 4 MB binder_alloc->buffer_size = vm_area_struct->vm_end - vm_area_struct->vm_startCopy the code

binder_init

  1. Binder’s initialization function is binder_init, which calls misc_register() to register binder drivers with the kernel
  2. The miscDevice structure object is passed in for registration, which in turn defines the actions of the Binder driver. Hence the binder principle is the method defined in File_operations
    // Binder corresponding operation
    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,
    };
    // misc_register()
    static struct miscdevice binder_miscdev = {
            .minor = MISC_DYNAMIC_MINOR,
            .name = "binder",
            .fops = &binder_fops
    };
    Copy the code

binder_open

Create a binder_proc object for the source process and add the object to binder_procs

  1. binder_procsIs a hash table (a lower version of Java’s implementation of HashMap)
  2. Create binder_PROC objects to store calling process information, and then use binder_procs to centrally manage all binder_PROC objects
static int binder_open(struct inode *nodp, struct file *filp)
{
        // In the following code, current represents the process currently executing the system call
        
	struct binder_proc *proc;
	struct binder_device *binder_dev;
	struct binderfs_info *info;
        // Create a new binder_proc instance
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
        // Initialize the proc->todo list
        // INIT_LIST_HEAD initializes a bidirectional list by pointing the next and prev of the argument to itself
	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->freeze_wait);
	proc->context = &binder_dev->context;
        // Initialize proc->alloc
	binder_alloc_init(&proc->alloc);

	binder_stats_created(BINDER_STAT_PROC);
	proc->pid = current->group_leader->pid;
        // Initializes two properties in proc
	INIT_LIST_HEAD(&proc->delivered_death);
	INIT_LIST_HEAD(&proc->waiting_threads);
        // Record to filp
	filp->private_data = proc;
        // Add the newly created binder_proc instance to binder_procs
        // Add proc_node to the head node of the corresponding list
	hlist_add_head(&proc->proc_node, &binder_procs);

	return 0;
}
Copy the code

binder_mmap

The memory mapping function is complete

  1. The maximum size is 4M
  2. Allocate a block of memory in kernel virtual memory that is the same size as the user process, and then use binder_alloc to manage all memory, where no actual physical memory is allocated. There was a page of physical memory allocated on the old version, but not on the new version
// The vMA parameter refers to the address information of the process making the system call
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
        / / binder_open created
	struct binder_proc *proc = filp->private_data;

        // Limit the size to 4M Max
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
        // The main method. Proc ->alloc is also created in binder_open
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);

        // Returns the files property of the current process
        Task_struct ->files; // Task_struct ->files
	proc->files = get_files_struct(current);
	return 0;
}

int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma)
{
	int ret;
	const char *failure_string;
	struct binder_buffer *buffer;
        / / lock
	mutex_lock(&binder_alloc_mmap_lock);
        // It has already been allocated
	if (alloc->buffer) {
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}
        // Point to the starting address of the source process
	alloc->buffer = (void __user *)vma->vm_start;
        / / unlock
	mutex_unlock(&binder_alloc_mmap_lock);
        // Allocate an array. Each element is sizeof(alloc->pages[0]) and the total length of the array is the first argument
        // This array will correspond to the actual physical memory
	alloc->pages = kcalloc((vma->vm_end - vma->vm_start) / PAGE_SIZE,
			       sizeof(alloc->pages[0]),
			       GFP_KERNEL);

	alloc->buffer_size = vma->vm_end - vma->vm_start;
        // Create a binder_buffer, which is the object managed by binder_alloc
	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);

	buffer->user_data = alloc->buffer;
	list_add(&buffer->entry, &alloc->buffers);
	buffer->free = 1;
        // Associated with alloc and managed by alloc
	binder_insert_free_buffer(alloc, buffer);
        // Allocate memory size for asynchronous processing
	alloc->free_async_space = alloc->buffer_size / 2;
        // Use alloc-> vMA to record vMA
	binder_alloc_set_vma(alloc, vma);

	return 0;
}
Copy the code

Binder_alloc allocates a block of virtual memory of the same size in kernel space as the memory passed by the user, which is then managed by binder_alloc. There are synchronous and asynchronous: the size of asynchronous is half that of synchronous

In older versions this method also allocated a page of physical memory, which was then mapped to the current process (the process executing the system call) and to the in-memory process, but this is no longer the case.

binder_ioctl

Different operations are performed according to different commands passed in. The process of sending and receiving data between two processes is completed by this method

  1. First get a thread through binder_get_thread(), the thread in the source process, not the target process
  2. The binder_ioctl switches (CMD) to perform different operations depending on the CMD. For example, ServiceManger registers with binder to set itself as manager, where CMD is BINDER_SET_CONTEXT_MGR
    • The most common CMD is BINDER_WRITE_READ, which executes binder_ioctl_write_read. The following analysis
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	thread = binder_get_thread(proc);

	switch (cmd) {
	case BINDER_WRITE_READ: // The most common one
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		break; . }Copy the code

binder_write_read

This structure is often referred to in subsequent analyses, so it is explained in advance. You can think of it as a directory where user space and kernel space exchange data, storing how much data to read and write, before kernel space knows where in the source process to start and how much to copy.

It can also be understood that there is a handshake between user space and kernel space (done by the kernel space call copy_from_user) before the actual data exchange, which gives the kernel an idea of how much data the user needs to pass before it can continue copying data. The information exchanged by the handshake is encapsulated in the binder_write_read structure

struct binder_write_read {
     signed long write_size;
     signed long write_consumed;
     unsigned long write_buffer;
     signed long read_size;
     signed long read_consumed;
     unsigned long read_buffer;
};
Copy the code

Data replication process

Binder does not copy data directly from the source process, but does so in multiple steps

Here is a summary of the general process of copying:

  1. The first copy is a binder_write_read object, denoted as BWR, which stores whether the calling process needs to read/write data to the target process. It is determined by the write_size and read_size properties in the BWR

  2. If you want to write, that is, write_size > 0, read a CMD from the source process space and then perform different operations based on the CMD

    • Typically, you copy the data as binder_transaction_data, or tr for short
  3. Buffer with tr.data.ptr.buffer and tr.data.ptr.offsets are used to copy data to the kernel space, and the kernel space receives all data using binder_TRANSACTION ->buffer

  4. The kernel space receives data in two parts: the first part reads ipcDataSize, denoted as data; The second part reads ipcObjects, followed by object. Here is the part where the source process writes data to the driver:

    / / IPCThreadData: : writeTransactionData excerpt
    // This is the logic for the user process to populate the data
    tr.target.handle = handle;
    tr.data_size = data.ipcDataSize(a); tr.data.ptr.buffer = data.ipcData(a); tr.offsets_size = data.ipcObjectsCount(*)sizeof(binder_size_t);
    tr.data.ptr.offsets = data.ipcObjects(a);Copy the code

binder_get_thread

  1. First get the binder_thread object from the binder_proc created by binder_open.
  2. If not, create a new one and add it to binder_proc
  3. Returns the obtained binder_thread structure object

binder_ioctl_write_read

This method actually copies data from user space to content space, and copy_from_user is used three times throughout this method

Here is a specific analysis of the above summary:

static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
        // The binder_proc object created in binder_open
	struct binder_proc *proc = filp->private_data;
        // Force the arg to a void* pointer, ignoring __user in the phase
	void __user *ubuf = (void __user *)arg;
        // Initialize a structure
	struct binder_write_read bwr;

        // Only the size of binder_write_read is copied
        // BWR will have a value after this sentence is executed
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		
	}
        
	if (bwr.write_size > 0) {
                // If the source process needs to write data to the target process, the kernel uses copy_from_user to copy the data
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
	}
        
	if (bwr.read_size > 0) {
                // If the source process needs to read the return result
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
	}
        // Copy the BWR to the source process
        // If the source process needs to read data, it already has it
	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {

	}
	return ret;
}
Copy the code

As you can see, binder’s two core methods are binder_thread_write and binder_thread_read. So far, to summarize:

binder_thread_write

It performs different operations based on the request code. The request code is passed by the calling process. Common examples are BC_TRANSACTION and BC_REPLY. In both cases, the code looks like this:

/ / buffer is BWR write_buffer
/ / is consumed BWR. Write_consumed
void __user *ptr = buffer + *consumed;
Get_user, like copy_from_user, reads data from the source process
get_user(cmd, (uint32_t __user *)ptr)

// Execute different logic through switch-cash according to CMD read above
case BC_TRANSACTION:
case BC_REPLY: {
	struct binder_transaction_data tr;
        // Copy the data from the calling process space again, filling in binder_transaction_data
	if (copy_from_user(&tr, ptr, sizeof(tr)))
		return -EFAULT;
	ptr += sizeof(tr);
        // The data is then passed to binder_transaction
	binder_transaction(proc, thread, &tr,
			   cmd == BC_REPLY, 0);
	break;
}
Copy the code

So far, copy_from_USER has been called again, but instead of copying the actual data, the binder_transaction_data structure object has been copied. The next step should be distribution to the target process. This step is in binder_TRANSACTION.

binder_transaction

Now that you have the binder_transaction_data object, whose properties point to the location of the data that the user is actually sending, the next step is to map the target process to the kernel and copy the data. The logic of this method is quite complex, the analysis is not moving, paste the deleted code and part of the analysis:

// Thread refers to the thread of the source process
static void binder_transaction(struct binder_proc *proc,
                struct binder_thread *thread,
                struct binder_transaction_data *tr, int reply,
                binder_size_t extra_buffers_size)
{
   struct binder_transaction *t;
   struct binder_work *tcomplete;
   
   binder_size_t buffer_offset = 0;
   binder_size_t off_start_offset, off_end_offset;
   
   struct binder_proc *target_proc = NULL; // Target process
   struct binder_thread *target_thread = NULL; // Target thread
   struct binder_node *target_node = NULL; // Target node
   
   struct binder_transaction *in_reply_to = NULL;
   struct binder_context *context = proc->context;

   if (reply) {
      in_reply_to = thread->transaction_stack;
      thread->transaction_stack = in_reply_to->to_parent;
      target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);
      // According to the judgment part of the deletion, the following conclusions can be drawn:
      // in_reply_to->to_thread == thread
      // target_thread->transaction_stack == in_reply_to
      target_proc = target_thread->proc;
      target_proc->tmp_ref++;
   } else {
       // Find the target process according to handle
      if (tr->target.handle) {
         struct binder_ref *ref;
         ref = binder_get_ref_olocked(proc, tr->target.handle,
                       true);
         target_node = binder_get_node_refs_for_txn(
                  ref->node, &target_proc,
                  &return_error);
      } else {// Handle is 0, pointing to the service Manager
         target_node = context->binder_context_mgr_node;
         target_node = binder_get_node_refs_for_txn(
                  target_node, &target_proc,
                  &return_error);
      }
      // This is not clear. Anyway, we're going to reuse Transaction_stack
      // If the target_thread is not assigned, the target_thread will be null
      if(! (tr->flags & TF_ONE_WAY) && thread->transaction_stack) {struct binder_transaction *tmp;
         tmp = thread->transaction_stack;
         while (tmp) {
            struct binder_thread *from;
            from = tmp->from;
            if (from && from->proc == target_proc) {
               target_thread = from;
               break; } tmp = tmp->from_parent; }}}// Create t and tComplete, then assign their attributes
   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;

   t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, tr->offsets_size, extra_buffers_size, ! reply && (t->flags & TF_ONE_WAY), current->tgid); t->buffer->transaction = t; t->buffer->target_node = target_node;// Copy the data section. This time it is the real data that is copied to the memory-mapped area
   if (binder_alloc_copy_user_to_buffer(
            &target_proc->alloc,
            t->buffer, 0,
            (const void __user *)
               (uintptr_t)tr->data.ptr.buffer,
            tr->data_size)) {
   }
   // Data is divided into two parts, the top copy data, here copy object part
   if (binder_alloc_copy_user_to_buffer(
            &target_proc->alloc,
            t->buffer,
            ALIGN(tr->data_size, sizeof(void *)),
            (const void __user *)
               (uintptr_t)tr->data.ptr.offsets,
            tr->offsets_size)) {
   }

   off_start_offset = ALIGN(tr->data_size, sizeof(void *));
   buffer_offset = off_start_offset;
   off_end_offset = off_start_offset + tr->offsets_size;
   for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
        buffer_offset += sizeof(binder_size_t)) {
      struct binder_object object;
      binder_size_t object_offset;

      binder_alloc_copy_from_buffer(&target_proc->alloc,
                     &object_offset,
                     t->buffer,
                     buffer_offset,
                     sizeof(object_offset));
      / / copy the object
      binder_get_object(target_proc, t->buffer,
                  object_offset, &object);
   }
   tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
   t->work.type = BINDER_WORK_TRANSACTION;

   if (reply) {
      binder_enqueue_thread_work(thread, tcomplete);
      binder_inner_proc_lock(target_proc);

      binder_pop_transaction_ilocked(target_thread, in_reply_to);
      binder_enqueue_thread_work_ilocked(target_thread, &t->work);
      target_proc->outstanding_txns++;
      binder_inner_proc_unlock(target_proc);
      wake_up_interruptible_sync(&target_thread->wait);
      binder_restore_priority(current, in_reply_to->saved_priority);
      binder_free_transaction(in_reply_to);
   } else if(! (t->flags & TF_ONE_WAY)) {// Non-one-way, need to return data
      binder_inner_proc_lock(proc);
      // Add tcomplete to thread->todo
      binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
      // Add t to thread->transaction_stack
      // thread->transaction_stack is a single linked list that is added to the table header
      t->need_reply = 1;
      t->from_parent = thread->transaction_stack;
      thread->transaction_stack = t;
      // Pass to the target mileage and wake up the target process
      binder_proc_transaction(t,
            target_proc, target_thread);

   } else {
      // One-way, no return is required
      
      // Add tcomplete to thread->todo and thread->process_todo = true
      binder_enqueue_thread_work(thread, tcomplete);
      // Pass to the target mileage and wake up the target process
      binder_proc_transaction(t, target_proc, NULL); }}Copy the code

binder_thread_read

Back in binder_iocTL_write_read, after calling binder_thread_write, it will call binder_thread_read if the source process needs to read data (i.e. Bwr.read_size > 0).

Physical memory allocation

In the analysis of binder_transaction, we saw that this method allocates physical memory and maps the target process to the kernel. So let’s see,

  1. Call binder_alloc_new_buf:
    // tr is the binder_transaction_data object
    // data_size refers to the amount of data passed by the source process to the kernel
    // offsets_size refers to the amount of object data passed by the source process to the 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), current->tgid);Copy the code
  2. The previous method directly calls binder_alloc_new_buF_locked, which has the following code:
    // Note that the whole range is passed in
    // There was only one page in the old version
    ret = binder_update_page_range(alloc, 1, (void __user *)
       PAGE_ALIGN((uintptr_t)buffer->user_data), end_page_addr);
    Copy the code
  3. Binder_update_page_range is basically the same as the old version: leveragealloc_pageAllocate physical memory one page at a time. Except instead of passing in a page, you pass in the entire memory
    / / binder_update_page_range excerpt
    // Allocate physical memory for each block of virtual memory one page at a time
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
            index = (page_addr - alloc->buffer) / PAGE_SIZE;
            Pages of binder_alloc, created in binder_mmap
            page = &alloc->pages[index];
            // Call alloc_page to start allocating physical memory
            page->page_ptr = alloc_page(GFP_KERNEL |
                                        __GFP_HIGHMEM |
                                        __GFP_ZERO);
            page->alloc = alloc;
            INIT_LIST_HEAD(&page->lru);
            user_page_addr = (uintptr_t)page_addr;
            // Map the allocated physical memory to the target
            // VMA here refers to the VMA recorded in target_proc, which is the target process information
            ret = vm_insert_page(vma, user_page_addr, page[0].page_ptr);
    }
    Copy the code