2011年2月2日水曜日

Zygoteへの起動要求を出している箇所を探す(その2)

というわけで、先日の続きです。

Zygoteを使って新しいVMプロセスを生成していそうな箇所は
  • android.os.Process.start()を呼び出すコード
  • /system/bin/dvz
の二つにとりあえず絞りました。
というわけで、Process.start()を呼び出していそうな箇所をきちんと探してみたら、あっさり見つかりました。

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr) {
        :
        :
    int pid = Process.start("android.app.ActivityThread",
        mSimpleProcessManagement ? app.processName : null, uid, uid,
        gids, debugFlags, null);
        :
        :
}

しかしまぁ、このActivityManagerServiceさんは難敵です。
何せ、メソッドの行数の長いこと・・・・。このコード、なかなか難易度が高いです。
とりあえず、こういうときは焦らず最初から。

横浜PF部の勉強会ではちらりと述べたのですが、Zygoteは起動時に--start-system-serverという引数を受け取っており、プロセス生成後、最初の子プロセスとしてsystem_serverを生成しています。このSystemServerは、内部でServerThreadというスレッドをrunしています。

frameworks/base/services/java/com/android/server/SystemServer.java
public void run() {
        :
        :
    context = ActivityManagerService.main(factoryTest);
        :
        :
}

というわけで、mainが直接呼び出されています。mainはというと

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
public static final Context main(int factoryTest) {
    AThread thr = new AThread();
    thr.start();
    synchronized (thr) {
        while (thr.mService == null) {
            try {
                thr.wait();
            } catch (InterruptedException e) {
            }
        }
    }
    ActivityManagerService m = thr.mService;
    mSelf = m;
    ActivityThread at = ActivityThread.systemMain();
    mSystemThread = at;
    Context context = at.getSystemContext();

    m.mContext = context;
    m.mFactoryTest = factoryTest;
    m.mMainStack = new ActivityStack(m, context, true);
    m.mBatteryStatsService.publish(context);
    m.mUsageStatsService.publish(context);

    synchronized (thr) {
        thr.mReady = true;
        thr.notifyAll();
    }
    m.startRunning(null, null, null, null);
    return context;
}

まずは、Athreadを生成してrunした後、AThreadのmServiceが設定されるのを待っています。
Athreadとはというと
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
static class AThread extends Thread {
    ActivityManagerService mService;
    boolean mReady = false;

    public AThread() {
        super("ActivityManager");
    }

    public void run() {
        Looper.prepare();
        android.os.Process.setThreadPriority(
        android.os.Process.THREAD_PRIORITY_FOREGROUND);
        android.os.Process.setCanSelfBackground(false);
        ActivityManagerService m = new ActivityManagerService();
        synchronized (this) {
            mService = m;
            notifyAll();
        }
        synchronized (this) {
            while (!mReady) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        Looper.loop();
    }
}
ということで、Looperのprepareを呼び出した後、ActivityManagerServiceの生成を行いmServiceに設定したうえで、mReadyのフラグが立つのを待っています。
スレッドを生成したmainに処理が戻ると、mServiceのActivityManagerServiceにcontext等を設定した上で、mReadyフラグを立てて、AThreadの処理を実行させた後、AThreadのActivityManagerService#startRunning()を呼び出します。
AThreadの方はというと、Looper.loop()でメッセージループを実行していますね。

一方、startRunning()はというと
public final void startRunning(String pkg, String cls, String action,String data) {
    synchronized(this) {
        if (mStartRunning) {
            return;
        }
        mStartRunning = true;
        mTopComponent = pkg != null && cls != null ? new ComponentName(pkg, cls) : null;
        mTopAction = action != null ? action : Intent.ACTION_MAIN;
        mTopData = data;
        if (!mSystemReady) {
            return;
        }
    }
    systemReady(null);
}

初回起動ですので、systemReady(null)が呼び出されます。
正直な話、真面目に話そうと思うと、PF部の発表時間の枠に収まりませんし、とりあえず、重要そうな処理だけを追いかけます。
public void systemReady(final Runnable goingCallback) {
        :
        :
    Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
    ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0);
        :
        :
    for (int i=ris.size()-1; i>=0; i--) {
        if ((ris.get(i).activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
            ris.remove(i);
        }
    }
        :
        :

     intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();


                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        if (lastDoneReceivers.contains(comp)) {
                            ris.remove(i);
                            i--;
                        }
                    }
                  
                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        doneReceivers.add(comp);
                        intent.setComponent(comp);
                        IIntentReceiver finisher = null;
                        if (i == ris.size()-1) {
                            finisher = new IIntentReceiver.Stub() {
                                public void performReceive(Intent intent, int resultCode,
                                        String data, Bundle extras, boolean ordered,
                                        boolean sticky) {
                                    mHandler.post(new Runnable() {
                                        public void run() {
                                            synchronized (ActivityManagerService.this) {
                                                mDidUpdate = true;
                                            }
                                            writeLastDonePreBootReceivers(doneReceivers);
                                            systemReady(goingCallback);
                                        }
                                    });
                                }
                            };
                        }
                        Slog.i(TAG, "Sending system update to: " + intent.getComponent());
                        broadcastIntentLocked(null, null, intent, null, finisher,
                                0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);
                        if (finisher != null) {
                            mWaitingUpdate = true;
                        }
                    }

ということで、詳しいことは置いておいてコードを見る限り
1. PackageManagerからACTION_PRE_BOOT_COMPLETEDのReceiverのリストを取得
2. リストから、FLAG_SYSTEMを持たないものを削除
3. 既にreadLastDonePreBootReceiversで、既に起動済みのものを削除?
4. 残ったリストにintent処理の終了用コードを設定して
5. FLAG_RECEIVER_BOOT_UPGRADEフラグを追加したIntentを送信

といった処理のようですね。
割と込み入っているところを、かなり適当にこんな時間に追いかけているので、何か見落としもありそうですが、とりあえず、この辺りが起動処理に絡んでいそうだという雰囲気です。

追記:
とまぁ、ここまでコードを追いかけてみたもののここが当たりかはちょっと不安を抱いてます。
そもそもActivityManagerServiceとPackageManagerServiceだと、PackageManagerServiceの方が後に生成されたりとかしていますし。
ちょっとログを仕込んでビルドして、実際に動かしてみないと駄目だなぁ・・・。

2011年2月1日火曜日

Zygoteへの起動要求を出している箇所を探す(その1)

週末は病院巡りとQt的なお話で少々忙しかったのでお休みしてました。
で、続き行きます。そろそろ真面目にペースを上げないと、資料作成に入れませんし。

ここまでの調査でZygote(app_process)が、socketを待ち受け、sokectに起動パラメータが来るとそれに従って自身のコピーを使ってJavaアプリの起動を行うという流れまで確認しました。
では、誰がソケットを開いて要求を投げてくるのかを探してみたいなと思っています。

まぁ、まずは待ち受けてるソケットを調べて見るのがセオリーでしょうね。
先日は待ち受けているコードは見つかりました。

private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
        :
private static void registerZygoteSocket() {
        :
    String env = System.getenv(ANDROID_SOCKET_ENV);
    fileDesc = Integer.parseInt(env);
    sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));
        :
}

でも、環境変数から先がよくわかっていなかったので、とりあえず、実際に動いているエミュレータで当たってみましょう。
とりあえずadb shellでzygoteのプロセス番号をしらべます。
# ps
USER PID PPID VSIZE RSS WCHAN PC NAME
        :
        :
radio 32 1 5412 484 ffffffff afd0bdac S /system/bin/rild
root 33 1 63964 20588 c009b74c afd0b844 S zygote
media 34 1 17212 1364 ffffffff afd0b6fc S /system/bin/mediaserver
        :
        :

続いて、openされているfdを調べます。開いているsocketが2つありますね。
# ls -l /proc/33/fd
lrwx------ root root 2011-01-31 22:51 0 -> /dev/null
lrwx------ root root 2011-01-31 22:51 1 -> /dev/null
lrwx------ root root 2011-01-31 22:51 2 -> /dev/null
l-wx------ root root 2011-01-31 22:51 3 -> /dev/log/main
l-wx------ root root 2011-01-31 22:51 4 -> /dev/log/radio
l-wx------ root root 2011-01-31 22:51 5 -> /dev/log/events
lr-x------ root root 2011-01-31 22:51 6 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 7 -> /system/framework/bouncycastle.jar
lr-x------ root root 2011-01-31 22:51 8 -> /dev/__properties__ (deleted)
lrwx------ root root 2011-01-31 22:51 9 -> socket:[289]
lr-x------ root root 2011-01-31 22:51 10 -> /system/framework/ext.jar
lr-x------ root root 2011-01-31 22:51 11 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 12 -> /system/framework/android.policy.jar
lr-x------ root root 2011-01-31 22:51 13 -> /system/framework/services.jar
lr-x------ root root 2011-01-31 22:51 14 -> /system/framework/core-junit.jar
lr-x------ root root 2011-01-31 22:51 15 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 16 -> /system/fonts/DroidSans.ttf
lr-x------ root root 2011-01-31 22:51 17 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 18 -> /dev/urandom
lr-x------ root root 2011-01-31 22:51 19 -> /system/framework/framework-res.apk
lrwx------ root root 2011-01-31 22:51 20 -> socket:[583]
では、開いているsocketを調べてみましょう。LocalServerなんて名前からもunixかなぁと当て推量で・・・・。
# cat /proc/net/unix
Num RefCount Protocol Flags Type St Inode Path
c5924de0: 00000002 00000000 00010000 0001 01 257 /dev/socket/property_service
c5924960: 00000002 00000000 00010000 0001 01 276 /dev/socket/vold
c59247e0: 00000002 00000000 00010000 0001 01 283 /dev/socket/netd
c5924660: 00000002 00000000 00010000 0001 01 285 /dev/socket/rild-debug
c59244e0: 00000002 00000000 00010000 0001 01 287 /dev/socket/rild
c5924360: 00000002 00000000 00010000 0001 01 289 /dev/socket/zygote
c5b34800: 00000002 00000000 00010000 0001 01 319 @jdwp-control
c59241e0: 00000002 00000000 00010000 0001 01 296 /dev/socket/installd
c5924060: 00000002 00000000 00010000 0001 01 298 /dev/socket/keystore
c5b34e00: 00000002 00000000 00010000 0001 01 305 /dev/socket/qemud
c5b34c80: 00000002 00000000 00010000 0001 01 308 @android:debuggerd
        :
        :
c40fb3c0: 00000003 00000000 00000000 0001 03 613 /dev/socket/qemud
c40fb240: 00000003 00000000 00000000 0001 03 612
c40fb0c0: 00000002 00000000 00000000 0001 03 606
c40fbb40: 00000003 00000000 00000000 0001 03 583 /dev/socket/zygote
c40fbcc0: 00000003 00000000 00000000 0001 03 582
c40fbe40: 00000003 00000000 00000000 0001 03 564
c40716a0: 00000003 00000000 00000000 0001 03 563
c4071520: 00000003 00000000 00000000 0001 03 561
c40713a0: 00000003 00000000 00000000 0001 03 560
c4071220: 00000003 00000000 00000000 0001 03 558 /dev/socket/vold
c40710a0: 00000003 00000000 00000000 0001 03 557
c4071820: 00000003 00000000 00000000 0001 03 550 /dev/socket/netd
c40719a0: 00000003 00000000 00000000 0001 03 549
c4071b20: 00000003 00000000 00000000 0001 03 538 /dev/socket/qemud
c4071ca0: 00000003 00000000 00000000 0001 03 537
c4071e20: 00000003 00000000 00000000 0001 03 317 /dev/socket/installd
c5b34380: 00000003 00000000 00000000 0001 03 523
c5b34200: 00000003 00000000 00000000 0001 03 404 @jdwp-control
c5b34080: 00000003 00000000 00000000 0001 03 402
c5b34500: 00000003 00000000 00000000 0001 03 354 /dev/socket/qemud
c5b34680: 00000003 00000000 00000000 0001 03 353
c5b34980: 00000003 00000000 00000000 0001 03 316
c5b34b00: 00000003 00000000 00000000 0001 03 315
c5924ae0: 00000003 00000000 00000000 0001 03 260
c5924c60: 00000003 00000000 00000000 0001 03 259

どうやら、ビンゴです。/dev/socket/zygoteでUNIXドメインソケットを開いているようですね。肝心の起動要求はというと、いちいちaccept()して読み込んで閉じるという作業を繰り返しているわけですから、エミュレータでは探れません。というわけで、ソースコードに戻ります。

とりあえず、zygoteであることはわかったので、こういう時は"で囲まれた文字列としてのzygoteをコードで検索します。
gingerbread hermit4$ source build/envsetup.sh
gingerbread hermit4$ sgrep \"zygote\"
./cts/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java:36: "zygote"
./dalvik/vm/Jni.c:4322: * only be called in "zygote" mode, when we have one thread running.
./dalvik/vm/alloc/HeapDebug.c:87: if (strcmp(buf, "zygote") != 0) {
./dalvik/vm/alloc/HeapDebug.c:88: /* If the process is no longer called "zygote",
./dalvik/vm/hprof/HprofHeap.c:264: nameId = hprofLookupStringId("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:243: dvmDumpLoaderStats("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:396: dvmDumpLoaderStats("zygote");
./frameworks/base/cmds/app_process/app_main.cpp:156: setArgv0(argv0, "zygote");
./frameworks/base/cmds/app_process/app_main.cpp:157: set_process_name("zygote");
./frameworks/base/cmds/dumpstate/utils.c:394: if (len <= 0 || !memcmp(data, "zygote", 6)) continue;
./frameworks/base/cmds/rawbu/backup.cpp:707: property_set("ctl.stop", "zygote"); ./frameworks/base/cmds/rawbu/backup.cpp:727: property_set("ctl.start", "zygote"); ./frameworks/base/core/java/android/os/Process.java:46: private static final String ZYGOTE_SOCKET = "zygote";
./frameworks/base/core/java/com/android/internal/os/SamplingProfilerIntegration.java:129: writeSnapshot(dir, "zygote");
./frameworks/base/tools/preload/PrintHtmlDiff.java:44: if (proc.name.equals("zygote")) {
./frameworks/base/tools/preload/Proc.java:79: return parent != null && parent.name.equals("zygote")
./frameworks/base/tools/preload/WritePreloadedClassFile.java:118: addAllClassesFrom("zygote", root, toPreload);
./libcore/dalvik/src/main/java/dalvik/system/Zygote.java:20: * Provides access to the Dalvik "zygote" feature, which allows a VM instance to
./system/core/libcutils/zygote.c:34:#define ZYGOTE_SOCKET "zygote"
./system/core/toolbox/start.c:15: property_set("ctl.start", "zygote");
./system/core/toolbox/stop.c:15: property_set("ctl.stop", "zygote");

すばらしく順調です。それっぽい箇所が2カ所ほど見つかりました。
一カ所は、android.os.Process ですね。
frameworks/base/core/java/android/os/Process.java
private static void openZygoteSocketIfNeeded() throws ZygoteStartFailedEx {
        :
        :
    sZygoteSocket = new LocalSocket();
    sZygoteSocket.connect(new LocalSocketAddress(ZYGOTE_SOCKET,
            LocalSocketAddress.Namespace.RESERVED));

    sZygoteInputStream
        = new DataInputStream(sZygoteSocket.getInputStream());

    sZygoteWriter =
        new BufferedWriter(
            new OutputStreamWriter(
                sZygoteSocket.getOutputStream()),
                256);
        :
        :
}

private static int zygoteSendArgsAndGetPid(ArrayList args) throws ZygoteStartFailedEx {
    int pid;
    openZygoteSocketIfNeeded();
        :
        :
    sZygoteWriter.write(Integer.toString(args.size()));
    sZygoteWriter.newLine();

    int sz = args.size();
    for (int i = 0; i < sz; i++) { 

        String arg = args.get(i); 
        if (arg.indexOf('\n') >= 0) {
            throw new ZygoteStartFailedEx("embedded newlines not allowed");
        }
        sZygoteWriter.write(arg);
        sZygoteWriter.newLine();
    }
     sZygoteWriter.flush();
     // Should there be a timeout on this?
    pid = sZygoteInputStream.readInt();
        :
        :
}


private static int startViaZygote(final String processClass,
                                                  final String niceName,
                                                  final int uid, final int gid,
                                                  final int[] gids,
                                                  int debugFlags,
                                                  String[] extraArgs) throws ZygoteStartFailedEx {
    int pid;
        :
    pid = zygoteSendArgsAndGetPid(argsForZygote);
        :
    return pid;
}

public static final int start(final String processClass,
                                        final String niceName,
                                        int uid, int gid, int[] gids,
                                        int debugFlags,
                                        String[] zygoteArgs)
{
    if (supportsProcesses()) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                                                debugFlags, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
                throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
        }
    } else {
        // Running in single-process mode

        Runnable runnable = new Runnable() {
            public void run() {
                Process.invokeStaticMain(processClass);
            }
        };

        // Thread constructors must not be called with null names (see spec).
        if (niceName != null) {
            new Thread(runnable, niceName).start();
        } else {
            new Thread(runnable).start();
        }
        return 0;
    }
}

というわけで、一カ所はProcess.start()から始まるようです。が、適当にソースコードを探して見ましたが、呼び出していそうな箇所をまだ見つけられていません。

もう一カ所は、Nativeになります。
system/core/libcutils/zygote.c
int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int))
{
    int fd;
    int pid;
    int err;
    const char *newargv[argc + 1];

    fd = socket_local_client(ZYGOTE_SOCKET,
                ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL);

    if (fd < 0) {
         return -1;
     }
    newargv[0] = "--peer-wait";
    memcpy(newargv + 1, argv, argc * sizeof(*argv));
     pid =     send_request(fd, 1, argc + 1, newargv);
     if (pid > 0 && post_run_func != NULL) {
        post_run_func(pid);
    }

    // Wait for socket to close
    do {
        int dummy;
        err = read(fd, &dummy, sizeof(dummy));
    } while ((err < 0 && errno == EINTR) || err != 0);

    do {
         err = close(fd);
    } while (err < 0 && errno == EINTR);
     return 0;
}

int zygote_run_oneshot(int sendStdio, int argc, const char **argv)
{
    int fd = -1;
    int err;
    int i;
    int retries;
    int pid;
    const char **newargv = argv;
    const int newargc = argc;
    for (retries = 0; (fd < 0) && (retries < ZYGOTE_RETRY_COUNT); retries++) {
        if (retries > 0) {
           struct timespec ts;
           memset(&ts, 0, sizeof(ts));
           ts.tv_nsec = ZYGOTE_RETRY_MILLIS * 1000 * 1000;
           do {
               err = nanosleep (&ts, &ts);
           } while (err < 0 && errno == EINTR);
        }
        fd = socket_local_client(ZYGOTE_SOCKET, AF_LOCAL,
                                              ANDROID_SOCKET_NAMESPACE_RESERVED);
    }
    if (fd < 0) {
        return -1;
    }
    pid = send_request(fd, 0, newargc, newargv);
    do {
        err = close(fd);
    } while (err < 0 && errno == EINTR);
    return pid;
}
というわけで、利用されているのは、zygote_run_waitとzygote_run_oneshotの2カ所でした。

zygote_run_waitが使われている箇所を調べると、
dalvik/dvz/dvz.c
int main (int argc, const char **argv) {
    int err;

    if (argc > 1 && 0 == strcmp(argv[1], "--help")) {
        usage(argv[0]);
        exit(0);
    }

    err = zygote_run_wait(argc - 1, argv + 1, post_run_func);

    if (err < 0) {
         fprintf(stderr, "%s error: no zygote process found\n", argv[0]);
         exit(-1);
     }
     exit(0);
}
ということで、/system/bin/dvz にたどり着きました。Usageを見てみると
# dvz --help
Usage: dvz [--help] [-classpath ]
        [additional zygote args] fully.qualified.java.ClassName [args]

Requests a new Dalvik VM instance to be spawned from the zygote
process. stdin, stdout, and stderr are hooked up. This process remains
while the spawned VM instance is alive and forwards some signals.
The exit code of the spawned VM instance is dropped.

というわけで、コマンドラインからクラスを指定してVMプロセスを起動するコマンドです。

もう一方のoneshotの方を探してみると
frameworks/base/cmds/runtime/main_runtime.cpp

extern "C"
int main(int argc, char* const argv[])
{
        :
        :
    if (proc->supportsProcesses()) {
        // If stdio logging is on, system_server should not inherit our stdio
        // The dalvikvm instance will copy stdio to the log on its own
        char propBuf[PROPERTY_VALUE_MAX];
        bool logStdio = false;
        property_get("log.redirect-stdio", propBuf, "");
        logStdio = (strcmp(propBuf, "true") == 0);

        zygote_run_oneshot((int)(!logStdio),
                sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]),
                    ZYGOTE_ARGV);
    } else {
#ifndef HAVE_ANDROID_OS
        QuickRuntime* runt = new QuickRuntime();
        runt->start("com/android/server/SystemServer",
        false /* spontaneously fork system server from zygote */);
#endif
    }
    finish_system_init(proc);
    run(proc);
        :
        :
}
ということですが、このコードをビルドするAndroid.mkを見るとifeq ($(TARGET_SIMULATOR),true)ということで、シミュレータ専用のようですから、とりあえず除外で良いでしょう。

というわけで、本日追いかけた限りでは、可能性があるのは
  • android.os.Process.start()を呼び出すコード
  • /system/bin/dvz
の辺りを攻めると、回答に近づけるかなぁ?といった雰囲気でした。