android Source Analysis-Zygote

Keywords: Java Android socket Linux

android is also a linux-based system, so all processes start with the init process (directly or indirectly from the init process focus). Zygote is a fertilized egg process and created by init process during system startup. Look at the startup script/system/core/rootdir/init.zygote64.rc:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks

As you can see, the process to be executed is / system/bin/app_process64. The code is in / frameworks/base/cmds/app_process/app_main.cpp. The entry function is main:

......
// Create AppRuntime
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
......
while (i < argc) {
    const char* arg = argv[i++];
    if (strcmp(arg, "--zygote") == 0) {
        // Confirmation is the zygote process
        zygote = true;
        niceName = ZYGOTE_NICE_NAME;
    } else if (strcmp(arg, "--start-system-server") == 0) {
        startSystemServer = true;
    } else if (strcmp(arg, "--application") == 0) {
        application = true;
    } else if (strncmp(arg, "--nice-name=", 12) == 0) {
        niceName.setTo(arg + 12);
    } else if (strncmp(arg, "--", 2) != 0) {
        className.setTo(arg);
        break;
    } else {
        --i;
        break;
    }
}
......
if (zygote) {
    // Execute the zygote process
    runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
    runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
    fprintf(stderr, "Error: no class name or --zygote supplied.\n");
    app_usage();
    LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
......

If you ignore the details of the parameters, all you have left is to set up AppRuntime and call AppRuntime's start method, starting com.android.internal.os.ZygoteInit.
Look at AppRuntime:

class AppRuntime : public AndroidRuntime
{
public:
    AppRuntime(char* argBlockStart, const size_t argBlockLength)
        : AndroidRuntime(argBlockStart, argBlockLength)
        , mClass(NULL)
    {
    }
    ......
};

The constructor calls the constructor of the base class, which is in / frameworks / base / core / JNI / Android Runtime. cpp:

AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
        mExitWithoutCleanup(false),
        mArgBlockStart(argBlockStart),
        mArgBlockLength(argBlockLength)
{
    SkGraphics::Init();
    // There is also a global font cache, but its budget is specified by
    // SK_DEFAULT_FONT_CACHE_COUNT_LIMIT and SK_DEFAULT_FONT_CACHE_LIMIT.

    // Pre-allocate enough space to hold a fair number of options.
    mOptions.setCapacity(20);

    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this;
}

Keep yourself as the global gCurRuntime.
Look directly at the start method:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ......
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // Start Virtual Machine
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    // Callback Virtual Machine Creation
    onVmCreated(env);

    /*
     * Register android functions.
     */
     // Registration function
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    ......
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    // Get a reference to a string object
    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    // Create a String array object
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    // The first element to set the first string array is classNameStr, which is the full name of ZygoteInit.
    env->SetObjectArrayElement(strArray, 0, classNameStr);
    // Setting other parameters
    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
     // In the middle of the conversion class. For /, here is the conversion format.
    char* slashClassName = toSlashClassName(className);
    // Find this class from the jni environment
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        // Call the main method of the found class, which is the main method of ZygoteInit.
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);
    ......
}

Key sections have been commented. To sum up:
1. Start the virtual machine startVM;
2. Register jni method through startReg;
3. Call the main method of the ZygoteInit class;
startVm basically creates a Dalvik virtual machine environment for this process and initializes a jni environment for the current thread. startReg basically registers a bunch of jni methods for later invocation. This is not the focus of this article, so I will not repeat it here.

Next we'll focus on the main method of the ZygoteInit class.
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java:

public static void main(String argv[]) {
    ZygoteServer zygoteServer = new ZygoteServer();
    ......
    zygoteServer.registerServerSocket(socketName);
    ......
    preload();
    ......
    if (startSystemServer) {
        startSystemServer(abiList, socketName, zygoteServer);
    }
    ......
    zygoteServer.runSelectLoop(abiList);
    ......
}

1. Create ZygoteServer (as you can see is a cs architecture thing);
2. Register socket (using socket to communicate);
3. Preloading;
4. Start System Server;
5. Run the select loop;
It involves the operation of Zygote Hooks. In order not to affect the whole, make a mark temporarily and read later.
Here you can see that Zyget is basically a cs architecture, and communicates with this architecture through socket s. Let's first look at the preloading process:

    static void preload() {
        Log.d(TAG, "begin preload");
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");
        beginIcuCachePinning();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");
        //Preload the classes in the framework/base/preload-classes file
        preloadClasses();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
        // Preload resources
        preloadResources();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
        // Preload resources
        preloadOpenGL();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        //Through the System.loadLibrary() method, the three shared libraries of "android", "compiler_rt" and "jnigraphics" are preloaded.
        preloadSharedLibraries();
        //Preload text connector resources
        preloadTextResources();
        // Initialization of webview
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        endIcuCachePinning();
        warmUpJcaProviders();
        Log.d(TAG, "end preload");
    }

See, it's all about the initialization of some of android's own resources, which is done here.
Let's look at startSystem Server:

private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
            throws Zygote.MethodAndArgsCaller, RuntimeException {
        long capabilities = posixCapabilitiesAsBits(
            OsConstants.CAP_IPC_LOCK,
            OsConstants.CAP_KILL,
            OsConstants.CAP_NET_ADMIN,
            OsConstants.CAP_NET_BIND_SERVICE,
            OsConstants.CAP_NET_BROADCAST,
            OsConstants.CAP_NET_RAW,
            OsConstants.CAP_SYS_MODULE,
            OsConstants.CAP_SYS_NICE,
            OsConstants.CAP_SYS_RESOURCE,
            OsConstants.CAP_SYS_TIME,
            OsConstants.CAP_SYS_TTY_CONFIG,
            OsConstants.CAP_WAKE_ALARM
        );
        /* Containers run without this capability, so avoid setting it in that case */
        if (!SystemProperties.getBoolean(PROPERTY_RUNNING_IN_CONTAINER, false)) {
            capabilities |= posixCapabilitiesAsBits(OsConstants.CAP_BLOCK_SUSPEND);
        }
        /* 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,1018,1021,1032,3001,3002,3003,3006,3007,3009,3010",
            "--capabilities=" + capabilities + "," + capabilities,
            "--nice-name=system_server",
            "--runtime-args",
            "com.android.server.SystemServer",
        };
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            zygoteServer.closeServerSocket();
            handleSystemServerProcess(parsedArgs);
        }

        return true;
    }

In fact, the main sentence is Zygote.forkSystemServer, the previous is the configuration of parameters. Look down at the next layer/frameworks/base/core/java/com/android/internal/os/Zygote.java:

public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        VM_HOOKS.preFork();
        int pid = nativeForkSystemServer(
                uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities);
        // Enable tracing as soon as we enter the system_server.
        if (pid == 0) {
            Trace.setTracingEnabled(true);
        }
        VM_HOOKS.postForkCommon();
        return pid;
    }
    

According to the inbound uid,gid and other call functions nativeForkSystem Server, the fork function will eventually be called in ForkAndSpecializeCommon under / frameworks/base/core/jni/com_android_internal_os_Zygote.cpp. So in fact, it can be known that a process is split in c-level fork to serve as System Server.

Now let's go back to ZygoteInit.java at the java layer and continue to look at the socket-related parts. First, register Server Socket:

void registerServerSocket(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

Here the file descriptor is set, and then the LocalServerSocket is created and assigned to the mServerSocket. / frameworks/base/core/java/android/net/LocalServerSocket.java:

public LocalServerSocket(FileDescriptor fd) throws IOException
    {
        impl = new LocalSocketImpl(fd);
        impl.listen(LISTEN_BACKLOG);
        localAddress = impl.getSockAddress();
    }

After new goes out of LocalSocketImpl, listen begins directly. Next, I don't need to look at it specially. It's a normal network socket. This should prove to be socket-based communication. Then take a look at runSelectLoop:

void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce(this);
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }
    

Enter a dead loop, each time all the fd to be observed is set up into an array, and then Os.poll(pollFds, -1) is called to block and wait for the fd to change. The latter for loop calls the runOne method of ZygoteConnection when the fd changes (that is, there is a client connection, that is, there are other processes that want to communicate with ZygoteServer). This method, if you look at it simply, is ultimately to call Zygote.forkAndSpecialize to split the process. That is to say, once a connection is established, it means that an app is started. At this point, fork will split the new process, and the code will not be pasted for the time being.

So far, the process of Zygote has been basically analyzed. To sum up:
1. The system starts and the Zygote process starts through the init process. The exact thing will be to call ZygoteInit through runtime, the initialization process;
2.Zygote is based on cs architecture. Based on socket communication mechanism, ZygoteInit will start ZygoteServer in order to wait for socket communication to start app process.
3. Splitting out the System Server process, responsible for starting some of the key services of the system. It includes three categories (broadcasting, core and other);

Finally, a picture is attached for easy understanding.

Posted by Inkeye on Sat, 20 Apr 2019 12:27:33 -0700