天道酬勤,学无止境

Android10.0 Binder通信原理(五)-Binder驱动分析

摘要:本节主要来讲解Android10.0 Binder的驱动层分析

阅读本文大约需要花费35分钟。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

欢迎关注我的公众号!

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

  1. Android系统架构
  2. Android是怎么启动的
  3. Android 10.0系统启动之init进程
  4. Android10.0系统启动之Zygote进程
  5. Android 10.0 系统启动之SystemServer进程
  6. Android 10.0 系统服务之ActivityMnagerService
  7. Android10.0系统启动之Launcher(桌面)启动流程
  8. Android10.0应用进程创建过程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及启动流程
  10. Android 10.0 PackageManagerService(二)权限扫描
  11. Android 10.0 PackageManagerService(三)APK扫描
  12. Android 10.0 PackageManagerService(四)APK安装流程

《日志系统篇》

  1. Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
  2. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
  3. Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
  4. Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入门篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++实例分析
  5. Android10.0 Binder通信原理(五)-Binder驱动分析
  6. Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework层分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
  11. Android10.0 Binder通信原理(十一)-Binder总结

  《HwBinder通信原理》

  1. HwBinder入门篇-Android10.0 HwBinder通信原理(一)
  2.  HIDL详解-Android10.0 HwBinder通信原理(二)
  3. HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
  4. HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
  5. HwServiceManager篇-Android10.0 HwBinder通信原理(五)
  6. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
  7. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
  8. JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
  9. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
  10. HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
  11. HwBinder原理总结-Android10.0 HwBinder通信原理(十一)

《编译原理》

  1. 编译系统入门篇-Android10.0编译系统(一)
  2. 编译环境初始化-Android10.0编译系统(二)
  3. make编译过程-Android10.0编译系统(三)
  4. Image打包流程-Android10.0编译系统(四)
  5. Kati详解-Android10.0编译系统(五)

1.概述

在Android中,用户空间的应用程序都可以看做是一个独立的进程,进程间存在隔离,进程不能互相访问数据,如果需要访问就需要借助内核。

每个应用程序都有它自己独立的内存空间,若不同的应用程序之间涉及到通信,需要通过内核进行中转,因为需要用到内核的copy_from_user()和copy_to_user()等函数。因此在Binder的通信中,也引入了Binder内核驱动,用来提供数据中转。

Binder驱动就是一个多个进程之间的中枢神经,支撑起了Android中进程间通信,它内部的设计,与应用程序进程中的业务,不存在任何耦合关系,只负责实现进程间数据通信。

Binder驱动的核心是维护一个binder_proc类型的链表。里面记录了包括ServiceManager在内的所有Client信息,当Client去请求得到某个Service时,Binder驱动就去binder_proc中查找相应的Service返回给Client,同时增加当前Service的引用个数。

 

2.Binder架构

在整个Binder通信流程中,Binder驱动肩负了载体的作用,承上启下,使用户的数据可以顺畅交互。

 

3.Binder中重要的数据结构

 

4.核心内容

用户态的程序调用Kernel层驱动是需要陷入内核态,进行系统调用(syscall),比如打开Binder驱动方法的调用链为:open-> __open() -> binder_open()。open()为用户空间的方法,__open()便是系统调用中相应的处理方法,通过查找,对应调用到内核binder驱动的binder_open()方法,至于其他的从用户态陷入内核态的流程也基本一致。

几个重要方法说明:

  • binder_init:初始化字符设备 "/dev/binder","/dev/hwbinder","/dev/vndbinder";
  • binder_open:打开驱动设备;
  • binder_mmap:申请内存空间;
  • binder_ioctl:执行相应的ioctl操作;

 

4.1 初始化 binder_init()

内核初始化时,会调用到device_initcall()进行初始化,从而启动binder_init。

binder_init()主要负责注册misc设备,通过调用misc_register()来实现。

在Android8.0之后,现在Binder驱动有三个:/dev/binder; /dev/hwbinder; /dev/vndbinder.

device_initcall(binder_init);
static HLIST_HEAD(binder_devices);

static int __init binder_init(void)
{
       int ret;
       char *device_name, *device_names, *device_tmp;
       struct binder_device *device;
       struct hlist_node *tmp;

       ret = binder_alloc_shrinker_init();
       if (ret)
               return ret;

       atomic_set(&binder_transaction_log.cur, ~0U);
       atomic_set(&binder_transaction_log_failed.cur, ~0U);

    //在debugfs文件系统中创建一个目录,返回值是指向dentry的指针
    //在手机对应的目录:/sys/kernel/debug/binder,里面创建了几个文件,用来记录binder操作过程中的信息和日志:
    //failed_transaction_log、state、stats、transaction_log、transactions
       binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
       if (binder_debugfs_dir_entry_root)
        //创建目录:/sys/kernel/debug/binder/proc
               binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
                                                binder_debugfs_dir_entry_root);
    ...

       device_names = kzalloc(strlen(binder_devices_param) + 1, GFP_KERNEL);
       if (!device_names) {
               ret = -ENOMEM;
               goto err_alloc_device_names_failed;
       }
       strcpy(device_names, binder_devices_param);

       device_tmp = device_names;
   //Android8.0 中引入了hwbinder,vndbinder,所以现在有三个binder,分别需要创建三个binder device:
   // /dev/binder、/dev/hwbinder、/dev/vndbinder
   //循环注册binder 的三个设备:/dev/binder、/dev/hwbinder、/dev/vndbinder
       while ((device_name = strsep(&device_tmp, ","))) {
               ret = init_binder_device(device_name);
               if (ret)
                       goto err_init_binder_device_failed;
       }

       return ret;

err_init_binder_device_failed:
       hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
               misc_deregister(&device->miscdev);
               hlist_del(&device->hlist);
               kfree(device);
       }

       kfree(device_names);

err_alloc_device_names_failed:
       debugfs_remove_recursive(binder_debugfs_dir_entry_root);

       return ret;
}

static int __init init_binder_device(const char *name)
{
       int ret;
       struct binder_device *binder_device;

    //申请内存空间,
       binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
       if (!binder_device)
               return -ENOMEM;

       binder_device->miscdev.fops = &binder_fops;
       binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
       binder_device->miscdev.name = name;

       binder_device->context.binder_context_mgr_uid = INVALID_UID;
       binder_device->context.name = name;
       mutex_init(&binder_device->context.context_mgr_node_lock);

       ret = misc_register(&binder_device->miscdev);
       if (ret < 0) {
               kfree(binder_device);
               return ret;
       }

       hlist_add_head(&binder_device->hlist, &binder_devices);

       return ret;
}

Android8.0及之后的Binder域如下图所示:

 

4.1.1 注册设备的几个重要结构体

binder的device包含了一个哈希链表,一个misc设备结构,一个binder context,结构如下:

struct binder_device {
       struct hlist_node hlist;
       struct miscdevice miscdev;  //misc 设备
       struct binder_context context;  //context
};

misc 设备结构中,我们主要关注name,fops,结构如下:

struct miscdevice  {
       int minor;                          //次设备号 动态分配 MISC_DYNAMIC_MINOR
       const char *name;                   //设备名          
                                           //"/dev/binder、/dev/hwbinder、/dev/vndbinder"
       const struct file_operations *fops; //设备的文件操作结构,这是file_operations结构
       struct list_head list;
       struct device *parent;
       struct device *this_device;
       const struct attribute_group **groups;
       const char *nodename;
       umode_t mode;
};

在获取了一些设备编号后,我们还没有将任何驱动程序操作连接到这些编号,file_operations结构就是用来建立这种连接的。

binder_fops主要包含了Binder的一些操作方法配置,例如open、mmap、ioctl,结构如下:

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,
};

 

4.2 binder_open

当我们在Native C\C++层通过系统调用open()来打开binder驱动时,驱动层会根据设备文件的主设备号,找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。

因此,open()到驱动层,就会调用binder_open()

binder_open()主要负责打开驱动设备,创建binder_proc对象,并把当前进程等信息保存到binder_proc对象,该对象管理IPC所需的各种信息并拥有其他结构体的根结构体;再把binder_proc对象保存到文件指针filp,以及把binder_proc加入到全局链表binder_procs。

 

binder_open()职责如下

  1. 首先创建了binder_proc结构体实例proc
  2. 接着开始初始化一系列成员:tsk, todo, default_priority, pid, delivered_death等。
  3. 更新了统计数据:binder_proc的创建个数加1
  4. 紧接着将初始化好的proc,存放到文件指针filp->private_data中,以便于在之后的mmap、ioctl中获取。
  5. 将binder_proc链入binder_procs哈希链表中;
  6. 最后查看是否创建的了/sys/kernel/debug/binde/proc/目录,有的话再创建一个/sys/kernel/debug/binde/proc/pid文件,用来记录binder_proc的状态
static HLIST_HEAD(binder_procs);
static int binder_open(struct inode *nodp, struct file *filp)
{
       struct binder_proc *proc;           // binder进程
       struct binder_device *binder_dev;   // binder device

       binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
                    current->group_leader->pid, current->pid);

       proc = kzalloc(sizeof(*proc), GFP_KERNEL);  // 为binder_proc结构体在分配kernel内存空间
       if (proc == NULL)
               return -ENOMEM;
       spin_lock_init(&proc->inner_lock);
       spin_lock_init(&proc->outer_lock);
       atomic_set(&proc->tmp_ref, 0);
       get_task_struct(current->group_leader);//增加线程引用计数
       proc->tsk = current->group_leader;     //将当前线程的task保存到binder进程的tsk
       mutex_init(&proc->files_lock);
       INIT_LIST_HEAD(&proc->todo);                 //初始化todo队列,用于存放待处理的请求(server端)
    //配置binder优先级
       if (binder_supported_policy(current->policy)) {
               proc->default_priority.sched_policy = current->policy;
               proc->default_priority.prio = current->normal_prio;
       } else {
               proc->default_priority.sched_policy = SCHED_NORMAL;
               proc->default_priority.prio = NICE_TO_PRIO(0);
       }

       binder_dev = container_of(filp->private_data, struct binder_device,
                                 miscdev);
       proc->context = &binder_dev->context;   //拿到binder device的context,传给binder_proc 
       binder_alloc_init(&proc->alloc);

       binder_stats_created(BINDER_STAT_PROC); //类型为BINDER_STAT_PROC对象的创建个数加1
       proc->pid = current->group_leader->pid; //记录当前进程的pid
       INIT_LIST_HEAD(&proc->delivered_death);
       INIT_LIST_HEAD(&proc->waiting_threads);
       filp->private_data = proc;  //将binder_proc存放在filp的private_data域,以便于在之后的mmap、ioctl中获取

       mutex_lock(&binder_procs_lock);
       hlist_add_head(&proc->proc_node, &binder_procs);    //将proc_node节点添加到binder_procs为表头的队列
       mutex_unlock(&binder_procs_lock);

    // 如果/sys/kernel/debug/binder/proc 目录存在,在该目录中创建相应pid对应的文件,名称为pid,用来记录binder_proc的状态
       if (binder_debugfs_dir_entry_proc) {
               char strbuf[11];

               snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
               proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
                       binder_debugfs_dir_entry_proc,
                       (void *)(unsigned long)proc->pid,
                       &binder_proc_fops);
       }

       return 0;
}

4.2.1 重要结构 binder_proc

binder_proc 与应用层的binder实体一一对应,每个进程调用open()打开binder驱动都会创建该结构体,用于管理IPC所需的各种信息。

其中有4个红黑树threads、nodes、refs_by_desc、refs_by_node, 在一个进程中,有多少“被其他进程进行跨进程调用的”binder实体,就会在该进程对应的nodes树中生成多少个红黑树节点。另一方面,一个进程要访问多少其他进程的binder实体,则必须在其refs_by_desc树中拥有对应的引用节点。

struct binder_proc {
       struct hlist_node proc_node;    //进程节点
       struct rb_root threads;         //记录执行传输动作的线程信息, binder_thread红黑树的根节点
       struct rb_root nodes;           //用于记录binder实体  ,binder_node红黑树的根节点,它是Server在Binder驱动中的体现
       struct rb_root refs_by_desc;    //记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以handle为key),它是Client在Binder驱动中的体现
       struct rb_root refs_by_node;    //记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以ptr为key),它是Client在Binder驱动中的体现
       struct list_head waiting_threads;
       int pid;    //相应进程id
       struct task_struct *tsk;    //相应进程的task结构体
       struct files_struct *files; //相应进程的文件结构体
       struct mutex files_lock;
       struct hlist_node deferred_work_node;
       int deferred_work;
       bool is_dead;

       struct list_head todo;      //进程将要做的事
       struct binder_stats stats;  //binder统计信息
       struct list_head delivered_death;   //已分发的死亡通知
       int max_threads;        //最大线程数
       int requested_threads;  //请求的线程数
       int requested_threads_started;  //已启动的请求线程数
       atomic_t tmp_ref;
       struct binder_priority default_priority;    //默认优先级
       struct dentry *debugfs_entry;
       struct binder_alloc alloc;
       struct binder_context *context;
       spinlock_t inner_lock;
       spinlock_t outer_lock;
};

binder_procs哈希链表, 存储了所有open() binder驱动的进程对象,如下图所示:

 

4.3 binder_mmap

主要功能:首先在内核虚拟地址空间,申请一块与用户虚拟内存相同大小的内存;然后再申请page物理内存,

再将同一块物理内存分别映射到内核虚拟地址空间和用户虚拟内存空间,从而实现了用户空间的Buffer和内核空间的Buffer同步操作的功能。

 

参数:

filp: 文件描述符

vma: 用户虚拟内存空间

 

流程:

  1. filp->private_data保存了我们open设备时创建的binder_proc信息;
  2. 为用户进程分配一块内核空间作为缓冲区;
  3. 把分配的缓冲区指针存放到binder_proc的buffer字段;
  4. 分配pages空间;
  5. 在内核分配一块同样页数的内核空间,并把它的物理内存和前面为用户进程分配的内存地址关联;
  6. 将刚才分配的内存块加入用户进程内存链表;
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
       int ret;
       struct binder_proc *proc = filp->private_data; //private_data保存了我们open设备时创建的binder_proc信息
       const char *failure_string;

       if (proc->tsk != current->group_leader)
               return -EINVAL;

       //vma->vm_end, vma->vm_start 指向要 映射的用户空间地址, map size 不允许 大于 4M
       if ((vma->vm_end - vma->vm_start) > SZ_4M)
               vma->vm_end = vma->vm_start + SZ_4M;
       ...
       //mmap 的 buffer 禁止用户进行写操作。mmap 只是为了分配内核空间,传递数据通过 ioctl()
       if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
               ret = -EPERM;
               failure_string = "bad vm_flags";
               goto err_bad_arg;
       }

       // 将 VM_DONTCOP 置起,禁止 拷贝,禁止 写操作
       vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
       vma->vm_flags &= ~VM_MAYWRITE;

       vma->vm_ops = &binder_vm_ops;
       vma->vm_private_data = proc;

       // 再次完善 binder buffer allocator
       ret = binder_alloc_mmap_handler(&proc->alloc, vma);
       if (ret)
               return ret;
       mutex_lock(&proc->files_lock);  //同步锁
       proc->files = get_files_struct(current);
       mutex_unlock(&proc->files_lock);    //释放锁
       return 0;

err_bad_arg:
       pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
              proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
       return ret;
}


int binder_alloc_mmap_handler(struct binder_alloc *alloc,
                             struct vm_area_struct *vma)
{
       int ret;
       const char *failure_string;
       struct binder_buffer *buffer;   //每一次Binder传输数据时,都会先从Binder内存缓存区中分配一个binder_buffer来存储传输数据

       mutex_lock(&binder_alloc_mmap_lock);    //同步锁
       if (alloc->buffer) {        // 不需要重复mmap
               ret = -EBUSY;
               failure_string = "already mapped";
               goto err_already_mapped;
       }

       alloc->buffer = (void __user *)vma->vm_start; //指向用户进程内核虚拟空间的 start地址
       mutex_unlock(&binder_alloc_mmap_lock);               //释放锁

   //分配物理页的指针数组,数组大小为vma的等效page个数
       alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
                                  ((vma->vm_end - vma->vm_start) / PAGE_SIZE),
                              GFP_KERNEL);
       if (alloc->pages == NULL) {
               ret = -ENOMEM;
               failure_string = "alloc page array";
               goto err_alloc_pages_failed;
       }
       alloc->buffer_size = vma->vm_end - vma->vm_start;

       buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);  //申请一个binder_buffer的内存
       if (!buffer) {
               ret = -ENOMEM;
               failure_string = "alloc buffer struct";
               goto err_alloc_buf_struct_failed;
       }

       buffer->user_data = alloc->buffer;                 //指向用户进程内核虚拟空间的 start地址,即为当前进程mmap的内核空间地址
       list_add(&buffer->entry, &alloc->buffers);  //将binder_buffer地址 加入到所属进程的buffers队列
       buffer->free = 1;
       binder_insert_free_buffer(alloc, buffer);   //将 当前 buffer 加入到 红黑树 alloc->free_buffers 中,表示当前 buffer 是空闲buffer
       alloc->free_async_space = alloc->buffer_size / 2; // 将 异步事务 的空间大小设置为 整个空间的一半
       barrier();
       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;

err_alloc_buf_struct_failed:
       kfree(alloc->pages);
       alloc->pages = NULL;
err_alloc_pages_failed:
       mutex_lock(&binder_alloc_mmap_lock);
       alloc->buffer = NULL;
err_already_mapped:
       mutex_unlock(&binder_alloc_mmap_lock);
       pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
              alloc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
       return ret;
}

 

4.3.1 重要结构 binder_buffer

每一次Binder传输数据时,都会先从Binder内存缓存区中分配一个binder_buffer来存储传输数据。

struct binder_buffer {
       struct list_head entry; //buffer实体的地址
       struct rb_node rb_node; //buffer实体的地址
       unsigned free:1;            //标记是否是空闲buffer,占位1bit
       unsigned allow_user_free:1;  //是否允许用户释放,占位1bit
       unsigned async_transaction:1;//占位1bit
       unsigned debug_id:29;          //占位29bit

       struct binder_transaction *transaction; //该缓存区的需要处理的事务

       struct binder_node *target_node; //该缓存区所需处理的Binder实体
       size_t data_size;          //数据大小
       size_t offsets_size;      //数据偏移量
       size_t extra_buffers_size;
       void __user *user_data;   //用户数据
};

 

4.3.3 内存分配情况

ServiceManager启动后,会通过系统调用mmap向内核空间申请128K的内存,用户进程会通过mmap向内核申请(1M-8K)的内存空间。

这里用户空间mmap (1M-8K)的空间,为什么要减去8K,而不是直接用1M?

 Android的git commit记录:

Modify the binder to request 1M - 2 pages instead of 1M. The backing store in the kernel requires a guard page, so 1M allocations fragment memory very badly. Subtracting a couple of pages so that they fit in a power of two allows the kernel to make more efficient use of its virtual address space.

大致的意思是:kernel的“backing store”需要一个保护页,这使得1M用来分配碎片内存时变得很差,所以这里减去两页来提高效率,因为减去一页就变成了奇数。

 

系统定义:BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)   = (1M- sysconf(_SC_PAGE_SIZE) * 2)        

这里的8K,其实就是两个PAGE的SIZE, 物理内存的划分是按PAGE(页)来划分的,一般情况下,一个Page的大小为4K。

内核会增加一个guard page,再加上内核本身的guard page,正好是两个page的大小,减去后,就是用户空间可用的大小。        

在内存分配这块,还要分为32位和64位,32位的系统很好区分,虚拟内存为4G,用户空间从低地址开始占用3G,内核空间占用剩余的1G。

ARM32内存占用分配:

但随着现在的硬件发展越来越迅速,应用程序的运算也越来越复杂,占用空间越来越大,原有的4G虚拟内存已经不能满足用户的需求,因此,现在的Android基本都是用64位的内存机制。

理论上讲,64位的地址总线可以支持高达16EB(2^64)的内存。AMD64架构支持52位(4PB)的地址总线和48位(256TB)的虚拟地址空间。在linux arm64中,如果页的大小为4KB,使用3级页表转换或者4级页表转换,用户空间和内核空间都支持有39bit(512GB)或者48bit(256TB)大小的虚拟地址空间。

2^64 次方太大了,Linux 内核只采用了 64 bits 的一部分(开启 CONFIG_ARM64_64K_PAGES 时使用 42 bits,页大小是 4K 时使用 39 bits),该文假设使用的页大小是 4K(VA_BITS = 39)

ARM64 有足够的虚拟地址,用户空间和内核空间可以有各自的 2^39 = 512GB 的虚拟地址。

ARM64内存占用分配:

用户地址空间(服务端-数据接收端)和内核地址空间都映射到同一块物理地址空间。

Client(数据发送端)先从自己的用户进程空间把IPC数据通过copy_from_user()拷贝到内核空间。而Server端(数据接收端)与内核共享数据(mmap到同一块物理内存),不再需要拷贝数据,而是通过内存地址空间的偏移量,即可获悉内存地址,整个过程只发生一次内存拷贝。

 图片来源于Gityuan

 

4.4 binder_ioctl

binder_ioctl()函数负责在两个进程间收发IPC数据和IPC reply数据,Native C\C++ 层传入不同的cmd和数据,根据cmd的值,进行相应的处理并返回

参数:

filp:文件描述符

cmd:ioctl命令

arg:数据类型

 

ioctl命令说明:


static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
       int ret;
   //filp->private_data 在open()binder驱动时,保存了一个创建的binder_proc,即是此时调用进程的binder_proc.
       struct binder_proc *proc = filp->private_data;
       //binder线程
       struct binder_thread *thread;
       unsigned int size = _IOC_SIZE(cmd);
       void __user *ubuf = (void __user *)arg;

       binder_selftest_alloc(&proc->alloc);

       trace_binder_ioctl(cmd, arg);
       //进入休眠状态,直到中断唤醒
       ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
       if (ret)
               goto err_unlocked;

       //获取binder线程信息,如果是第一次调用ioctl(),则会为该进程创建一个线程
       thread = binder_get_thread(proc);
       if (thread == NULL) {
               ret = -ENOMEM;
               goto err;
       }

       switch (cmd) {
       //binder的读写操作,使用评论较高
       case BINDER_WRITE_READ: 
               ret = binder_ioctl_write_read(filp, cmd, arg, thread);
               if (ret)
                       goto err;
               break;
   //设置Binder线程最大个数
       case BINDER_SET_MAX_THREADS: { 
               int max_threads;

               if (copy_from_user(&max_threads, ubuf,
                                  sizeof(max_threads))) {
                       ret = -EINVAL;
                       goto err;
               }
               binder_inner_proc_lock(proc);
               proc->max_threads = max_threads;
               binder_inner_proc_unlock(proc);
               break;
       }
   //设置Service Manager节点,带flag参数, servicemanager进程成为上下文管理者
       case BINDER_SET_CONTEXT_MGR_EXT: { 
               struct flat_binder_object fbo;

               if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
                       ret = -EINVAL;
                       goto err;
               }
               ret = binder_ioctl_set_ctx_mgr(filp, &fbo);
               if (ret)
                       goto err;
               break;
       }
   //设置Service Manager节点,不带flag参数, servicemanager进程成为上下文管理者
       case BINDER_SET_CONTEXT_MGR:
               ret = binder_ioctl_set_ctx_mgr(filp, NULL);
               if (ret)
                       goto err;
               break;
       ...
   //获取Binder版本信息
       case BINDER_VERSION: {
               struct binder_version __user *ver = ubuf;

               if (size != sizeof(struct binder_version)) {
                       ret = -EINVAL;
                       goto err;
               }
               if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                            &ver->protocol_version)) {
                       ret = -EINVAL;
                       goto err;
               }
               break;
       }
       ...
       default:
               ret = -EINVAL;
               goto err;
       }
       ret = 0;
err:
       if (thread)
               thread->looper_need_return = false;
       wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
       if (ret && ret != -ERESTARTSYS)
               pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
       trace_binder_ioctl_done(ret);
       return ret;
}

 

4.4.1 获取binder线程

方法:binder_get_thread()

作用:从当前进程中获取线程信息,如果当前进程中没有线程信息,那么创建一个线程,把proc指向当前进程,并进行线程初始化

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
       struct binder_thread *thread;
       struct binder_thread *new_thread;

       binder_inner_proc_lock(proc);
       //从当前进程中获取线程
       thread = binder_get_thread_ilocked(proc, NULL);
       binder_inner_proc_unlock(proc);
       if (!thread) {
              //如果当前进程中没有线程,那么创建一个
               new_thread = kzalloc(sizeof(*thread), GFP_KERNEL);
               if (new_thread == NULL)
                       return NULL;
               binder_inner_proc_lock(proc);
               thread = binder_get_thread_ilocked(proc, new_thread);
               binder_inner_proc_unlock(proc);
               if (thread != new_thread)
                       kfree(new_thread);
       }
       return thread;
}

binder_get_thread_ilocked()流程:

  1. 先遍历threads节点的红黑树链表;
  2. 如果没有查找到,则分配一个struct binder_thread长度的空间;
  3. 初始化等待队列头节点和thread的todo链表;
  4. 将该线程插入到进程的threads节点;
static struct binder_thread *binder_get_thread_ilocked(
               struct binder_proc *proc, struct binder_thread *new_thread)
{
       struct binder_thread *thread = NULL;
       struct rb_node *parent = NULL;
       struct rb_node **p = &proc->threads.rb_node;

       //根据当前进程的pid,从binder_proc中查找相应的binder_thread
       while (*p) {
               parent = *p;
               thread = rb_entry(parent, struct binder_thread, rb_node);

               if (current->pid < thread->pid)
                       p = &(*p)->rb_left;
               else if (current->pid > thread->pid)
                       p = &(*p)->rb_right;
               else
                       return thread;
       }
       if (!new_thread)
               return NULL;
       //若当前进程中没有线程信息,那么创建一个新的线程,并进行相应的初始化操作
       thread = new_thread;
       binder_stats_created(BINDER_STAT_THREAD);
       thread->proc = proc;                //线程的proc指向当前进程
       thread->pid = current->pid; //线程pid为当前进程的pid
       get_task_struct(current);
       thread->task = current;
       atomic_set(&thread->tmp_ref, 0);
       init_waitqueue_head(&thread->wait);
       INIT_LIST_HEAD(&thread->todo); //初始化等待队列头节点和thread的todo链表
       //把线程节点加入到proc的 threads红黑树中,平衡红黑树
       rb_link_node(&thread->rb_node, parent, p);
       rb_insert_color(&thread->rb_node, &proc->threads);
       thread->looper_need_return = true;
       thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
       thread->return_error.cmd = BR_OK;
       thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
       thread->reply_error.cmd = BR_OK;
       INIT_LIST_HEAD(&new_thread->waiting_thread_node);
       return thread;
}

 

4.4.1.1 重要结构 binder_thread

binder_thread结构体代表当前binder操作所在的线程

struct binder_thread {
       struct binder_proc *proc;   //线程所属的进程
       struct rb_node rb_node;         //红黑树节点
       struct list_head waiting_thread_node;
       int pid;                          //线程pid
       int looper;               //looper的状态
       bool looper_need_return;  
       struct binder_transaction *transaction_stack;   //线程正在处理的事务
       struct list_head todo;                   //将要处理的链表
       bool process_todo;
       struct binder_error return_error;   //write失败后,返回的错误码
       struct binder_error reply_error;
       wait_queue_head_t wait;                 //等待队列的队头
       struct binder_stats stats;          //binder线程的统计信息
       atomic_t tmp_ref;
       bool is_dead;
       struct task_struct *task;
};

4.4.2 ServiceManager守护进程设置

方法:binder_ioctl_set_ctx_mgr()

ServiceManager、HwServiceManager、VNDServiceManager,在Native C层通过ioctl()发送BINDER_SET_CONTEXT_MGR_EXT 命令,让自身成为上下文管理者,即各自的守护进程。

binder_ioctl_set_ctx_mgr()处理如下:

  1. open()binder驱动时得到的filp->private_data,存入binder_proc,代表当前进程的信息

  2. 检查当前进程是否具注册Context Manager的SELinux安全权限

  3. 进行uid检查,线程只能注册自己,且只能有一个线程设置为Context Manager

  4. 设置当前线程euid作为ServiceManager的uid

  5. 创建一个binder实体binder_node,并加入到当前进程的nodes红黑树中,我们这里可以是ServiceManager

  6. 把新创建的binder_node,赋值给当前进程的binder_context_mgr_node,这样该进程就成为了上下文的管理者,这是一个约定的过程

static int binder_ioctl_set_ctx_mgr(struct file *filp,
                                   struct flat_binder_object *fbo)
{
       int ret = 0;
       //filp->private_data 在open()binder驱动时,保存了一个创建的binder_proc,即是此时调用进程的binder_proc.
       struct binder_proc *proc = filp->private_data;
       //获得当前进程的context
       struct binder_context *context = proc->context;
       struct binder_node *new_node;
       kuid_t curr_euid = current_euid();

       mutex_lock(&context->context_mgr_node_lock);

   //保证只创建一次mgr_node对象
       if (context->binder_context_mgr_node) {
               pr_err("BINDER_SET_CONTEXT_MGR already set\n");
               ret = -EBUSY;
               goto out;
       }

   //检查当前进程是否具注册Context Manager的SEAndroid安全权限
       ret = security_binder_set_context_mgr(proc->tsk);
       if (ret < 0)
               goto out;
   //检查已的uid是否有效
       if (uid_valid(context->binder_context_mgr_uid)) {
               //uid有效但是与当前运行线程的效用户ID不相等,则出错。
               //即线程只能注册自己,且只能有一个线程设置为Context Manager
               if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
                       pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
                              from_kuid(&init_user_ns, curr_euid),
                              from_kuid(&init_user_ns,
                                        context->binder_context_mgr_uid));
                       ret = -EPERM;
                       goto out;
               }
       } else {
               //设置当前线程euid作为ServiceManager的uid
               context->binder_context_mgr_uid = curr_euid;
       }

       //创建binder实体,并加入到当前进程的nodes红黑树中,我们这里可以是ServiceManager
       new_node = binder_new_node(proc, fbo);
       if (!new_node) {
               ret = -ENOMEM;
               goto out;
       }
       binder_node_lock(new_node);
       //更新new_node的相关强弱引用计数
       new_node->local_weak_refs++;
       new_node->local_strong_refs++;
       new_node->has_strong_ref = 1;
       new_node->has_weak_ref = 1;
   //new_node 赋值给进程的上下文管理节点,作为上下文管理者
       context->binder_context_mgr_node = new_node;
       binder_node_unlock(new_node);
       binder_put_node(new_node);
out:
       mutex_unlock(&context->context_mgr_node_lock);
       return ret;
}

4.4.2.1 重要结构 binder_node

binder_node代表binder实体

struct binder_node {
       int debug_id;   //节点创建时分配,具有全局唯一性,用于调试使用
       spinlock_t lock;
       struct binder_work work;
       union {
               struct rb_node rb_node;          //binder节点正常使用,union
               struct hlist_node dead_node;//binder节点已销毁,union
       };
       struct binder_proc *proc;   //binder所在的进程
       struct hlist_head refs;     //所有指向该节点的binder引用队列
       int internal_strong_refs;
       int local_weak_refs;
       int local_strong_refs;
       int tmp_refs;
       binder_uintptr_t ptr;     //指向用户空间binder_node的指针,对应flat_binder_object.binder
       binder_uintptr_t cookie;   //数据,对应flat_binder_object.cookie
       struct {
               /*
                * bitfield elements protected by
                * proc inner_lock
                */
               u8 has_strong_ref:1;
               u8 pending_strong_ref:1;
               u8 has_weak_ref:1;
               u8 pending_weak_ref:1;
       };
       struct {
               /*
                * invariant after initialization
                */
               u8 sched_policy:2;
               u8 inherit_rt:1;
               u8 accept_fds:1;
               u8 txn_security_ctx:1;
               u8 min_priority;
       };
       bool has_async_transaction;
       struct list_head async_todo;    //异步todo队列
};

 

4.4.3 Binder读写操作

方法:binder_ioctl_write_read()

作用:根据从用户空间传来的binder_write_read数据进行判断,是否进行读写操作, 这也是binder数据交互的核心入口。

流程如下:

  1. 如果write_size大于0,表示用户进程有数据发送到驱动,则调用binder_thread_write()发送数据,binder_thread_write()中有错误发生,则read_consumed设为0,表示kernel没有数据返回给进程;
  2. 如果read_size大于0, 表示进程用户态地址空间希望有数据返回给它,则调用binder_thread_read()进行处理,读取完后,如果proc->todo链表不为空,则唤醒在proc->wait等待队列上的进程,如果binder_thread_read返回小于0,可能处理一半就中断了,需要将bwr拷贝回进程的用户态地址;
  3. 处理成功的情况,也需要将bwr拷贝回进程的用户态地址空间

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;
       }
       if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
               ret = -EFAULT;
               goto out;
       }
       ...
       if (bwr.write_size > 0) {
               //write_size大于0,表示用户进程有数据发送到驱动,则调用binder_thread_write发送数据
               ret = binder_thread_write(proc, thread,
                                         bwr.write_buffer,
                                         bwr.write_size,
                                         &bwr.write_consumed);
               trace_binder_write_done(ret);
               if (ret < 0) {
                       //binder_thread_write中有错误发生,则read_consumed设为0,表示kernel没有数据返回给进程
                       bwr.read_consumed = 0;
                       //将bwr返回给用户态调用者,bwr在binder_thread_write中会被修改
                       if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                               ret = -EFAULT;
                       goto out;
               }
       }
       //read_size大于0, 表示进程用户态地址空间希望有数据返回给它,则调用binder_thread_read进行处理
       if (bwr.read_size > 0) {
               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);
               //读取完后,如果proc->todo链表不为空,则唤醒在proc->wait等待队列上的进程
               if (!binder_worklist_empty_ilocked(&proc->todo))
                       binder_wakeup_proc_ilocked(proc);
               binder_inner_proc_unlock(proc);
               if (ret < 0) {
                       //如果binder_thread_read返回小于0,可能处理一半就中断了,需要将bwr拷贝回进程的用户态地址
                       if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                               ret = -EFAULT;
                       goto out;
               }
       }
       ...
   //处理成功的情况,也需要将bwr拷贝回进程的用户态地址空间
       if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
               ret = -EFAULT;
               goto out;
       }
out:
       return ret;
}

 

4.4.3.1 重要结构 binder_write_read

binder的读写结构, 记录了binder中读和写的数据信息

struct binder_write_read {
       binder_size_t       write_size;     //要写入的字节数,write_buffer的总字节数
       binder_size_t       write_consumed; //驱动程序占用的字节数,write_buffer已消费的字节数
       binder_uintptr_t    write_buffer;   //写缓冲数据的指针
       binder_size_t       read_size;      //要读的字节数,read_buffer的总字节数
       binder_size_t       read_consumed;  //驱动程序占用的字节数,read_buffer已消费的字节数
       binder_uintptr_t    read_buffer;    //读缓存数据的指针
};

binder_thread_write() 和 binder_thread_read()的逻辑太冗长,加起来有1千多行,放到下一节的《Binder数据定向打击》 进行单独分析

 

6.红黑树分析

6.1 为什么binder驱动中要用到红黑树

其实我也不知道为什么,采用红黑树,说明我们要存储数据,要复合插入、修改、删除、查找等操作。既然不知道为什么,那就找几个复合类型的数据结构做个比较:
1)数组

优点:内存大小固定,操作流程简单,查找数据方便;

缺点:内存要么很大,会浪费内存,要么很小,数据存储空间不够,删除或插入数据比较麻烦;

 

2)链表

优点:在内存中可以存在任何地方,不要求连续,不指定大小,扩展方便。链表大小不用定义,数据随意增删,增加数据和删除数据很容易;

缺点:查找数据时效率低,因为不具随机访问性,所以访问某个位置的数据都要从第一个数据开始访问,然后根据第一个数据保存的下一个数据的地址找到第二个数据,以此类推。要找到第三个人,必须从第一个人开始问起;

 

3)二叉树

 特点:左子树的节点值比父亲节点小,而右子树的节点值比父亲节点大

 优点:快速查找,不需要为二叉树预先分配固定的空间,所的元素在树中是排序好的

 缺点:极端情况下,时间复杂度会由O(logn)变成O(n),即变成了一种链式结构

 

4)平衡二叉树

特点:具有二叉树的全部特性,每个节点的左子树和右子树的高度差至多等于1 

优点:具有二叉树的所有优点,并解决了二叉树的 链式极端情况,时间复杂度保持在O(logn)

缺点:每次进行插入/删除节点的时候,几乎都会破坏平衡树的规则(每个节点的左子树和右子树的高度差至多等于1 ),每次都需要调整,使得性能大打折扣

 

5)红黑树

特点:具有二叉树的特点;根节点是黑色的;叶子节点不存数据;任何相邻的节点都不能同时为红色;每个节点,从该节点到达其可达的叶子节点是所路径,都包含相同数目的黑色节点

优点:具有二叉树所有特点,与平衡树不同的是,红黑树在插入、删除等操作,不会像平衡树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,减少性能消耗;

缺点:代码复杂,查找效率比平衡二叉树低

 

通过上面几个存储的数据结构,当我们需要频繁对数据进行插入、修改、删除、查找时,我们的选择如下

平衡二叉树\红黑树 > 二叉树 > 链表 > 数组

其中红黑树虽然查找效率比平衡二叉树低,但是减少了性能消耗,这在内核中尤为重要,因此binder驱动中使用了红黑树。

当然上面的流程也只是我的推测,也有可能谷歌工程师开发时就喜欢用红黑树呢,当然这是题外话,不影响我们继续撸代码。

 

6.2 binder_proc 中的4棵红黑树

struct binder_proc {
    struct hlist_node proc_node;
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    ...
}

threads 执行传输动作的线程信息, nodes 记录binder实体,refs_by_desc 和refs_by_node 记录binder代理, 便于快速查找

以nodes树为例,每个binder_node中都有一个rb_node节点,rb_node 又分为左子树和右子树,如图所示:

truct rb_root {
    struct rb_node *rb_node;
};

struct rb_node {
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

 

6.3 Binder 中红黑树的转换

在Binder驱动中,会为每个Client创建对应的Binder引用,即会为每个Client创建binder_ref对象;会为每一个Server都创建一个Binder实体,即会为每个Server都创建一个binder_node对象

 "Binder实体"和"Binder引用"可以很好的将Server和Client关联起来:因为Binder实体和Binder引用分别是Server和Client在Binder驱动中的体现。Client获取到Server对象后,"Binder引用所引用的Biner实体(即binder_ref.node)" 会指向 "Server对应的Biner实体";同样的,Server被某个Client引用之后,"Server对应的Binder实体的引用列表(即,binder_node.refs)" 会包含 "Client对应的Binder引用"。

 

7.Binder协议码

BC码--Binder响应码:

 

BR码--Binder回复码:

代码路径:

\kernel\msm-4.9\drivers\android\binder.c

\kernel\msm-4.9\drivers\android\binder_alloc.c

github 中 LineageOS Kernel4.9代码下载地址:

https://github.com/LineageOS/android_kernel_google_msm-4.9

 

我的微信公众号:IngresGe

 

参考:

《Binder机制情景分析之深入驱动》

《Binder系列1—Binder Driver初探》

《Binder(传输机制篇_上)》

《Binder中的数据结构》

《linux内核中的红黑树代码解析》

 

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • Android10.0 Binder通信原理(二)-Binder入门篇
    摘要:本节主要来讲解Android10.0 Binder的设计原理,如何设计一个Binder通信 阅读本文大约需要花费15分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(一)工作原理及启动流程Android 10.0 PackageManagerService(二)权限扫描Android 10.0 PackageManagerService(三)APK扫描Android 10.0 PackageManagerService(四)APK安装流程 《日志系统篇》
  • Android 10.0 PackageManagerService(二)权限扫描-[Android取经之路]
    摘要:PackageManagerService在systemReady()后,进行了/system/etc/permissions中的各种xml进行扫描,进行相应的权限存储,供以后使用 阅读本文大约需要花费15分钟。 文章的内容主要还是从源码进行分析,虽然又臭又长,但是如果想要学习Android系统源码,这是必要走的路,没有捷径。 相对于碎片学习,我更倾向于静下心来花费1个小时认真的学习一段内容。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(¸
  • Android 性能优化必知必会(2020-5-16)
    做了这么久性能相关的工作,也接触了不少模块,说实话要做好性能这一块,真心不容易.为什么这么说? 是因为需要接触的知识实在是太多了, Android 是一个整体,牵一发而动全身,不是说只懂一个模块就可以做好 在学习的过程中,除了看源码,我还接触到了很多互联网上已有的知识,各位前辈们,将他们的知识和经验倾囊相授,让我少走了很多弯路. 我在自己的笔记里面存了很多很优秀的技术文章和技术文档,现在我决定将这些放到网上,让每一个想进入 Android 系统开发和优化这个领域的人,能通过阅读这篇文章,快速入门. 同时也算是我对知识的一个梳理,查漏补缺,终身学习 这篇文章记录了 Android 性能优化所必须掌握的知识,涵盖性能优化相关的方方面面(当然如果读者同学你也有很棒的私藏文章,也可以加入到这篇文章里面).部分文章可能需要特殊的技巧才能看到,希望你已经掌握了这一部分技巧。另外附送Android 开发者学习路线(2020 版本) 这篇文章会持续更新,最新更新时间:2020-04-27. 更多 Android 相关知识文章可以去个人博客 优化心得和经验 系列视频 Android Performance Patterns给 App 提速:Android 性能优化总结 移动端性能监控方案 Hertz Android 性能优化后续Android性能优化之虚拟机调优Android UI
  • Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]
    摘要:本节主要来讲解Android10.0 日志系统的架构分析,以及logd、logcat的初始化操作 阅读本文大约需要花费15分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路]系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(一)工作原理及启动流程Android 10.0 PackageManagerService(二)权限扫描Android 10.0 PackageManagerService(三)APK扫描Android 10.0 PackageManagerService(四)APK安装流程 《日志系统篇》
  • Android 10.0 PackageManagerService(一)工作原理及启动流程-[Android取经之路]
    摘要:PackageManagerService是Android系统核心服务之一,在Android中的非常重要,主要负责APK、jar包等的管理。 阅读本文大约需要花费50分钟。 文章的内容主要还是从源码进行分析,虽然又臭又长,但是如果想要学习Android系统源码,这是必要走的路,没有捷径。 相对于碎片学习,我更倾向于静下心来花费1个小时认真的学习一段内容。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(一)工作原理及启动流程Android 10.0
  • Android10.0应用进程创建过程以及Zygote的fork流程-[Android取经之路]
    摘要:点击手机桌面图标,例如微信,它是如何启动的呢,让我们从系统源码级来一起分析。 阅读本文大约需要花费1小时。 文章的内容主要还是从源码进行分析,虽然又臭又长,但是如果想要学习Android系统源码,这是必要走的路,没有捷径。 相对于碎片学习,我更倾向于静下心来花费1个小时认真的学习一段内容。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 上一节我们讲完了Launcher的启动流程,这一节来看看应用进程的创建过程以及Zygote的fork流程。 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(¸
  • Android 10.0 PackageManagerService(四)APK安装流程-[Android取经之路]
    摘要:上一节讲解了APK的扫描,本节讲解APK的安装流程 阅读本文大约需要花费40分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众号! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构Android是怎么启动的Android 10.0系统启动之init进程Android10.0系统启动之Zygote进程Android 10.0 系统启动之SystemServer进程Android 10.0 系统服务之ActivityMnagerServiceAndroid10.0系统启动之Launcher(桌面)启动流程Android10.0应用进程创建过程以及Zygote的fork流程Android 10.0 PackageManagerService(一)工作原理及启动流程Android 10.0 PackageManagerService(二)权限扫描Android 10.0 PackageManagerService(三)APK扫描Android 10.0 PackageManagerService(四)APK安装流程 《日志系统篇》 Android10.0 日志系统分析(一)
  • Android 开发一比一年难做!面试题都这么难了
    前言 Android 开发如今的行情想必大家应该都有了解,如果有不了解的小伙伴可以去看我的这篇文章《都2021年了,你怎么看待Android 开发前景?》,前几年,Android开发火爆一时,风头一般无二,很多Java,c+都转过来分一杯羹。市场暴涨的红利消失趋于稳定后,转Android的热潮渐渐退隐,现在,Android程序员都想成为架构师,但这期间,需要付出的辛苦和努力远超过我们的想象。 据Android 行业调研数据发现:97% 的Android开发 技术人都会面临以下这些困境: 身处外包公司/小型团队,技术闭塞 如果你长期在小型软件公司或外包公司工作,那么你的技术是很难有机会经历完整且大型项目的开发,整个技术视野会比较窄,导致薪资长期处于停滞不前。重复同样的编码工作,一年经验重复多年 长期重复同样的编码工作,项目对你的技术要求就是那些,不会让你按照自己的期望去发展,导致很长时间自己的能力都无法突破,企业更是难以接受新技术。自控力差,没时间学习 白天工作,晚上加班,只能依靠碎片化的时间学习,如果自控力比较差,基本就三天打鱼两天晒网,无法系统性学习,无法沉淀自己的技术实力;目前的简历,难进大厂 现有的技术能力、项目经验,无法通过大厂简历初筛,更别说拿到大厂的高薪 Offer。 这些问题,当时你还不会觉得有什么,等到你想跳槽,想要升职的时候,他们就会成为你职业发展的绊脚石
  • Android 系统架构图
    Android 操作系统架构开篇: http://gityuan.com/android/ https://cloud.tencent.com/developer/article/1429122 Android架构图(五层框架):https://www.cnblogs.com/pengdonglin137/p/3858254.html 官方系统架构图:https://developer.android.google.cn/guide/platform/ Android Studio官方介绍:https://developer.android.google.cn/studio/ Android Studio官方详解:https://developer.android.google.cn/studio/intro/ Android控件架构:https://cloud.tencent.com/developer/article/1337229 一、引言 虽然 Android 系统非常庞大且错综复杂,需要具备全面的技术栈,但整体架构设计清晰。Android 底层内核空间以 Linux Kernel 作为基石,上层用户空间由 Native系统库、虚拟机运行环境、框架层组成,通过系统调用(Syscall)连通系统的内核空间 与 用户空间。对于用户空间主要采用 C++ 和 Java 代码编写
  • Android C++底层Binder通信机制原理分析总结【通俗易懂】
    Android C++底层Binder通信机制原理总结: 通过transact()方法可以向远端的IBinder对象发出调用,再通过onTransact()函数让你自己的远程对象能够响应接收到的调用。 例如MediaRecorder的继承关系(向右箭头表示继承关系,#XXX#中间是关键方法,/斜线表示多继承): 客户端: 1、MediaRecorder->BnMediaRecorderClient#onTransact()#->BnInterface-> BBinder#transact()#(->IBinder->RefBase)/IMediaRecorderClient#notify(xx)/asInterface(IBinder obj)#(->IInterface->RefBase) 2、代理:BpMediaRecorderClient(IBinder impl)->BpInterface(IBinder remote)-> BpRefBase(IBinder o)#remote()#->RefBase/IMediaRecorderClient#notify(xx)/asInterface(IBinder obj)#(->IInterface->RefBase) 服务器: 3、MediaRecorderClient->BnMediaRecorder->BnInterface
  • Android Binder框架实现之Binder的设计思想
         Android Binder框架实现之Binder的设计思想 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Binder框架实现之何为匿名/实名Binder Android Binder通信一次拷贝你真的理解了吗? Android Binder框架实现之Binder中的数据结构 Android Binder框架实现之Binder相关的接口和类 Android Binder框架实现之Parcel详解之基本数据的读写 Android Binder框架实现之Parcel read/writeStrongBinder实现 Android Binder框架实现之servicemanager守护进程 Android Binder框架实现之defaultServiceManager()的实现 Android Binder框架实现之Native层addService详解之请求的发送 Android Binder框架实现之Native层addService详解之请求的处理 Android Binder框架实现之Native层addService详解之请求的反馈 Android Binder框架实现之Binder服务的消息循环 Android Binder框架实现之Native层getService详解之请求的发送
  • 大厂技术总监总结的Android Framework开发笔记火了!知乎已1.7k赞!不吃透都对不起他
    为什么要学Android Framework? 想要成为一名优秀的Android开发,就需要有一个完备的知识体系,Android Framework 的知识是很重要的一个组成部分,他广泛的应用在各个领域。 像掉帧监控,函数插装,慢函数检测,ANR 监控,启动监控,都需要对 Framework 有比较深入的了解。只有这样才能知道怎么去做监控,利用什么机制去监控,函数插桩插到哪里,反射调用该反射哪个类哪个方法哪个属性…… 另外 Framework 作为 Android 框架层,为 App 提供了众多 API 去调用 ,但是很多机制都是 Framework 包装好了给 App 来用的,如果不知道这些机制的原理,那么很难去在这基础上做优化。 举几个栗子 如果你了解 Android App 的启动机制,优化启动速度的时候会更得心应手:定制什么样的 StartingWindow;什么时候可以拿到图片的宽高;DelayLoad 怎么做才会更合适;Service 什么时候启动可以不影响启动速度;Activity onResume 回调的时候真的可见了么?Redex 为什么会加快应用启动速度?ContentProvider 会不会影响启动速度?为什么会影响? 再比如我们经常说的 Handler,MessageQueue,Looper。看源码你就可以更好的理解那些概念:ThreadLocal 做什么的
  • Android Binder原理解析
    前言 IPC 是 Inter-Process Communication 的缩写,含义为进程间通信。 Binder是一个很深入的话题,本篇文章不打算深入探讨Binder的底层细节,重点介绍Binder的使用以及上层原理。 Binder 是Android中的一个类,他实现了IBinder接口。Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,他的设驱动是/dev/binder。从Framework层来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。从Android应用层来说,Binder是客户端和服务端进行通信的媒介,通过binderService去绑定远程服务,进行通信。 AIDL中支持的数据类型 基本数据类型(int、long、char、boolean、double等)String 和CharSequenceList:只支持ArrayList,里面每个元素都必须能够被AIDL支持Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key。Parcelable:所有实现了Parcelable接口的对象。AIDL:所有的AIDL接口本身也可以在AIDL中使用。 AIDL生成结构图 下面我们实现一个简单的AIDL并分析一下他的通信过程 Intent intent
  • Binder驱动分析 Parcel写入Binder对象
    前言 本文是参考Android Binder机制(五) addService详解01之 请求的发送 来总结Parcel是如何写入Binder对象 Parcel是Framework层中的一个类,对应java层中Parcelable, 在Binder驱动下,客户端与服务端进行跨进程通信时会构件内一个Parcel类对象然后放入具体数据在向下传输。 我们举例说明MediaPlaySerice向ServiceManager调用addSerivce的时候Parcel是如何写入的. Parcel 类介绍 Parcel声明和实现位于frameworks/native/libs/binder/Parcel.h ,frameworks/native/libs/binder/Parcel.cpp 由于类内部属性过多,我们只选取部分重要字段进行说明. class Parcel { //指针 指向存放所有数据的地址 uint8_t* mData; //mData 所指向内存区域已经写入的数据大小 size_t mDataSize; //mData 所指向的内存区域最大可写入容量 size_t mDataCapacity; //下一次写入的位置 mutable size_t mDataPos; } 举个例子 Parcel p; p.writeInt32(15) 写入writeInt32(15)
  • 实现原理讲解!分析Android未来几年的发展前景,顺利通过阿里Android岗面试
    开头 都说程序员是在吃青春饭,这一点的确有一点对的成分,以前我不这么认为,但随着年龄的增长,事实告诉我的确是这样的,过了30以后,就会发现身体各方面指标下降,体力和身心上都多少有点跟不上了,这个年龄往往是很尴尬的,与年轻的程序员相比,产出没人家高,但公司还要为你发着高的薪资,没有更优秀的表现凭什么让公司发高工资呢,因此这部分人就是进入了所谓的中年危机,为了帮助这部分朋友成功的渡过中年危机,我大概梳理出以下思路。 1.框架源码+使用 okhttp源码 同步 异步处理责任链缓存连接池 retrofit 动态代理源码总结 Rxjava的使用 mp的连接功能防抖回调统一线程切换源码分析 了解原理(源码) gsonglide 2.四大组件 activity 生命周期启动模式(启动模式的应用场景)场景切换下的生命周期 service 两种状态三种绑定生命周期通信方式 Broadcast 主要作用广播的原理使用两种注册方式广播执行顺序 ContentProvider 原理使用 3.View相关 view的事件分发 流程不同的事件ACTION -CANCEL机制 滑动冲突的解决view的工作原理自定义viewrecyclerview 缓存和listview的相比有点 listview 优化如何实现缓存 view几个标签的作用view动画 4.Android基础库 mvp.mvc
  • 火爆知乎的Android面试题-谈谈Android-Binder机制及AIDL使用,学习路线+知识点梳理
    一,鸿蒙核心内容掌握程度 看看下面这些鸿蒙知识点你掌握了多少: 基础环境和开发工具 开发工具安装 运行开发工具完成基础配置DevEco Studio 运行第一个hello world 运行第一个页面 通过代码创建页面 Feature Ability 编程实现页面跳转 市面上的鸿蒙教程大多仅限于理论知识讲解,很少有具体的实现方案案例.在这里小编给大家分享 一份《全面最全最系统的鸿蒙学习笔记》 笔记带你2个礼拜吃透鸿蒙技术开发里的核心原理问题及解决方案,有需要这份鸿蒙学习笔记的朋友看文末有免费的获取方式! 1.框架源码+使用 okhttp源码 同步 异步处理责任链缓存连接池 retrofit 动态代理源码总结 Rxjava的使用 mp的连接功能防抖回调统一线程切换源码分析 了解原理(源码) gsonglide 2.四大组件 activity 生命周期启动模式(启动模式的应用场景)场景切换下的生命周期 service 两种状态三种绑定生命周期通信方式 Broadcast 主要作用广播的原理使用两种注册方式广播执行顺序 ContentProvider 原理使用 3.View相关 view的事件分发 流程不同的事件ACTION -CANCEL机制 滑动冲突的解决view的工作原理自定义viewrecyclerview 缓存和listview的相比有点 listview 优化如何实现缓存
  • MMKV框架原理解密之05-Binder关于mmap函数面试流程 (续集)
    谈谈你对 binder 的理解? 面试官提了一个问题,我们来看看 😎、😨 和 🤔️ 三位同学的表现如何吧 😎 自认为无所不知,水平已达应用开发天花板,目前月薪 10k 面试官️:谈谈你对 binder 的理解 😎:binder 是用来跨进程通信的,可以分为 client、server、binder 驱动以及 service manager 四部分。 面试官:一次拷贝原理知道吗? 😎:不太清楚,其实对应用开发来说,没必要知道的。 面试官:好的,回去等通知吧 😨 业余时间经常打游戏、追剧、熬夜,目前月薪 15k 面试官:谈谈你对 binder 的理解 😨:binder 是一种 IPC 方式,相比于 Linux 原有的管道、共享内存、Socket 等,它通过 mmap 实现一次拷贝,比 Socket 、管道传输速度更快,比共享内存更安全可控,是 Android 系统中主要的 IPC 通信方式。 面试官:Intent 传参有大小限制,这跟 binder 有关系吗? 😨:嗯… 应该有关系吧 面试官:binder 是如何限制这个大小的? 😨:这个不了解,我还没有深入看过相关源码。 面试官:好的,回去等通知吧 🤔️ 坚持每天学习、不断的提升自己,目前月薪 30k 面试官:谈谈你对 binder 的理解 🤔️:binder 是 Android 中主要的跨进程通信方式,binder 驱动和
  • 史上最全,1307页Android面试全套真题解析,P7大神秃头整理
    前言 下面的题目都是在Android交流群大家在面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。 参考解析:郭霖、鸿洋、玉刚、极客时间、腾讯课堂… 内容特点:条理清晰,含图像化表示更加易懂。 内容概要:包括 Handler、Activity相关、Fragment、service、布局优化、AsyncTask相关 、Android 事件分发机制、 Binder、Android 高级必备 :AMS,WMS,PMS、Glide、 Android 组件化与插件化等面试题和技术栈! Handler 相关知识,面试必问! 常问的点: Handler Looper Message 关系是什么? Messagequeue 的数据结构是什么?为什么要用这个数据结构? 如何在子线程中创建 Handler? Handler post 方法原理? Android消息机制的原理及源码解析 Android Handler 消息机制 Activity 相关 启动模式以及使用场景? onNewIntent()和onConfigurationChanged() onSaveInstanceState()和onRestoreInstanceState() Activity 到底是如何启动的 启动模式以及使用场景
  • 被字节跳动、小米、美团面试官问的AndroidFramework难倒了? 这里有23道面试真题,助力成为offer收割机!
    目录 1.Android中多进程通信的方式有哪些? a.进程通信你用过哪些?原理是什么?(字节跳动、小米) 2.描述下Binder机制原理?(东方头条) 3.Binder线程池的工作过程是什么样?(东方头条) 4.Handler怎么进行线程通信,原理是什么?(东方头条) 5.Handler如果没有消息处理是阻塞的还是非阻塞的?(字节跳动、小米) 6.handler.post(Runnable) runnable是如何执行的?(字节跳动、小米) 7.handler的Callback和handlemessage都存在,但callback返回true handleMessage还会执行么?(字节跳动、小米) 8.Handler的sendMessage和postDelay的区别?(字节跳动) 9.IdleHandler是什么?怎么使用,能解决什么问题? 10.为什么Looper.loop不阻塞主线程? a.Looper无限循环为啥没有ANR(B站) 11.Looper如何在子线程中创建?(字节跳动、小米) 12.Looper、handler、线程间的关系。例如一个线程可以有几个Looper可以对应几个Handler?(字节跳动、小米) 13.如何更新UI,为什么子线程不能更新UI?(美团) 14.ThreadLocal的原理,以及在Looper是如何应用的?(字节跳动、小米) 15
  • IPC机制 Binder
    引言 Binder是Android系统中最重要的特性之一。正如其名“粘合剂”,它是粘合系统间各个组件的桥梁,Android系统的开放式设计也很大程度上得益于这种跨进程的通信机制。 理解Binder对于理解整个Android系统有着非常重要的作用,Android系统的四大组件,AMS,PMS等系统服务无一不与Binder挂钩;如果对Binder不甚了解,那么就很难了解这些系统机制,从而仅仅浮游与表面,不懂Binder你都不好意思说自己会Android开发;要深入Android,Binder是必须迈出的一步。 但是,Binder机制终究不是三言两语就能解释清楚的,一上来就扒出源码很可能深陷细节无法自拔,如果看不懂强行看很容易睡着;勉强看完还是云里雾里;相反如果直接大谈特谈Binder的设计,那么完全就是不知所云。对于初学者并不友好,本文不会深入源码细节,也不会对于Binder的设计高谈阔论。如果你已经对Binder机制有了一定的了解,但是又想深入了解,我建议你可以先看一下如下倆篇,个人理解是网络资料讲述的最好的,最后您可以在查看一下源代码,最终定有收获。 1. Android Bander设计与实现 - 设计篇 2. Android进程间通信(IPC)机制Binder简要介绍和学习计划 而本篇的重点大致可以分为如下几点。 #mermaid-svg-aDXk5N5SahzF3q5s