[关闭]
@TryLoveCatch 2023-04-18T16:15:18.000000Z 字数 11184 阅读 607

Android知识体系之Binder

Android知识体系


什么是Binder

从机制来说,Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。
从代码实现来说,Binder和BinderProxy是一个实现了IBinder接口的类。

为什么是Binder

Android是在Linux内核的基础上设计的。而在Linux中,已经拥有"管道、消息队列、共享内存、Socket等等"众多的IPC通信手段;但是,Android为什么单单选择了Binder,而不是其它的IPC机制呢?

为什么是一次copy?

首先,我们知道两个进程之前因为进程隔离的原因,是不能直接通信的,需要通过系统调用(copy_from_user, copy_to_user):

我们来看下传统的IPC是如何通信的:

可看出来,是经过了两次的数据copy。而Binder只有一次copy,如下图:

Binder机制只需1次,主要是使用到了内存映射,也就是mmap():
1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2. 接着在内核空间开辟一块内核缓存区,用于接手发送进程发送的数据。
3. 实现内存映射,内核缓存区和接收进程的用户空间地址都映射到同一个接收缓存区。
4. 发送进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,这是唯一的一次数据copy。
5. 由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样就实现了一次跨进程通信。

流程

service_manager启动

service_manager的作用很简单就是提供了查询服务和注册服务的功能。

  1. service_manager会在开机的时候启动,执行open(),打开驱动(/dev/binder),并创建全局链表binder_procs。
  2. 通过ioctl(BINDER_SET_CONTEXT_MGR),告诉Binder驱动,要注册成为service_manager,并且保存自己的信息到binder_procs
  3. 进入loop循环,等待请求

Server要注册服务,Client要获取服务,都需要通过service_manager,但是如何访问呢?
Binder机制为service_manager预留了一个特殊的位置。这个位置是预先定好的,任何想要使用service_manager的进程只要通过这个特定的位置就可以访问到Sservice_manager了
注意:在Binder驱动中,service_manager的引用其实是一个全局变量,通过handle = 0这个位置来访问service_manager。

Server注册服务

Client获取服务

Client和Server通信

Client要和Server通信,它就是通过保存一个Server对象的Binder引用,再通过该Binder引用在内核中找到对应的Binder实体,进而找到Server对象,然后将通信内容发送给Server对象。



进程1的BpBinder在发起跨进程调用时,向binder驱动传入了自己记录的句柄值,binder驱动就会在”进程1对应的binder_proc结构”的引用树中查找和句柄值相符的binder_ref节点,一旦找到binder_ref节点,就可以通过该节点的node域找到对应的binder_node节点,这个目标binder_node当然是从属于进程2的binder_proc啦,不过不要紧,因为binder_ref和binder_node都处于binder驱动的地址空间中,所以是可以用指针直接指向的。目标binder_node节点的cookie域,记录的其实是进程2中BBinder的地址,binder驱动只需把这个值反映给应用层,应用层就可以直接拿到BBinder了。这就是Binder完成精确打击的大体过程。

当Client需要向Server发送请求时,它会调用远程服务接口;远程服务能够获取到BpBinder对象,而BpBinder则通过IPCThreadState和Binder驱动进行通信。由于BpBinder中保存了Server在Binder驱动中的Binder引用;因此,IPCThreadState和Binder驱动通信时,是知道该请求是需要传给哪个Server的。Binder驱动通过Binder引用找到对应的Binder实体,然后将Binder实体中保存的"Server对应的本地服务对象的地址"返回给用户空间。当IPC收到Binder驱动反馈的内容之后,它从内容中找到"Server对应的本地服务对象",然后调用该对象的onTransact()。不同的本地服务都可以实现自己的onTransact();这样,不同的服务就可以按照自己的需求来处理请求

小结

小结2

通过AIDL来理解Binder

假设IDE生成的接口的java格式的名字位IMyAidlInterface.java,继承了android.os.IInterface(唯一方法:IBinder asBinder())。
Stub是IMyAidlInterface的静态内部类,Proxy是Stub的静态内部类。

- Stub Proxy
实现接口 IMyAidlInterface IMyAidlInterface
继承类 android.os.Binder
构造函数 无参,会以(key,value)方式绑定描述信息和Stub 需要传入android.os.IBinder对象,记为remote
抽象类 是,没有实现IMyAidlInterface的方法 否,IMyAidlInterface的方法都调用remote#transact()
asBinder()返回值 返回this 返回remote
重要方法 1、IMyAidlInterface asInterface(android.os.IBinder obj)
2、boolean onTransact()
作用 Server端使用 Client主要使用的是这个类

特别的,Stub里面有一个重要的静态方法:IMyAidlInterface asInterface(android.os.IBinder obj)

小结

假设接口为IMyAidlInterface,提供了一个String getName(),那么大体的步骤如下:

对齐系统源码

理解AIDL之后,再去看系统源码,就不会被各种乱七八糟的名字给绕晕了:

当然,系统比我们这个会复杂很多,它还会涉及到Binder的传递,Client会把Binder对象传递给Server,这样Server可以主动调用Client的能力,例如IApplicationThread,就是通过IActivityManager#attachApplication(),由App进程传递给system_server进程的。

开发中,获取BinderProxy对象的方式

bindService()

平常,我们Binder的获取,是客户端调用bindService(),服务器onBinder返回的一个IBinder。

writeStrongBinder & readStrongBinder

通过IPC传递Binder对象也可以获取到BinderProxy对象,这里涉及到几个方法:
1、APP进程
  1.1、IInterface#asBinder()
  1.2、Parcel#writeStrongBinder()
2、system_server进程
  2.1、Parcel#readStrongBinder()
  2.2、Stub#asInterface()

  1. // ActivityManagerProxy
  2. public void attachApplication(IApplicationThread app) throws RemoteException
  3. {
  4. Parcel data = Parcel.obtain();
  5. Parcel reply = Parcel.obtain();
  6. data.writeInterfaceToken(IActivityManager.descriptor);
  7. data.writeStrongBinder(app.asBinder());
  8. mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);
  9. reply.readException();
  10. data.recycle();
  11. reply.recycle();
  12. }
  13. // ActivityManagerNative
  14. case ATTACH_APPLICATION_TRANSACTION: {
  15. data.enforceInterface(IActivityManager.descriptor);
  16. IApplicationThread app = ApplicationThreadNative.asInterface(data.readStrongBinder());
  17. if (app != null) {
  18. attachApplication(app);
  19. }
  20. reply.writeNoException();
  21. return true;
  22. }

ServiceManager#getService()

这个是从service_manager进程里面获取系统的一些服务,一般我们自己提供的能力,不能通过这个来获取。
只有在获取系统的一些能力的时候,才会这样使用。

  1. private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
  2. protected IActivityManager create() {
  3. IBinder b = ServiceManager.getService("activity");
  4. if (false) {
  5. Log.v("ActivityManager", "default service binder = " + b);
  6. }
  7. IActivityManager am = asInterface(b);
  8. if (false) {
  9. Log.v("ActivityManager", "default service = " + am);
  10. }
  11. return am;
  12. }
  13. };

通过IBinder b = ServiceManager.getService("activity");这种方式获取,也是IPC通信,是App进程去service_manager进程查询服务,因为系统开机的时候,system_server进程已经去service_manager进程注册了一些服务,查询到之后会返回BinderProxy对象给App进程,然后调用asInterface得到ActivityManagerProxy对象。

问题

system_server进程和service_manager进程区别

我们自己些的Server如何注册到SeviceManager呢?

自己写的server不需要注册。
因为自己注册的Server,基本上都是通过上面提的到“获取BinderProxy对象的方式”的方法一和方法二来进行获取BinderProxy对象,不需要提前注册到SeviceManager。

为什么是Zygote来fork进程,而不是新建进程呢?

每个应用程序都是运行在各自的Dalvik虚拟机中,应用程序每次运行都要重新初始化和启动虚拟机,这个过程会耗费很长时间。Zygote会把已经运行的虚拟机的代码和内存信息共享,起到一个预加载资源和类的作用,从而缩短启动时间。

为什么Zygote 孵化新进程和与system_server进程通信要使用Socket呢?

原因是因为fork只能拷贝当前线程,不支持多线程的fork。

如果zygote使用binder的多线程模型与system_server进程进行通讯的话,fork()出的App进程的binder通讯没法用,那这样的就失去了fork的作用了。

https://blog.csdn.net/qq_39037047/article/details/88066589
https://www.zhihu.com/question/312480380
https://cloud.tencent.com/developer/article/1639738

Binder传入数据大小?

普通的由Zygote孵化而来的用户进程,所映射的 Binder 内存大小是不到1M的,准确说是1M-8K。

Binder 内存限制是1m-8k, 为什么一次调用最大传输数据只有大约508k

Binder 的线程池数量默认是15个,由15个线程共享这 1MB-8KB 的内存空间,所以实际传输大小并没有那么大

Binder的线程池理解?为什么只有16个线程?

Binder 通信,归根结底是位于不同进程中的线程之间的通信.假如进程 S 是 Server 端,提供 Binder 实体,线程 T1 从 Client 进程 C 中通过 Binder 的引用向进程 S 发送请求。S 为了处理这个请求需要启动线程 T2,而此时线程 T1 处于接收返回数据的等待状态。T2 处理完请求就会将处理结果返回给 T1,T1 被唤醒得到处理结果.这个是 Binder 通信的基本过程。
————————————————
版权声明:本文为CSDN博主「Big Skipper」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liuwg1226/article/details/108570553

xxx

首先要管理线程池就要知道池子有多大,应用程序通过 BINDER_SET_MAX_THREADS 告诉驱动,最多可以创建几个线程。以后每个线程在创建,进入主循环,退出主循环时,都要分别使用 BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP 告知驱动,以便驱动标记当前线程池中各个线程的状态。

  • 每当驱动接收完数据包,并且把数据包返回给读 Binder 线程的用户空间时,都要检查一下,线程池中是不是已经没有闲置线程了 .
  • 如果是,并且线程总数还没有达到线程池设定的最大线程数,就会在当前读出的数据包后面再追加一条 BR_SPAWN_LOOPER 命令,告诉 Server 端,线程即将不够用了,请再启动一个新线程否则下一个请求可能不能及时响应。
  • 新线程一启动,又会通过 BC_xxx_LOOP 等一系列命令告知驱动更新线程的状态.
  • 这样确保了只要线程池的线程数量没有耗尽,总是会有空闲的线程在等待队列中随时待命,及时处理请求,这个就是 Binder 机制线程池管理的基本流程,
  • 创建新的线程是Binder驱动来控制的,Binder驱动会判断service是否需要更多的线程来处理
  • Server进程会创建很多线程处理Binder请求,这些线程采用Binder驱动的线程池,由Binder驱动自身进行管理。一个进程的Binder线程池默认最大是16个,超过的请求会阻塞等待空闲的线程。
    ————————————————
    版权声明:本文为CSDN博主「Big Skipper」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/liuwg1226/article/details/108570553

默认是16个,包括main线程,所以可以新建的线程是15个。

参考

https://zhuanlan.zhihu.com/p/475303968
https://blog.csdn.net/freekiteyu/article/details/70082302
https://blog.csdn.net/freekiteyu/article/details/79318031
https://www.an.rustfisher.com/android/binder/Binder_mechanism_intro/
https://www.jianshu.com/p/620a42759e8b

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注