Binder机制的原理、使用、源码

3,239 阅读10分钟

Binder很重要,几乎面试必问。
Binder很缥缈,工作中感觉几乎用不到,系统第三方给我们实现完了。
Binder很难,但是又不得不学,因为Binder哪都用到了,内卷时代,除了写写页面,一想研究底层的东西,Binder就是一座山立在那。
小到ActivityA启动ActivityB,就用的是Binder(面试题:为啥startActivity需要用Intent 序列化然后才能传值,因为AMS(ActivityManageService)跟App是两进程)
看了几篇binder博客,还是云里雾里。要么净讲都没差的原理,要么全是底层代码,直呼看天书。

为什么面试要问Binder

中小公司表示没用过,但是大厂子。一个app都是分好几个进程的。更何况多个app的通信。
Android多进程APP的优势:
1.享受更多的资源,每个app的内存是有限的,多进程就让内存变大了。
2.主进程的安全隔离。一个进程崩了,主app不会崩。
3.进程保活几率提升,一个被杀了相互拉。

android系统本身就是多进程的,init进程、AMS、WMS...各种系统服务都是独立进程,相互之间通信,与app通信都需要跨进程通信。

Binder有什么优势

linux自带很多IPC跨进程通信方式(管道、信号量、Socket、共享内存...),Android为啥还要整一个Binder呢?

自然是Binder的综合起来更好一点。
性能:一个拷贝。 共享内存 > Binder > Socket
安全性: Binder最安全,给每个app都分配单独的UID,并支持实名和匿名。 Socket用的PID,不靠谱
架构:CS > 共享内存(实现使用麻烦)

image.png

Binder原理

linux 的构造

image.png

普通IPC进程间的通信(Socket),需要拷贝两次,
从进程1的用户空间拷贝到内核空间
从内核空间 拷贝到 进程2的用户空间 image.png

Binder的操作
1.从进程1的用户空间拷贝到内核空间 跟之前一样
2.Binder通过mmap技术,将内核空间中的数据虚拟内存地址和接收进程的用户空间的虚拟地址的 真正对应的物理内存地址强行映射(内存映射)到一快内存,这样在接收地方取出来的时候就是那个数据
解释:有点像内存共享的方案。
送快递,内核区写的地址是XX房间,但是他将快递放到快递柜里。
取的人收件地址是xx房间,收到的虚拟地址是快递柜的号,可以通过号到真实的快递柜(物理内存)取东西。 20210729163723.png

image-20210717153144724.png

mmap

Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。
对文件进行mmap,会在进程的虚拟内存分配地址空间,创建映射关系。
实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上

Binder的整体架构

非常重要! 具体下面讲

YN05PDJK.png

Binder的使用 --- AIDL

Binder机制使用起来比较麻烦,所以Android给做了一个AIDL。AIDL让我们能简单的使用Binder。

AIDL的使用

进程间通信,所以得整两个app,一个为客户端,一个为服务端。

  1. 在服务端建一个文件夹aidl,里面新建一个aidl的接口。并在里面写通信的接口

image.png

  1. 服务端是需要写一个Service供客户端调用的。

Service里必须实现Stub。然后将Stub当做Ibinder返回。 image.png

Stub是extends Binder的,并且实现了IInterface接口。

image.png

Service设置可以隐式启动的action

image.png

  1. 客户端调用,客户端需要拷贝一份一模一样的aidl

image.png

  1. 客户端调用的时候bindService,绑定服务端的写的Service

image.png

  1. 客户端获取aidl接口对象 Stub.asInterface(service)

image.png

  1. 客户端使用

直接掉接口就行,就像同进程通信
image.png

AIDL的源码解析,AIDL怎么调用的Binder

AIDL在定义完以后,编译一下会生成一个java文件。这个java文件是真正的调用Binder的。

  1. 要进行通信必须要实现接口,而这个接口就是IInterface

image.png

image.png

  1. 客户端要使用这个接口,获取数据得获取他的实例,一般用new,但是AIDL用的是Stub.asInterface

image.png

重要!
image.png

如何判断是否跨进程?如果是跨进程那么Binder应该返回BinderProxy,而BinderProxy的queryLocalInterface返回null

image.png

  1. 客户端拿到了Proxy对象,调用接口里的方法

image.png

重要!
mRemote.transact就是真正Binder通信的方法 image.png

这是一个int值,code,在服务端的onTransact有一个code接收 image.png

服务端。
这个code值用来区分具体调的哪个接口,0.1.2.3... 像startActivityForResult的返回值 image.png

  1. 服务端必须实现Stub。然后将Stub当做Ibinder返回。

image.png

Stub是继承Binder的 image.png

  1. 服务端接收调用的地方在onTransact

image.png

this.getName会走到这

image.png

Binder源码解析

由简到难 image.png

内核层 - Binder驱动

binder的数据结构

  1. binder_node :用来描述Binder实体对象 。在Service里都有一个Binder实体对象,表示在内核中的状态。
  2. binder_proc : 该结构体用来描述正在使用binder机制的进程
  3. binder_buffer : 描述内核的缓冲区,表示传递的数据

Binder驱动最重要的就4个方法。
binder_init(初始化)、binder_open(打开驱动)、binder_mmap(内存映射)、binder_ioctl(IO控制)

binder_init

  1. 为binder_device分配内存
  2. 初始化binder_device
  3. 将hlist节点添加到链表 binder_devices

binder_open

  1. 创建binder_proc对象(在内核分配内存) --- binder_proc该结构体用来描述正在使用binder的进程
  2. 这个proc , 记录当前进程的信息(当前进程信息,proc)
  3. filp->private_data = proc;将proc与filp关联,这样以后就能通过filp找到proc
  4. 将proc添加到binder_procs链表中

binder_mmap

  1. 保证映射内存最大不超过4M(默认 1M-8k)
  2. 上锁
  3. 通过用户空间的虚拟内存大小 --- 分配一块连续的内核的虚拟内存
  4. 将proc中的指针指向这块内存并计算偏移量 偏移量= 用户空间 - 内核虚拟内存地址
  5. 释放锁
  6. ------------------上面都是分配的内核空间的虚拟内存,下面分配真实内存----------------------
  7. 分配了物理页的真实内存,先分配一页 1page = 4k 所以最小是4K
  8. 把这块物理内存分别映射到 用户空间的虚拟内存和内核的虚拟内存

binder_ioctl

  1. 进入休眠,直到被唤醒
  2. 根据进程的pid,从binder_proc中找binder_thread。如果找到,返回(读写操作时当参数用),没找到就创建一个binder_thread,并添到binder_proc,并返回
  3. binder_ioctl_write_read(filp, cmd, arg, thread) 读写操作
    1. copy_from_user 先将用户空间 的数据拷贝到 -> 内核空间
    2. 看读缓存有数据还是写缓存有数据,如读缓存有数据,就执行 binder读操作。执行binder_thread_write()方法
    3. copy_to_user 读写完事,将内核空间 的数据拷贝到 -> 用户空间

Binder JNI层的注册

image-20210717153144724.png

image-20210717153420516.png

当手机启动的时候,会先启动第一个用户态的进程init(),init进程通过解析init.rc会启动很多进程,如Zygote进程、ServiceManage进程。在ServiceManage.c 的 main()方法里会进程Binder的注册。

而ServiceManage进程就是Binder的大管家,如果需要通过binder机制跟系统服务通信,得知道系统服务的IBinder,所以得有一个大管家来管理这些。

image-20210717154540018.png

main-> 注册三部曲

  1. binder_open()打开binder驱动
  2. binder_become_context_manage()设置SM为binder大管家
  3. binder_loop() 开启无限循环,循环监听是否有人访问我。 binder_node --- binder对象
    binder_ref -- binder引用
  1. binder_open()

    1. open() 打开binder驱动
    2. mmap() 内存映射,设置sm的大小为 128K
  2. binder_become_context_manage() (设置 SM 为大管家) -> 调用ioctl()

    1. 创建 binder_node 结构体对象,并分配内核空间
    2. proc 指向--》 binder_node 对象里
    3. 创建 work 和 todo 队列(这两队列是客户端服务端的消息队列,类似 messageQueue,循环读取的时候,在这个队列里读的)
  3. binder_loop()开启无限循环,循环监听是否有人访问我。

    1. 初始化binder_write_read
    2. binder_write()处理写、设置状态为looper( binder_write()、binder_read()调用的都是内核层的ioctl() )
    3. for(;;) - > 循环 ioctl() 处理读、读数据 ---- 此时没数据的,所以会进入等待状态,表明了SM注册完成了。

注册完成,现在sm是等待状态,等待接收Binder消息,被唤醒

========
流程图:

image-20210717161810987.png

从native层获取ServiceManage

如果要使用Binder通信,得使用BpBinder,才能将消息传入内核层。在Java层和native层调用方式不同。 PVZ.png

1.png

image.png

  1. 获取ServiceManager是通过defaultServiceManager()方法来完成。

//获取sm的代码。主要有三块1.ProcessState::self() 2.getContextObject(NULL) 3.interface_cast image.png

  1. ProcessState::self()

    1. 实例化ProcessState单例 一个进程只有一个

      1. open_driver 打开驱动(驱动又叫设备device) - binder

        1. open()调用内核的open打开binder驱动
        2. ioctl() 通过ioctl设置支持的最大线程数 15个(也就是一个进程最多15个线程)
      2. mmap 用mmap,给binder分配一块(1M - 8K)的虚拟内存空间用来接收事务。(共享内存大小为1M - 8K = 普通服务的大小 = sm的是128k

  2. getContextObject

    1. 创建一个BpBinder --- 客户端的对象
  3. interface_cast

    1. new BpServiceManager(new BpBinder) ==》 new Proxy(binder==BinderProxy)
    2. remote.transact -->远程调用
    3. remote == BpBinder
  4. java 层(上图java层) --- ServiceManager.addService

    1. new ServiceManagerProxy(new BinderProxy)
    2. mRemote == BinderProxy
    3. BinderProxy.mObject == BpBinder
    4. mRemote.transact == BpBinder.transact

之前到现在sm是等待状态,客户端.transact()方法会发送一个消息唤醒sm,客户端会挂起。

========

从java层获取ServiceManage

如AMS等系统服务需要获取ServiceManage的入口在SystemServer 的 main 方法

整体流程

image-20210723105236535.png

起点:SystemServer 的 main 方法里就一句 执行run()

run()里

  1. 创建了SystemServiceManager

  2. startBootstrapServices()

    1. 获取 AMS 的对象 (mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); 也就是SystemServiceManager 这个.getService能获取AMS)

    2. mActivityManagerService.setSystemProcess();

      1. ServiceManager.addService() 添加 AMS("activity")到 service_manager中

        1. getIServiceManager.addService()真正调到这

getIServiceManager().addService(name, service, false);

  1. 通过单例创建ServiceManagerProxy对象(具体就是创建BinderProxy 、BpBinder并且相互绑定(我中存有你,你中存有我))
  • getIServiceManager --- new ServiceManagerProxy(new BinderProxy())

    • ServiceManagerNative.asInterface(BinderInternal.getContextObject())

      • BinderInternal.getContextObject --- 返回 BinderProxy 对象

        • ProcessState::self()->getContextObject:创建一个BpBinder
        • javaObjectForIBinder -- BinderProxy 和 BpBinder 互相绑定
      • ServiceManagerNative.asInterface

        • 返回 ServiceManagerProxy

  • addService (ServiceManagerProxy.java里的addService)

    SMP.addService -1

    • data.writeStrongBinder(service); -- service == AMS --- 将AMS 放入 Parcel对象的data中

      SMP.addService -2

    • BinderProxy.transact -- (transact是IBinder接口下的方法,用来通信的。客户端调用BinderProxy.transact,服务端通过Proxy.transact接收,这样传递了数据)

      • 获取BpBinder --- IPCThreadState::transact

        用transact传递数据,肯定是一层一层往下传的(见图),

        所以java层调用 -->framwork层的BinderProxy.transact --> native层 BpBinder. transact --> 写入数据、命令 writeTransactionData

        • 1.writeTransactionData --- out 写入命令 --write --- cmd == BC_TRANSACTION (客户端AMS给binder驱动发消息BC_TRANSACTION ,binder驱动做两件事,1.唤醒sm 2.挂起客户端ASM)

        • 2.waitForResponse --- 客户端给binder发送BC_TRANSACTION命令。然后客户端就挂起了,循环等待binder给答复

          while(1){

          if( talkWithDriver () ) -- 循环调用talkWithDriver ,等待回复

          }

          • talkWithDriver -- 非常重要 --- 代码非常长

            • binder_transaction(会走驱动层的binder_transaction方法)

              • if(handle == 0) --》 如果是sm
                1. 获取target_node(实际就是binder对象)
                2. 获取proc
                3. 获取todo,wait
                4. 创建t=binder_transaction用来描述这次transaction操作,tcomplete=binder_work 用来记录未完成的transaction,添到todo队列
                5. 数据拷贝cope_from_user 拷贝到用户空间的binder_transaction_binder 和内核空间
                6. binder_transaction_binder 转化--> handle
                7. thread->transaction_stack = t (保存本次transaction操作); ---> 方便sm找到client
                8. t->work.type = BINDER_WORK_TRANSACTION; -- 给sm -- 做事(service添加到sm)
                9. tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; -- 给client--挂起
                10. wake_up_interruptible 唤醒sm

          ====现在客户端ASM已经给binder发消息transaction,binder唤醒sm,挂起客户端====

        • client挂起

          • BR_NOOP ,BR_TRANSACTION_COMPLETE
          • wait_event_freezable --- 挂起
        • sm处理添加服务

          binder_thread_read接收到消息,将命令设置成BR_TRANSACTION,然后循环binder_loop读

          • BINDER_WORK_TRANSACTION --- 要处理 cmd == BR_TRANSACTION

          • binder_loop -- > binder_reply(翻译回复)

            1. reply初始化

            2. res = func(bs, txn, &msg, &reply); --- 函数指针 --- svcmgr_handler作用:获取或者添加 service

              1. sm是用 svclist 保存所有服务的
            3. binder_send_reply --- bc_reply --- 将reply发给binder驱动

            4. t->work.type = BINDER_WORK_TRANSACTION; --- 给Client list_add_tail(&t->work.entry, target_list); tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; -- 给SM --- 被挂起 list_add_tail(&tcomplete->entry, &thread->todo);

            5. wake_up_interruptible(target_wait); -- 唤醒 Client

        • client 被唤醒

          • BINDER_WORK_TRANSACTION --- cmd = BR_REPLY;

SM 处理 onTransact

  • IPCThreadState::executeCommand

    • error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags);

    • JavaBBinder.onTransact --- C++

    • jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, reinterpret_cast(&data), reinterpret_cast(reply), flags); -- Binder.java.execTransact 方法

线程池管理

  • 主线程 -- 不会退出 || 非主线程 -- 会退出
  • 线程最大数 --- 15个 --- 指的是非主线程,主线程有一个 -- 不算这在线程最大数
  • 所以,线...程真正最大数 : 15 + 1 + 其他线程