Binder is the interprocess communication mechanism of Android system, which is a necessary knowledge to understand the operating mechanism of Android, and also a must-ask knowledge for first-line enterprise interviews. Such as:

  1. What are the advantages of Binder? (bytes)
  2. Binder one copy principle? (tencent)
  3. Intents deliver big data limitations? (ali)
  4. AIDL principles? (bytes)
  5. What do you know about Binder drivers? (bytes)

Can you answer them all?! ?

With Binder, how can you thoroughly master the binder mechanism and handle the interview questions with ease to impress the interviewers and get a high salary offer?

There is no shortcut, Read The Fucking Source Code! “This is definitely a good quote.

To achieve a thorough grasp of Binder, you should not memorize others’ general descriptions of binder, but dig into the source code to see what it is. Only with your own understanding can you be confident in dealing with relevant interview questions.

Binder_open () binder_mmap() binder_ioctl() binder_ioctl() binder_ioctl() binder_ioctl()

Upper-level calls get stuck in the kernel

Before looking at these three key functions, let’s take a look at how they are called by the upper layer so that we can understand binder mechanisms as a whole and not be too isolated in our understanding of binder drivers.

Binder drivers from user-space applications to kernel-space binder drivers encapsulate classes through ProcessState, IPCThreadState, etc., and then sink into the system kernel through system calls.

Mastering binder mechanics? Understand these key classes first! Binder, IPCThreadState, BpBinder and BinderProxy operation package class source code, do not understand the recommendation to go to the collection to read.

ProcessState is a process singleton responsible for opening binder driver devices and MMAP. The binder_open(), binder_mmap() functions driven by binder are called in the constructor of ProcessState as follows:

ProcessState::ProcessState()
    : mDriverFD(open_driver()) // Open binder devices, binder_open()
    , mVMStart(MAP_FAILED)
    ...
    , mMaxThreads(DEFAULT_MAX_binder_THREADS)
    , mThreadPoolSeq(1) {if (mDriverFD >= 0) {
           Binder_mmap () binder_mmap()
           mVMStart = mmap(0, binder_VM_SIZE, PROT_READ,
                            MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
           if (mVMStart == MAP_FAILED) {
               close(mDriverFD);
               mDriverFD = - 1; }}}Copy the code

IPCThreadState is a thread singleton responsible for communicating specific commands with binder drivers. In its talkWithDriver() method, the binder_ioctl() function driven by binder is called:

// binder_ioctl
ioctl(mProcess->mDriverFD, binder_WRITE_READ, &bwr)
Copy the code

Binder driver registration

Binder drivers run in kernel mode and provide upper layer /dev/binder device nodes that do not correspond to real hardware devices. The registration logic for binder drivers is in binder.c:

//drivers/staging/android/Binder.c
static init __init binder_init(void){
    ...
    ret = misc_register(&binder_miscdev); // Register as misC driver
}
Copy the code

Binder_miscdev Binder devices are described as follows:

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR, // Automatically assign the device number
    .name = "binder".// Driver name
    .fops = &binder_fops // File operations supported by binder drivers
}
Copy the code

Binder_fops is the operation function supported by Binder devices, as follows:

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

binder_open

Binder_open () is the binder_open() method used to open the binder_open() driver when user applications communicate with Binder. Binder_open () does two things:

//binder.c
static int binder_open(struct inode *nodp, struct file *filp)
{
    structbinder_proc *proc; . proc = kzalloc(sizeof(*proc), GFP_KERNEL); / / create binder_proc
    if (proc == NULL)
        return -ENOMEM;
    get_task_struct(current);
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo); // Initialize the TOdo queue
    init_waitqueue_head(&proc->wait); // Initialize the TOdo queue
    proc->default_priority = task_nice(current);
Copy the code

The main job of the above code is to create and initialize binder_proc, the binder_proc structure that holds binder related data, one for each process.

binder_lock(__func__); binder_stats_created(BINDER_STAT_PROC); hlist_add_head(&proc->proc_node, &binder_procs); proc->pid = current->group_leader->pid; INIT_LIST_HEAD(&proc->delivered_death); filp->private_data = proc; binder_unlock(__func__); . }Copy the code

The second major task is to log binder_proc to the kernel binder_procs table using the hlist_add_head() method, as shown in the code above. Binder_proc is also stored in the private_data field of filp for subsequent calls to mmap, IOCtl, and other methods.

binder_mmap

Binder_mmap () maps the virtual memory blocks of the upper layer to the physical memory blocks required by binder drivers. The application and Binder have shared memory space so that data can be shared between different applications through the Binder.

  • Binder has a physical memory block P; There is a memory block B in process B
  • Map P to B, so that P and B can be regarded as the same memory
  • If process A wants to send data to process B, it simply copies the data to memory P, and process B can read it directly

Binder_mmap () is used to map P to B. Binder_mmap () is used to map P to B.

static int binder_mmap(struct file *filp, struct vm_area_struct *vma){
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    // Mapping space is at most 4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
          vma->vm_end = vma->vm_start + SZ_4M;
    // Check whether the VMA is disabled
    if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
          ret = -EPERM;
          failure_string = "bad vm_flags";
          goto err_bad_arg;
    }
Copy the code
  • Vma (vm_areA_struct) is the address space of user-mode virtual memory, that is, B
  • Area (vm_struct) is a kernel-formatted virtual address space pointing to P
  • Proc (binder_proc) is the structure created in binder_open() to hold binder-related data
  • In addition, the mapping rules limiting the mapping space to 4M at most are checked and processed
     mutex_lock(&binder_mmap_lock);
    // Check whether binder_mmap has been performed
    if (proc->buffer) {
          ret = -EBUSY;
          failure_string = "already mapped";
          goto err_already_mapped;
    }
    // Apply for the kernel virtual memory address space
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    if (area == NULL) {
          ret = -ENOMEM;
          failure_string = "get_vm_area";
          goto err_get_vm_area_failed;
    }
    // Records the kernel virtual memory address in proc
    proc->buffer = area->addr;
    // Record the user virtual memory address and the kernel virtual memory address offset
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
    mutex_unlock(&binder_mmap_lock);
Copy the code
  • Proc ->buffer is used to store the final mapped kernel-state virtual address, which is controlled by this variable to map only once
  • The get_vm_area() method applies for a kernel-mode virtual address space of the same size as the user-mode space. Note that no physical memory has been allocated
  • Proc ->user_buffer_offset Records the offset of user-mode virtual memory and kernel-mode virtual memory addresses to facilitate the subsequent acquisition of user-mode virtual memory addresses
    // Allocate an array of physical page addresses
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    proc->buffer_size = vma->vm_end - vma->vm_start;
    // Request a page of physical memory
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
          ret = -ENOMEM;
          failure_string = "alloc small buf";
          goto err_alloc_small_buf_failed;
    }
    // The final touches: record the memory in the corresponding linked list, set the state, etc
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers);
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    proc->free_async_space = proc->buffer_size / 2;
    proc->files = get_files_struct(current);
    proc->vma = vma;
Copy the code
  • Proc -> Pages is a two-dimensional pointer to administrative physical pages
  • The binder_update_page_range() method actually applies for physical pages and maps them to kernel-mode and user-mode virtual memory address Spaces, respectively

Now that the binder_mmap method is finished, let’s move on to the binder_update_page_range() method, whose code is useful for understanding the page box and mapping logic to virtual memory addresses. To understand the parameters of this method:

  • Proc: Binder_proc object held by the process requesting memory
  • Allocate: 1 indicates that memory is allocated; 0 indicates that memory is released
  • Start: indicates the starting point of the virtual memory address
  • End: indicates the end of the virtual memory address
  • Vma: user virtual memory address space
static int binder_update_page_range(struct binder_proc *proc, int allocate,
                    void *start, void *end,
                    struct vm_area_struct *vma){
    if (allocate == 0)  // Distinguish between applying and releasing
         goto free_range;
    // Allocate physical pages according to start and end cycles
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
          // Allocate 1 page box at a time */
         *page = **alloc_page**(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
         // Map the page box to the kernel virtual memory address
         ret = **map_kernel_range_noflush**((unsigned long)page_addr, PAGE_SIZE, PAGE_KERNEL, page);
         // Calculate the user virtual memory address based on the offset recorded in the binder_mmap method
         user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
         // Map the page box to the user virtual memory address
         ret = vm_insert_page(vma, user_page_addr, page[0]);
     }
     return 0;
Copy the code

The allocate argument for binder_mmap() is passed in as 1 to allocate memory. To release, execute the following code:

free_range:
    // proceed from start to end
    for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        if (vma)
            // Remove the mapping between the user virtual address and the physical page box
            zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
        // Remove the mapping between the kernel virtual address and the physical page box
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
        // Free the physical memory of the page frame
        __free_page(*page);
        *page = NULL;
}
Copy the code

binder_ioctl

Binder_ioctl () is one of the most complex binder_ioctl() drivers. Binder_ioctl () is the most complex binder_ioctl() driver. It does most of the business for Binder drivers. Instead of digging into the source code, here is a list of commands supported by binder_ioctl() :

The command instructions
BINDER_WRITE_READ Write or read data to binder drivers
BINDER_SET_MAX_THREADS Set the maximum number of threads supported
BINDER_SET_CONTEXT_MGR Service Manager specific registration command
BINDER_THREAD_EXIT Notifies binder drivers that a thread exits, freeing resources
BINDER_VERSION Obtain the Binder version number

The BINDER_WRITE_READ command is the most critical and is divided into several subcommands:

The command instructions
BC_INCREFS, BC_ACQUIRE, BC_RELEASE, BC_DECREFS Manages the reference count for binder_ref
BC_INCREFS_DONE, BC_ACQUIRE_NODE Manages the reference count for binder_node
BC_FREE_BUFFER Release Binder memory buffers
BC_TRANSACTION Sending communication data to binder drivers (active invocation)
BC_REPLY Sending communication data to binder drivers (return result)
BC_REGISTER_LOOPER, BC_ENTER_LOOPER, and BC_EXIT_LOOPER Set the Binder Looper state
BC_REQUEST_DEATH_NOTIFICATION Register for Binder death notifications
BC_CLEAR_DEATH_NOTIFICATION Clear Binder death notices
BC_DEAD_BINDER_DONE Inform Binder that Binder death notice has been processed

These are all commands received by the Binder driver as the recipient binder_ioctl() method, as well as the corresponding BR_ commands, such as BR_TRANSACTION, BR_REPLY, This is applied in an IPC call:

The last

Binder source code, how to use this knowledge to handle interview questions? The thoughtful Android interviewer has rehearsed the q&A scenario for you:

What do you know about Binder drivers?

As shown in the figure above, there is a Binder source code divided by modules, with key step notes, Binder, you can obtain the necessary source code Binder study.