Android 跨进程通信-(十)Binder机制传输数据限制—罪魁祸首Binder线程池

目录

前言

一 Linux系统启动

二 APP进程的创建

1.Binder线程池的唯一性

2.Binder线程池

(1)Binder线程命名

(2)Binder主线程

(3)Binder普通线程

三 分析在Binder机制中之所以传递数据达不到1M-8k的原因

1.引入第一个的疑问:为什么当前进程中可以有多个Binder线程?

2.引入第二个疑问:一个APP启动的时候,存在多个Binder线程吗?

3.大胆结论

4.简单补充进程信息查看

四 Binder驱动对内存限制

五 总结


前言

在Android 跨进程通信-(三)Binder机制之Client中提到2.APP进程初始化在通过ProcessState来获取驱动设备文件"/dev/binder/"文件描述符,并且在用户和内核的虚拟空间的时候,会开辟1M-8K的内存空间,用于当前进程与Binder驱动进行传递数据,但是在实际传输过程中,其实并不能达到1M-8k的数据。这次主要是分析总结下原因。

一 Linux系统启动

Linux系统在启动的时候,会有init进程创建Zygote进程和ServiceManager进程。ServceManager进程主要是用来注册和查询系统Service,主要和system_server进程进行跨进程通信。Android的其他进程都是有Zygote进程fork出来的,在Zygote进程完成初始化进行功能加载(即执行ZygoteInit.main())的时候,会完成fork出system_server进程和建立与AMS能够socket通信的ZygoteServer。

在system_server进程完成初始化进行功能加载(即SystemServer.main())的时候,在通过startBootstrapService()加载ActivityManagerService,在startOtherServices()启动其他Service之后,会调用到  mActivityManagerService.systemReady()来启动Launcher。

此时完成整个系统的启动。

详细内容可参见Android 跨进程通信-(三)Binder机制之Client中的一 准备工作和二 system_server进程。

二 APP进程的创建

在APP进程创建或者AIDL服务进程在创建的时候,AMS就会通知Zygote进程fork一个APP进程,在Zygote进程中初始化该APP进程的时候,会调用到Native层的app_main.cpp中的onZygoteInit()

 
    virtual void onZygoteInit()
    {
        sp proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }

主要就是通过ProcessState来初始化Binder(获取设备文件"/dev/binder"的文件描述符以及内存映射),此时就会以进程pid在设备上创建/proc/pid目录,从该目录中可以读取到该进程的Binder线程池、Binder实体对象、Binder引用对象以及内核缓冲区的信息。

同时Binder驱动就会在内核空间为该进程创建一个binder_proc结构体,并将该结构体放到全局的hash队列binder_procs中。通过遍历该集合中的数量就知道有多少个进程正在使用Binder驱动进行通信。

注意ProcessState为单例模式,即这里每个进程中仅有一个ProcessState对象,为打开Binder驱动的设备文件"/dev/binder"以及内存映射

具体的详细内容可参见Android 跨进程通信-(三)Binder机制之Client的(1)第一行代码实现的具体功能。

另外在就是启动了Binder线程池。在Binder机制中之所以传递数据达不到1M-8k的原因也在于这里。重点在分析下这个proc->startThreadPool(),具体的代码实现逻辑见 /frameworks/native/libs/binder/ProcessState.cpp。

1.Binder线程池的唯一性


void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

首先通过mThreadPoolStarted来保证在一个进程中有且仅有一个Binder线程池。

2.Binder线程池

通过spawnPooledThread()来创建Binder线程。

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        //详解见(1)Binder线程命名
        String8 name = makeBinderThreadName();
        //详解见(2)Binder线程
        sp t = new PoolThread(isMain);
        t->run(name.string());
    }
}

(1)Binder线程命名

从上面的代码中可以看出通过makeBinderThreadName()对线程进行命名规范:“Binder:进程pid_数字(就是从1开始每次增加mThreadPoolSeq的一个值)”。

String8 ProcessState::makeBinderThreadName() {
    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
    pid_t pid = getpid();
    String8 name;
    name.appendFormat("Binder:%d_%X", pid, s);
    return name;
}

这个注意下,因为2.引入第二个疑问:一个APP启动的时候,存在多个Binder线程吗?在打印APP进程在启动获取当前进程里面所有线程 ,可以根据该命名获取APP进程创建的Binder线程。

(2)Binder主线程

此时传入的isMain为true,表示为Binder主线程,该Binder线程为Client进程主动创建的(所以Binder主线程为创建的第一个线程,所以该Binder线程的名字为Binder:pid_1),然后执行run()执行到threadLoop()方法。

   virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }

而这里就是创建了之前在前面几篇总结中多次提到的IPCThreadState,该类为IPC中执行真正的与Binder驱动进行交互的Native层的封装类。那么此时为该线程创建一个IPCThreadState对象(对于当前线程该对象也为单例,在self()通过gHaveTLS来保证单例),通过joinThreadPool()开启一个循环,来读取解析命令。

每一个线程都有一个IPCThreadState,针对当前进程该对象也为单例,用来实现进程来访问Binder驱动。(我觉得更像这个线程的概念理解为一次跨进程通信的进程与Binder驱动的链接似乎更容易理解一些。在后面的2.引入第二个疑问:一个APP启动的时候,存在多个Binder线程吗?中有提到对这个理解)

(3)Binder普通线程

刚才在(2)Binder主线程是由Client进程主动创建的Binder线程,对于Binder线程池中的其他线程都是由Binder驱动根据IPC通信需求创建的。在为当前进程创建ProcessState的时候,通过mMaxThread传入了Binder驱动可创建的最大Binder线程数,默认为15个。

Android 跨进程通信-(十)Binder机制传输数据限制—罪魁祸首Binder线程池_第1张图片

当Binder驱动的线程在读取信息的时候,首先会检查是否有空闲的Binder线程可用。如果发现没有空闲的Binder线程,并且没有超出最大线程数的限制的时候,发送BR_SPAWN_LOOPER消息通知Client/Server进程创建新的Binder线程。

此时的IPCThreadState循环一直在读取Binder驱动发送的指令,然后接收到该指令之后,就会通过ProcessState创建一个非Binder主线程

   case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;

此时创建的Binder线程仍为“Binder:进程pid_数字"格式。

所以在前面设置的mMaxThread也只是限制了Binder驱动通知Client/Server进程最多可以创建Binder线程的数量。

三 分析在Binder机制中之所以传递数据达不到1M-8k的原因

有了Binder线程池的概念,再来分析这个问题。在当前进程在用户空间开辟了的确是1M-8k的内存空间,但是这些Binder线程会同时共享这部分空间,都通过这部分空间来和Binder驱动进行传递数据。那么所以对于单个Binder线程能够传递的数据不会超过1M-8k。

1.引入第一个的疑问:为什么当前进程中可以有多个Binder线程?

其实举例system_server进程来看这个问题,似乎就明了了。所有的系统Service都是system_server进程中的一个线程,那么就会对应多个Binder线程来支持不同的系统Service在与Binder驱动进行传递数据。而这些Binder线程共享这1M-8k的空间,那么当其他进程与system_server进程进行IPC的时候,数据完全不会超过1M-8k。

2.引入第二个疑问:一个APP启动的时候,存在多个Binder线程吗?

在APP进程启动的时候,到底有几个Binder线程呢?先看一个简单的APP实例。在APP启动的时候,打印所有的Binder线程。在前面的(1)Binder线程命名中可以看到Binder线程都是以“Binder:进程pid_数字”进行命名,所以只打印下当前以这种方式命名的线程,具体代码见https://github.com/wenjing-bonnie/pattern.git的app/src/main/java/com/android/common/BinderThreadPool.java文件,代码这里不在列举。

当APP启动加载出Launcher Activity的时候,我们可以看到打印出来的线程有:

2021-05-13 11:09:04.244 21827-21827/? V/BinderThreadPool.getAllBinderThread(L:30): PatternApplication, Binder:21827_2
2021-05-13 11:09:04.246 21827-21827/? V/BinderThreadPool.getAllBinderThread(L:30): PatternApplication, Binder:21827_3
2021-05-13 11:09:04.248 21827-21827/? V/BinderThreadPool.getAllBinderThread(L:30): PatternApplication, Binder:21827_1

此时当APP启动的时候,至少启动了三个Binder线程。当APP进程创建出来之后,加载APP进程对应的功能类ActivityThread,此时在ActivityThread中通过消息机制和AMS所在的system_server进程有多次的通信,每次通信都会产生一个Binder线程。这大胆猜测一下三个Binder线程会是什么呢?

在ActivityThread#attach()中调用mgr.attachApplication(mAppThread),与AMS进行第一次通信,通知AMS创建Application并启动APP的Launcher Activity。

注意AMS的attachApplication()不仅创建Application,在完成创建Application之后,在后面还要启动需要启动的应用的四大组件,大体代码如下:

  private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
        .......
        //创建application
          if (app.instr != null) {
                thread.bindApplication(processName, appInfo, providers,
                        app.instr.mClass,
                        profilerInfo, app.instr.mArguments,
                        app.instr.mWatcher,
                        app.instr.mUiAutomationConnection, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial);
            } else {
                thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial);
            }
          .......
        // 启动Launcher Activity
        if (normalMode) {
            try {
                if (mStackSupervisor.attachApplicationLocked(app)) {
                    didSomething = true;
                }
            }
      .......  
        //servive : Find any services that should be running in this process...
        if (!badApp) {
            try {
                didSomething |= mServices.attachApplicationLocked(app, processName);
             .......
        }
       
        //broadcast: Check if a next-broadcast receiver is in this process...
        if (!badApp && isPendingBroadcastProcessLocked(pid)) {
            try {
                didSomething |= sendPendingBroadcastsLocked(app);
         .......

在APP启动流程中:

  • (1)首先在ActivityThread#attach(),通过IPC调用到AMS的attachApplication()(一个Binder线程
  • (2)AMS在attachApplication()又调用的是IApplicationThread#bindApplication()(一个Binder线程),完成Application的初始化
  • (3)AMS在mStackSupervisor.attachApplicationLocked(app)调用IApplicationThread#scheduleLaunchActivity()(一个Binder线程),在IApplicationThread中最终又通过AMS的activityReLaunched()启动Launcher Activity(一个Binder线程)
  • (4)后面的流程分析不动了。。。。呜呜。在一个Activity的每个生命周期方法,都是依赖与AMS进行管理的,所以在Activity的不同生命周期方法的加载过程中,都会有Binder线程的体现。
  • (5)Activity中添加view的时候,通过WindowManager进行addView,这里还有和WindowManagerService之间的IPC(一个Binder线程
  • 引入一个问题:为什么进程创建完之后,不直接调用IApplicationThread#scheduleLaunchActivity(),而是调用到AMS的attachApplication()。

因为在APP进程的Applcation创建过程,在AMS中不仅仅完成了Application的创建和初始化,同时还检测了是否有需要启动的其他应用组件。而IApplicationThread#scheduleLaunchActivity()仅仅是一个Application的创建及初始化完成。

所以这些Binder:21827_1:Binder:21827_2、Binder:21827_3都是用来和AMS完成跨进程通信。但是为什么只有3个呢?我觉得理解这个Binder线程池就和理解Java的线程池管理线程的思维来理解,肯定是有空闲的Binder线程就直接用了,没有空闲的时候再去创建。并且大胆的结论:每传递一次消息就对应一个Binder线程(遗留问题:暂时认为这个结论是正确的,因为每传递一次消息Binder驱动就要进行一次write和read操作,然后在read操作的时候就会去做检查,是否有可用的Binder线程。

遗留问题:怎么来验证下这个猜想,也就是看下这些线程是不是这个作用,暂时还没有想到怎么去验证这个问题,后面可能看下源码是不是能得到些什么信息


3.大胆结论

现在只是一个简单的APP在启动过程中,就会有这么多次与AMS的跨进程通信,这些AMS的跨进程通信都有可能创建多个Binder线程来向Binder驱动传递数据,而这些Binder线程共享在创建进程分配的内存空间,所以在传递数据的时候,不可能超过1M-8K。

另外当我们在用户空间分配1M-8k的虚拟内存空间的时候,当然还会一些进程中的堆、栈以及代码块等占用了一部分内存。但我觉得重点在于多个Binder线程共享内存的原因。

4.简单补充进程信息查看

前面在APP进程创建的过程中会在设备上创建以/proc/pid创建一个文件夹,进入到该文件夹下面的文件,不同的文件夹有不同的功能,如下:

其中这个status文件可以看到进行的一些信息:如下:


2021-05-13 11:09:04.248 21827-21827/? V/BinderThreadPool.getAllBinderThread(L:30): PatternApplication, Binder:21827_1
2021-05-13 11:09:04.248 21827-21827/? D/BinderThreadPool.getProcPidInfo(L:83): 正在读取status : Name:	android.pattern
    Umask:	0077
    State:	R (running)
    Tgid:	21827
    Ngid:	0
    Pid:	21827
    PPid:	664
    TracerPid:	0
    Uid:	10152	10152	10152	10152
    Gid:	10152	10152	10152	10152
    FDSize:	128
    Groups:	9997 20152 50152 
    VmPeak:	 5812432 kB
    VmSize:	 5567904 kB
    VmLck:	       0 kB
    VmPin:	       0 kB
    VmHWM:	   53956 kB
    VmRSS:	   53860 kB
    RssAnon:	   32120 kB
    RssFile:	   21452 kB
    RssShmem:	     288 kB
    VmData:	 1501340 kB
    VmStk:	    8192 kB
    VmExe:	      28 kB
    VmLib:	  127380 kB
    VmPTE:	     936 kB
    VmPMD:	      36 kB
    VmSwap:	   14928 kB
    Threads:	19
    SigQ:	0/27382
    SigPnd:	0000000000000000
    ShdPnd:	0000000000000000
    SigBlk:	0000000080001204
    SigIgn:	0000000000000001
    SigCgt:	0000000e400084f8
    CapInh:	0000000000000000
    CapPrm:	0000000000000000
    CapEff:	0000000000000000
    CapBnd:	0000000000000000
    CapAmb:	0000000000000000
    NoNewPrivs:	0
    Seccomp:	2
    Speculation_Store_Bypass:	thread vulnerable
    Cpus_allowed:	ff
    Cpus_allowed_list:	0-7
    Mems_allowed:	1
    Mems_allowed_list:	0
    voluntary_ctxt_switches:	75
    nonvoluntary_ctxt_switches:	105

其中一些看得懂的字段如下:

第一部分:线程的基本信息

第二部分:进程的一些信息

Android 跨进程通信-(十)Binder机制传输数据限制—罪魁祸首Binder线程池_第2张图片

四 Binder驱动对内存限制

前面只是提到在Native层或者说是在用户空间对该进程分配内存空间的时候,做的内存限制。同时在Binder驱动同样也做了内存限制,防止用户空间传入太大的数据。如下,具体代码位于kernel/goldfish/drivers/staging/android/binder.c 

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
        ......

如果用户空间在开辟内存空间的时候,如果超出了4M,会强制该空间设置为4M。

五 总结

当一个Launcher启动一个APP的时候,AMS会通知Zygote进程fork出一个APP进程,然后加载APP进程的功能类ActivityThread:

  • 1.在APP进程初始化的时候,需要做两件事情:
    • (1)调用到Native层的ProcessState来获得Binder驱动的设备文件"/dev/binder"的文件描述符,并内存映射。ProcessState为单例模式,在该进程中有且仅有一个;(Binder驱动就会创建一个binder_proc的结构体对应该进程,会将结构体放到hash集合中,通过遍历该集合中的元素个数就可以知道当前有多个进程在使用Binder驱动进行IPC;)
    • (2)通过ProcessState启动Binder线程池,并且创建Binder主线程。该线程池对于当前进程有且仅有一个
  • 2.对于Binder线程池中的Binder线程都是用IPCThreadState来实现的,里面维护一个循环来读取Binder驱动发过来的指令,对应有两种Binder线程:
    • (1)Binder主线程:在创建该APP进程的时候创建的该线程;
    • (2)Binder普通线程:当Binder驱动发现无可用的Binder线程的时候,就会发送BR_SPAWN_LOOPER通知进程创建一个可用的Binder线程,该Binder线程为非主线程;
  • 3.IPCThreadState为真正的去向Binder驱动交互的操作类。
  • 4.在ProcessState去获取Binder驱动的设备文件的描述符的时候,会限制Binder线程数,默认的为15个。但是这个是限制的Binder驱动可让进程创建的Binder线程的最大数量。所以对于一个进程与Binder驱动的最大连接数为16个。
  • 5.Binder线程理解为进程与Binder驱动进行交互一次的一个链接,似乎更容易理解。

    上面的过程为APP进程创建的时候完成的相关内容,在加载功能类ActivityThread的时候,就可以和AMS进行IPC了:

  • 1.在ActivityThread加载attach()的时候,其实是通过IPC调用到AMS的attachApplication()来完成下面的内容:
    • (1)Application创建与初始化
      • 最终通过IPC调用到IApplicationThread#bindApplication()实现该逻辑。
    • (2)APP中LauncherActivity的启动
      • 最终通过IPC调用到IApplicationThread#scheduleLaunchActivity完成该逻辑,而这里面的Launch Activity的生命周期加载又是通过AMS来完成
    • (3)其他需要启动的四大组件;
  • 2.上面的过程都是通过Binder线程实现的IPC

    Binder传输数据限制原因:

  • 1.每个进程中会有多个Binder线程,而这些Binder线程又共享创建进程的时候开辟的1M-8K的内存空间,所以在每个Binder线程传输数据的时候,不可能传递1M-8K;
  • 2.另外开辟的这部分内存空间,还会有一些堆栈以及代码块等占用部分内存。

    Binder驱动在对该进程分配的虚拟内存的时候,也会限制大小不超过4M,超出4M会强制限制为4M。

你可能感兴趣的:(android基本知识,Binder机制传输数据限制,Binder线程,Binder线程池,APP启动Binder线程)