Merge "Add Zygote.startChildZygote() to fork a new process that itself is a zygote."

This commit is contained in:
Treehugger Robot
2018-02-17 01:39:54 +00:00
committed by Gerrit Code Review
10 changed files with 222 additions and 29 deletions

View File

@@ -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;
}
}

View File

@@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets;
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 @@ public class ZygoteProcess {
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 @@ public class ZygoteProcess {
* @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 @@ public class ZygoteProcess {
String instructionSet,
String appDataDir,
String invokeWith,
boolean startChildZygote,
String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -404,6 +409,10 @@ public class ZygoteProcess {
argsForZygote.add(invokeWith);
}
if (startChildZygote) {
argsForZygote.add("--start-child-zygote");
}
argsForZygote.add(processClass);
if (extraArgs != null) {
@@ -417,6 +426,18 @@ public class ZygoteProcess {
}
}
/**
* 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
@@ -549,4 +570,36 @@ public class ZygoteProcess {
}
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);
}
}

View File

@@ -230,7 +230,7 @@ public class RuntimeInit {
* @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;

View File

@@ -129,7 +129,7 @@ class WebViewZygoteInit {
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));

View File

@@ -71,6 +71,13 @@ public final class Zygote {
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 @@ public final class Zygote {
* @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 final class Zygote {
*/
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 @@ public final class Zygote {
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 @@ public final class Zygote {
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);
}
/**

View File

@@ -221,8 +221,8 @@ class ZygoteConnection {
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 @@ class ZygoteConnection {
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.
@@ -414,6 +415,14 @@ class ZygoteConnection {
*/
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
@@ -565,6 +574,8 @@ class ZygoteConnection {
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 @@ class ZygoteConnection {
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 @@ class ZygoteConnection {
* @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 @@ class ZygoteConnection {
// 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 */);
}
}
}

View File

@@ -756,7 +756,7 @@ public class ZygoteInit {
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) {
@@ -871,5 +871,16 @@ public class ZygoteInit {
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();
}

View File

@@ -44,8 +44,20 @@ class ZygoteServer {
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}.
*/
@@ -59,11 +71,12 @@ class ZygoteServer {
}
/**
* 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 @@ class ZygoteServer {
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);
@@ -85,6 +99,22 @@ class ZygoteServer {
}
}
/**
* 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 @@ class ZygoteServer {
if (mServerSocket != null) {
FileDescriptor fd = mServerSocket.getFileDescriptor();
mServerSocket.close();
if (fd != null) {
if (fd != null && mCloseSocketFd) {
Os.close(fd);
}
}
@@ -219,6 +249,11 @@ class ZygoteServer {
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;
}
}
}

View File

@@ -516,7 +516,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra
jint mount_external,
jstring java_se_info, jstring java_se_name,
bool is_system_server, jintArray fdsToClose,
jintArray fdsToIgnore,
jintArray fdsToIgnore, bool is_child_zygote,
jstring instructionSet, jstring dataDir) {
SetSignalHandlers();
@@ -699,7 +699,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra
UnsetChldSignalHandler();
env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
is_system_server, instructionSet);
is_system_server, is_child_zygote, instructionSet);
if (env->ExceptionCheck()) {
RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
}
@@ -748,8 +748,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring se_name,
jintArray fdsToClose,
jintArray fdsToIgnore,
jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote,
jstring instructionSet, jstring appDataDir) {
jlong capabilities = 0;
@@ -786,13 +785,22 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
capabilities |= (1LL << CAP_BLOCK_SUSPEND);
}
// If forking a child zygote process, that zygote will need to be able to change
// the UID and GID of processes it forks, as well as drop those capabilities.
if (is_child_zygote) {
capabilities |= (1LL << CAP_SETUID);
capabilities |= (1LL << CAP_SETGID);
capabilities |= (1LL << CAP_SETPCAP);
}
// Containers run without some capabilities, so drop any caps that are not
// available.
capabilities &= GetEffectiveCapabilityMask(env);
return ForkAndSpecializeCommon(env, uid, gid, gids, runtime_flags,
rlimits, capabilities, capabilities, mount_external, se_info,
se_name, false, fdsToClose, fdsToIgnore, instructionSet, appDataDir);
se_name, false, fdsToClose, fdsToIgnore, is_child_zygote == JNI_TRUE,
instructionSet, appDataDir);
}
static jint com_android_internal_os_Zygote_nativeForkSystemServer(
@@ -803,7 +811,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
runtime_flags, rlimits,
permittedCapabilities, effectiveCapabilities,
MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, NULL,
NULL, NULL, NULL);
NULL, false, NULL, NULL);
if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -877,7 +885,7 @@ static const JNINativeMethod gMethods[] = {
{ "nativeSecurityInit", "()V",
(void *) com_android_internal_os_Zygote_nativeSecurityInit },
{ "nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
(void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
(void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -892,7 +900,7 @@ static const JNINativeMethod gMethods[] = {
int register_com_android_internal_os_Zygote(JNIEnv* env) {
gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
"(IZLjava/lang/String;)V");
"(IZZLjava/lang/String;)V");
return RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
}

View File

@@ -313,10 +313,12 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
return false;
}
// This is a local socket with an abstract address, we do not accept it.
// This is a local socket with an abstract address. Remove the leading NUL byte and
// add a human-readable "ABSTRACT/" prefix.
if (unix_addr->sun_path[0] == '\0') {
LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with abstract address.";
return false;
*result = "ABSTRACT/";
result->append(&unix_addr->sun_path[1], path_len - 1);
return true;
}
// If we're here, sun_path must refer to a null terminated filesystem