2012年3月16日金曜日

Zygoteは何をしているのか?

Zygoteは何をしているのか?

さて、/system/bin/app_processがZygoteの本体で、JavaのZygoteInit.main()を呼び出すという話を以前の勉強会で出しました。で、前回はそのまま、system_serverプロセスをforkするお話に突入してしまったわけですが、ここからは、その続きの始まりです。ここからは、コードを拾い出して書いて行きますが、かなり大幅に省略していたりしますのでご了承下さい。

public static void main(String argv[]) {
        :
        :
    registerZygoteSocket();
        :
        :
    if (argv[1].equals("true")) {
        startSystemServer();  //前回はここまで
    } else if (!argv[1].equals("false")) {
        throw new RuntimeException(argv[0] + USAGE_STRING);
    }

    Log.i(TAG, "Accepting command socket connections");

    if (ZYGOTE_FORK_MODE) {
        runForkMode();
    } else {
        runSelectLoopMode();
    }

    closeServerSocket();
}
まず先に、registerZygoteSocket()を見ておきます。名前からしてZygoteの必要なソケットの登録とわかります。
        :
        :
private static void registerZygoteSocket() {
    String env = System.getenv(ANDROID_SOCKET_ENV);
    fileDesc = Integer.parseInt(env);
    sServerSocket = new LocalServerSocket(
    createFileDescriptor(fileDesc));

とりあえず、環境変数を取得してLocalServerSocketを生成しています。

続いては前回入り込んだstartSystemServer()では、子プロセスをforkしていましたが、system_serverの生成が終わると、親のプロセスは普通にここまで戻ってきます。続きを見てみると、if文に囲まれた個所が目に入ります。このif文ですが
    private static final boolean ZYGOTE_FORK_MODE = false;
ということで、Gingerbreadさんのコードだと、runSelectLoopMode()に入ります。というわけで処理を追いかけて見ます。
private static void runSelectLoopMode() throws MethodAndArgsCaller {
        :
        :
   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);
            }
        }
    }
}

「先生!!なんだか、Zygoteちゃんがgc()呼んでます!」的なコードが見える気がしますが、とりあえず今は無視します。「え〜」って声が聞こえる気はしますが、今回はそこはメインじゃないですし。誰か、ここを追いかけてPF部で発表してくれる人はいないものかな・・・・。

とりあえず進みます。ここでは保持しているfdのリストを元にselectReadable()を呼び出します。selectReadable()は、Nativeコードで、実体はframeworks/base/core/jni/com_android_internal_os_ZygoteInit.cpp にあります。
Jniのためコードがちょっと面倒なので簡単に何をやってるかだけ抜き出すと、引数で渡したfdのArrayをfdsetにFD_SETした後、

do {
    err = select (nfds, &fdset, NULL, NULL, NULL);
} while (err < 0 && errno == EINTR);

とまぁ、select待ちしたうえで、selectを抜けると、Arrayの何番目がFD_ISSETかを判定して返します。

index==0の場合、つまり、最初に待ち受けしているFDに接続要求が来ると
private static ZygoteConnection acceptCommandPeer() {
    try {
        return new ZygoteConnection(sServerSocket.accept());
    } catch (IOException ex) {
        throw new RuntimeException("IOException during accept()", ex);
    }
}

ということで、acceptしたfdを持ったZygoteConnectionを生成しており、それをarrayに追加した後、acceptしたfdをfdsに追加しているわけです。

で、もしも0以外のfd(つまりsServerSocket.accept()でacceptしたfd)がreadableになると、対象となるZygoteConnection#runOnce()を呼び出します。

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
        :
        :
    args = readArgumentList();
        :
        :
   parsedArgs = new Arguments(args);

    applyUidSecurityPolicy(parsedArgs, peer);
    applyDebuggerSecurityPolicy(parsedArgs);
    applyRlimitSecurityPolicy(parsedArgs, peer);
    applyCapabilitiesSecurityPolicy(parsedArgs, peer);

    int[][] rlimits = null;

    if (parsedArgs.rlimits != null) {
        rlimits = parsedArgs.rlimits.toArray(intArray2d);
    }

    pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
                                                        parsedArgs.gids, parsedArgs.debugFlags, rlimits);
        :
        :
    if (pid == 0) {
        // in child
        handleChildProc(parsedArgs, descriptors, newStderr);
        // should never happen
        return true;
    } else { /* pid != 0 */
        // in parent...pid of < 0 means failure
        return handleParentProc(pid, descriptors, parsedArgs);
    }

readArgumentList()は、acceptした時のsocketから引数のリストを読み出しています。何をどう読み込んでパースしているのか、その後のチェック等は後日に回すものとして先に進みます。

取得した引数に従って、まずは子プロセスをforkしています。何となく想像がつくかと思いますが、uid,gid等の設定に従って子プロセスをSpecializeしているわけです。

その後、親プロセスであるZygoteの本体は
private boolean handleParentProc(int pid,FileDescriptor[] descriptors, Arguments parsedArgs) {
        :
        :
    ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
        :
        :
    for (FileDescriptor fd: descriptors) {
        ZygoteInit.closeDescriptor(fd);
    }
        :
        :
    mSocketOutStream.writeInt(pid);
        :
        :
    if (parsedArgs.peerWait) {
        mSocket.close();
        return true;
    }
    return false;
}
のように、子プロセスをpeerのプロセスグループへの登録を行った後、descriptorを閉じ、要求をしてきた相手にpidを返した後、peerを維持する必要がなければ、socketを閉じて処理をLoopに戻し、ZygoteConnectionを削除します。

起こされた子プロセスは・・・というと
private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
                                               PrintStream newStderr) {
    if (parsedArgs.peerWait) {
        ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
        sPeerWaitSocket = mSocket;
    } else {
        closeSocket();
        ZygoteInit.closeServerSocket();
    }

    if (descriptors != null) {
        ZygoteInit.reopenStdio(descriptors[0],
        descriptors[1], descriptors[2]);

        for (FileDescriptor fd: descriptors) {
            ZygoteInit.closeDescriptor(fd);
        }
        newStderr = System.err;
    }
サーバー側のFDをcloseした後、
    if (parsedArgs.runtimeInit) {
        RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
    } else {
        ClassLoader cloader;

        if (parsedArgs.classpath != null) {
            cloader
                = new PathClassLoader(parsedArgs.classpath,
                                                        ClassLoader.getSystemClassLoader());
        } else {
            cloader = ClassLoader.getSystemClassLoader();
        }
        String className;
        className = parsedArgs.remainingArgs[0];
        String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];

        System.arraycopy(parsedArgs.remainingArgs, 1,mainArgs, 0, mainArgs.length);
        ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
    }
}
runtimeInitがどのような場合にtrueになっているのか調べ切れていませんが、後者の処理になった場合、引数で指定されたクラスをロードし、Static Mainを呼び出しているようです。

大ざっぱにまとめると、Zygoteは、socketを生成して待ち受け、要求が来ると、自身のコピーである子プロセスで送信されてきた引数を元にJavaのアプリケーションを起動していると考えて良いかと思います。

0 件のコメント:

コメントを投稿