Start the App Process
One of the links in the Activity startup process is to call. If the process where the App does not exist yet, first call AMS's startProcessLocked:
void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? ProcessRecord app = (, , true); (r); if (app != null && != null) { //... } (, , true, 0, "activity", (), false, false, true); }
The startProcessLocked function has multiple overloads. Look at the longest one, and the most important thing is to call it.
if (entryPoint == null) entryPoint = ""; startResult = (entryPoint, , uid, uid, gids, debugFlags, mountExternal, , , requiredAbi, instructionSet, , entryPointArgs);
The first parameter is the execution goal, which will be discussed later.
Simply call startViaZygote, encapsulate some parameters, and then call zygoteSendArgsAndGetResult. As the name suggests, the next process startup work is handed over to Zygote.
Zygote
Zygote translates to mean "fertilized egg", which is also Zygote's main job - the hatching process. There are three main tasks in summary of Zygote, and ZygoteInit's main function is also clearly reflected. Zygote's startup and other functions are analyzed in another article. This time, we focus on Zygote's monitoring of Socket.
: Start the server side of Socket
: Preload resources
: Start the system_server process
public static void main(String argv[]) { // Mark zygote start. This ensures that thread creation will throw // an error. (); try { //... registerZygoteSocket(socketName); //... preload(); //... if (startSystemServer) { startSystemServer(abiList, socketName); } //... runSelectLoop(abiList); //... } catch (MethodAndArgsCaller caller) { (); } catch (Throwable ex) { //... } }
Finally, Zygote executes runSelectLoop, and infinite loops to wait for the process to start the request.
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); (()); (null); while (true) { StructPollfd[] pollFds = new StructPollfd[()]; for (int i = 0; i < ; ++i) { pollFds[i] = new StructPollfd(); pollFds[i].fd = (i); pollFds[i].events = (short) POLLIN; } try { (pollFds, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } for (int i = - 1; i >= 0; --i) { if ((pollFds[i].revents & POLLIN) == 0) { continue; } if (i == 0) { ZygoteConnection newPeer = acceptCommandPeer(abiList); (newPeer); (()); } else { boolean done = (i).runOnce(); if (done) { (i); (i); } } } } }
The IPC method used to communicate with Zygote is socket, type LocalSocket, which is essentially a package of Linux LocalSocket. Unlike socket binding that requires IP and port in memory, LocalSocket uses FileDescriptor file descriptor, which can represent files or sockets.
runSelectLoop maintains two lists, which save file descriptors FileDescriptor and ZygoteConnection, respectively, and correspond one by one. FileDescriptor with index=0 represents ServerSocket, so ZygoteConnection with index=0 is filled with null.
In each loop, determine which fds is readable:
- When i=0, it means there is a new client. Call acceptCommandPeer to create ZygoteConnection and save
- When i>0, it means that there is a new command in the socket that has established a connection. Call the runOnce function to execute
Fork process
The runOnce function is very long, and we only focus on the process creation part.
boolean runOnce() throws { //... try { //... pid = (, , , , rlimits, , , , fdsToClose, , ); } catch (ErrnoException ex) { //... } try { if (pid == 0) { // in child (serverPipeFd); serverPipeFd = null; handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); // should never get here, the child is expected to either // throw or exec(). return true; } else { // in parent...pid of < 0 means failure (childPipeFd); childPipeFd = null; return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); } } finally { //... } }
First, the forkAndSpecialize function is called, and the creation process returns a pid.
public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { (true); // Note that this event ends at the end of handleChildProc, (Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); } VM_HOOKS.postForkCommon(); return pid; }
In forkAndSpecialize, the core is to use JNI to call the native fork function. VM_HOOKS.preFork() will be executed before the call, and VM_HOOKS.postForkCommon() will be executed after the call.
The function of VM_HOOKS.preFork() is to stop the running of Zygote's 4 Daemon subthreads, ensure that Zygote is a single thread, and improve fork efficiency. Initialize the gc heap when the thread stops. VM_HOOKS.postForkCommon() can be regarded as an inverse operation, and the more in-depth content about virtual machines will not be discussed yet.
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
nativeForkSystemServer is a native function, corresponding to com_android_internal_os_Zygote_nativeForkAndSpecialize of com_android_internal_os_Zygote.cpp. Continue to call ForkAndSpecializeCommon, and the most core sentence is to call the fork function.
pid_t pid = fork();
A simple memory fork function is used, which copies the current process, and the properties are the same as the current process, using copy on write (copy on write). After executing the function, the new process has been created and the returned pid=0; for the copied process, the new process's pid is returned; when an error occurs, -1 is returned.
Therefore, after executing the forkAndSpecialize function, the subsequent code of runOnce is executed in two processes respectively, to determine the current pid and distinguish whether it is in the current process or the new process.
- pid == 0: New process, call handleChildProc
- pid != 0: The current process, call handleParentProc
The handleParentProc function is a process for cleaning the current process and is relatively simple. Let's focus on the handleChildProc function that is carried out in the new process.
Initialization of new processes
private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr) throws { //... if ( != null) { (, , , (), pipeFd, ); } else { (, , null /* classLoader */); } }
Two ways to start:
I don’t understand the purpose of the first one, so I hang up first and analyze it later.
The second type is to continue calling applicationInit, which is getting closer and closer to our goal, and finally call invokeStaticMain.
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) throws { Class<?> cl; try { cl = (className, true, classLoader); } catch (ClassNotFoundException ex) { //... } Method m; try { m = ("main", new Class[] { String[].class }); } catch (NoSuchMethodException ex) { //... } int modifiers = (); if (! ((modifiers) && (modifiers))) { throw new RuntimeException( "Main method is not public and static on " + className); } throw new (m, argv); }
Here we use reflection to obtain the classes and functions that need to be executed. The objective function is of course main, and the variable entryPoint in the target class comes from, as mentioned earlier.
entryPoint = ""
The last sentence executes throw new (m, argv), which is a bit confusing.
public static class MethodAndArgsCaller extends Exception implements Runnable { //... public void run() { try { (null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { //... } } }
Through the above analysis, it is easy to know that MethodAndArgsCaller is the main thread of the App, and the run method inside implements the reflected call. When does execution trigger and why does it need to be designed like this?
Since MethodAndArgsCaller is an exception, throwing it is sure that somewhere will receive it, review the call chain along the way:
Looking at the previous function, I caught the MethodAndArgsCaller exception and directly called the run function. The comments explain why this is done:
This throw gets caught in (), 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.
Functions are stored in the virtual machine on the stack. Every time a function is called, the function-related data is pushed onto the stack; after executing the function, the function is popped out of the stack. Therefore, the main function at the bottom of the stack is.
In the above study, after a new process is created, it only takes a series of functions to call the main function. If the main function is called directly, the initialization function in the call chain will always exist. In order to clean up this part of the functions, the method of throwing exceptions is used. The functions that do not catch the exception will end immediately, and the functions above will end to achieve the purpose of cleaning.
Finally, I would like to add that starting with the handleChildProc function, a series of procedures call the main function of ActivityThread. This is not unique to starting the App. When starting the SystemServer process in the subsequent research, you will find that the logic is the same.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.