Add Zygote.startChildZygote() to fork a new process that itself is a zygote.
This adds a new --start-child-zygote argument that instructs the main
zygote to create a new child process that will also be a zygote. The
system_server generates a random name in the abstract socket namespace
for it and the child-zygote to communicate over, and that is passed as
an argument to the new process.
A child-zygote bypasses the normal post-fork-child of the zygote process
in order to preserve itself as a zygote. This means not starting the
Binder threadpool nor launching into ActivityThread. Instead, a
child-zygote calls into its own main function. The main function runs a
ZygoteServer select loop, listening on the socket name specified by the
system_server when it was forked.
Unlike the system zygotes, a child-zygote can be killed without bringing
down the system. Killing a child-zygote will not terminate its child
processes, which will be reparented to init for reaping when they
eventually exit.
Bug: 63749735
Test: m (with multi-project commits landed)
Change-Id: I3e7ebbdba498f8fec1d84cdf927dc43a92be4b68
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
new file mode 100644
index 0000000..337a3e2
--- /dev/null
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.net.LocalSocketAddress;
+
+/**
+ * Represents a connection to a child-zygote process. A child-zygote is spawend from another
+ * zygote process using {@link startChildZygote()}.
+ *
+ * {@hide}
+ */
+public class ChildZygoteProcess extends ZygoteProcess {
+ /**
+ * The PID of the child zygote process.
+ */
+ private final int mPid;
+
+ ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) {
+ super(socketAddress, null);
+ mPid = pid;
+ }
+
+ /**
+ * Returns the PID of the child-zygote process.
+ */
+ public int getPid() {
+ return mPid;
+ }
+}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 4a97640..57418c8 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -33,6 +33,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
/*package*/ class ZygoteStartFailedEx extends Exception {
ZygoteStartFailedEx(String s) {
@@ -217,7 +218,8 @@
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
- abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
+ abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
+ zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -333,6 +335,8 @@
* @param abi the ABI the process should use.
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
+ * @param startChildZygote Start a sub-zygote. This creates a new zygote process
+ * that has its state cloned from this zygote process.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -348,6 +352,7 @@
String instructionSet,
String appDataDir,
String invokeWith,
+ boolean startChildZygote,
String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -404,6 +409,10 @@
argsForZygote.add(invokeWith);
}
+ if (startChildZygote) {
+ argsForZygote.add("--start-child-zygote");
+ }
+
argsForZygote.add(processClass);
if (extraArgs != null) {
@@ -418,6 +427,18 @@
}
/**
+ * Closes the connections to the zygote, if they exist.
+ */
+ public void close() {
+ if (primaryZygoteState != null) {
+ primaryZygoteState.close();
+ }
+ if (secondaryZygoteState != null) {
+ secondaryZygoteState.close();
+ }
+ }
+
+ /**
* Tries to establish a connection to the zygote that handles a given {@code abi}. Might block
* and retry if the zygote is unresponsive. This method is a no-op if a connection is
* already open.
@@ -549,4 +570,36 @@
}
Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
}
+
+ /**
+ * Starts a new zygote process as a child of this zygote. This is used to create
+ * secondary zygotes that inherit data from the zygote that this object
+ * communicates with. This returns a new ZygoteProcess representing a connection
+ * to the newly created zygote. Throws an exception if the zygote cannot be started.
+ */
+ public ChildZygoteProcess startChildZygote(final String processClass,
+ final String niceName,
+ int uid, int gid, int[] gids,
+ int runtimeFlags,
+ String seInfo,
+ String abi,
+ String instructionSet) {
+ // Create an unguessable address in the global abstract namespace.
+ final LocalSocketAddress serverAddress = new LocalSocketAddress(
+ processClass + "/" + UUID.randomUUID().toString());
+
+ final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()};
+
+ Process.ProcessStartResult result;
+ try {
+ result = startViaZygote(processClass, niceName, uid, gid,
+ gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
+ abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
+ true /* startChildZygote */, extraArgs);
+ } catch (ZygoteStartFailedEx ex) {
+ throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
+ }
+
+ return new ChildZygoteProcess(serverAddress, result.pid);
+ }
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 895be08..bb5a0ad 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -230,7 +230,7 @@
* @param argv Argument vector for main()
* @param classLoader the classLoader to load {@className} with
*/
- private static Runnable findStaticMain(String className, String[] argv,
+ protected static Runnable findStaticMain(String className, String[] argv,
ClassLoader classLoader) {
Class<?> cl;
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index cadb66a..b38c851 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -129,7 +129,7 @@
final Runnable caller;
try {
- sServer.registerServerSocket("webview_zygote");
+ sServer.registerServerSocketFromEnv("webview_zygote");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 89d63f6..e23cbf8 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -71,6 +71,13 @@
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
+ /**
+ * 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.
+ */
+ public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";
+
private Zygote() {}
/** Called for some security initialization before any fork. */
@@ -102,6 +109,8 @@
* @param fdsToIgnore null-ok an array of ints, either null or holding
* one or more POSIX file descriptor numbers that are to be ignored
* in the file descriptor table check.
+ * @param startChildZygote if true, the new child process will itself be a
+ * new zygote process.
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
*
@@ -110,13 +119,13 @@
*/
public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
- int[] fdsToIgnore, String instructionSet, String appDataDir) {
+ int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, instructionSet, appDataDir);
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -130,7 +139,7 @@
native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
- int[] fdsToIgnore, String instructionSet, String appDataDir);
+ int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir);
/**
* Called to do any initialization before starting an application.
@@ -190,8 +199,8 @@
native protected static void nativeUnmountStorageOnInit();
private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
- String instructionSet) {
- VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, instructionSet);
+ boolean isZygote, String instructionSet) {
+ VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
}
/**
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 6a87b1f..a32fb43 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -221,8 +221,8 @@
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
- parsedArgs.appDataDir);
+ parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
+ parsedArgs.instructionSet, parsedArgs.appDataDir);
try {
if (pid == 0) {
@@ -233,7 +233,8 @@
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
- return handleChildProc(parsedArgs, descriptors, childPipeFd);
+ return handleChildProc(parsedArgs, descriptors, childPipeFd,
+ parsedArgs.startChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
@@ -415,6 +416,14 @@
boolean preloadDefault;
/**
+ * Whether this is a request to start a zygote process as a child of this zygote.
+ * Set with --start-child-zygote. The remaining arguments must include the
+ * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
+ * should be used for communication.
+ */
+ boolean startChildZygote;
+
+ /**
* Constructs instance and parses args
* @param args zygote command-line args
* @throws IllegalArgumentException
@@ -565,6 +574,8 @@
preloadPackageCacheKey = args[++curArg];
} else if (arg.equals("--preload-default")) {
preloadDefault = true;
+ } else if (arg.equals("--start-child-zygote")) {
+ startChildZygote = true;
} else {
break;
}
@@ -587,6 +598,20 @@
remainingArgs = new String[args.length - curArg];
System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
}
+
+ if (startChildZygote) {
+ boolean seenChildSocketArg = false;
+ for (String arg : remainingArgs) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ seenChildSocketArg = true;
+ break;
+ }
+ }
+ if (!seenChildSocketArg) {
+ throw new IllegalArgumentException("--start-child-zygote specified " +
+ "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+ }
+ }
}
}
@@ -739,9 +764,10 @@
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
* @param pipeFd null-ok; pipe for communication back to Zygote.
+ * @param isZygote whether this new child process is itself a new Zygote.
*/
private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
- FileDescriptor pipeFd) {
+ FileDescriptor pipeFd, boolean isZygote) {
/**
* By the time we get here, the native code has closed the two actual Zygote
* socket connections, and substituted /dev/null in their place. The LocalSocket
@@ -778,8 +804,13 @@
// Should not get here.
throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
- return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
- null /* classLoader */);
+ if (!isZygote) {
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+ null /* classLoader */);
+ } else {
+ return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
+ parsedArgs.remainingArgs, null /* classLoader */);
+ }
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 21f1fb6..f603f4f 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -755,7 +755,7 @@
throw new RuntimeException("No ABI list supplied.");
}
- zygoteServer.registerServerSocket(socketName);
+ zygoteServer.registerServerSocketFromEnv(socketName);
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
@@ -870,5 +870,16 @@
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
+ /**
+ * The main function called when starting a child zygote process. This is used as an
+ * alternative to zygoteInit(), which skips calling into initialization routines that
+ * start the Binder threadpool.
+ */
+ static final Runnable childZygoteInit(
+ int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+ RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
+ return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
+ }
+
private static final native void nativeZygoteInit();
}
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 8baa15a..fecf9b9 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -44,9 +44,21 @@
private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+ /**
+ * Listening socket that accepts new server connections.
+ */
private LocalServerSocket mServerSocket;
/**
+ * Whether or not mServerSocket's underlying FD should be closed directly.
+ * If mServerSocket 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
+ * with a name instead, then closing the socket will close the underlying FD
+ * and it should not be double-closed.
+ */
+ private boolean mCloseSocketFd;
+
+ /**
* Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
*/
private boolean mIsForkChild;
@@ -59,11 +71,12 @@
}
/**
- * Registers a server socket for zygote command connections
+ * Registers a server socket for zygote command connections. This locates the server socket
+ * file descriptor through an ANDROID_SOCKET_ environment variable.
*
* @throws RuntimeException when open fails
*/
- void registerServerSocket(String socketName) {
+ void registerServerSocketFromEnv(String socketName) {
if (mServerSocket == null) {
int fileDesc;
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
@@ -78,6 +91,7 @@
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
mServerSocket = new LocalServerSocket(fd);
+ mCloseSocketFd = true;
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
@@ -86,6 +100,22 @@
}
/**
+ * Registers a server socket for zygote command connections. This opens the server socket
+ * at the specified name in the abstract socket namespace.
+ */
+ void registerServerSocketAtAbstractName(String socketName) {
+ if (mServerSocket == null) {
+ try {
+ mServerSocket = new LocalServerSocket(socketName);
+ mCloseSocketFd = false;
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error binding to abstract socket '" + socketName + "'", ex);
+ }
+ }
+ }
+
+ /**
* Waits for and accepts a single command connection. Throws
* RuntimeException on failure.
*/
@@ -112,7 +142,7 @@
if (mServerSocket != null) {
FileDescriptor fd = mServerSocket.getFileDescriptor();
mServerSocket.close();
- if (fd != null) {
+ if (fd != null && mCloseSocketFd) {
Os.close(fd);
}
}
@@ -219,6 +249,11 @@
Log.e(TAG, "Caught post-fork exception in child process.", e);
throw e;
}
+ } finally {
+ // Reset the child flag, in the event that the child process is a child-
+ // zygote. The flag will not be consulted this loop pass after the Runnable
+ // is returned.
+ mIsForkChild = false;
}
}
}