ZygoteInitpublic class ZygoteInit extends Object Startup class for the zygote process.
Pre-initializes some classes, and then waits for commands on a UNIX domain
socket. Based on these commands, forks of child processes that inherit
the initial state of the VM.
Please see {@link ZygoteConnection.Arguments} for documentation on the
client protocol. |
Fields Summary |
---|
private static final String | TAG | private static final String | ANDROID_SOCKET_ENV | private static final int | LOG_BOOT_PROGRESS_PRELOAD_START | private static final int | LOG_BOOT_PROGRESS_PRELOAD_END | private static final int | PRELOAD_GC_THRESHOLDwhen preloading, GC after allocating this many bytes | private static android.net.LocalServerSocket | sServerSocket | private static android.content.res.Resources | mResourcesUsed to pre-load resources. We hold a global reference on it so it
never gets destroyed. | static final int | GC_LOOP_COUNTThe number of times that the main Zygote loop
should run before calling gc() again. | private static final boolean | ZYGOTE_FORK_MODEIf true, zygote forks for each peer. If false, a select loop is used
inside a single process. The latter is preferred. | private static final String | PRELOADED_CLASSESThe name of a resource file that contains classes to preload. | private static final boolean | PRELOAD_RESOURCESControls whether we should preload resources during zygote init. | private static final int | UNPRIVILEGED_UID | private static final int | UNPRIVILEGED_GID | private static final int | ROOT_UID | private static final int | ROOT_GID |
Constructors Summary |
---|
private ZygoteInit()Class not instantiable.
|
Methods Summary |
---|
private static ZygoteConnection | acceptCommandPeer()Waits for and accepts a single command connection. Throws
RuntimeException on failure.
try {
return new ZygoteConnection(sServerSocket.accept());
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
| static native long | capgetPermitted(int pid)Retrieves the permitted capability set from another process.
| static native void | closeDescriptor(java.io.FileDescriptor fd)Calls close() on a file descriptor
| static void | closeServerSocket()Close and clean up zygote sockets. Called on shutdown and on the
child's exit path.
try {
if (sServerSocket != null) {
sServerSocket.close();
}
} catch (IOException ex) {
Log.e(TAG, "Zygote: error closing sockets", ex);
}
sServerSocket = null;
| static native java.io.FileDescriptor | createFileDescriptor(int fd)Creates a file descriptor from an int fd.
| static void | gc()Runs several special GCs to try to clean up a few generations of
softly- and final-reachable objects, along with any other garbage.
This is only useful just before a fork().
final VMRuntime runtime = VMRuntime.getRuntime();
/* runFinalizationSync() lets finalizers be called in Zygote,
* which doesn't have a HeapWorker thread.
*/
runtime.gcSoftReferences();
runtime.runFinalizationSync();
runtime.gcSoftReferences();
runtime.runFinalizationSync();
runtime.gcSoftReferences();
runtime.runFinalizationSync();
| static native int | getpgid(int pid)Invokes the linux syscall "getpgid"
| private static void | handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)Finish remaining work for the newly forked system server process.
/*
* First, set the capabilities if necessary
*/
if (parsedArgs.uid != 0) {
try {
setCapabilities(parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IOException ex) {
Log.e(TAG, "Error setting capabilities", ex);
}
}
closeServerSocket();
/*
* Pass the remaining arguments to SystemServer.
* "--nice-name=system_server com.android.server.SystemServer"
*/
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
/* should never reach here */
| static void | invokeStaticMain(java.lang.ClassLoader loader, java.lang.String className, java.lang.String[] argv)Invokes a static "main(argv[]) method on class "className".
Converts various failing exceptions into RuntimeExceptions, with
the assumption that they will then cause the VM instance to exit.
Class<?> cl;
try {
cl = loader.loadClass(className);
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
"Missing class when invoking static main " + className,
ex);
}
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });
} catch (NoSuchMethodException ex) {
throw new RuntimeException(
"Missing static main on " + className, ex);
} catch (SecurityException ex) {
throw new RuntimeException(
"Problem getting static main on " + className, ex);
}
int modifiers = m.getModifiers();
if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new RuntimeException(
"Main method is not public and static on " + className);
}
/*
* This throw gets caught in ZygoteInit.main(), which responds
* by invoking the exception's run() method. This arrangement
* clears up all the stack frames that were required in setting
* up the process.
*/
throw new ZygoteInit.MethodAndArgsCaller(m, argv);
| public static void | main(java.lang.String[] argv)
try {
registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preloadClasses();
preloadResources();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
// Do an initial gc to clean up after startup
gc();
// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(
"ZygoteInit.main expects two arguments");
}
if (argv[1].equals("true")) {
startSystemServer();
}
Log.i(TAG, "Accepting command socket connections");
if (ZYGOTE_FORK_MODE) {
runForkMode();
} else {
runSelectLoopMode();
}
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
| private static void | preloadClasses()Performs Zygote process initialization. Loads and initializes
commonly used classes.
Most classes only cause a few hundred bytes to be allocated, but
a few will allocate a dozen Kbytes (in one case, 500+K).
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream(
PRELOADED_CLASSES);
if (is == null) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
} else {
Log.i(TAG, "Preloading classes...");
long startTime = SystemClock.uptimeMillis();
// Drop root perms while running static initializers.
setEffectiveGroup(UNPRIVILEGED_GID);
setEffectiveUser(UNPRIVILEGED_UID);
// Alter the target heap utilization. With explicit GCs this
// is not likely to have any effect.
float defaultUtilization = runtime.getTargetHeapUtilization();
runtime.setTargetHeapUtilization(0.8f);
// Start with a clean slate.
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Debug.startAllocCounting();
try {
BufferedReader br
= new BufferedReader(new InputStreamReader(is), 256);
int count = 0;
String line;
String missingClasses = null;
while ((line = br.readLine()) != null) {
// Skip comments and blank lines.
line = line.trim();
if (line.startsWith("#") || line.equals("")) {
continue;
}
try {
if (Config.LOGV) {
Log.v(TAG, "Preloading " + line + "...");
}
Class.forName(line);
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
if (Config.LOGV) {
Log.v(TAG,
" GC at " + Debug.getGlobalAllocSize());
}
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
count++;
} catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found for preloading: " + line);
if (missingClasses == null) {
missingClasses = line;
} else {
missingClasses += " " + line;
}
}
}
if (missingClasses != null &&
"1".equals(SystemProperties.get("persist.service.adb.enable"))) {
throw new IllegalStateException(
"Missing class(es) for preloading, update preloaded-classes ["
+ missingClasses + "]");
}
Log.i(TAG, "...preloaded " + count + " classes in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
// Restore default.
runtime.setTargetHeapUtilization(defaultUtilization);
Debug.stopAllocCounting();
// Bring back root. We'll need it later.
setEffectiveUser(ROOT_UID);
setEffectiveGroup(ROOT_GID);
}
}
| private static int | preloadColorStateLists(dalvik.system.VMRuntime runtime, android.content.res.TypedArray ar)
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
if (Config.LOGV) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
if (Config.LOGV) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
mResources.getColorStateList(id);
}
}
return N;
| private static int | preloadDrawables(dalvik.system.VMRuntime runtime, android.content.res.TypedArray ar)
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
if (Config.LOGV) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
if (Config.LOGV) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
Drawable dr = mResources.getDrawable(id);
if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
Log.w(TAG, "Preloaded drawable resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ") that varies with configuration!!");
}
}
}
return N;
| private static void | preloadResources()Load in commonly used resources, so they can be shared across
processes.
These tend to be a few Kbytes, but are frequently in the 20-40K
range, and occasionally even larger.
final VMRuntime runtime = VMRuntime.getRuntime();
Debug.startAllocCounting();
try {
runtime.gcSoftReferences();
runtime.runFinalizationSync();
mResources = Resources.getSystem();
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
int N = preloadDrawables(runtime, ar);
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
N = preloadColorStateLists(runtime, ar);
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
}
mResources.finishPreloading();
} catch (RuntimeException e) {
Log.w(TAG, "Failure preloading resources", e);
} finally {
Debug.stopAllocCounting();
}
| private static void | registerZygoteSocket()Registers a server socket for zygote command connections
if (sServerSocket == null) {
int fileDesc;
try {
String env = System.getenv(ANDROID_SOCKET_ENV);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(
ANDROID_SOCKET_ENV + " unset or invalid", ex);
}
try {
sServerSocket = new LocalServerSocket(
createFileDescriptor(fileDesc));
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
| static native void | reopenStdio(java.io.FileDescriptor in, java.io.FileDescriptor out, java.io.FileDescriptor err)Invokes the syscall dup2() to copy the specified descriptors into
stdin, stdout, and stderr. The existing stdio descriptors will be
closed and errors during close will be ignored. The specified
descriptors will also remain open at their original descriptor numbers,
so the caller may want to close the original descriptors.
| private static void | runForkMode()Runs the zygote in accept-and-fork mode. In this mode, each peer
gets its own zygote spawner process. This code is retained for
reference only.
while (true) {
ZygoteConnection peer = acceptCommandPeer();
int pid;
pid = Zygote.fork();
if (pid == 0) {
// The child process should handle the peer requests
// The child does not accept any more connections
try {
sServerSocket.close();
} catch (IOException ex) {
Log.e(TAG, "Zygote Child: error closing sockets", ex);
} finally {
sServerSocket = null;
}
peer.run();
break;
} else if (pid > 0) {
peer.closeSocket();
} else {
throw new RuntimeException("Error invoking fork()");
}
}
| private static void | runSelectLoopMode()Runs the zygote process's select loop. Accepts new connections as
they happen, and reads commands from connections one spawn-request's
worth at a time.
ArrayList<FileDescriptor> fds = new ArrayList();
ArrayList<ZygoteConnection> peers = new ArrayList();
FileDescriptor[] fdArray = new FileDescriptor[4];
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
int loopCount = GC_LOOP_COUNT;
while (true) {
int index;
/*
* Call gc() before we block in select().
* It's work that has to be done anyway, and it's better
* to avoid making every child do it. It will also
* madvise() any free memory as a side-effect.
*
* Don't call it every time, because walking the entire
* heap is a lot of overhead to free a few hundred bytes.
*/
if (loopCount <= 0) {
gc();
loopCount = GC_LOOP_COUNT;
} else {
loopCount--;
}
try {
fdArray = fds.toArray(fdArray);
index = selectReadable(fdArray);
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {
ZygoteConnection newPeer = acceptCommandPeer();
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done;
done = peers.get(index).runOnce();
if (done) {
peers.remove(index);
fds.remove(index);
}
}
}
| static native int | selectReadable(java.io.FileDescriptor[] fds)Invokes select() on the provider array of file descriptors (selecting
for readability only). Array elements of null are ignored.
| static native void | setCapabilities(long permittedCapabilities, long effectiveCapabilities)Sets the permitted and effective capability sets of this process.
| static native void | setCloseOnExec(java.io.FileDescriptor fd, boolean flag)Toggles the close-on-exec flag for the specified file descriptor.
| private static void | setEffectiveGroup(int gid)Sets effective group ID.
int errno = setregid(ROOT_GID, gid);
if (errno != 0) {
Log.e(TAG, "setregid() failed. errno: " + errno);
}
| private static void | setEffectiveUser(int uid)Sets effective user ID.
int errno = setreuid(ROOT_UID, uid);
if (errno != 0) {
Log.e(TAG, "setreuid() failed. errno: " + errno);
}
| static native int | setpgid(int pid, int pgid)Invokes the linux syscall "setpgid"
| static native int | setregid(int rgid, int egid)The Linux syscall "setregid()"
| static native int | setreuid(int ruid, int euid)The Linux syscall "setreuid()"
| private static boolean | startSystemServer()Prepare the arguments and fork for the system server process.
/* Hardcoded command line to start the system server */
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
"--capabilities=121715744,121715744",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);
/*
* Enable debugging of the system process if *either* the command line flags
* indicate it should be debuggable or the ro.debuggable system property
* is set to "1"
*/
int debugFlags = parsedArgs.debugFlags;
if ("1".equals(SystemProperties.get("ro.debuggable")))
debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, debugFlags, null);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {
handleSystemServerProcess(parsedArgs);
}
return true;
|
|