Merge changes from topic "manual-zygote-snap"

* changes:
  Fixed an issue Zygote initialization.
  Improved timekeeping logic for USAP Pool refill mechanism.
  Resets the refill action/timing info after a pool fill event.
  Make the USAP Pool refill delay configurable.
  Re-adds a trace  point that was accidentally removed.
  Moved some thread priority changes.
  Adjust Java Language thread priority in new processes.
  Adjusts the USAP pool refill mechanism.
  Change USAP name to application name as soon as possible.
This commit is contained in:
Christian Wailes
2020-01-16 19:00:00 +00:00
committed by Gerrit Code Review
5 changed files with 369 additions and 191 deletions

View File

@@ -49,9 +49,9 @@ import java.io.InputStreamReader;
/** @hide */
public final class Zygote {
/*
* Bit values for "runtimeFlags" argument. The definitions are duplicated
* in the native code.
*/
* Bit values for "runtimeFlags" argument. The definitions are duplicated
* in the native code.
*/
/** enable debugging over JDWP */
public static final int DEBUG_ENABLE_JDWP = 1;
@@ -187,6 +187,11 @@ public final class Zygote {
*/
public static final int SOCKET_BUFFER_SIZE = 256;
/**
* @hide for internal use only
*/
private static final int PRIORITY_MAX = -20;
/** a prototype instance for a future List.toArray() */
protected static final int[][] INT_ARRAY_2D = new int[0][0];
@@ -251,8 +256,7 @@ public final class Zygote {
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
int targetSdkVersion) {
ZygoteHooks.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir);
@@ -264,6 +268,10 @@ public final class Zygote {
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
ZygoteHooks.postForkCommon();
return pid;
}
@@ -298,7 +306,7 @@ public final class Zygote {
int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
niceName, startChildZygote, instructionSet, appDataDir);
niceName, startChildZygote, instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,6 +314,9 @@ public final class Zygote {
// Note that this event ends at the end of handleChildProc.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
/*
* This is called here (instead of after the fork but before the specialize) to maintain
* consistancy with the code paths for forkAndSpecialize.
@@ -350,15 +361,19 @@ public final class Zygote {
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
ZygoteHooks.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkSystemServer(
uid, gid, gids, runtimeFlags, rlimits,
permittedCapabilities, effectiveCapabilities);
// Enable tracing as soon as we enter the system_server.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
}
// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
ZygoteHooks.postForkCommon();
return pid;
}
@@ -476,13 +491,16 @@ public final class Zygote {
/**
* Fork a new unspecialized app process from the zygote
*
* @param usapPoolSocket The server socket the USAP will call accept on
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @param isPriorityFork Value controlling the process priority level until accept is called
* @return In the Zygote process this function will always return null; in unspecialized app
* processes this function will return a Runnable object representing the new
* application that is passed up from usapMain.
*/
static Runnable forkUsap(LocalServerSocket usapPoolSocket,
int[] sessionSocketRawFDs) {
int[] sessionSocketRawFDs,
boolean isPriorityFork) {
FileDescriptor[] pipeFDs = null;
try {
@@ -492,7 +510,8 @@ public final class Zygote {
}
int pid =
nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(),
sessionSocketRawFDs, isPriorityFork);
if (pid == 0) {
IoUtils.closeQuietly(pipeFDs[0]);
@@ -506,8 +525,9 @@ public final class Zygote {
}
private static native int nativeForkUsap(int readPipeFD,
int writePipeFD,
int[] sessionSocketRawFDs);
int writePipeFD,
int[] sessionSocketRawFDs,
boolean isPriorityFork);
/**
* This function is used by unspecialized app processes to wait for specialization requests from
@@ -518,7 +538,7 @@ public final class Zygote {
* @return A runnable oject representing the new application.
*/
private static Runnable usapMain(LocalServerSocket usapPoolSocket,
FileDescriptor writePipe) {
FileDescriptor writePipe) {
final int pid = Process.myPid();
Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
@@ -527,6 +547,11 @@ public final class Zygote {
Credentials peerCredentials = null;
ZygoteArguments args = null;
// Change the priority to max before calling accept so we can respond to new specialization
// requests as quickly as possible. This will be reverted to the default priority in the
// native specialization code.
boostUsapPriority();
while (true) {
try {
sessionSocket = usapPoolSocket.accept();
@@ -568,6 +593,7 @@ public final class Zygote {
try {
// SIGTERM is blocked on loop exit. This prevents a USAP that is specializing from
// being killed during a pool flush.
setAppProcessName(args, "USAP");
applyUidSecurityPolicy(args, peerCredentials);
applyDebuggerSystemProperty(args);
@@ -628,23 +654,18 @@ public final class Zygote {
}
specializeAppProcess(args.mUid, args.mGid, args.mGids,
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
args.mInstructionSet, args.mAppDataDir);
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
args.mInstructionSet, args.mAppDataDir);
disableExecuteOnly(args.mTargetSdkVersion);
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.mDisabledCompatChanges,
args.mRemainingArgs,
null /* classLoader */);
args.mDisabledCompatChanges,
args.mRemainingArgs,
null /* classLoader */);
} finally {
// Unblock SIGTERM to restore the process to default behavior.
unblockSigTerm();
@@ -663,6 +684,22 @@ public final class Zygote {
private static native void nativeUnblockSigTerm();
private static void boostUsapPriority() {
nativeBoostUsapPriority();
}
private static native void nativeBoostUsapPriority();
static void setAppProcessName(ZygoteArguments args, String loggingTag) {
if (args.mNiceName != null) {
Process.setArgV0(args.mNiceName);
} else if (args.mPackageName != null) {
Process.setArgV0(args.mPackageName);
} else {
Log.w(loggingTag, "Unable to set package name.");
}
}
private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: ";
/**
@@ -685,7 +722,7 @@ public final class Zygote {
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote");
} else if (args.mApiBlacklistExemptions != null) {
throw new IllegalArgumentException(
USAP_ERROR_PREFIX + "--set-api-blacklist-exemptions");
USAP_ERROR_PREFIX + "--set-api-blacklist-exemptions");
} else if (args.mHiddenApiAccessLogSampleRate != -1) {
throw new IllegalArgumentException(
USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
@@ -696,8 +733,8 @@ public final class Zygote {
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with");
} else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: "
+ "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+ ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+ "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+ ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
}
}
@@ -754,7 +791,7 @@ public final class Zygote {
if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
throw new ZygoteSecurityException(
"System UID may not launch process with UID < "
+ Process.SYSTEM_UID);
+ Process.SYSTEM_UID);
}
}
@@ -804,8 +841,8 @@ public final class Zygote {
if (args.mInvokeWith != null && peerUid != 0
&& (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
throw new ZygoteSecurityException("Peer is permitted to specify an "
+ "explicit invoke-with wrapper command only for debuggable "
+ "applications.");
+ "explicit invoke-with wrapper command only for debuggable "
+ "applications.");
}
}
@@ -888,7 +925,7 @@ public final class Zygote {
return new LocalServerSocket(fd);
} catch (IOException ex) {
throw new RuntimeException(
"Error building socket from file descriptor: " + fileDesc, ex);
"Error building socket from file descriptor: " + fileDesc, ex);
}
}
@@ -902,15 +939,6 @@ public final class Zygote {
ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
}
/**
* Resets the calling thread priority to the default value (Thread.NORM_PRIORITY
* or nice value 0). This updates both the priority value in java.lang.Thread and
* the nice value (setpriority).
*/
static void resetNicePriority() {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
}
/**
* Executes "/system/bin/sh -c &lt;command&gt;" using the exec() system call.
* This method throws a runtime exception if exec() failed, otherwise, this

View File

@@ -346,7 +346,7 @@ class ZygoteConnection {
if (zygoteServer.isUsapPoolEnabled()) {
Runnable fpResult =
zygoteServer.fillUsapPool(
new int[]{mSocket.getFileDescriptor().getInt$()});
new int[]{mSocket.getFileDescriptor().getInt$()}, false);
if (fpResult != null) {
zygoteServer.setForkChild();
@@ -485,9 +485,7 @@ class ZygoteConnection {
closeSocket();
if (parsedArgs.mNiceName != null) {
Process.setArgV0(parsedArgs.mNiceName);
}
Zygote.setAppProcessName(parsedArgs, TAG);
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

View File

@@ -825,6 +825,18 @@ public class ZygoteInit {
return result;
}
/**
* This is the entry point for a Zygote process. It creates the Zygote server, loads resources,
* and handles other tasks related to preparing the process for forking into applications.
*
* This process is started with a nice value of -20 (highest priority). All paths that flow
* into new processes are required to either set the priority to the default value or terminate
* before executing any non-system code. The native side of this occurs in SpecializeCommon,
* while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess,
* ZygoteConnection.handleChildProc, and Zygote.usapMain.
*
* @param argv Command line arguments used to specify the Zygote's configuration.
*/
@UnsupportedAppUsage
public static void main(String argv[]) {
ZygoteServer zygoteServer = null;
@@ -888,8 +900,6 @@ public class ZygoteInit {
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
} else {
Zygote.resetNicePriority();
}
// Do an initial gc to clean up after startup

View File

@@ -66,6 +66,12 @@ class ZygoteServer {
/** The default value used for the USAP_POOL_SIZE_MIN device property */
private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1";
/** The default value used for the USAP_REFILL_DELAY_MS device property */
private static final String USAP_POOL_REFILL_DELAY_MS_DEFAULT = "3000";
/** The "not a timestamp" value for the refill delay timestamp mechanism. */
private static final int INVALID_TIMESTAMP = -1;
/**
* Indicates if this Zygote server can support a unspecialized app process pool. Currently this
* should only be true for the primary and secondary Zygotes, and not the App Zygotes or the
@@ -131,6 +137,24 @@ class ZygoteServer {
*/
private int mUsapPoolRefillThreshold = 0;
/**
* Number of milliseconds to delay before refilling the pool if it hasn't reached its
* minimum value.
*/
private int mUsapPoolRefillDelayMs = -1;
/**
* If and when we should refill the USAP pool.
*/
private UsapPoolRefillAction mUsapPoolRefillAction;
private long mUsapPoolRefillTriggerTimestamp;
private enum UsapPoolRefillAction {
DELAYED,
IMMEDIATE,
NONE
}
ZygoteServer() {
mUsapPoolEventFD = null;
mZygoteSocket = null;
@@ -160,9 +184,8 @@ class ZygoteServer {
Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
}
fetchUsapPoolPolicyProps();
mUsapPoolSupported = true;
fetchUsapPoolPolicyProps();
}
void setForkChild() {
@@ -267,6 +290,13 @@ class ZygoteServer {
mUsapPoolSizeMax);
}
final String usapPoolRefillDelayMsPropString = Zygote.getConfigurationProperty(
ZygoteConfig.USAP_POOL_REFILL_DELAY_MS, USAP_POOL_REFILL_DELAY_MS_DEFAULT);
if (!usapPoolRefillDelayMsPropString.isEmpty()) {
mUsapPoolRefillDelayMs = Integer.parseInt(usapPoolRefillDelayMsPropString);
}
// Sanity check
if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
@@ -293,9 +323,16 @@ class ZygoteServer {
}
}
private void fetchUsapPoolPolicyPropsIfUnfetched() {
if (mIsFirstPropertyCheck) {
mIsFirstPropertyCheck = false;
fetchUsapPoolPolicyProps();
}
}
/**
* Checks to see if the current policy says that pool should be refilled, and spawns new USAPs
* if necessary.
* Refill the USAP Pool to the appropriate level, determined by whether this is a priority
* refill event or not.
*
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @return In the Zygote process this function will always return null; in unspecialized app
@@ -303,39 +340,48 @@ class ZygoteServer {
* application that is passed up from usapMain.
*/
Runnable fillUsapPool(int[] sessionSocketRawFDs) {
Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillUsapPool");
// Ensure that the pool properties have been fetched.
fetchUsapPoolPolicyPropsWithMinInterval();
fetchUsapPoolPolicyPropsIfUnfetched();
int usapPoolCount = Zygote.getUsapPoolCount();
int numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
int numUsapsToSpawn;
if (usapPoolCount < mUsapPoolSizeMin
|| numUsapsToSpawn >= mUsapPoolRefillThreshold) {
// Disable some VM functionality and reset some system values
// before forking.
ZygoteHooks.preFork();
Zygote.resetNicePriority();
while (usapPoolCount++ < mUsapPoolSizeMax) {
Runnable caller = Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs);
if (caller != null) {
return caller;
}
}
// Re-enable runtime services for the Zygote. Services for unspecialized app process
// are re-enabled in specializeAppProcess.
ZygoteHooks.postForkCommon();
if (isPriorityRefill) {
// Refill to min
numUsapsToSpawn = mUsapPoolSizeMin - usapPoolCount;
Log.i("zygote",
"Filled the USAP pool. New USAPs: " + numUsapsToSpawn);
"Priority USAP Pool refill. New USAPs: " + numUsapsToSpawn);
} else {
// Refill up to max
numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
Log.i("zygote",
"Delayed USAP Pool refill. New USAPs: " + numUsapsToSpawn);
}
// Disable some VM functionality and reset some system values
// before forking.
ZygoteHooks.preFork();
while (--numUsapsToSpawn >= 0) {
Runnable caller =
Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs, isPriorityRefill);
if (caller != null) {
return caller;
}
}
// Re-enable runtime services for the Zygote. Services for unspecialized app process
// are re-enabled in specializeAppProcess.
ZygoteHooks.postForkCommon();
resetUsapRefillState();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return null;
@@ -358,13 +404,18 @@ class ZygoteServer {
mUsapPoolEnabled = newStatus;
if (newStatus) {
return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() }, false);
} else {
Zygote.emptyUsapPool();
return null;
}
}
void resetUsapRefillState() {
mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
}
/**
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
@@ -377,8 +428,11 @@ class ZygoteServer {
socketFDs.add(mZygoteSocket.getFileDescriptor());
peers.add(null);
mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
while (true) {
fetchUsapPoolPolicyPropsWithMinInterval();
mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
int[] usapPipeFDs = null;
StructPollfd[] pollFDs;
@@ -430,140 +484,199 @@ class ZygoteServer {
}
}
int pollTimeoutMs;
if (mUsapPoolRefillTriggerTimestamp == INVALID_TIMESTAMP) {
pollTimeoutMs = -1;
} else {
long elapsedTimeMs = System.currentTimeMillis() - mUsapPoolRefillTriggerTimestamp;
if (elapsedTimeMs >= mUsapPoolRefillDelayMs) {
// Normalize the poll timeout value when the time between one poll event and the
// next pushes us over the delay value. This prevents poll receiving a 0
// timeout value, which would result in it returning immediately.
pollTimeoutMs = -1;
} else if (elapsedTimeMs <= 0) {
// This can occur if the clock used by currentTimeMillis is reset, which is
// possible because it is not guaranteed to be monotonic. Because we can't tell
// how far back the clock was set the best way to recover is to simply re-start
// the respawn delay countdown.
pollTimeoutMs = mUsapPoolRefillDelayMs;
} else {
pollTimeoutMs = (int) (mUsapPoolRefillDelayMs - elapsedTimeMs);
}
}
int pollReturnValue;
try {
Os.poll(pollFDs, -1);
pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
boolean usapPoolFDRead = false;
if (pollReturnValue == 0) {
// The poll timeout has been exceeded. This only occurs when we have finished the
// USAP pool refill delay period.
while (--pollIndex >= 0) {
if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
continue;
}
mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
if (pollIndex == 0) {
// Zygote server socket
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
socketFDs.add(newPeer.getFileDescriptor());
} else if (pollIndex < usapPoolEventFDIndex) {
// Session socket accepted from the Zygote server socket
try {
ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);
// TODO (chriswailes): Is this extra check necessary?
if (mIsForkChild) {
// We're in the child. We should always have a command to run at this
// stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
if (command != null) {
throw new IllegalStateException("command != null");
}
// We don't know whether the remote side of the socket was closed or
// not until we attempt to read from it from processOneCommand. This
// shows up as a regular POLLIN event in our regular processing loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(pollIndex);
socketFDs.remove(pollIndex);
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// We're in the server so any exception here is one that has taken place
// pre-fork while processing commands or reading / writing from the
// control socket. Make a loud noise about any such exceptions so that
// we know exactly what failed and why.
Slog.e(TAG, "Exception executing zygote command: ", e);
// Make sure the socket is closed so that the other end knows
// immediately that something has gone wrong and doesn't time out
// waiting for a response.
ZygoteConnection conn = peers.remove(pollIndex);
conn.closeSocket();
socketFDs.remove(pollIndex);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
// method). Log the details of the exception and bring down the process.
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;
}
} else {
// Either the USAP pool event FD or a USAP reporting pipe.
// If this is the event FD the payload will be the number of USAPs removed.
// If this is a reporting pipe FD the payload will be the PID of the USAP
// that was just specialized.
long messagePayload = -1;
try {
byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
DataInputStream inputStream =
new DataInputStream(new ByteArrayInputStream(buffer));
messagePayload = inputStream.readLong();
} else {
Log.e(TAG, "Incomplete read from USAP management FD of size "
+ readBytes);
continue;
}
} catch (Exception ex) {
if (pollIndex == usapPoolEventFDIndex) {
Log.e(TAG, "Failed to read from USAP pool event FD: "
+ ex.getMessage());
} else {
Log.e(TAG, "Failed to read from USAP reporting pipe: "
+ ex.getMessage());
}
} else {
boolean usapPoolFDRead = false;
while (--pollIndex >= 0) {
if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
continue;
}
if (pollIndex > usapPoolEventFDIndex) {
Zygote.removeUsapTableEntry((int) messagePayload);
}
if (pollIndex == 0) {
// Zygote server socket
usapPoolFDRead = true;
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
socketFDs.add(newPeer.getFileDescriptor());
} else if (pollIndex < usapPoolEventFDIndex) {
// Session socket accepted from the Zygote server socket
try {
ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);
// TODO (chriswailes): Is this extra check necessary?
if (mIsForkChild) {
// We're in the child. We should always have a command to run at
// this stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
if (command != null) {
throw new IllegalStateException("command != null");
}
// We don't know whether the remote side of the socket was closed or
// not until we attempt to read from it from processOneCommand. This
// shows up as a regular POLLIN event in our regular processing
// loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(pollIndex);
socketFDs.remove(pollIndex);
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// We're in the server so any exception here is one that has taken
// place pre-fork while processing commands or reading / writing
// from the control socket. Make a loud noise about any such
// exceptions so that we know exactly what failed and why.
Slog.e(TAG, "Exception executing zygote command: ", e);
// Make sure the socket is closed so that the other end knows
// immediately that something has gone wrong and doesn't time out
// waiting for a response.
ZygoteConnection conn = peers.remove(pollIndex);
conn.closeSocket();
socketFDs.remove(pollIndex);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other
// main() method). Log the details of the exception and bring down
// the process.
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;
}
} else {
// Either the USAP pool event FD or a USAP reporting pipe.
// If this is the event FD the payload will be the number of USAPs removed.
// If this is a reporting pipe FD the payload will be the PID of the USAP
// that was just specialized. The `continue` statements below ensure that
// the messagePayload will always be valid if we complete the try block
// without an exception.
long messagePayload;
try {
byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
int readBytes =
Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
DataInputStream inputStream =
new DataInputStream(new ByteArrayInputStream(buffer));
messagePayload = inputStream.readLong();
} else {
Log.e(TAG, "Incomplete read from USAP management FD of size "
+ readBytes);
continue;
}
} catch (Exception ex) {
if (pollIndex == usapPoolEventFDIndex) {
Log.e(TAG, "Failed to read from USAP pool event FD: "
+ ex.getMessage());
} else {
Log.e(TAG, "Failed to read from USAP reporting pipe: "
+ ex.getMessage());
}
continue;
}
if (pollIndex > usapPoolEventFDIndex) {
Zygote.removeUsapTableEntry((int) messagePayload);
}
usapPoolFDRead = true;
}
}
if (usapPoolFDRead) {
int usapPoolCount = Zygote.getUsapPoolCount();
if (usapPoolCount < mUsapPoolSizeMin) {
// Immediate refill
mUsapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE;
} else if (mUsapPoolSizeMax - usapPoolCount >= mUsapPoolRefillThreshold) {
// Delayed refill
mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
}
}
}
// Check to see if the USAP pool needs to be refilled.
if (usapPoolFDRead) {
if (mUsapPoolRefillAction != UsapPoolRefillAction.NONE) {
int[] sessionSocketRawFDs =
socketFDs.subList(1, socketFDs.size())
.stream()
.mapToInt(FileDescriptor::getInt$)
.toArray();
final Runnable command = fillUsapPool(sessionSocketRawFDs);
final boolean isPriorityRefill =
mUsapPoolRefillAction == UsapPoolRefillAction.IMMEDIATE;
final Runnable command =
fillUsapPool(sessionSocketRawFDs, isPriorityRefill);
if (command != null) {
return command;
} else if (isPriorityRefill) {
// Schedule a delayed refill to finish refilling the pool.
mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
}
}
}

View File

@@ -169,6 +169,15 @@ static int gUsapPoolEventFD = -1;
*/
static constexpr int USAP_POOL_SIZE_MAX_LIMIT = 100;
/** The numeric value for the maximum priority a process may possess. */
static constexpr int PROCESS_PRIORITY_MAX = -20;
/** The numeric value for the minimum priority a process may possess. */
static constexpr int PROCESS_PRIORITY_MIN = 19;
/** The numeric value for the normal priority a process should have. */
static constexpr int PROCESS_PRIORITY_DEFAULT = 0;
/**
* A helper class containing accounting information for USAPs.
*/
@@ -903,7 +912,8 @@ static void ClearUsapTable() {
// Utility routine to fork a process from the zygote.
static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
const std::vector<int>& fds_to_close,
const std::vector<int>& fds_to_ignore) {
const std::vector<int>& fds_to_ignore,
bool is_priority_fork) {
SetSignalHandlers();
// Curry a failure function.
@@ -936,6 +946,12 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
pid_t pid = fork();
if (pid == 0) {
if (is_priority_fork) {
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
} else {
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
}
// The child process.
PreApplicationInit();
@@ -1133,6 +1149,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
is_system_server, is_child_zygote, managed_instruction_set);
// Reset the process priority to the default value.
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);
if (env->ExceptionCheck()) {
fail_fn("Error calling post fork hooks.");
}
@@ -1372,7 +1391,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
fds_to_ignore.push_back(gUsapPoolEventFD);
}
pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
@@ -1399,7 +1418,8 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
pid_t pid = ForkCommon(env, true,
fds_to_close,
fds_to_ignore);
fds_to_ignore,
true);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
@@ -1441,13 +1461,15 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
* zygote in managed code.
* @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by
* the FD hygiene code and automatically "closed" in the new USAP.
* @param is_priority_fork Controls the nice level assigned to the newly created process
* @return
*/
static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env,
jclass,
jint read_pipe_fd,
jint write_pipe_fd,
jintArray managed_session_socket_fds) {
jintArray managed_session_socket_fds,
jboolean is_priority_fork) {
std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
fds_to_ignore(fds_to_close);
@@ -1469,7 +1491,8 @@ static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env,
fds_to_ignore.push_back(write_pipe_fd);
fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,
is_priority_fork == JNI_TRUE);
if (usap_pid != 0) {
++gUsapPoolCount;
@@ -1706,6 +1729,10 @@ static void com_android_internal_os_Zygote_nativeUnblockSigTerm(JNIEnv* env, jcl
UnblockSignal(SIGTERM, fail_fn);
}
static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env, jclass) {
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
}
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
@@ -1718,7 +1745,7 @@ static const JNINativeMethod gMethods[] = {
(void *) com_android_internal_os_Zygote_nativePreApplicationInit },
{ "nativeInstallSeccompUidGidFilter", "(II)V",
(void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
{ "nativeForkUsap", "(II[I)I",
{ "nativeForkUsap", "(II[IZ)I",
(void *) com_android_internal_os_Zygote_nativeForkUsap },
{ "nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
@@ -1740,7 +1767,9 @@ static const JNINativeMethod gMethods[] = {
{ "nativeBlockSigTerm", "()V",
(void* ) com_android_internal_os_Zygote_nativeBlockSigTerm },
{ "nativeUnblockSigTerm", "()V",
(void* ) com_android_internal_os_Zygote_nativeUnblockSigTerm }
(void* ) com_android_internal_os_Zygote_nativeUnblockSigTerm },
{ "nativeBoostUsapPriority", "()V",
(void* ) com_android_internal_os_Zygote_nativeBoostUsapPriority }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {