Démarrage d'une application Android S depuis le lanceur : le rôle de Zygote

Cet article explique comment le processus système (system_server) initie la création d'un nouveau processus d'application via le processus Zygote.

ZygoteProcess#start

La méthode d'entrée pour démarrer un processus via Zygote se trouve dans la classe ZygoteProcess. Elle vérifie d'abord l'état du pool USAP (Unspecialized App Process) puis délègue la tâche à une méthode interne.

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,
                                              int zygotePolicyFlags,
                                              boolean isTopApp,
                                              @Nullable long[] disabledCompatChanges,
                                              @Nullable Map<string long="" pair="">> pkgDataInfoMap,
                                              @Nullable Map<string long="" pair="">> allowlistedDataInfoList,
                                              boolean bindMountAppsData,
                                              boolean bindMountAppStorageDirs,
                                              @Nullable String[] zygoteArgs) {
    if (fetchUsapPoolEnabledPropWithMinInterval()) {
        informZygotesOfUsapPoolStatus();
    }

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

ZygoteProcess#startViaZygote

Cette méthode assemble les arguments nécessaires pour la création du processus et les envoie à Zygote via un socket. Le choix entre Zygote ou Zygote64 est déterminé par l'ABI cible.

private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,
                                                  @Nullable final String niceName,
                                                  final int uid, final int gid,
                                                  @Nullable final int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  @Nullable String seInfo,
                                                  @NonNull String abi,
                                                  @Nullable String instructionSet,
                                                  @Nullable String appDataDir,
                                                  @Nullable String invokeWith,
                                                  boolean startChildZygote,
                                                  @Nullable String packageName,
                                                  int zygotePolicyFlags,
                                                  boolean isTopApp,
                                                  @Nullable long[] disabledCompatChanges,
                                                  @Nullable Map<string long="" pair="">> pkgDataInfoMap,
                                                  @Nullable Map<string long="" pair="">> allowlistedDataInfoList,
                                                  boolean bindMountAppsData,
                                                  boolean bindMountAppStorageDirs,
                                                  @Nullable String[] extraArgs)
                                                  throws ZygoteStartFailedEx {
    ArrayList<string> zygoteArguments = new ArrayList<>();

    zygoteArguments.add("--runtime-args");
    zygoteArguments.add("--setuid=" + uid);
    zygoteArguments.add("--setgid=" + gid);
    zygoteArguments.add("--runtime-flags=" + runtimeFlags);

    // Gestion du montage externe
    if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
        zygoteArguments.add("--mount-external-default");
    } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
        zygoteArguments.add("--mount-external-installer");
    } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
        zygoteArguments.add("--mount-external-pass-through");
    } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
        zygoteArguments.add("--mount-external-android-writable");
    }

    zygoteArguments.add("--target-sdk-version=" + targetSdkVersion);

    if (gids != null && gids.length > 0) {
        StringBuilder groupsBuilder = new StringBuilder("--setgroups=");
        for (int i = 0; i < gids.length; i++) {
            if (i > 0) {
                groupsBuilder.append(',');
            }
            groupsBuilder.append(gids[i]);
        }
        zygoteArguments.add(groupsBuilder.toString());
    }

    if (niceName != null) {
        zygoteArguments.add("--nice-name=" + niceName);
    }
    if (seInfo != null) {
        zygoteArguments.add("--seinfo=" + seInfo);
    }
    if (instructionSet != null) {
        zygoteArguments.add("--instruction-set=" + instructionSet);
    }
    if (appDataDir != null) {
        zygoteArguments.add("--app-data-dir=" + appDataDir);
    }
    if (invokeWith != null) {
        zygoteArguments.add("--invoke-with");
        zygoteArguments.add(invokeWith);
    }
    if (startChildZygote) {
        zygoteArguments.add("--start-child-zygote");
    }
    if (packageName != null) {
        zygoteArguments.add("--package-name=" + packageName);
    }
    if (isTopApp) {
        zygoteArguments.add(Zygote.START_AS_TOP_APP_ARG);
    }

    zygoteArguments.add(processClass);

    if (extraArgs != null) {
        Collections.addAll(zygoteArguments, extraArgs);
    }

    synchronized (mLock) {
        // Sélection du socket Zygote approprié basée sur l'ABI
        ZygoteState connectionState = openZygoteSocketIfNeeded(abi);
        return transmitArgumentsAndAwaitResult(connectionState, zygotePolicyFlags, zygoteArguments);
    }
}</string></string></string>

ZygoteProcess#transmitArgumentsAndAwaitResult

Cette méthode formate les arguments, tente un lencement via le pool USAP si possible, sinon envoie directement la requête au processus Zygote principle via son socket.

private Process.ProcessStartResult transmitArgumentsAndAwaitResult(
        ZygoteState connectionState, int zygotePolicyFlags, @NonNull ArrayList<string> arguments)
        throws ZygoteStartFailedEx {
    // Validation des arguments
    for (String argument : arguments) {
        if (argument.indexOf('\n') >= 0 || argument.indexOf('\r') >= 0) {
            throw new ZygoteStartFailedEx("Invalid control characters in arguments");
        }
    }

    String messagePayload = arguments.size() + "\n" + String.join("\n", arguments) + "\n";

    if (shouldAttemptUsapLaunch(zygotePolicyFlags, arguments)) {
        try {
            return sendToUsapAndReadResult(connectionState, messagePayload);
        } catch (IOException ioEx) {
            Log.e(LOG_TAG, "Communication with USAP pool failed: " + ioEx.getMessage());
        }
    }

    return sendToZygoteAndReadResult(connectionState, messagePayload);
}</string>

ZygoteProcess#sendToZygoteAndReadResult

Cette méthode écrit les arguments dans le flux de sortie du socket vers Zygote, puis attend la réponse contenant le PID du nouveau processus créé.

private Process.ProcessStartResult sendToZygoteAndReadResult(
        ZygoteState connectionState, String messagePayload) throws ZygoteStartFailedEx {
    try {
        final BufferedWriter outputStream = connectionState.mZygoteOutputWriter;
        final DataInputStream inputStream = connectionState.mZygoteInputStream;

        outputStream.write(messagePayload);
        outputStream.flush();

        Process.ProcessStartResult creationResult = new Process.ProcessStartResult();
        // Lecture bloquante jusqu'à ce que Zygote réponde
        creationResult.pid = inputStream.readInt();
        creationResult.usingWrapper = inputStream.readBoolean();

        if (creationResult.pid < 0) {
            throw new ZygoteStartFailedEx("Process fork failed");
        }

        return creationResult;
    } catch (IOException ex) {
        connectionState.close();
        Log.e(LOG_TAG, "IO error during Zygote communication: " + ex.toString());
        throw new ZygoteStartFailedEx(ex);
    }
}

Le processus system_server utilise cette voie de communication socket pour ordonner à Zygote de forker un nouveau processus. Zygote, après avoir reçu et analysé les arguments, procède à la création du processus enfant. La suite logique de l'exécution de l'application se retrouve ensuite dans la classe ActivityThread.

Étiquettes: Android Zygote processus Systeme Java

Publié le 4 juin à 22h44