05. Android Binder图解 小米权威系统专家 解析binder总结调用流程 (安卓12)

需要掌握的点

1.一个流程:servier--->binder---->servermanager ** server注册的流程图一个整体的类和方法流向**

2.重要的几个方法:open ,mmap ,iocontro。特别是mmap内存映射 (图片解析)

3.唤醒流程图,bp和br

4.binder_thread_write 和 binder_thread_read 和 copy_from_user 和copy_to_user

5.Java层,分析总的一个流程图,4个角色的关系和代理的关系。从一个activityManager开始!

6.数据流向?

1642653981295-7oj.png

  1. 流程总结:

[图片上传失败...(image-9fecb0-1643110660184)]

1).Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。

2). 有了服务端,客户端就可以跟服务端通讯了,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用

获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。

3).有了服务端的引用我们就可以向服务端发送请求了,通过 BinderProxy 将我们的请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。

2.重要的几个方法:open ,mmap ,iocontro。特别是mmap内存映射

内存的拷贝次数有几次?

肯定不是 1 次,服务端把数据拷到内核再拷回去 2 次,客户端把数据拷到内核再拷回去 2 次,还有一次是客户端把数据拷到服务端映射的内存中,还有一次是从服务端的映射内存拷贝到驱动层创建的内存。6 次拷贝

[图片上传失败...(image-36616-1643110660185)]

一次拷贝的过程:

发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内(这一次拷贝),核缓存区,

由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

Binder在一次拷贝中的作用则是怎么样的?

连接 两个进程,实现了mmap()系统调用,主要负责 创建数据接收的缓存空间 & 管理数据接收缓存

3、唤醒流程图,bp和br 解析

内核层内核层的通信都是通过ioctl来进行的,client打开一个ioctl,进入到轮询队列,一直阻塞直到时间到或者有消息。

[图片上传失败...(image-f9baa9-1643110660185)]

流程:

1.开始servermanager处于等待状态

2.然后media进程发送BP_transation

3.binder收到后发送br_transation。把servermanager唤醒,同时br_transtation_complete发送给客户端

4.客户端收到指令后,客户端处于等待的状态!

5.同上serverManager收到指令后。开始执行执行完后发送br_replay给binder驱动。同时还会发送

br_transtation_complete给servermanager。binder驱动发送br_rep给客户端唤醒

问题:

.是先serverManager在进行循环等待吗?然后客户端进行请求add吗?

4.binder_thread_write 和 binder_thread_read 和 copy_from_user 和copy_to_user 关系

binder_thread_write :主要作用:

binder_thread_read :主要作用:

copy_from_user :主要作用:

copy_to_user :主要作用:

调用流程:

binder_ioctl

    binder_ioctl_write_read

                  copy_from_user

                   binder_thread_write

binder_thread_read

                    copy_to_user

binder_thread_write里面又会调用 copy_from_user

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)
{
return_error == BR_OK) {

       switch (cmd) {
       ...
       case BC_TRANSACTION:
       case BC_REPLY: {
           struct binder_transaction_data tr;
           // 把数据从用户空间拷贝到内核空间 binder_transaction_data 
           if (copy_from_user(&tr, ptr, sizeof(tr)))

           binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
           break;
       }
   }
   return 0;

binder_thread_read里面又会调用copy_to_user

static int binder_thread_read(struct binder_proc *proc,
                 struct binder_thread *thread,
                 binder_uintptr_t binder_buffer, size_t size,
                 binder_size_t *consumed, int non_block)
{
   // 开始不断循环读取数据 
       switch (w->type) {
       case BINDER_WORK_TRANSACTION: {

       ptr += sizeof(uint32_t);
       //  把数据拷贝到用户空间
       if (copy_to_user(ptr, &tr, sizeof(tr)))
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
   int ret;
   // 从 filp 获取 binder_proc 
   struct binder_proc *proc = filp->private_data;
   struct binder_thread *thread;
   // 进入休眠直到中断被唤醒
   ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
   if (ret)
       goto err_unlocked;
   // 从 binder_proc 获取 binder 线程
   thread = binder_get_thread(proc);

   switch (cmd) {
   case BINDER_WRITE_READ:
       ret = binder_ioctl_write_read(filp, cmd, arg, thread);

static int binder_ioctl_write_read(struct file *filp,
               unsigned int cmd, unsigned long arg,
               struct binder_thread *thread)
{
   int ret = 0;
   // 从 filp 中获取 binder_proc 
   struct binder_proc *proc = filp->private_data;
   // arg 是上层传下来的 binder_write_read 的结构体对象地址

   struct binder_write_read bwr;

   // 将用户空间的 binder_write_read 拷贝到内核空间的 bwr
   if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
       ret = -EFAULT;
       goto out;
   }

   // 进入这里不断的读取数据
   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);
       if (!list_empty(&proc->todo))
           wake_up_interruptible(&proc->wait);
       if (ret < 0) {
           if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
               ret = -EFAULT;
           goto out;
       }
   }
   // 将内核空间的 bwr 数据拷贝到用户空间的 binder_write_read 
   if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
       ret = -EFAULT;
       goto out;
   }
out:
   return ret;
}

static int binder_thread_read(struct binder_proc *proc,
                 struct binder_thread *thread,
                 binder_uintptr_t binder_buffer, size_t size,
                 binder_size_t *consumed, int non_block)
{
   void __user *buffer = (void __user *)(uintptr_t)binder_buffer;

   // 如果线程事务栈和 todo 队列都为空,说明此时没有要当前线程处理的任务,将增加空闲线程的计数器(即将 wait_for_proc_work 设为1),让线程等待在**进程**的 wait 队列上
   wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo);

   if (wait_for_proc_work)
       proc->ready_threads++;

   binder_unlock(__func__);

   if (wait_for_proc_work) {
       if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED))) {
           // 线程还未进入 binder 循环,输出错误信息,并阻塞直到 binder_stop_on_user_error 小于2
           wait_event_interruptible(binder_user_error_wait,
                        binder_stop_on_user_error < 2);
       }
       binder_set_nice(proc->default_priority);
       // 非阻塞
       if (non_block) {
           ...
       } else{
           // 如果是阻塞的读操作,则让进程阻塞在 proc 的 wait 队列上,直到 binder_has_proc_work(thread) 为 true,即进程有工作待处理
           ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
       }
   } else {
       ...
   }
   // 下面的代码目前还执行不到,需要等待 thread 线程的 todo 里面有内容
   return 0;
}来源: https://www.jianshu.com/p/4472c7dabe6c

[图片上传失败...(image-c656fd-1643110660184)]

5.java层调用

[图片上传失败...(image-f837a8-1643110660184)]

但是上面还有个问题就是client和service要直接和binder driver打交道,但是实际上client和service并不想知道binder相关协议,所以进一步client通过添加proxy代理,service通过添加stub来进一步处理与binder的交互。

[图片上传失败...(image-b7ea4c-1643110660184)]

这样的好处是client和service都可以不用直接去和binder打交道。上面的图好像已经很完善了,但是Android系统更进一步封装,不让client知道Binder的存在,Android系统提供了Manager来管理client。如下图:

[图片上传失败...(image-14601e-1643110660184)]

这样client只需要交给manager来管理就好了,根本就不用关心进程通信相关的事,关于manager其实是很熟悉的,比如说activity的就是由ActivityManager来控制的,ActivityManager是通过Binder获取ActivityManagerService来控制activity的。这样就不用我们自己来使用Binder来ActivityManagerService通信了。

在service和binder之间还有一个contextManager,也就是serviceManage

总结流程:

注册服务过程:就是前面4篇文章讲的

第一步: service通过调用serviceManager中的addService方法,然后调用ServiceManagerNative类中的addservice(name)方法。

第二步: ServiceManagerNative会通过Binder发送一条SVG_MGR_ADD_SERVICE的指令,然后通过svcmgr_handler()调用do_add_service()方法往svc_list中添加相应的service。

重点:所有的服务都要先注册到svc_list中才能被client调用到。svc_list以linkedlist的形式保存这些服务。

获取服务过程:

第一步:client要请求服务,比如说在activity中调用context.getSystemService()方法,这个时候serviceManager就会使用getService(name),然后就会调用到native层中的ServiceManagerNative类中的getService(name)方法。

第二步:ServiceManagerNative会通过Binder发送一条SVG_MGR_GET_SERVICE的指令,然后通过svcmgr_handler()调用do_find_service()方法去svc_list中查找到相关的service。

第三步:查找到相应的服务后就会通过Binder将服务传给ServiceManagerNative,然后传给serviceManager,最后client就可以使用了。

注意: 服务实在svclist中保存的,svclist是一个链表,因此客户端调用的服务必须要先注册到svclist中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容