目录
前言
一 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系统在启动的时候,会有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进程创建或者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。
void ProcessState::startThreadPool()
{
AutoMutex _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
首先通过mThreadPoolStarted来保证在一个进程中有且仅有一个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());
}
}
从上面的代码中可以看出通过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线程。
此时传入的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线程吗?中有提到对这个理解)
刚才在(2)Binder主线程是由Client进程主动创建的Binder线程,对于Binder线程池中的其他线程都是由Binder驱动根据IPC通信需求创建的。在为当前进程创建ProcessState的时候,通过mMaxThread传入了Binder驱动可创建的最大Binder线程数,默认为15个。
当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线程会同时共享这部分空间,都通过这部分空间来和Binder驱动进行传递数据。那么所以对于单个Binder线程能够传递的数据不会超过1M-8k。
其实举例system_server进程来看这个问题,似乎就明了了。所有的系统Service都是system_server进程中的一个线程,那么就会对应多个Binder线程来支持不同的系统Service在与Binder驱动进行传递数据。而这些Binder线程共享这1M-8k的空间,那么当其他进程与system_server进程进行IPC的时候,数据完全不会超过1M-8k。
在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启动流程中:
- 引入一个问题:为什么进程创建完之后,不直接调用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线程。)
遗留问题:怎么来验证下这个猜想,也就是看下这些线程是不是这个作用,暂时还没有想到怎么去验证这个问题,后面可能看下源码是不是能得到些什么信息
现在只是一个简单的APP在启动过程中,就会有这么多次与AMS的跨进程通信,这些AMS的跨进程通信都有可能创建多个Binder线程来向Binder驱动传递数据,而这些Binder线程共享在创建进程分配的内存空间,所以在传递数据的时候,不可能超过1M-8K。
另外当我们在用户空间分配1M-8k的虚拟内存空间的时候,当然还会一些进程中的堆、栈以及代码块等占用了一部分内存。但我觉得重点在于多个Binder线程共享内存的原因。
前面在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
其中一些看得懂的字段如下:
第一部分:线程的基本信息
第二部分:进程的一些信息
前面只是提到在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。