2011年8月16日火曜日

startActivityの深層 後編

android.os.ProcessクラスのzygoteSendArgsAndGetPid()でsocketにいろいろwriteすると、pidがreadできる。
socketのreadをしているプロセスがいて、それがforkしてpidを返している。

アプリのプロセスの親プロセスはzygote

emulatorを起動してadb shellでpsコマンドを実行すると、各プロセスの親プロセスがわかる。
PIDがプロセスID、PPIDが親プロセスのID。
# ps
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 268 180 c009b74c 0000875c S /init
root 2 0 0 0 c004e72c 00000000 S kthreadd
root 3 2 0 0 c003fdc8 00000000 S ksoftirqd/0
root 4 2 0 0 c004b2c4 00000000 S events/0
root 5 2 0 0 c004b2c4 00000000 S khelper

...

root 32 1 63964 21732 c009b74c afd0b844 S zygote
media 33 1 17212 1368 ffffffff afd0b6fc S
/system/bin/mediaserver
root 34 1 812 240 c02181f4 afd0b45c S /system/bin/installd
keystore 35 1 1796 280 c01b52b4 afd0c0cc S /system/bin/keystore
root 37 1 824 276 c00b8fec afd0c51c S /system/bin/qemud
shell 39 1 732 204 c0158eb0 afd0b45c S /system/bin/sh
root 40 1 3376 180 ffffffff 00008294 S /sbin/adbd
system 60 32 125208 31256 ffffffff afd0b6fc S system_server
app_12 112 32 76032 19472 ffffffff afd0c51c S
jp.co.omronsoft.openwnn
radio 118 32 88352 20500 ffffffff afd0c51c S com.android.phone
system 121 32 76268 20780 ffffffff afd0c51c S com.android.systemui
app_1 123 32 79236 23488 ffffffff afd0c51c S com.android.launcher
app_5 180 32 76352 20236 ffffffff afd0c51c S android.process.acore
root 186 40 732 204 c003da38 afd0c3ac S /system/bin/sh
root 187 186 688 280 c009b74c afd0b844 S logcat
app_8 201 32 74148 18368 ffffffff afd0c51c S com.android.deskclock
app_3 216 32 85184 18344 ffffffff afd0c51c S com.android.mms
app_9 220 32 75120 18892 ffffffff afd0c51c S android.process.media
app_13 247 32 75608 19472 ffffffff afd0c51c S com.android.email
app_11 262 32 72816 16836 ffffffff afd0c51c S com.android.protips
app_20 274 32 73256 17272 ffffffff afd0c51c S com.android.music
app_26 282 32 73852 18080 ffffffff afd0c51c S
com.android.quicksearchbox
root 292 40 732 328 c003da38 afd0c3ac S /system/bin/sh
root 294 292 888 328 00000000 afd0b45c R ps
com.android.xxxなどのアプリは全てPID 32のzygoteがforkしている。

zygoteプロセスはinit.rcから起動される。ソースは
/frameworks/base/cmds/app_process/app_main.cpp

app_main.cppのmain関数でcom.android.internal.os.ZygoteInitのstatic mainを実行する。

ZygoteInit
main()
registerZygoteSocket() // サーバー側socketを初期化

preloadClasses() // frameworkのクラスを予め読む
// Class.forName()を呼んで、戻り値は見ない

preloadResources() // Resourceを読む

runSelectLoopMode() // select()でsocketの書き込み待ち
// 書き込みがあると、ZygoteConnection.runOnce()へ


ZygoteConnection
runOnce() // 引数をチェックしたり、uidとか決める

Zygote
forkAndSpecialize() //
libcore/dalvik/src/main/java/dalvik/system/Zygote.java
// native method呼ぶだけ

/dalvik/vm/native/dalvik_system_Zygote.c
forkAndSpecializeCommon() // ココでfork()してる!
preloadClasses()はLogcatに
INFO/Zygote(32): ...preloaded 1830 classes in 7118ms.
みたいなログを出すやつ。
予め全部クラスをロードしたプロセスを作っておき、Processクラスの要求でforkしている。
zygoteはアプリのための初期化済みプロセスで、forkするサーバを兼ねているようだ。

AndroidstartActivityの深層 前編

android-2.3.1_r1のソースコードを、Activity.startActivity()からアプリが起動するまでを追いかけてみる。
Activity
startActivity()

startActivityForResult()

Instrumentation
execStartActivity()

IActivityManager
startActivity() // BinderでIPC

---- プロセス境界 ----

ActivityManagerService
startActivity()

ActivityStack
startActivityMayWait()

ActivityManagerService
startActivityLocked()

startActivityUncheckedLocked() // この辺から汚すぎる…

startActivityLocked(ActivityRecord r, ...) // 上のとはシグネチャ違う
// ActivityRecord受け取る方

resumeTopActivityLocked() // すげー汚い…
// プロセスを起動する初回パスと思われるところだけ追っかける

startSpecificActivityLocked()

startProcessLocked()

startProcessLocked(ProcessRecord app, ...) // 上とはシグネチャが違う
// ProcessRecord受け取る方

Process
start() // これの戻り値がpid

startViaZygote()

zygoteSendArgsAndGetPid() // ここでsocketにwriteすると、pidがreadできるようだ
// socketで別プロセスにプロセス生成を依頼している模様
// static main()を実行したいクラス名をsocketで渡すらしい
Processクラスのsocketの通信相手がforkしてpidを返してると思われる。

今回はここまで。forkしているところを見つけるまで読む予定。

Android on UbuntuでUbuntu側からAndroidのアプリを起動する

Androidでコマンドラインからintentを発行してアプリを起動するのにamというコマンドが使えます。
それの中身は以下の通り。たった数行のshellスクリプトでした。
/system/bin/am
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
簡単なアイディアとして、これをchrootで実行すれば、Ubuntu側から同じことができるはずです。amコマンドそのままではchrootでうまく動かなかったので、以下のように修正してam2を作りました。
/system/bin/am2
#!/system/bin/sh
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
export ANDROID_ASSETS=/system/app
export ANDROID_BOOTLOGO=1
export ANDROID_DATA=/data
export ANDROID_PROPERTY_WORKSPACE=9,32768
export ANDROID_ROOT=/system
export ASEC_MOUNTPOINT=/mnt/asec
export BOOTCLASSPATH=/system/framework/core.jar:\
/system/framework/ext.jar:/system/framework/framework.jar:\
/system/framework/android.policy.jar:\
/system/framework/services.jar
export EXTERNAL_STORAGE=/mnt/sdcard
export LD_LIBRARY_PATH=/system/lib
export PATH=/sbin:/system/sbin:/system/bin:/system/xbin

base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
今度はUbuntu側で使用するスクリプトです。単に引数を与えてAndroid側のam2をchrootで実行するだけです。
Androidの電卓を起動するスクリプト
android_calc
sudo chroot /android /system/bin/am2 start -n
com.android.calculator2/.Calculator
Androidのブラウザを起動するスクリプト
android_view
url=$1
if [ $# -eq 0 ]; then
url="http://www.kmckk.co.jp/"
fi
sudo chroot /android /system/bin/am2 start -a android.intent.action.VIEW
$url
これでUbuntuのshellから以下のようにすればAndroidのブラウザが起動できます。
# ./android_view http://www.asahi.com/

Zygoteの簡単な説明

Androidではアプリケーションや多くのその他のプログラムはJavaで記述されています。これらのプログラムはDalvikVMの上で動きます。
それらのJavaのプログラムを起動するたびにDalvikVMを起動するのではなく、Zygoteと呼ばれるDalvikVMの大元のプロセスに要求を出すと、そのプロセスがforkシステムコールを使って自分自身のコピーの子プロセスを生成し、その子プロセスで指定されたJavaのプログラムを実行するようになっています。
Zygoteは通常必要となる全てのダイナミックリンクライブラリとJavaのクラスライブラリをロードした状態で待機しています。子プロセスはそれをコピーした状態から開始されるので起動にかかる時間を短縮することができます。また、書き込みを行っていないメモリページは複数のプロセスで共有されることになるので、たくさんのJavaのプログラムが同時に動いているときでも実際のメモリの使用量を少なくおさえることができます。Linuxの仮想メモリのしくみを上手に使っています。
Androidの中にあるJavaプログラムを実行する3つのコマンド
今回は軽い紹介だけですが、そのうちソースコードを探検したいと思います。
/system/bin/app_process
常駐するZygoteプロセスの実体ですが、引数にクラス名を指定して起動することもできます。
/system/bin/am コマンドを使うとコマンドラインからintentを発行してアプリケーションを起動することもできますが、その中で使われているのがapp_processです。amコマンドは以下のようなshellスクリプトです。
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"


このコマンドのソースコードは
frameworks/base/cmds/app_process/app_main.cpp
/system/bin/dalvikvm
普通のjavaコマンドに近いコマンドラインオプションを受け付けます。(dalvik -h
でヘルプが出ます。)既存のJavaのプログラムを移植して使うのに便利です。例えば
JRubyをAndroidで動かしたい
JRubyをAndroidで動かしてみた
このコマンドのソースコードは
dalvik/dalvikvm/Main.c
JNIのInvocation APIを使ってJavaVMを生成しています。
/system/bin/dvz
上記の2つは実行するたびに新たにDalvikVMを起動しますが、dvzコマンドではZygoteプロセスにコマンドを送ってforkさせ、その子プロセスで指定したJavaプログラムを動かします。このため起動時間やメモリ効率の点で有利です。
このコマンドでやっていることを真似れば、自分で作ったネイティブのプログラムからJavaのプログラムを起動することもできると思います。
このコマンドのソースコードは
dalvik/dvz/dvz.c

AndroidのZygoteでのJavaのクラスのpreloading

Androidの起動時に最初のDalvikVMのプロセスであるZygoteではJavaのクラスのpreloadingというテクニックが使われています。

全てのJavaのプロセスはZygoteからforkして作られるので、Zygoteでpreloadingされたクラスは、新しいJavaのプロセスでは最初からロードされた状態から始まります。これによって、個々のJavaのプロセスの起動の高速化とシステム全体としてのメモリ使用量の削減を実現しています。

このクラスのpreloadingというのは、さぞかし特別なことをやっていると思いきや、 そのソースはとても単純なものでした。



少し長いですが、そのpreloadingを行っているメソッドのソースをまるごと貼付けてみます。

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java 
  /**      * 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).      */     private static void preloadClasses() {         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;                         }                     } catch (Throwable t) {                         Log.e(TAG, "Error preloading " + line + ".", t);                         if (t instanceof Error) {                             throw (Error) t;                         }                         if (t instanceof RuntimeException) {                             throw (RuntimeException) t;                         }                         throw new RuntimeException(t);                     }                 }                  if (THROW_ON_MISSING_PRELOAD &&                     missingClasses != null) {                     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);             }         }     } 

PRELOADED_CLASSESで定義されているファイル(実際には/system/framework/framework.jarに含まれる"preloaded-classes")を開いて、そこに書かれているクラス名で順番に Class.forName を呼んでいるだけです。

Class.forNameは普通のアプリからでも使用できるメソッドです。
アプリケーションの中でも、先にまとめてロードしてしまって、後の処理を速くしたい時には役にたつテクニックかもしれません。
ただし、使わないかもしれないクラスを先に読むことになるのでメモリ消費量のことも考慮する必要があります。

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さんは難敵です。
何せ、メソッドの行数の長いこと・・・・。このコード、なかなか難易度が高いです。
とりあえず、こういうときは焦らず最初から。

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の方が後に生成されたりとかしていますし。
ちょっとログを仕込んでビルドして、実際に動かしてみないと駄目だなぁ・・・。

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のアプリケーションを起動していると考えて良いかと思います。

とりあえず、今夜の調査はここまでとしておきましょう。

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

ここまでの調査で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 */);

kthreadd

kthreaddはカーネルスレッド・デーモンです。Linux kernelは、起動すると2つのスレッドを起こします。一つがinitプロセスを起動するためのkernel_initで、もう一つがこのkthreaddだったと思います(他には無かったと思う)。

カーネルは、今まさにやらなければならない仕事をこなしているわけですが、やっぱりどこかCPUがあいた時にでもやればいいような仕事もあるわけです。そんな時に、カーネル・スレッドが使われます。

で、このカーネルスレッド、psコマンドで表示されることからなんとなく想像できるかと思いますが、一般のプロセスと同じプロセス情報を持っています。なぜかというと、カーネルスレッドのスケジューリングは、一般のプロセスと同じようにスケジューリングされているようなのです。

ところで、先日の日記では、kthreaddの下に子プロセスが複数いましたね。実はこれこそが、kthreaddのお仕事のようです。
先ほど、CPUが空いた時に行えば良い事をカーネルスレッドにやらせると言いましたが、後回しで良い処理のためにプロセス情報の初期化やスケジューラへの登録(これはつまり再スケジューリングの契機になる?)なんていう重たい処理が走るのは嫌なので、Kernelにおけるスレッドの生成は、何をスレッドとするのかの最低限の情報をリストに追加するだけにとどめ、kthreaddが後からそのリストを読み、必要なプロセス情報の初期化、スケジュールへの登録などの作業を行うわけですね。

system_process以降

Zygoteは、/system/bin/app_processを使って起動されるすべてのVMプロセスの親となるプロセスで、起動時にオプション --start-system-serverの引数が渡されると、起動時にsystem_processという名前の子プロセスを生成するように実装されています。

system_processは、Zygoteをコピーして生成されたAndroidの中枢を担うシステム用のVMプロセスで、Java側の各種サービススレッド群は、すべてこのsystem_processのスレッドとして生成されます。

DBMSを使ってスレッドの一覧を取得できます。

*1 59 native 69 37 main
*2 60 vmwait 21 341 HeapWorker
*3 61 vmwait 106 18 GC
*4 62 vmwait 0 0 Signal Catcher
*5 63 running 138 431 JDWP
*6 64 vmwait 76 54 Compiler
7 65 native 1 0 Binder Thread #1
8 66 native 0 0 Binder Thread #2
9 71 native 479 150 android.server.ServerThread
10 72 native 248 311 ActivityManager
11 76 timed-wait 19 21 ProcessStats
12 77 native 16 5 PackageManager
13 78 native 0 0 FileObserver
14 79 native 0 0 AccountManagerService
15 81 native 0 0 SyncHandlerThread
16 86 native 55 10 AlarmManager
17 83 native 0 1 UEventObserver
18 84 native 1 0 PowerManagerService.mScreenOffThread
19 85 native 5 0 PowerManagerService
20 87 native 16 2 WindowManager
21 88 native 706 64 WindowManagerPolicy
22 89 native 1 3 InputDispatcher
23 90 native 2 0 InputReader
24 91 native 4 4 NetdConnector
25 94 native 0 0 WifiService
26 92 native 6 0 ConnectivityThread
27 95 native 0 0 Tethering
28 96 native 0 0 MountService
29 97 native 4 0 VoldConnector
30 99 native 0 0 AudioService
31 101 native 0 0 SoundPool
32 102 native 0 0 SoundPoolThread
33 103 native 0 0 backup
34 107 timed-wait 7 17 watchdog
35 110 native 2 1 LocationManagerService
36 111 native 21 13 ThrottleService
37 122 native 0 0 GpsLocationProvider
38 160 native 0 0 Binder Thread #3
39 163 native 0 0 Binder Thread #4
40 164 native 3 0 Binder Thread #5
42 292 native 0 0 Binder Thread #9
44 231 native 0 0 Binder Thread #8
45 217 native 0 0 Binder Thread #6
46 229 native 0 0 Binder Thread #7
47 293 native 0 0 Binder Thread #10
48 295 native 0 0 Binder Thread #11

結構たくさんのスレッドが立ってますね。

ここまでの流れを簡単に述べると

  1. kernelが/initを起動する
  2. /initがinit.rcに従い /system/bin/app_process(Zygote) を --start-system-server引数付きで起動する
  3. app_processがfork後、com.android.server.SystemServer#main を実行する(system_server)
  4. com.android.server.SystemServer#main が android_server_SystemServer_init1(JNIEnv* env, jobject clazz)をJNI経由で呼び出し
  5. android_server_SystemServer_init1() が system_init()を呼び出し
  6. system_init()では Surfaceflingerのinstantiate等を行った後、SystemServer#init2をコールバック
  7. SystemServer#init2が、 amdrpod.server.ServerThreadを生成、runする。
  8. ServerThreadが各種サービス・マネージャー関連を生成、登録していく。それっぽい箇所を抜き出してみると
    • new EntropyService()
    • new PowerManagerService()
    • ActivityManagerService.main()
    • new TelephonyRegistry(context)
    • AttributeCache.init(context)
    • PackageManagerService.main()
    • ActivityManagerService.setSystemProcess()
    • context.getContentResolver()
    • new AccountManagerService(context)
    • ContentService.main()
    • ActivityManagerService.installSystemProviders()
    • new BatteryService(context)
    • new LightsService(context)
    • new VibratorService(context)
    • PowerManagerService#init()
    • new AlarmManagerService(context)
    • Watchdog.getInstance().init()
    • WindowManagerService.main()
    • ((ActivityManagerService)ServiceManager.getService("activity").setWindowManager
    • new BluetoothService(context)
    • new BluetoothA2dpService()
    • new DevicePolicyManagerService(context)
    • new StatusBarManagerService(context)
    • new ClipboardService(context)
    • new InputMethodManagerService()
    • new NetStatService()
    • NetworkManagementService.create()
    • ConnectivityService.getInstance()
    • new ThrottleService(context)
    • new AccessibilityManagerService(context)
    • new MountService(context)
    • new NotificationManagerService()
    • new DeviceStorageMonitorService(context)
    • new LocationManagerService(context)
    • new SearchManagerService(context)
    • new DropBoxManagerService()
    • new WallpaperManagerService(context)
    • new AudioService(context)
    • new HeadsetObserver(context)
    • new DockObserver()
    • new UsbObserver(context)
    • new UiModeManagerService(context)
    • new BackupManagerService(context)
    • new AppWidgetService(context)
    • new RecognitionManagerService(context)
    • new DiskStatsService(context)
    • new AdbSettingsObserver
    • VMRuntime.getRuntime().startJitCompilation()
  9. この後、生成したサービスの一部のsystemReady()メソッドを実行していく
  10. Looper.loop()

とまぁ、こんな処理が長々と一つのメソッドに・・・・長いわ!と途中で放り投げたくなるところでしたけど。余所からアクセスできるようにするものは、ServiceManager#addServiceしています。とりあえずにしておきました。なお、メソッドがstaticか否かまで確かめ切れていないので、色々記載に間違いがある気もしていますが・・・とりあえずメモ書きレベルだということでお許し下さい。

名前からもなんとなく想像がつきますが、Threadは、これらのクラス群の処理の中で生成、runされていくのではないでしょうか。近いうちにこのあたりも追いかけてみたいなと思います。

さて、このsystem_processも重要なのですが、先日psコマンドの一覧をtreeにして日記にあげた通り、他のJavaアプリケーションも、それぞれZygoteを親として生成されたVMプロセスとして動作しています。
  • jp.co.omronsoft.openwnn
  • com.android.phone
  • com.android.systemui
  • com.android.launcher
  • android.process.acore
  • com.android.mms
  • android.process.media
  • com.android.deskclock
  • com.android.email
  • com.android.protips
  • com.android.music
  • com.android.quicksearchbox
ここで気になるのが、Javaアプリを起動しているのは誰?という話です。
Zygoteが親なので、fork自体はZygoteプロセスなのでしょうが、誰がどのように起動しているものやら。