Android USAP 进程启动流程

从Android Q(10)开始,Google引入了一种新的机制,加快了app的启动时间,具体请看Android Framework | 一种新型的应用启动机制:USAP,本篇将会详细介绍USAP 进程启动的流程。

Activity启动流程 上篇(Android 10),我们得知在Activity启动过程中,我们会调用到\frameworks\base\core\java\android\os\ZygoteProcess.javastart方法,然后调用startViaZygote(),其实在调用startViaZygote()之前还有一步:

    public final Process.ProcessStartResult start(@NonNull final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, @Nullable int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  @Nullable String seInfo,
                                                  @NonNull String abi,
                                                  @Nullable String instructionSet,
                                                  @Nullable String appDataDir,
                                                  @Nullable String invokeWith,
                                                  @Nullable String packageName,
                                                  boolean useUsapPool,
                                                  @Nullable String[] zygoteArgs) {
        // TODO (chriswailes): Is there a better place to check this value?
        //---------------------------- 就是这里 ----------------------------
        if (fetchUsapPoolEnabledPropWithMinInterval()) {
            informZygotesOfUsapPoolStatus();
        }

        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                    packageName, useUsapPool, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

从函数名称,我们可以得知,这里用fetchUsapPoolEnabledPropWithMinInterval判断系统是否开启了USAP功能,如果开启则调用informZygotesOfUsapPoolStatus():

    /**
     * Sends messages to the zygotes telling them to change the status of their USAP pools.  If
     * this notification fails the ZygoteProcess will fall back to the previous behavior.
     */
    private void informZygotesOfUsapPoolStatus() {
        final String command = "1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n";

        synchronized (mLock) {
            try {
                attemptConnectionToPrimaryZygote();

                primaryZygoteState.mZygoteOutputWriter.write(command);
                primaryZygoteState.mZygoteOutputWriter.flush();
            } catch (IOException ioe) {
                mUsapPoolEnabled = !mUsapPoolEnabled;
                Log.w(LOG_TAG, "Failed to inform zygotes of USAP pool status: "
                        + ioe.getMessage());
                return;
            }

            if (mZygoteSecondarySocketAddress != null) {
                try {
                    attemptConnectionToSecondaryZygote();

                    try {
                        secondaryZygoteState.mZygoteOutputWriter.write(command);
                        secondaryZygoteState.mZygoteOutputWriter.flush();

                        // Wait for the secondary Zygote to finish its work.
                        secondaryZygoteState.mZygoteInputStream.readInt();
                    } catch (IOException ioe) {
                        throw new IllegalStateException(
                                "USAP pool state change cause an irrecoverable error",
                                ioe);
                    }
                } catch (IOException ioe) {
                    // No secondary zygote present.  This is expected on some devices.
                }
            }

            // Wait for the response from the primary zygote here so the primary/secondary zygotes
            // can work concurrently.
            try {
                // Wait for the primary zygote to finish its work.
                primaryZygoteState.mZygoteInputStream.readInt();
            } catch (IOException ioe) {
                throw new IllegalStateException(
                        "USAP pool state change cause an irrecoverable error",
                        ioe);
            }
        }
    }

可以看到该函数将调用attemptConnectionToPrimaryZygoteattemptConnectionToSecondaryZygote()这其实和Zygote()运行的位数有关,32或者64位,他们都调用了ZygoteState.connect,只是传入的参数不同,我们看attemptConnectionToPrimaryZygote:

    private void attemptConnectionToPrimaryZygote() throws IOException {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            primaryZygoteState =
                    ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);

            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
            maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
        }
    }

然后看ZygoteState.connect:

        static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
                @Nullable LocalSocketAddress usapSocketAddress)
                throws IOException {

            DataInputStream zygoteInputStream;
            BufferedWriter zygoteOutputWriter;
            final LocalSocket zygoteSessionSocket = new LocalSocket();

            if (zygoteSocketAddress == null) {
                throw new IllegalArgumentException("zygoteSocketAddress can't be null");
            }

            try {
                zygoteSessionSocket.connect(zygoteSocketAddress);
                zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
                zygoteOutputWriter =
                        new BufferedWriter(
                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
                                Zygote.SOCKET_BUFFER_SIZE);
            } catch (IOException ex) {
                try {
                    zygoteSessionSocket.close();
                } catch (IOException ignore) { }

                throw ex;
            }

            return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
        }

这段代码比较好理解,就是创建一个LocalSocketZygote建立连接,并获取输入输出流设置到ZygoteState中,待会儿我们会用到,至此attemptConnectionToPrimaryZygote调用完成,回到上面的informZygotesOfUsapPoolStatus,代码将command写给了Zygote,而command"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n",这里mUsapPoolEnabled自然为true, 记住这个command,接下来可以到zygote进程中查看了。

Android系统启动流程末尾,我们说到Zygote进程会执行zygoteServer.runSelectLoop(abiList),接收并处理AMS传过来的消息,比如fork app进程。这里我们直接看runSelectLoop函数即可:

Runnable runSelectLoop(String abiList) {
       while (true) {
            ....
            try {
                Os.poll(pollFDs, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            ....
                ....
                        ZygoteConnection connection = peers.get(pollIndex);
                        final Runnable command = connection.processOneCommand(this);

       }
}

这里runSelectLoop会使用epoll机制,阻塞在Os.poll(pollFDs, -1),获取对方连接请求后,执行\frameworks\base\core\java\com\android\internal\os\ZygoteConnection.javaprocessOneCommand方法:

    Runnable processOneCommand(ZygoteServer zygoteServer) {
        ....
        parsedArgs = new ZygoteArguments(args);
        ....
        if (parsedArgs.mUsapPoolStatusSpecified) {
            return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
        }
        ....

ZygoteArguments有代码如下:

    ....
        ....
            } else if (arg.startsWith("--usap-pool-enabled=")) {
                mUsapPoolStatusSpecified = true;
                mUsapPoolEnabled = Boolean.parseBoolean(arg.substring(arg.indexOf('=') + 1));
                expectRuntimeArgs = false;
            } else {
                break;
            }
    ....

还记的我们传入的参数是什么吗?"1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n",这里就将mUsapPoolStatusSpecified设置为truemUsapPoolEnabled设置为 mUsapPoolEnabled也为true。所以会执行
handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled) => zygoteServer.setUsapPoolStatus:

    Runnable setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
        if (!mUsapPoolSupported) {
            Log.w(TAG,
                    "Attempting to enable a USAP pool for a Zygote that doesn't support it.");
            return null;
        } else if (mUsapPoolEnabled == newStatus) {
            return null;
        }

        Log.i(TAG, "USAP Pool status change: " + (newStatus ? "ENABLED" : "DISABLED"));

        mUsapPoolEnabled = newStatus;

        if (newStatus) {
            return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
        } else {
            Zygote.emptyUsapPool();
            return null;
        }
    }

这里newStatus就是mUsapPoolEnabled,我们这里为true开启UsapPool,如果这个值为false,就是关闭UsapPool。我们直接看fillUsapPool

    Runnable fillUsapPool(int[] sessionSocketRawFDs) {
        ....
        if (usapPoolCount < mUsapPoolSizeMin
                || numUsapsToSpawn >= mUsapPoolRefillThreshold) {
            ....

            while (usapPoolCount++ < mUsapPoolSizeMax) {
                Runnable caller = Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs);

                if (caller != null) {
                    return caller;
                }
            }
            ....
        }
        ....
        return null;
    }

这里判断如果当前USAP进程小于最大USAP进程,则调用Zygote.forkUsap,这里注意传入的mUsapPoolSocket参数,他在ZygoteServer构造函数中初始化了:

mUsapPoolSocket = Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);

public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

我们的AMS之后会通过这个USAP_POOL_PRIMARY_SOCKET_NAME创建LocalSocket与USAP进程通信的。
不过你可能意识到了,这些fork出的进程将会监听在同一个SocketServer上,这里就是一个技术细节了:

如果多个进程或者线程在等待同一个事件,当事件发生时,所有线程和进程都会被内核唤醒,唤醒后通常只有一个进程获得了该事件并进行处理,其他进程发现获取事件失败后又继续进入了等待状态,在一定程度上降低了系统性能,这称为 惊群效应。
这里多个 USAP 共同监听了同一个 Socket,而在 Linux Kernel 2.6 后 Socket 的 accept() 通过维护一个等待队列来解决这一问题,因此这段代码中避免了惊群效应

回到fillUsapPoolZygote.forkUsap函数:

    static Runnable forkUsap(LocalServerSocket usapPoolSocket,
                             int[] sessionSocketRawFDs) {
        FileDescriptor[] pipeFDs = null;

        try {
            pipeFDs = Os.pipe2(O_CLOEXEC);
        } catch (ErrnoException errnoEx) {
            throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
        }

        int pid =
                nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);

        if (pid == 0) {
            IoUtils.closeQuietly(pipeFDs[0]);
            return usapMain(usapPoolSocket, pipeFDs[1]);
        } else {
            // The read-end of the pipe will be closed by the native code.
            // See removeUsapTableEntry();
            IoUtils.closeQuietly(pipeFDs[1]);
            return null;
        }
    }

这里通过底层函数fork出了新的进程。在子进程中调用了usapMain:

    private static Runnable usapMain(LocalServerSocket usapPoolSocket,
                                     FileDescriptor writePipe) {
        ....

        while (true) {
            try {
                //等待socket客户端的连接
                sessionSocket = usapPoolSocket.accept();
                ....

            } catch (Exception ex) {
                Log.e("USAP", ex.getMessage());
                IoUtils.closeQuietly(sessionSocket);

                // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
                unblockSigTerm();
            }
        }

        try {
            ....
            specializeAppProcess(args.mUid, args.mGid, args.mGids,
                                 args.mRuntimeFlags, rlimits, args.mMountExternal,
                                 args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                 args.mInstructionSet, args.mAppDataDir);


            if (args.mNiceName != null) {
                Process.setArgV0(args.mNiceName);
            }

            // End of the postFork event.
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

            return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
                                         args.mRemainingArgs,
                                         null /* classLoader */);
        } finally {
            // Unblock SIGTERM to restore the process to default behavior.
            unblockSigTerm();
        }
    }

这里调用usapPoolSocket.accept() 阻塞等待客户端连接,这个客户端就是是来自AMS的请求,后面有提到。接收到请求之后specializeAppProcess将进程''specialize''成app进程,然后调用我们熟悉的ZygoteInit.zygoteInit最终执行到了ActivityThreadmain函数。

我们再来看AMS是如何和USAP进程通信的,首先回到本文开始的ZygoteProcessstartViaZygote方法:

private Process.ProcessStartResult startViaZygote(....) throws ZygoteStartFailedEx {
        ....

        synchronized(mLock) {
            // The USAP pool can not be used if the application will not use the systems graphics
            // driver.  If that driver is requested use the Zygote application start path.
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              useUsapPool,
                                              argsForZygote);
        }
    }

来看zygoteSendArgsAndGetResult

    private Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, boolean useUsapPool, @NonNull ArrayList<String> args)
            throws ZygoteStartFailedEx {

        ....

        if (useUsapPool && mUsapPoolEnabled && canAttemptUsap(args)) {
            try {
                return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
            } catch (IOException ex) {
                // If there was an IOException using the USAP pool we will log the error and
                // attempt to start the process through the Zygote.
                Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "
                        + ex.getMessage());
            }
        }

        return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);
    }

如果使用USAP就调用attemptUsapSendArgsAndGetResult(zygoteState, msgStr),我们显然是使用的情况:

    private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr)
            throws ZygoteStartFailedEx, IOException {
        try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
            final BufferedWriter usapWriter =
                    new BufferedWriter(
                            new OutputStreamWriter(usapSessionSocket.getOutputStream()),
                            Zygote.SOCKET_BUFFER_SIZE);
            final DataInputStream usapReader =
                    new DataInputStream(usapSessionSocket.getInputStream());

            usapWriter.write(msgStr);
            usapWriter.flush();

            Process.ProcessStartResult result = new Process.ProcessStartResult();
            result.pid = usapReader.readInt();
            // USAPs can't be used to spawn processes that need wrappers.
            result.usingWrapper = false;

            if (result.pid >= 0) {
                return result;
            } else {
                throw new ZygoteStartFailedEx("USAP specialization failed");
            }
        }
    }

getUsapSessionSocket通过 mUsapSocketAddress创建LocalSocket():

        LocalSocket getUsapSessionSocket() throws IOException {
            final LocalSocket usapSessionSocket = new LocalSocket();
            usapSessionSocket.connect(this.mUsapSocketAddress);

            return usapSessionSocket;
        }

mUsapSocketAddress的值如下:

mUsapPoolSocketAddress =new LocalSocketAddress(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME,LocalSocketAddress.Namespace.RESERVED);

public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";

和上面说到的USAP阻塞等待的socket地址是一致的,这里就和上面的内容接上了。回到attemptUsapSendArgsAndGetResult,socket连接成功后,获取输入输出流,并写入参数,读取USAP进程的pid,和直接和Zygote进程通信是一致的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容