diff options
| -rw-r--r-- | core/java/android/os/ZygoteProcess.java | 92 | ||||
| -rw-r--r-- | core/java/android/provider/DeviceConfig.java | 32 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/Zygote.java | 167 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteInit.java | 46 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteServer.java | 204 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_Zygote.cpp | 3 |
6 files changed, 365 insertions, 179 deletions
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index ee3d35427b29..1de811792e7d 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.provider.DeviceConfig; import android.util.Log; import android.util.Slog; @@ -66,16 +67,6 @@ public class ZygoteProcess { /** * @hide for internal use only. */ - public static final String ZYGOTE_SOCKET_NAME = "zygote"; - - /** - * @hide for internal use only. - */ - public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary"; - - /** - * @hide for internal use only. - */ public static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000; /** @@ -89,17 +80,12 @@ public class ZygoteProcess { /** * @hide for internal use only */ - public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool"; - - /** - * @hide for internal use only - */ - public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary"; + private static final String LOG_TAG = "ZygoteProcess"; /** - * @hide for internal use only + * The default value for enabling the blastula pool. */ - private static final String LOG_TAG = "ZygoteProcess"; + private static final String BLASTULA_POOL_ENABLED_DEFAULT = "false"; /** * The name of the socket used to communicate with the primary zygote. @@ -110,6 +96,7 @@ public class ZygoteProcess { * The name of the secondary (alternate ABI) zygote socket. */ private final LocalSocketAddress mZygoteSecondarySocketAddress; + /** * The name of the socket used to communicate with the primary blastula pool. */ @@ -122,17 +109,21 @@ public class ZygoteProcess { public ZygoteProcess() { mZygoteSocketAddress = - new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); + new LocalSocketAddress(Zygote.PRIMARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); mZygoteSecondarySocketAddress = - new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME, + new LocalSocketAddress(Zygote.SECONDARY_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); mBlastulaPoolSocketAddress = - new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME, + new LocalSocketAddress(Zygote.BLASTULA_POOL_PRIMARY_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); mBlastulaPoolSecondarySocketAddress = - new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME, + new LocalSocketAddress(Zygote.BLASTULA_POOL_SECONDARY_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); + + // TODO (chriswailes): Uncomment when the blastula pool can be enabled. +// fetchBlastulaPoolEnabledProp(); } public ZygoteProcess(LocalSocketAddress primarySocketAddress, @@ -272,6 +263,15 @@ public class ZygoteProcess { private ZygoteState secondaryZygoteState; /** + * If the blastula pool should be created and used to start applications. + * + * Setting this value to false will disable the creation, maintenance, and use of the blastula + * pool. When the blastula pool is disabled the application lifecycle will be identical to + * previous versions of Android. + */ + private boolean mBlastulaPoolEnabled = false; + + /** * Start a new process. * * <p>If processes are enabled, a new process is created and the @@ -327,6 +327,14 @@ public class ZygoteProcess { @Nullable String sandboxId, boolean useBlastulaPool, @Nullable String[] zygoteArgs) { + if (fetchBlastulaPoolEnabledProp()) { + // TODO (chriswailes): Send the appropriate command to the zygotes + Log.i(LOG_TAG, "Blastula pool enabled property set to: " + mBlastulaPoolEnabled); + + // This can't be enabled yet, but we do want to test this code path. + mBlastulaPoolEnabled = false; + } + try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, @@ -407,7 +415,7 @@ public class ZygoteProcess { Process.ProcessStartResult result = new Process.ProcessStartResult(); // TODO (chriswailes): Move branch body into separate function. - if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) { + if (useBlastulaPool && isValidBlastulaCommand(args)) { LocalSocket blastulaSessionSocket = null; try { @@ -620,6 +628,7 @@ public class ZygoteProcess { final StringBuilder sb = new StringBuilder(); sb.append("--packages-for-uid="); + // TODO (chriswailes): Replace with String.join for (int i = 0; i < packagesForUid.length; ++i) { if (i != 0) { sb.append(','); @@ -656,11 +665,42 @@ public class ZygoteProcess { synchronized(mLock) { return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), - useBlastulaPool, + useBlastulaPool && mBlastulaPoolEnabled, argsForZygote); } } + private boolean fetchBlastulaPoolEnabledProp() { + boolean origVal = mBlastulaPoolEnabled; + + final String propertyString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_ENABLED, + BLASTULA_POOL_ENABLED_DEFAULT); + + if (!propertyString.isEmpty()) { + mBlastulaPoolEnabled = + Zygote.getSystemPropertyBoolean( + DeviceConfig.RuntimeNative.BLASTULA_POOL_ENABLED, + Boolean.parseBoolean(BLASTULA_POOL_ENABLED_DEFAULT)); + } + + return origVal != mBlastulaPoolEnabled; + } + + private long mLastPropCheckTimestamp = 0; + + private boolean fetchBlastulaPoolEnabledPropWithMinInterval() { + final long currentTimestamp = SystemClock.elapsedRealtime(); + + if (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL) { + mLastPropCheckTimestamp = currentTimestamp; + return fetchBlastulaPoolEnabledProp(); + } + + return false; + } + /** * Closes the connections to the zygote, if they exist. */ @@ -940,7 +980,7 @@ public class ZygoteProcess { /** * Try connecting to the Zygote over and over again until we hit a time-out. - * @param socketName The name of the socket to connect to. + * @param zygoteSocketName The name of the socket to connect to. */ public static void waitForConnectionToZygote(String zygoteSocketName) { final LocalSocketAddress zygoteSocketAddress = @@ -950,7 +990,7 @@ public class ZygoteProcess { /** * Try connecting to the Zygote over and over again until we hit a time-out. - * @param address The name of the socket to connect to. + * @param zygoteSocketAddress The name of the socket to connect to. */ public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) { int numRetries = ZYGOTE_CONNECT_TIMEOUT_MS / ZYGOTE_CONNECT_RETRY_DELAY_MS; diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 7eb03007ee6a..f6a8388a7d40 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -145,6 +145,38 @@ public final class DeviceConfig { @SystemApi public interface RuntimeNative { String NAMESPACE = "runtime_native"; + + /** + * Zygote flags. See {@link com.internal.os.Zygote}. + */ + + /** + * If {@code true}, enables the blastula pool feature. + * + * @hide for internal use only + */ + String BLASTULA_POOL_ENABLED = "blastula_pool_enabled"; + + /** + * The maximum number of processes to keep in the blastula pool. + * + * @hide for internal use only + */ + String BLASTULA_POOL_SIZE_MAX = "blastula_pool_size_max"; + + /** + * The minimum number of processes to keep in the blastula pool. + * + * @hide for internal use only + */ + String BLASTULA_POOL_SIZE_MIN = "blastula_pool_size_max"; + + /** + * The threshold used to determine if the pool should be refilled. + * + * @hide for internal use only + */ + String BLASTULA_POOL_REFILL_THRESHOLD = "blastula_refill_threshold"; } /** diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 40d78688cb4c..22884ac9e3fc 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -29,6 +29,7 @@ import android.os.IVold; import android.os.Process; import android.os.SystemProperties; import android.os.Trace; +import android.provider.DeviceConfig; import android.system.ErrnoException; import android.system.Os; import android.util.Log; @@ -129,21 +130,6 @@ public final class Zygote { public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8; /** - * If the blastula pool should be created and used to start applications. - * - * Setting this value to false will disable the creation, maintenance, and use of the blastula - * pool. When the blastula pool is disabled the application lifecycle will be identical to - * previous versions of Android. - */ - public static final boolean BLASTULA_POOL_ENABLED = false; - - /** - * File descriptor used for communication between the signal handler and the ZygoteServer poll - * loop. - * */ - protected static FileDescriptor sBlastulaPoolEventFD; - - /** * An extraArg passed when a zygote process is forking a child-zygote, specifying a name * in the abstract socket namespace. This socket name is what the new child zygote * should listen for connections on. @@ -174,43 +160,39 @@ public final class Zygote { private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; /** - * The maximum value that the sBlastulaPoolMax variable may take. This value - * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. + * The duration to wait before re-checking Zygote related system properties. + * + * Five minutes in milliseconds. */ - static final int BLASTULA_POOL_MAX_LIMIT = 10; + public static final long PROPERTY_CHECK_INTERVAL = 300000; /** - * The minimum value that the sBlastulaPoolMin variable may take. + * @hide for internal use only */ - static final int BLASTULA_POOL_MIN_LIMIT = 1; + public static final int SOCKET_BUFFER_SIZE = 256; + + /** a prototype instance for a future List.toArray() */ + protected static final int[][] INT_ARRAY_2D = new int[0][0]; /** - * The runtime-adjustable maximum Blastula pool size. + * @hide for internal use only. */ - static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT; + public static final String PRIMARY_SOCKET_NAME = "zygote"; /** - * The runtime-adjustable minimum Blastula pool size. + * @hide for internal use only. */ - static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT; + public static final String SECONDARY_SOCKET_NAME = "zygote_secondary"; /** - * The runtime-adjustable value used to determine when to re-fill the - * blastula pool. The pool will be re-filled when - * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold. + * @hide for internal use only */ - // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax. - static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2); + public static final String BLASTULA_POOL_PRIMARY_SOCKET_NAME = "blastula_pool"; /** * @hide for internal use only */ - public static final int SOCKET_BUFFER_SIZE = 256; - - private static LocalServerSocket sBlastulaPoolSocket = null; - - /** a prototype instance for a future List.toArray() */ - protected static final int[][] INT_ARRAY_2D = new int[0][0]; + public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary"; private Zygote() {} @@ -428,70 +410,47 @@ public final class Zygote { protected static native void nativeGetSocketFDs(boolean isPrimary); /** - * Initialize the blastula pool and fill it with the desired number of - * processes. - */ - protected static Runnable initBlastulaPool() { - if (BLASTULA_POOL_ENABLED) { - sBlastulaPoolEventFD = getBlastulaPoolEventFD(); - - return fillBlastulaPool(null); - } else { - return null; - } + * Returns the raw string value of a system property. + * + * Note that Device Config is not available without an application so SystemProperties is used + * instead. + * + * TODO (chriswailes): Cache the system property location in native code and then write a JNI + * function to fetch it. + */ + public static String getSystemProperty(String propertyName, String defaultValue) { + return SystemProperties.get( + String.join(".", + "persist.device_config", + DeviceConfig.RuntimeNative.NAMESPACE, + propertyName), + defaultValue); } /** - * Checks to see if the current policy says that pool should be refilled, and spawns new - * blastulas if necessary. + * Returns the value of a system property converted to a boolean using specific logic. * - * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is - * only called from contexts that are only valid if the pool is enabled. + * Note that Device Config is not available without an application so SystemProperties is used + * instead. * - * @param sessionSocketRawFDs Anonymous session sockets that are currently open - * @return In the Zygote process this function will always return null; in blastula processes - * this function will return a Runnable object representing the new application that is - * passed up from blastulaMain. - */ - protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); - - int blastulaPoolCount = getBlastulaPoolCount(); - - int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount; - - if (blastulaPoolCount < sBlastulaPoolMin - || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) { - - // Disable some VM functionality and reset some system values - // before forking. - ZygoteHooks.preFork(); - resetNicePriority(); - - while (blastulaPoolCount++ < sBlastulaPoolMax) { - Runnable caller = forkBlastula(sessionSocketRawFDs); - - if (caller != null) { - return caller; - } - } - - // Re-enable runtime services for the Zygote. Blastula services - // are re-enabled in specializeBlastula. - ZygoteHooks.postForkCommon(); - - Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); - } - - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - - return null; + * @see SystemProperties.getBoolean + * + * TODO (chriswailes): Cache the system property location in native code and then write a JNI + * function to fetch it. + */ + public static boolean getSystemPropertyBoolean(String propertyName, Boolean defaultValue) { + return SystemProperties.getBoolean( + String.join(".", + "persist.device_config", + DeviceConfig.RuntimeNative.NAMESPACE, + propertyName), + defaultValue); } /** * @return Number of blastulas currently in the pool */ - private static int getBlastulaPoolCount() { + static int getBlastulaPoolCount() { return nativeGetBlastulaPoolCount(); } @@ -501,7 +460,7 @@ public final class Zygote { * @return The event FD used for communication between the signal handler and the ZygoteServer * poll loop */ - private static FileDescriptor getBlastulaPoolEventFD() { + static FileDescriptor getBlastulaPoolEventFD() { FileDescriptor fd = new FileDescriptor(); fd.setInt$(nativeGetBlastulaPoolEventFD()); @@ -518,7 +477,8 @@ public final class Zygote { * this function will return a Runnable object representing the new application that is * passed up from blastulaMain. */ - private static Runnable forkBlastula(int[] sessionSocketRawFDs) { + static Runnable forkBlastula(LocalServerSocket blastulaPoolSocket, + int[] sessionSocketRawFDs) { FileDescriptor[] pipeFDs = null; try { @@ -532,7 +492,7 @@ public final class Zygote { if (pid == 0) { IoUtils.closeQuietly(pipeFDs[0]); - return blastulaMain(pipeFDs[1]); + return blastulaMain(blastulaPoolSocket, pipeFDs[1]); } else { // The read-end of the pipe will be closed by the native code. // See removeBlastulaTableEntry(); @@ -553,7 +513,8 @@ public final class Zygote { * of the ZygoteServer. * @return A runnable oject representing the new application. */ - static Runnable blastulaMain(FileDescriptor writePipe) { + private static Runnable blastulaMain(LocalServerSocket blastulaPoolSocket, + FileDescriptor writePipe) { final int pid = Process.myPid(); LocalSocket sessionSocket = null; @@ -563,7 +524,7 @@ public final class Zygote { while (true) { try { - sessionSocket = sBlastulaPoolSocket.accept(); + sessionSocket = blastulaPoolSocket.accept(); BufferedReader blastulaReader = new BufferedReader(new InputStreamReader(sessionSocket.getInputStream())); @@ -611,7 +572,7 @@ public final class Zygote { System.exit(-1); } finally { IoUtils.closeQuietly(sessionSocket); - IoUtils.closeQuietly(sBlastulaPoolSocket); + IoUtils.closeQuietly(blastulaPoolSocket); } try { @@ -660,7 +621,7 @@ public final class Zygote { * exception if an invalid arugment is encountered. * @param args The arguments to test */ - static void validateBlastulaCommand(ZygoteArguments args) { + private static void validateBlastulaCommand(ZygoteArguments args) { if (args.mAbiListQuery) { throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list"); } else if (args.mPidQuery) { @@ -851,20 +812,6 @@ public final class Zygote { } /** - * Creates a managed object representing the Blastula pool socket that has - * already been initialized and bound by init. - * - * TODO (chriswailes): Move the name selection logic into this function. - * - * @throws RuntimeException when open fails - */ - static void createBlastulaSocket(String socketName) { - if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) { - sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName); - } - } - - /** * Creates a managed LocalServerSocket object using a file descriptor * created by an init.rc script. The init scripts that specify the * sockets name can be found in system/core/rootdir. The socket is bound diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index e132abd7e4cb..7cddf7588b28 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -755,7 +755,7 @@ public class ZygoteInit { } public static void main(String argv[]) { - ZygoteServer zygoteServer = new ZygoteServer(); + ZygoteServer zygoteServer = null; // Mark zygote start. This ensures that thread creation will throw // an error. @@ -783,7 +783,7 @@ public class ZygoteInit { RuntimeInit.enableDdms(); boolean startSystemServer = false; - String socketName = "zygote"; + String zygoteSocketName = "zygote"; String abiList = null; boolean enableLazyPreload = false; for (int i = 1; i < argv.length; i++) { @@ -794,26 +794,19 @@ public class ZygoteInit { } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { - socketName = argv[i].substring(SOCKET_NAME_ARG.length()); + zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } + final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME); + if (abiList == null) { throw new RuntimeException("No ABI list supplied."); } - // TODO (chriswailes): Wrap these three calls in a helper function? - final String blastulaSocketName = - socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME) - ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME - : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME; - - zygoteServer.createZygoteSocket(socketName); - Zygote.createBlastulaSocket(blastulaSocketName); - - Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)); + Zygote.getSocketFDs(isPrimaryZygote); // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. @@ -846,8 +839,10 @@ public class ZygoteInit { ZygoteHooks.stopZygoteNoThreadCreation(); + zygoteServer = new ZygoteServer(isPrimaryZygote); + if (startSystemServer) { - Runnable r = forkSystemServer(abiList, socketName, zygoteServer); + Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); // {@code r == null} in the parent (zygote) process, and {@code r != null} in the // child (system_server) process. @@ -857,23 +852,18 @@ public class ZygoteInit { } } - // If the return value is null then this is the zygote process - // returning to the normal control flow. If it returns a Runnable - // object then this is a blastula that has finished specializing. - caller = Zygote.initBlastulaPool(); + Log.i(TAG, "Accepting command socket connections"); - if (caller == null) { - Log.i(TAG, "Accepting command socket connections"); - - // The select loop returns early in the child process after a fork and - // loops forever in the zygote. - caller = zygoteServer.runSelectLoop(abiList); - } + // The select loop returns early in the child process after a fork and + // loops forever in the zygote. + caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { - zygoteServer.closeServerSocket(); + if (zygoteServer != null) { + zygoteServer.closeServerSocket(); + } } // We're in the child process and have exited the select loop. Proceed to execute the @@ -894,8 +884,8 @@ public class ZygoteInit { } private static void waitForSecondaryZygote(String socketName) { - String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName) - ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME; + String otherZygoteName = Zygote.PRIMARY_SOCKET_NAME.equals(socketName) + ? Zygote.SECONDARY_SOCKET_NAME : Zygote.PRIMARY_SOCKET_NAME; ZygoteProcess.waitForConnectionToZygote(otherZygoteName); } diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index a78c095a8deb..24269efc3f26 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -20,12 +20,17 @@ import static android.system.OsConstants.POLLIN; import android.net.LocalServerSocket; import android.net.LocalSocket; +import android.os.SystemClock; +import android.os.Trace; +import android.provider.DeviceConfig; import android.system.ErrnoException; import android.system.Os; import android.system.StructPollfd; import android.util.Log; import android.util.Slog; +import dalvik.system.ZygoteHooks; + import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.FileDescriptor; @@ -38,7 +43,7 @@ import java.util.ArrayList; * Provides functions to wait for commands on a UNIX domain socket, and fork * off child processes that inherit the initial state of the VM.% * - * Please see {@link ZygoteConnection.Arguments} for documentation on the + * Please see {@link ZygoteArguments} for documentation on the * client protocol. */ class ZygoteServer { @@ -46,11 +51,48 @@ class ZygoteServer { public static final String TAG = "ZygoteServer"; /** + * The maximim value that will be accepted from the BLASTULA_POOL_SIZE_MAX device property. + * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. + */ + private static final int BLASTULA_POOL_SIZE_MAX_LIMIT = 100; + + /** + * The minimum value that will be accepted from the BLASTULA_POOL_SIZE_MIN device property. + */ + private static final int BLASTULA_POOL_SIZE_MIN_LIMIT = 1; + + /** The default value used for the BLASTULA_POOL_SIZE_MAX device property */ + private static final String BLASTULA_POOL_SIZE_MAX_DEFAULT = "10"; + + /** The default value used for the BLASTULA_POOL_SIZE_MIN device property */ + private static final String BLASTULA_POOL_SIZE_MIN_DEFAULT = "1"; + + /** + * If the blastula pool should be created and used to start applications. + * + * Setting this value to false will disable the creation, maintenance, and use of the blastula + * pool. When the blastula pool is disabled the application lifecycle will be identical to + * previous versions of Android. + */ + private boolean mBlastulaPoolEnabled = false; + + /** * Listening socket that accepts new server connections. */ private LocalServerSocket mZygoteSocket; /** + * The name of the blastula socket to use if the blastula pool is enabled. + */ + private LocalServerSocket mBlastulaPoolSocket; + + /** + * File descriptor used for communication between the signal handler and the ZygoteServer poll + * loop. + * */ + private FileDescriptor mBlastulaPoolEventFD; + + /** * Whether or not mZygoteSocket's underlying FD should be closed directly. * If mZygoteSocket is created with an existing FD, closing the socket does * not close the FD and it must be closed explicitly. If the socket is created @@ -64,25 +106,55 @@ class ZygoteServer { */ private boolean mIsForkChild; - ZygoteServer() { } + /** + * The runtime-adjustable maximum Blastula pool size. + */ + private int mBlastulaPoolSizeMax = 0; - void setForkChild() { - mIsForkChild = true; + /** + * The runtime-adjustable minimum Blastula pool size. + */ + private int mBlastulaPoolSizeMin = 0; + + /** + * The runtime-adjustable value used to determine when to re-fill the + * blastula pool. The pool will be re-filled when + * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold. + */ + private int mBlastulaPoolRefillThreshold = 0; + + ZygoteServer() { + mBlastulaPoolEventFD = null; + mZygoteSocket = null; + mBlastulaPoolSocket = null; } /** - * Creates a managed object representing the Zygote socket that has already - * been initialized and bound by init. - * - * TODO (chriswailes): Move the name selection logic into this function. + * Initialize the Zygote server with the Zygote server socket, blastula pool server socket, + * and blastula pool event FD. * - * @throws RuntimeException when open fails + * @param isPrimaryZygote If this is the primary Zygote or not. */ - void createZygoteSocket(String socketName) { - if (mZygoteSocket == null) { - mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName); - mCloseSocketFd = true; + ZygoteServer(boolean isPrimaryZygote) { + mBlastulaPoolEventFD = Zygote.getBlastulaPoolEventFD(); + + if (isPrimaryZygote) { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME); + mBlastulaPoolSocket = + Zygote.createManagedSocketFromInitSocket( + Zygote.BLASTULA_POOL_PRIMARY_SOCKET_NAME); + } else { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME); + mBlastulaPoolSocket = + Zygote.createManagedSocketFromInitSocket( + Zygote.BLASTULA_POOL_SECONDARY_SOCKET_NAME); } + + fetchBlastulaPoolPolicyProps(); + } + + void setForkChild() { + mIsForkChild = true; } /** @@ -151,6 +223,104 @@ class ZygoteServer { return mZygoteSocket.getFileDescriptor(); } + private void fetchBlastulaPoolPolicyProps() { + final String blastulaPoolSizeMaxPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MAX, + BLASTULA_POOL_SIZE_MAX_DEFAULT); + + if (!blastulaPoolSizeMaxPropString.isEmpty()) { + mBlastulaPoolSizeMax = + Integer.min( + Integer.parseInt(blastulaPoolSizeMaxPropString), + BLASTULA_POOL_SIZE_MAX_LIMIT); + } + + final String blastulaPoolSizeMinPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MIN, + BLASTULA_POOL_SIZE_MIN_DEFAULT); + + if (!blastulaPoolSizeMinPropString.isEmpty()) { + mBlastulaPoolSizeMin = + Integer.max( + Integer.parseInt(blastulaPoolSizeMinPropString), + BLASTULA_POOL_SIZE_MIN_LIMIT); + } + + final String blastulaPoolRefillThresholdPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_REFILL_THRESHOLD, + Integer.toString(mBlastulaPoolSizeMax / 2)); + + if (!blastulaPoolRefillThresholdPropString.isEmpty()) { + mBlastulaPoolRefillThreshold = + Integer.min( + Integer.parseInt(blastulaPoolRefillThresholdPropString), + mBlastulaPoolSizeMax); + } + + } + + private long mLastPropCheckTimestamp = 0; + + private void fetchBlastulaPoolPolicyPropsWithMinInterval() { + final long currentTimestamp = SystemClock.elapsedRealtime(); + + if (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL) { + fetchBlastulaPoolPolicyProps(); + mLastPropCheckTimestamp = currentTimestamp; + } + } + + /** + * Checks to see if the current policy says that pool should be refilled, and spawns new + * blastulas if necessary. + * + * @param sessionSocketRawFDs Anonymous session sockets that are currently open + * @return In the Zygote process this function will always return null; in blastula processes + * this function will return a Runnable object representing the new application that is + * passed up from blastulaMain. + */ + private Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { + Log.i(TAG, "FDHUNT - Marker 2 - fillBlastulaPool"); + + if (mBlastulaPoolEnabled) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); + + int blastulaPoolCount = Zygote.getBlastulaPoolCount(); + int numBlastulasToSpawn = mBlastulaPoolSizeMax - blastulaPoolCount; + + if (blastulaPoolCount < mBlastulaPoolSizeMin + || numBlastulasToSpawn >= mBlastulaPoolRefillThreshold) { + + // Disable some VM functionality and reset some system values + // before forking. + ZygoteHooks.preFork(); + Zygote.resetNicePriority(); + + while (blastulaPoolCount++ < mBlastulaPoolSizeMax) { + Runnable caller = Zygote.forkBlastula(mBlastulaPoolSocket, sessionSocketRawFDs); + + if (caller != null) { + return caller; + } + } + + // Re-enable runtime services for the Zygote. Blastula services + // are re-enabled in specializeBlastula. + ZygoteHooks.postForkCommon(); + + Log.i("zygote", + "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); + } + + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + return null; + } + /** * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's @@ -164,6 +334,8 @@ class ZygoteServer { peers.add(null); while (true) { + fetchBlastulaPoolPolicyPropsWithMinInterval(); + int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); // Space for all of the socket FDs, the Blastula Pool Event FD, and @@ -181,7 +353,7 @@ class ZygoteServer { final int blastulaPoolEventFDIndex = pollIndex; pollFDs[pollIndex] = new StructPollfd(); - pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD; + pollFDs[pollIndex].fd = mBlastulaPoolEventFD; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; @@ -275,6 +447,8 @@ class ZygoteServer { } else { // Either the blastula pool event FD or a blastula reporting pipe. + Log.i(TAG, "FDHUNT - Marker 1 - runSelectLoop"); + // If this is the event FD the payload will be the number of blastulas removed. // If this is a reporting pipe FD the payload will be the PID of the blastula // that was just specialized. @@ -316,7 +490,7 @@ class ZygoteServer { .mapToInt(fd -> fd.getInt$()) .toArray(); - final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs); + final Runnable command = fillBlastulaPool(sessionSocketRawFDs); if (command != null) { return command; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 7b4e4ea43415..4649b5262723 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1357,6 +1357,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, SetSchedulerPolicy(fail_fn); + __android_log_close(); + stats_log_close(); + const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; |