Android 11 起動シーケンス:Kernel から SystemServer までの内部構造

カーネル起動と Init プロセスの生成

Android システムの起動は、ブートローダーによってカーネルがメモリ上にロードされることから始まります。カーネル起動後、最初に生成されるのは PID 0 の Idle プロセス(swapper とも呼ばれる)です。このプロセスはメモリ管理などのカーネル内部初期化を担当し、続いて init プロセスと kthread を生成します。kthread はカーネルスレッドを管理し、ユーザー空間のプロセスは init によって起動されます。

カーネルソースの init/main.c にある kernel_init 関数において、init プロセスの実行パスが決定されます。以下のコードは、指定されたコマンドまたは標準的なパスから init バイナリを実行する逻辑を示しています。

static int __init kernel_init(void *unused)
{
    int status;
    const char *init_command = boot_command_line; // 例:コマンドラインから取得

    if (init_command) {
        status = attempt_init_execution(init_command);
        if (status == 0)
            return 0;
        panic("Requested init %s failed (error %d).",
              init_command, status);
    }

    // 標準的な init パスを試行
    const char *init_paths[] = {
        "/sbin/init",
        "/etc/init",
        "/bin/init",
        "/bin/sh"
    };

    for (int i = 0; i < sizeof(init_paths)/sizeof(init_paths[0]); i++) {
        if (attempt_init_execution(init_paths[i]) == 0)
            return 0;
    }

    panic("No working init found.");
    return 0;
}

static int attempt_init_execution(const char *init_path)
{
    const char *argv[] = { init_path, NULL };
    pr_info("Executing init process: %s\n", init_path);
    // 用户空間プログラムの実行を行うカーネル関数
    return do_execve(getname_kernel(init_path),
        (const char __user *const __user *)argv,
        (const char __user *const __user *)envp_init);
}

do_execve はユーザー空間のプログラムを実行するためのカーネル関数であり、内部で __do_execve_file を呼び出します。ここでは、ファイルディスクリプタの処理、プロセス数の制限チェック、メモリ空間の準備、および引数や環境変数のコピーが行われた後、実際にバイナリが実行されます。

Init プロセスの起動ステージ

カーネルから制御が渡されると、system/core/init/main.cppmain 関数が実行されます。init プロセスは起動引数に応じて異なるステージに分かれて動作します。

int main(int arg_count, char** arg_vector) {
    // サンイタイザー設定(デバッグ用)
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif

    std::string process_name = basename(arg_vector[0]);
    if (process_name == "ueventd") {
        return ueventd_main(arg_count, arg_vector);
    }

    if (arg_count > 1) {
        std::string mode = arg_vector[1];
        if (mode == "subcontext") {
            android::base::InitLogging(arg_vector, &android::base::KernelLogger);
            const BuiltinFunctionMap& func_map = GetBuiltinFunctionMap();
            return SubcontextMain(arg_count, arg_vector, &func_map);
        }

        if (mode == "selinux_setup") {
            return SetupSelinux(arg_vector);
        }

        if (mode == "second_stage") {
            return SecondStageMain(arg_count, arg_vector);
        }
    }

    // デフォルトは第一段階
    return FirstStageMain(arg_count, arg_vector);
}

第一段階(FirstStageMain)では、ファイルシステムのマウント、デバイスの作成、ログシステムの初期化などが行われます。その後、SELinux の設定を行い、第二段階(SecondStageMain)へ遷移します。

第二段階と init.rc の解析

第二段階では、プロパティサービスの開始や SELinux のコンテキスト復元などが実行されます。重要な処理として、init.rc スクリプトの解析があります。

static void LoadBootScripts(ActionManager& action_mgr, ServiceList& svc_list) {
    Parser parser = CreateParser(action_mgr, svc_list);
    std::string bootscript = GetProperty("ro.boot.init_rc", "");

    if (bootscript.empty()) {
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        // 各パーティションの init スクリプトを順次解析
        parser.ParseConfig("/system/etc/init");
        parser.ParseConfig("/system_ext/etc/init");
        parser.ParseConfig("/product/etc/init");
        parser.ParseConfig("/odm/etc/init");
        parser.ParseConfig("/vendor/etc/init");
    } else {
        parser.ParseConfig(bootscript);
    }
}

パーサーは "service"、"on"、"import" などのセクションを解釈し、サービス定義とアクションを登録します。例えば、Zygote の起動は init.rc 内のトリガーによって制御されます。

# 暗号化状態に基づいて Zygote を起動
trigger zygote-start

この際、import /system/etc/init/hw/init.${ro.zygote}.rc のように、システムプロパティ ro.zygote の値に応じて具体的な RC ファイルが動的に読み込まれます。これにより、32 ビット環境、64 ビット環境、またはその組み合わせに応じて適切な Zygote 構成が適用されます。

Zygote プロセスの起動と Java 環境

Zygote は Android ランタイム環境の中核であり、すべてのアプリプロセスの親となります。init によって fork された Zygote は、ネイティブ層から Java 層へ遷移します。

ネイティブ側では AppRuntime クラスが AndroidRuntime を継承しており、runtime.start メソッドによって VM が起動されます。

  • VM 起動: startVm で Java 仮想機械を生成(ヒープサイズ初期化など)。
  • JNI 登録: startReg でネイティブ関数を Java から呼び可能に注册。
  • Java エントリー: ZygoteInit.main を呼び出し。

Java 層に入った Zygote は、クラスやリソースのプリロードを行い、その後ソケットサーバーとして動作し、SystemServer の fork を待ち受けます。

// ZygoteInit.java の主要フロー
public static void main(String argv[]) {
    // リソースの事前読み込み
    preload(bootTimingsTraceLog);
    
    // ソケットサーバーの作成
    zygoteServer = new ZygoteServer(isPrimaryZygote);
    
    // SystemServer プロセスの fork
    Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
    
    if (r != null) {
        r.run(); // SystemServer として実行
        return;
    }
    
    // Zygote はループしてアプリ起動要求を待つ
    caller = zygoteServer.runSelectLoop(abiList);
}

SystemServer とサービス管理

Zygote によって fork された SystemServer プロセスは、AMS(Activity Manager Service)や WMS(Window Manager Service)など、主要なシステムサービスホストします。サービスの起動は SystemServiceManager によって統一的に管理されます。

// SystemServer.java 内のサービス起動例
mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class);

SystemServiceManager は、SystemService を継承したクラスを引数に取ります。AMS 本体は IActivityManager.Stub を継承していますが、システムサービスとしてのライフサイクル管理を行うために、静的内部クラス Lifecycle が用意されています。

public static final class Lifecycle extends SystemService {
    private final ActivityManagerService mService;

    public Lifecycle(Context context) {
        super(context);
        mService = new ActivityManagerService(context);
    }

    @Override
    public void onStart() {
        mService.start();
    }

    public ActivityManagerService getService() {
        return mService;
    }
}

startService メソッドは反射を用いて Lifecycle クラスのインスタンスを生成し、onStart を呼び出すことでサービスを実際に起動します。起動されたサービスは、ServiceManager.addService を介して Binder サービスとして登録され、他のプロセスから利用可能になります。

ServiceManager.addService(Context.ACTIVITY_SERVICE, mService, true, dumpFlags);

一部のサービスは SystemServiceManager を経由せず、直接 main メソッドなどから起動されて ServiceManager に登録される場合もあります。すべての主要サービスが起動し終わると、systemReady メソッドが呼び出され、システムが利用可能な状態になったことが通知されます。

タグ: android-bootflow kernel-init-process init-rc-configuration zygote-fork-model system-service-manager

6月1日 06:51 投稿