本文分析基于Android R
java
一说到应用启动,估计大伙儿就会想到zygote进程。确实,正如其中文释义“受精卵”同样,其主要的做用就是孵化出一个又一个的应用进程。android
传统的应用启动模式由system_server中的AMS接收请求,以后经过socket告知zygote,让其完成fork动做,这样新进程便建立出来。不过从Android Q(10)开始,Google引入了一种新的机制:USAP(Unspecialized App Process)。经过prefork的方式提早建立好一批进程,当有应用启动时,直接将已经建立好的进程分配给它,从而省去了fork的动做,所以能够提高性能。markdown
这种机制在AOSP的源码中默认是关闭的,但估计不少手机厂家已经提早尝鲜了。session
当开启USAP的功能后,zygote会维护一个进程池,其中最多可容纳10个USAP进程。如下是根据图示罗列的详细工做流程。socket
[frameworks/base/core/java/com/android/internal/os/Zygote.java]oop
635 private static Runnable usapMain(LocalServerSocket usapPoolSocket, 636 FileDescriptor writePipe) {
637 final int pid = Process.myPid();
638 Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
639
640 LocalSocket sessionSocket = null;
641 DataOutputStream usapOutputStream = null;
642 Credentials peerCredentials = null;
643 ZygoteArguments args = null;
644
645 // Change the priority to max before calling accept so we can respond to new specialization
646 // requests as quickly as possible. This will be reverted to the default priority in the
647 // native specialization code.
648 boostUsapPriority(); <================= 提高进程调度优先级
649
650 while (true) {
651 try {
652 sessionSocket = usapPoolSocket.accept();
复制代码
Process.start()
。当决定采用USAP的方式启动时,system_server便会发起socket通讯,将全部启动参数发送过去。因为此时有10个USAP进程都在等待同一个socket端口,所以系统底层会随机唤醒一个进程来处理这次通讯。[frameworks/base/core/java/android/os/ZygoteProcess.java]性能
495 try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
496 final BufferedWriter usapWriter =
497 new BufferedWriter(
498 new OutputStreamWriter(usapSessionSocket.getOutputStream()),
499 Zygote.SOCKET_BUFFER_SIZE);
500 final DataInputStream usapReader =
501 new DataInputStream(usapSessionSocket.getInputStream());
502
503 usapWriter.write(msgStr); <============== 发起socket请求
504 usapWriter.flush();
505
506 Process.ProcessStartResult result = new Process.ProcessStartResult();
507 result.pid = usapReader.readInt(); <=========== 等待USAP进程将本身的pid告知system_server
复制代码
specializeAppProcess
来完成“腾笼换鸟”的工做。最终调入ActivityThread.main
方法中去,进而完成应用的启动。当该进程退出时,它不会回到USAP Pool中,而是直接被zygote回收。zygote接收到SIGCHLD信号后,会调用SigChldHandler
进行处理。过程当中会经过socket将回收的进程数告知zygote。目前这个信息未被使用,可能将来会有些新的设计。测试
随着启动的应用愈来愈多,USAP Pool中的进程将会不断消耗,所以便会涉及到从新填充(refill)的过程。ui
USAP Pool中设定了两个阈值,分别对应两种refill的方式。spa
每当USAP进程被使用时,它都会经过pipe将本身的pid告知zygote。这样zygote就能够将它从pool中删去。删去以后,zygote会去检查pool中剩余(空闲)进程的数量。当数量不超过一半(5)时,便会发起一次refill事件来fork出新的进程填充到pool中去。
真实的refill动做是滞后3秒的,这样能够和应用启动过程间隔开。由于当zygote接收到USAP进程的pid时,也意味着USAP进程正要执行specializeAppProcess
,为了不refill过程和应用启动过程抢夺系统资源(CPU资源)从而影响启动速度,原生设计中采用延时执行的方式。
[frameworks/base/core/java/android/os/ZygoteProcess.java]
731 try {
732 ByteArrayOutputStream buffer =
733 new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
734 DataOutputStream outputStream = new DataOutputStream(buffer);
735
736 // This is written as a long so that the USAP reporting pipe and USAP pool event FD
737 // handlers in ZygoteServer.runSelectLoop can be unified. These two cases should
738 // both send/receive 8 bytes.
739 outputStream.writeLong(pid);
740 outputStream.flush();
741
742 Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); <======= 往zygote发送pid
743 } catch (Exception ex) {
744 Log.e("USAP",
745 String.format("Failed to write PID (%d) to pipe (%d): %s",
746 pid, writePipe.getInt$(), ex.getMessage()));
747 throw new RuntimeException(ex);
748 } finally {
749 IoUtils.closeQuietly(writePipe);
750 }
751
752 specializeAppProcess(args.mUid, args.mGid, args.mGids, <================ 应用后续启动过程
753 args.mRuntimeFlags, rlimits, args.mMountExternal,
754 args.mSeInfo, args.mNiceName, args.mStartChildZygote,
755 args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
756 args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
757 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
复制代码
在Delay Refill的3秒内,系统有可能会有新的启动请求。当USAP Pool中的进程不断被消耗,以致于消耗殆尽时,这时便须要另外一种机制,来保证USAP Pool的正常轮转。
当USAP Pool中最后一个进程被用掉后,zygote会发起一次immediate fork。因为此次fork在时间上和应用启动过程冲突,因此zygote只fork出一个进程,从而将影响降到最低。新fork出的进程会当即补充到pool中,这样接下来再有应用启动时,不会落入无USAP可用的境地。
此外,zygote会再安排一次Delayed Refill用于完整填充,多数状况下这一步没有必要(以前有一次Delayed Refill正在执行的路上),但加上它更加保险。
USAP的开启/关闭都经过property来实现。
一种方式是在build.prop中增长一行。
persist.device_config.runtime_native.usap_pool_enabled=true
复制代码
另外一种方式是获取root权限后,调用setprop设置。
setprop persist.device_config.runtime_native.usap_pool_enabled true
复制代码
开启和关闭的过程都是动态的,但生效的时机较为有趣。只有等property修改完后再一次启动应用时,10个USAP进程才会建立出来。关闭的时机也同样,只不过zygote会给空闲的USAP进程发送SIGTERM信号来结束它的生命,同时清空pool。
整体而言,USAP的机制较为简单,只是源码中的socket/pipe名称容易混乱,罗列以下。
名称 | 做用 |
---|---|
名为"usap_pool_primary"的socket | system_server经过它将启动参数发送给USAP进程 |
gUsapPoolEventFD ZygoteServer.mUsapPoolEventFD |
SigChldHandler经过它将已退出的进程数发送给zygote进程 |
(gUsapTable中元素的)read_pipe_fd | USAP进程经过它将自身pid发送给zygote进程 |
名为"zygote"的socket | system_server经过它将命令参数发送给zygote进程 |
至于此项机制到底能带来多少性能提高,笔者还没有测试过。若是有手机厂家的伙伴掌握了一手测试数据,不妨在评论里分享下😊。