目次
1。概要:
init プロセスは、Linux システムのユーザー空間の最初のプロセスであり、プロセス番号は 1 です。
- ブート ROM: 電話機の電源がオフのときに、電源ボタンを押したままにしてオンにすると、ブート チップが ROM に固定されたプリセット コードから実行を開始し、ブート プログラムを RAM にロードします。
- ブート ローダー: これは Android システムを起動する前のブート プログラムで、主に RAM をチェックし、ハードウェア パラメーターやその他の機能を初期化します。
ブートローダーの起動時にカーネルを起動し、カーネル起動後、ユーザー空間で init プロセスを起動し、init プロセスを通じて init.rc 内の関連する設定を読み取り、他の関連プロセスやその他の操作を開始します。
init プロセスには多くの重要な作業が与えられており、init プロセスの起動は主に 2 つの段階に分かれています。
最初のフェーズでは次の作業が完了します。
ueventd/watchdogdジャンプと環境変数設定
ファイルシステムのマウントとディレクトリの作成
ログ出力の初期化、パーティションデバイスのマウント
SELinuxセキュリティポリシーの有効化
第2段階の前準備開始
第 2 フェーズでは、次の作業が完了します。
属性システムを初期化する
SELinux の第 2 ステージを実行し、一部のファイル セキュリティ コンテキストを復元する
新しい epoll を作成し、子プロセス終了信号処理関数を初期化する
他のシステム属性を設定し、属性サービスを有効にする
2. アーキテクチャ
2.1 Init プロセスはどのように開始されますか?
Init プロセスは、カーネルの起動後に開始される最初のユーザー空間プロセスであり、PID 1 が付いています。
kernel_init が開始したら、いくつかの init 初期化操作を完了してから、システムのルート ディレクトリに移動して、ramdisk_execute_command とexecute_command によって設定されたアプリケーションを順番に見つけます。これら 2 つのディレクトリが見つからない場合は、ルート ディレクトリに移動して /sbin/init を見つけます。 /etc/init、/bin/init、/bin/sh これら 4 つのアプリケーションが起動されます。これらのアプリケーションの 1 つが起動される限り、他のアプリケーションは起動されません。
Android システムは通常、init 実行可能ファイルをルート ディレクトリに置きます。つまり、Linux システムの init プロセスは、カーネルの初期化が完了した後に init ファイルを直接実行します。
2.2 Init プロセスは開始後に何をしましたか?
Init プロセスの開始後、最初にファイル システムをマウントし、次に対応するパーティションをマウントし、SELinux セキュリティ ポリシーを開始し、属性サービスを開始し、rc ファイルを解析し、対応する属性サービス プロセスを開始し、epoll を初期化し、シグナルを設定します。プロパティとキーコードを順番に fd が読み取り可能な場合の対応するコールバック関数。各プロセスの変更と再構築に対応するために使用されるワイヤレス ループに入ります。
3. カーネル起動初期化プロセスのソースコード解析
3.1 kernel_init
カーネル/msm-4.19/init/main.c
kernel/msm-4.19/init/main.c
kernel_init()
|
run_init_process(ramdisk_execute_command) //実行可能ファイルを実行し、初期化プロセスを開始します
static int __ref kernel_init(void *unused)
{ kernel_init_freeable(); //初期化プロセスを実行します初期化操作 /* メモリを解放する前に、すべての非同期 __init コードを完了する必要があります */ async_synchronize_full();// すべての非同期呼び出しが完了するまで待ち、メモリを解放する前に、すべての非同期 __init コードを完了する必要があります free_initmem();// すべてを解放しますinit.* セグメントのメモリ mark_rodata_ro(); // arm64 の空の実装 system_state = SYSTEM_RUNNING;// システム状態を実行状態に設定します uma_default_policy(); // NUMA システムのデフォルトのメモリ アクセス ポリシーを設定します flash_layed_fput(); // 遅延した構造体ファイル構造をすべて解放します if (ramdisk_execute_command) { //ramdisk_execute_command の値は "/init" です
if (!run_init_process(ramdisk_execute_command)) //ルート ディレクトリで init プログラムを実行します
return 0;
pr_err("%s の実行に失敗しました\n", ramdisk_execute_command);
}
/*
* いずれかが成功するまで、これらのそれぞれを試します
。 * 本当に壊れたマシンを回復しようとしている
場合、init の代わりに Bourne シェルを使用できます */ if (execute_command) { //execute_command の値が定義されている場合は、ルート ディレクトリに移動して対応するアプリケーションを見つけます。そして開始 if (!run_init_process(execute_command)) return 0; pr_err("%s の実行に失敗しました。デフォルトを試行しています...\n", execute_command); }
if (!run_init_process("/sbin/init")
|| ,/bin/sh これら 4 つのアプリケーションが開始されます
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process ("/bin/sh"))
return 0;
Panic("init が見つかりません。init= オプションをカーネルに渡してみてください。" "
ガイダンスについては、Linux Documentation/init.txt を参照してください。");
}
3.2 do_basic_setup
kernel_init_freeable()
|
do_basic_setup()
static void __init do_basic_setup(void)
{ cpuset_init_smp();//SMP システムの場合、カーネル制御グループの cpuset サブシステムを初期化します。 usermodehelper_init();// ユーザー空間プログラムの作成と実行を支援する khelper シングルスレッド作業キューを作成します shmem_init();// 共有メモリを初期化します driver_init();// デバイス ドライバを初期化します init_irq_proc();// /proc を作成します/ irq ディレクトリを作成し、システム内のすべての割り込みに対応するサブディレクトリを初期化します do_ctors();// カーネル コンストラクターを実行します usermodehelper_enable();// usermodehelper を有効にします do_initcalls();// initcall_levels 配列を走査し、内部の initcall 関数を呼び出します、ここでは主に、デバイス、ドライバー、ファイル システムを初期化することです。 // すべての関数は、主にrandom_int_secret_init()を拡張する目的で、走査のために配列にカプセル化されます ;// 乱数生成プールを初期化します}
4. Initプロセス起動のソースコード解析
主にAndroid Q(10.0)のinitコードを解析します。
関連するソース コード ファイル:
プラットフォーム/システム/コア/init/main.cpp
プラットフォーム/システム/コア/init/init.cpp
プラットフォーム/システム/コア/init/ueventd.cpp
プラットフォーム/システム/コア/init/selinux.cpp
プラットフォーム/システム/コア/ init/subcontext.cpp
プラットフォーム/システム/コア/ベース/logging.cpp
プラットフォーム
/システム/コア/init/first_stage_init.cpp プラットフォーム/システム/コア/init/first_stage_main.cpp
プラットフォーム/システム/コア/init/first_stage_mount.cpp
プラットフォーム/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core /init/service.cpp
4.1 初期化プロセスのエントリ
init プロセスは kernel_init によってすでに起動されています。init プロセスはデーモン プロセスに属します。正確には、Linux システムでユーザーが制御する最初のプロセスであり、そのプロセス番号は 1 です。そのライフサイクルは、Linux カーネル全体を通じて実行されます。Android の他のすべてのプロセスの共通の発信元は init プロセスです。
initのプロセス番号は「adb shell ps |grep init」コマンドで確認できます。
Android Q (10.0) の init エントリ関数は、オリジナルの init.cpp から main.cpp に調整され、各段階の動作が分離され、コードがより簡潔になりました。
[システム/コア/初期化/main.cpp]
/*
* 1. 最初のパラメータ argc はパラメータの数を示し、2 番目のパラメータはパラメータ リスト、つまり特定のパラメータです * 2. main 関数には
4 つのパラメータ エントリがあります。
* 1 つはパラメータ内の ueventd です。ueventd_main と入力します
* 2 番目は、パラメーターにサブコンテキストがある場合、InitLogging と SubcontextMain を入力します。
* 3 番目は、パラメーターに selinux_setup がある場合、SetupSelinux を入力します
。 * 4 番目は、パラメーターに Second_stage がある場合、SecondStageMain を入力します。
*3. main の実行順序は以下の通りです
* (1) ueventd_main init プロセスは子プロセス ueventd を作成し、
* デバイスノードファイルの作成作業を ueventd に委託し、ueventd は 2 つの方法でデバイスノードファイルを作成します
* (2) FirstStageMain は最初のフェーズ 1 を開始します
* (3)SetupSelinux は selinux ルールをロードし、selinux ログを設定し、SELinux 関連の作業を完了します
* (4)SecondStageMain は第 2 フェーズを開始します
*/
int main(int argc, char** argv) { //argv[0]の場合 内容がueventdの場合、strcmpの値は0です! strcmp is 1 //1 は true を意味し、ueventd_main が実行されます。ueventd は主にデバイス ノードの作成、権限の設定、その他の作業を担当します if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } //渡されたパラメータの数が 1 より大きい場合、次の操作を実行します if (argc > 1) { //パラメータはサブコンテキストであり、ログ システムを初期化します、 if (!strcmp( argv [1], "サブコンテキスト")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
//パラメータは「selinux_setup」で、Selinux セキュリティ ポリシーを開始します
( !strcmp (argv[1], "selinux_setup")) { return SetupSelinux(argv); } //パラメータは "second_stage" で、init プロセスの第 2 ステージを開始します if (!strcmp(argv[1], "second_stage ")) { return SecondStageMain(argc, argv); } } // デフォルトで init プロセスの最初のステージを開始 return FirstStageMain(argc, argv); }
4.2 ueventd_main
コードパス: platform/system/core/init/ueventd.cpp
「/dev」ディレクトリは、Android ルート ファイル システムのイメージには存在しません。このディレクトリは、init プロセスの開始後に動的に作成されます。
したがって、Android でデバイス ノード ファイルを確立するという重いタスクも init プロセスにかかります。この目的を達成するために、init プロセスは子プロセス ueventd を作成し、デバイス ノード ファイルの作成作業を ueventd に委託します。
ueventd は 2 つの方法でデバイス ノード ファイルを作成します。
1つ目の方法は「コールドプラグ」に相当し、あらかじめ定義されたデバイス情報に基づいて、ueventd起動時にデバイスノードファイルを一律に作成します。このタイプのデバイス ノード ファイルは、静的ノード ファイルとも呼ばれます。
2 番目の方法は「ホットプラグ」に対応します。つまり、システムの実行中にデバイスが USB ポートに挿入されると、ueventd はこのイベントを受け取り、挿入されたデバイスのデバイス ノード ファイルを動的に作成します。このタイプのデバイス ノード ファイルは、ダイナミック ノード ファイルとも呼ばれます。
int ueventd_main(int argc, char** argv) { //chmod とは逆の新しいファイルのデフォルト値を設定します。ここでは、新しいファイルが 666 umask(000) になった後のパーミッションに相当します。 //カーネル ログ。node/dev/kmsg にあります。この時点では、logd プロセスと logcat プロセスはまだ開始されていません。 //カーネル ログ システムを使用し、デバイス ノード /dev/kmsg を開くと、cat でカーネル ログを取得できます。 /dev/kmsg。 android::base::InitLogging(argv, &android::base::KernelLogger); //ログを出力するための selinux 関連のコールバック関数を登録します SelinuxSetupKernelLogging(); SelabelInitialize(); //xml を解析し、異なる SOC に従って取得しますメーカー別のハードウェア rc ファイル auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc", "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"}) ; //コールドブート if (access(COLDBOOT_DONE,
ColdBoot cold_boot(uevent_listener, uevent_handlers);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) { uevent_handler->ColdbootDone(); } // 子プロセスの終了信号を無視します signal(SIGCHLD, SIG_IGN); // Reap とwaitpid() の最後の呼び出しと、 上記の SIGCHLD の SIG_IGN // 設定との間に終了した保留中の子 . } // ドライバーからの uevent をリッスンし、「ホット スワップ」処理を実行します uevent_listener.Poll([&uevent_handlers](const uevent& uevent) { for (auto& uevent_handler : uevent_handlers) {
uevent_handler->HandleUevent(uevent); //ホット スタート、デバイスを作成
}
return ListenerAction::kContinue;
});
return 0;
}
4.3 init プロセスによる最初のステージの開始
コードパス: platform\system\core\init\first_stage_init.cpp
init プロセスの最初の段階の主な作業は、パーティションのマウント、デバイス ノードといくつかの主要なディレクトリの作成、ログ出力システムの初期化、SELinux セキュリティ ポリシーの有効化です。
最初のフェーズでは次の作業が完了します。
/* 01. ファイル システム ディレクトリを作成し、関連するファイル システムをマウントします */
/* 02. 標準入出力のシールド/カーネルログシステムの初期化*/
4.3.1 FirstStageMain
ヌル)); #define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// 非特殊用途では Andrlid cmdline を使用できません
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t グループ[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) { CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11))); CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8 ) ));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))); // これは、
ueventd が実行される前に呼び出されるログ ラッパーに必要です。
CHECKCALL(mknod("/dev/ptmx") , S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); //
tmpfs、mnt/vendor、mount/ でハングします。製品のパーティション。他のパーティションは最初の段階でロードする必要はありません。
//2 番目の段階で rc ファイルを解析することによってのみロードする必要があります。
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "
mode=0755,uid=0,gid=1000"));
// 読み書き可能なベンダー ディレクトリを作成します
CHECKCALL ( mkdir("/mnt/vendor", 0755)); // /mnt/product は 、製品パーティションの一部に
できない // 製品固有のパーティションをマウントするために使用されます。
CHECKCALL(mkdir("/mnt/product", 0755));
// 断片化問題を解決するために Android 10.0 で特別に導入された APEX をマウントします。コンポーネント メソッドと同様、Treble の拡張機能です。 // いいえ、そうではありません
。 APK のアップグレードと同じように、Google の特別なアップデートを書き込むためにシステム バージョン全体を完全にアップグレードする必要があります。APEX コンポーネントのアップグレード CHECKCALL(
mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755) ,uid=0,gid=0"));
// /debug_ramdisk はデバッグ RAM ディスクからの追加ファイルを保存するために使用されます
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"モード=0755、uid=0、gid=0"));
#undef CHECKCALL
//標準入力、標準出力、および標準エラーを空のデバイス ファイル "/dev/null" にリダイレクト
SetStdioToDevNull(argv);
//tmpfs と kmsg をマウント
//このようにして、 /kernel ユーザーがログを印刷できるようにログ システムを初期化できます。
old_root_info.st_dev); }
SetInitAvbVersionInRecovery();
静的constexpr uint32_t kナノ秒/ミリ秒 = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNano秒/ミリ秒;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
//アニメーションinit手順、入力パラメータselinux_steup
//実行コマンド:/system/bin/init selinux_setup
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv(\"" << パス << "\") が失敗しました";
1 を返します。
}
4.4 SELinux ルールのロード
SELinuxとは「Security-Enhanced Linux」の略で、国家安全保障局「NSA=The National Security Agency」のことです。
SCC (Secure Computing Corporation) によって開発された Linux 用の拡張必須アクセス制御セキュリティ モジュール。
このアクセス制御システムの制限により、プロセスはそのタスクで必要なファイルのみにアクセスできます。
selinux には 2 つの動作モードがあります。
Permissive、すべての操作が許可されます (つまり、MAC は許可されません)。ただし、アクセス許可に違反した場合、ログが記録されます。通常、enforcing は
eng モードで使用され、すべての操作のアクセス許可がチェックされます。一般に、ユーザー モードとユーザー デバッグ モードは
、security_setenforce または security_getenforce のいずれであっても、/sys/fs/selinux/enforce ファイルを操作するために使用されます。0 は寛容を意味し、1 は強制を意味します。
4.4.1 Selinux のセットアップ
説明: selinux の初期化、SELinux ルールのロード、SELinux 関連のログ出力の構成、および第 2 ステージの開始
コードパス: platform\system\core\init\selinux.cpp
/* この関数は selinux を初期化し、init selinux で実行するために init を実行します */
int SetupSelinux(char** argv) { //カーネル ログを初期化します InitKernelLogging(argv); //デバッグ バージョンの init がクラッシュしたときにブートローダーを再起動します if ( REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } //kmsg に書き込む必要がある selinux ログを設定するコールバックを登録します SelinuxSetupKernelLogging(); //SELinux ルールをロードします SelinuxInitialize(); /* *カーネル ドメインにいて、初期ドメイン。xattrs に selabel を格納するファイルシステム (ext4 など) には明示的なrestorecon は必要ありません * が、他のファイルシステムには明示的なrestorecon が必要です。特に a/b デバイスのリカバリ イメージなどの RAM ディスクの場合、これは必要な手順です。 *実際には、現在はカーネルドメインにあります。Seliux をロードした後、init を再実行して C 空間のユーザーモードに切り替える必要があります */
if (selinux_android_restorecon("/system/bin/init", 0) == -1) { PLOG(FATAL) << "restorecon failed of /system/bin/init failed"; } // innit プロセスを開始する準備をします。パラメータ Second_stage const char* path = "/system/bin/init"; const char* args[] = {path, "first_stage", nullptr}; execv(path, const_cast<char**>(args));を渡します。 /* */system/bin/init Second_stage を実行し、第 2 ステージに入ります */ PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }
4.4.2 SelinuxInitialize()
"false") << ") 失敗しました"; } }
if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) { LOG(FATAL) << "/sys/fs/selinux/checkreqprot に書き込めません: " << result.error(); } } /* * SELinux ルールをロードします * ここでは 2 つのケースが区別されます。これらの 2 つのケースは、セキュリティ ポリシー ファイルをロードする場所のみを区別します。 * 最初のケースは /vendor/etc/selinux/precompiled_sepolicy から読み取られます。たとえば、 * 2 番目のものは /sepolicy から読み取られ、これらはすべて selinux_android_load_policy_from_fd メソッドを呼び出すことになります */ bool LoadPolicy() { return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy(); }
4.5 init プロセスが第 2 フェーズを開始する
第二段階の主な内容:
プロセス セッション キーを作成し、属性システムを初期化します。
SELinux の第 2 段階を実行し、いくつかのファイル セキュリティ コンテキストを復元します。
新しい epoll を作成し、子プロセス終了信号処理関数を初期化します。
詳細については、セクション 5-信号処理を参照してください。 一致する属性でサーバーを起動します。詳細については、セクション 6 を参照してください。 セクション - プロパティ サービス
init.rc およびその他のファイルを解析し、rc ファイルのアクションとサービスを作成し、他のプロセスを開始します。詳細については、セクション 7 - rc ファイルの分析を参照してください。
4.5.1 SecondStageMain
int SecondStageMain(int argc, char** argv) { /* 01. プロセスセッションキーを作成し、属性 system を初期化します*/ keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // マークである /dev/.booting ファイルを作成します起動中であることを示します close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); // プロパティ システムを初期化し、 指定されたファイルから プロパティ property_init() を読み取ります; /* 02. 実行SELinux の第 2 段階 そして、いくつかのファイル セキュリティ コンテキストを復元します */ SelinuxRestoreContext(); /* 03. 新しい epoll を作成し、子プロセス終了信号処理関数を初期化します */ Epoll epoll; if (auto result = epoll.Open(); !result) { PLOG(FATAL) << result.error(); } InstallSignalFdHandler(&epoll); /* 04. 他のシステム プロパティを設定し、システム プロパティ サービスを開始します*/ StartPropertyService(&epoll);
/* 05 init.rc などのファイルを解析し、rc ファイルのアクションやサービスを作成し、その他のプロセスを起動します*/
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, smコード フローの詳細な
分析:
int SecondStageMain(int argc, char** argv) { /* *init クラッシュ時にブート ローダーを再起動します * この関数は主に、SIGABRT、SIGBUS などのさまざまなセマフォの動作を SA_RESTART に設定するために使用されます。監視対象 システムの再起動を実行 */ if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } // 標準入力、標準出力、標準エラーを空のデバイス ファイル "/dev/null" にリダイレクト SetStdioToDevNull(argv); // /dev ディレクトリ下 マウントtmpfs と kmsg //この方法で、ユーザーがログを出力できるように /kernel ログ システムを初期化できます InitKernelLogging(argv); LOG(INFO) << "init Second stage starting!"; // 01. プロセス セッション キーを作成します属性システムを初期化します keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // 起動中であることを示すマークである /dev/.booting ファイルを作成します
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); //プロパティ システムを初期化し、 指定されたファイルから
プロパティ property_init() を読み取ります; /* * 1. パラメータがコマンドラインからの読み込みと同時にDTで渡すと、DTの優先順位は常にコマンドラインよりも高くなります *2。 DTとはdevice-tree、中国語でデバイスツリーを意味し、自身のハードウェア構成とシステム動作パラメータ、 */ process_kernel_dt(); // DT プロパティを処理します process_kernel_cmdline(); // コマンド ライン プロパティを処理します // 他のプロパティを処理します export_kernel_boot_props(); // bootstat で init 開始時刻を利用できるように log.property_set に記録します ("ro.boottime.init", getenv( "INIT_STARTED_AT")); property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK")); // Treble でのフレームワークのみの OTA マッチ用に libavb のバージョンを設定します建てる。
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
// デバイスのロックが解除されているときに、adb root を許可するためにデバッグ プロパティをロードする必要があるかどうかを確認します。
const char* Force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) { load_debug_prop = "true"s == Force_debuggable_env; } // cmdline 設定に基づく memcg プロパティ bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false); if (memcg_enabled) { // ルートメモリ制御 cgroup mkdir("/dev/memcg", 0700); chown("/dev/memcg",AID_ROOT,AID_SYSTEM);
SelinuxSetupKernelLogging(); SelabelInitialize(); /*
* 02. SELinux の第 2 段階を実行し、いくつかのファイル セキュリティ コンテキストを復元します
* 関連ファイルのセキュリティ コンテキストを復元します。これらのファイルは SELinux セキュリティ メカニズムが初期化される前に作成されるため、 * そのため
コンテキストを復元する必要があります
*/
SelinuxRestoreContext() ;
/*
* 03. 新しい epoll を作成し、子プロセス終了信号処理関数を初期化
* epoll インスタンスを作成し、epoll のファイル記述子を返す
*/
Epoll epoll;
if (auto result = epoll.Open(); !result) { PLOG(FATAL) << result.error(); } /* *主に子プロセスの終了シグナルを処理するハンドラーを作成し、監視用のシグナルをepollに登録 * サブ継承処理用 */ InstallSignalFdHandler(&epoll) ; // デフォルトのプロパティ設定に関連する作業を実行します property_load_boot_defaults(load_debug_prop); UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
/*
*04. 他のシステム プロパティを設定し、システム プロパティ サービスを有効にする
*/
StartPropertyService(&epoll);
MountHandler mount_handler(&epoll);
//USB ストレージの udc コントローラーを設定, sys/class/udc
set_usb_controller ();
// コマンドと関数の対応を一致させる
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) { PLOG(FATAL) << "SetupMountNamespaces failed"; } // 初期化ファイル Context サブコンテキスト= InitializeSubcontexts(); /* *05 init.rcなどのファイルを解析し、rcファイルのアクションやサービスを作成し、その他のプロセスを起動します */
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
// これをオンにして INFO ログを破棄すると、
// Nexus 9 の起動時間が 0.2 秒増加します。
if (false) DumpState(); // GSI スクリプトの
実行中、GSI 状態が利用可能であることを確認します
if (android::gsi::IsGsiRunning()) { property_set("ro.gsid .image_running", "1"); } else { property_set("ro.gsid.image_running", "0"); } am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); // トリガーが早期にオンになっているステートメントを実行します。 rcファイル amのinit。QueueEventTrigger("初期初期化");
// コールドプラグ デバイスの初期化が完了するまで待ちます
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// /dev からアクションのクエリを開始します
am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
am.QueueBuiltinAction(SetMmapRndBit sA)アクション, "SetMmapRndBits") ;
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
// デバイスのキーコードの初期化操作
keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> { for (const) auto& svc : ServiceList::GetInstance()) { keychords.Register(svc->keycodes()); }
keychords.Start(&epoll, HandleKeychord);
return Success();
},
"KeychordInit");
// Android の静的ロゴを画面に表示
am.QueueBuiltinAction(console_init_action, "console_init");
// rc でトリガーを実行file as init のステートメント
am.QueueEventTrigger("init");
// NIAP 認定準拠のため、BoringSSL セルフテストを開始します
am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
// /dev/hw_rand om の場合、mix_hwrng_into_linux_rng を繰り返しますまたは /dev /random
// 午前 wait_for_coldboot_done 直後には準備ができていませんでした
QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
// 他のシステム サービスを起動する前にバインダーを初期化します
am.QueueBuiltinAction(InitBinder, "InitBinder");
// デバイスが充電モードの場合、ファイル システムをマウントしたりシステム サービスを開始したりする必要はありません
// 充電モードでは、充電器は queue として実行され、それ以外の場合は実行キューの場合は late-init に置かれます
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") { am.QueueEventTrigger( "charger"); } else { am .QueueEventTrigger("late-init"); } // プロパティの現在の状態に基づいてすべてのプロパティ トリガーを実行します am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); while (true) { // デフォルトでは、何かが起こるまでスリープします。
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
if (do_shutdown && !shutting_down) { do_shutdown = false; if (HandlePowerctlMessage(shutdown_command)) { shutting_down = true; } } // 各 An を実行します。アクションは、コマンドに対応する実行関数を実行します if (!(waiting_for_prop || Service::is_exec_service_running())) { am.ExecuteOneCommand(); } if (!(waiting_for_prop || Service::is_exec_service_running())) { if ( !shutting_down) { auto next_process_action_time = HandleProcessActions();
// 再起動が必要なプロセスがある場合は、再起動に間に合うように起動します。
if (next_process_action_time) { epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>( *next_process_action_time - boot_ Clock::now()); if (*epoll_timeout < 0ms) epoll_timeout = 0ms; } } // 他にやるべき作業がある場合は、すぐに再び起動します。 if (am.HasMoreCommands()) epoll_timeout = 0ms; } // 循環等待機イベント発生 if (auto result = epoll.Wait(epoll_timeout); !result) { LOG(ERROR) << result.error(); } }
0を
返します。
}
5. 信号処理
initはデーモンプロセスですが、initの子プロセスがゾンビプロセス(ゾンビプロセス)になるのを防ぐために、子プロセス終了時に子プロセスの終了コードを取得し、プログラム内で子プロセスを削除する必要があります。ゾンビプロセスになるのを防ぐため、終了コードを介してテーブルを実行し、ゾンビプロセスの子プロセスがプログラムテーブルのスペースを占有します(プログラムテーブルのスペースが上限に達すると、システムは新しいプロセスを開始できなくなります)重大なシステム問題が発生する可能性があります)。
子プロセスの再起動プロセスを次の図に示します。
信号処理の主な仕事:
シグナル シグナル ハンドルの初期化
子プロセスのループ処理
epoll ハンドルの登録
子プロセスの終了処理
注: EPOLL は、Linux のイベント トリガーに使用される POLL に似ており、EventBus 関数と同様です。Linux は長い間、イベント トリガーに select を使用してきました。ポーリングによって処理されます。ポーリングされる fds が増えるほど、時間がかかります。大量の記述子の処理には、EPOLL の方が利点があります。
5.1SignalFdHandlerのインストール
Linux では、親プロセスは SIGCHLD シグナルをキャプチャすることで子プロセスの終了を認識します。SIGCHLD シグナルは、子プロセスが終了すると送信されます。これらの背景を理解した後、init プロセスがこのシグナルをどのように処理するかを見てみましょう。
まず、新しい sigaction 構造体を作成します。sa_handler は信号処理関数であり、カーネルによって指定された関数ポインターを指します。SIG_DFL は Android 9.0 以前のバージョンとは異なり、ここではソケットの読み取りおよび書き込みハンドルを通じて信号を受信しなくなりました。カーネル関数 SIG_DFL の信号処理に変更します。
次に、 sigaction(SIGCHLD, &act, nullptr) はシグナル バインディング関係を確立します。つまり、SIGCHLD シグナルが監視されると、act の sigaction 構造体によって処理されます。最後に、RegisterHandler の役割は signal_read_fd (前の s[1]
) シグナルを受信した後、handle_signal をトリガーします
前述したように、InstallSignalFdHandler 関数の機能は、SIGCHLD シグナルの受信時に信号処理のために HandleSignalFd をトリガーすることです。
信号処理の概略図:
コードパス: platform/system/core/init.cpp
説明: この関数の主な機能は、子プロセス終了信号処理プロセスを初期化することです。
static void InstallSignalFdHandler(Epoll* epoll) { // SA_NOCLDSTOP により、子プロセスが終了する場合にのみ init プロセスが SIGCHLD シグナルを受信するようになります const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP }; sigaction(SIGCHLD, &act, nullptr) ); sigset_t Mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); if (!IsRebootCapable()) { // init に CAP_SYS_BOOT の機能がない場合、 // このシナリオでは現在コンテナ内で実行されています } if (sigprocmask(SIG_BLOCK, & Mask, nullptr ) == -1) { PLOG(FATAL) << "シグナルのブロックに失敗しました"; } // 子プロセスでシグナルのブロックを解除するハンドラーを登録します
const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
if (result != 0) { LOG(FATAL) << "フォーク ハンドラーの登録に失敗しました: " << strerror(result); } // シグナル ハンドラー signal_fdを作成します= signalfd(-1, &mask, SFD_CLOEXEC); if (signal_fd == -1) { PLOG(FATAL) << "failed to create signalfd"; } //シグナル登録、signal_fd がシグナルを受信すると、HandleSignalFd をトリガーする if ( auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) { LOG(FATAL) << result.error(); } }
5.2 レジスタハンドラ
コード パス: /platform/system/core/epoll.cpp
説明: シグナル登録。fd ハンドルを epoll_fd_ のリスニング キューに追加します。
Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) { if (!events) { return Error() << "イベントを指定する必要があります"; auto [it、挿入] = epoll_handlers_.emplace(fd, std::move(handler)) ; if (!inserted) { return Error() << "特定の FD に 2 つの epoll ハンドラーを指定できません"; epoll_event ev ; ev.events = イベント; // std::map のイテレータは消去されるまで無効にならないため、 // マップ内の std::function へのポインタを epoll_ctl に直接使用します。 ev.data.ptr = reinterpret_cast<void*>(&it->second); // fd の可聴イベントを epoll_fd_ の监听队列に追加します
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) { Result<void> result = ErrnoError() << "epoll_ctl は fd を追加できませんでした"; epoll_handlers_.erase(fd); 結果を返します。 } {} を返します。}
5.3 ハンドル信号Fd
コードパス: platform/system/core/init.cpp
説明: SIGCHLD シグナルを監視し、ReapAnyOutstandingChildren を呼び出して問題のある子プロセスを終了します。
static void HandleSignalFd() { signalfd_siginfo siginfo; ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo))); if (bytes_read != sizeof(siginfo)) { PLOG(ERROR) << "signal_fd からの siginfo の読み取りに失敗しました"; 戻る; } //监控SIGCHLD信号 スイッチ (siginfo.ssi_signo) { case SIGCHLD: ReapAnyOutstandingChildren(); 壊す; case SIGTERM: HandleSigtermSignal(siginfo); 壊す; デフォルト: PLOG(ERROR) << "signal_fd: 予期しないシグナルを受信しました " << siginfo.ssi_signo;
壊す;
}
}
5.4 ReapOneプロセス
コードパス: /platform/system/core/sigchld_handle.cpp
説明: ReapOneProcess は最終処理関数であり、この関数は最初に waitpid を使用してハングしているプロセスの pid を見つけ、次にその pid に従って対応するサービスを見つけます。
最後に、Service の Reap メソッドを呼び出してリソースをクリアし、プロセスの種類に応じてマシンを再起動するかプロセスを再起動するかを決定します。
void ReapAnyOutstandingChildren() { while (ReapOneProcess()) { } } static bool ReapOneProcess() { siginfo_t siginfo = {}; //waitpid 関数を使用して、ステータスが変更された子プロセスの pid を取得します //waitpid フラグは WNOHANG、正の値が返された場合は、プロセスがハングアップしたことを意味します if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) { PLOG(ERROR) < < "waitid failed"; return false; } auto pid = siginfo.si_pid; if (pid == 0) return false; // ゾンビ PID があることがわかったら、scopeguard を使用して pid をクリアします auto reaper = make_scope_guard( [pid] { TEMP_FAILURE_RETRY(waitpid(pid , nullptr, WNOHANG)); }); std::string 名;
std::string wait_string;
サービス* サービス = nullptr;
if (SubcontextChildReap(pid)) { name = "サブコンテキスト"; } else { //pid を通過するサービス service = ServiceList::GetInstance().FindService(pid, &Service::pid); if (サービス) { name = StringPrintf("サービス '%s' (pid %d)", service->name().c_str(), pid); if (service->flags() & SVC_EXEC) { auto exec_duration = boot_lock::now() - service->time_started(); auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
wait_string = StringPrintf(" 待機には %f 秒かかりました", exec_duration_ms / 1000.0f);
} else if (service->flags() & SVC_ONESHOT) { auto exec_duration = boot_lock::now() - service->time_started(); auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration) .count(); wait_string = StringPrintf(" ワンショット サービスはバックグラウンドで %f 秒かかりました",exec_duration_ms / 1000.0f); } } else { name = StringPrintf("追跡されていない pid %d", pid); } } if (signinfo.
LOG(INFO) << name << " ステータス "< siginfo.si_status << wait_string;
} else { LOG(INFO) << name << " 受信シグナル "< siginfo.si_status << wait_string; } / /サービスが見つかりませんでした、終了したことを示します、終了 if (!service) return true; service->Reap(siginfo);//子プロセスに関連するリソースをクリア if (service->flags() & SVC_TEMPORARY) { ServiceList: : GetInstance().RemoveService(*service); // サービスを削除 } return true; }
6. 属性サービス
開発とデバッグのプロセス中に、システム プロパティは property_set を通じて簡単に設定できることがわかりました。なぜここでプロパティ サービスを開始するのでしょうか? これには実際にはいくつかの権限の問題が関係しており、すべてのプロセスがシステム プロパティを自由に変更できるわけではありません。
Android では、属性の設定を init プロセスに割り当てて管理します。他のプロセスは属性を直接変更できませんが、変更するように init プロセスに通知することしかできません。このプロセスでは、init プロセスが権限制御を行うことができます。具体的な内容を見てみましょうプロセス。
6.1 プロパティの初期化
コードパス: platform/system/core/property_service.cpp
説明: 属性システムを初期化し、指定されたファイルから属性を読み取り、SELinux に登録し、属性の権限制御を実行します。
キャッシュをクリアします。ここでは主にメモリ内のいくつかのリンク リストとマッピングをクリアし、新しい property_filename ディレクトリを作成します。このディレクトリの値は /dev/_properties_ です。
次に、CreateSerializedPropertyInfo を呼び出して一部のシステム プロパティのカテゴリ情報を読み込み、最後に読み込まれたリンク リストをファイルに書き込み、メモリにマップします。
void property_init() { //SELinux 回调,実行制限制御 selinux_callback cb; cb.func_audit = PropertyAuditCallback; selinux_set_callback(SELINUX_CB_AUDIT, cb); mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); CreateSerializedPropertyInfo(); if (__system_property_area_init()) { LOG(FATAL) << "プロパティ領域の初期化に失敗しました"; } if (!property_info_area.LoadDefaultPath()) { LOG(FATAL) << "シリアル化されたプロパティ情報ファイルのロードに失敗しました"; } }
CreateSerializedPropertyInfo を使用して、次のディレクトリのコンテキストを読み込みます。
1) SELinux関連
/system/etc/selinux/plat_property_contexts
/vendor/etc/selinux/vendor_property_contexts
/vendor/etc/selinux/nonplat_property_contexts /
product/etc/selinux/product_property_contexts
/odm/etc/selinux/odm_property_contexts
2) SELinux なし
/plat_property_contexts
/vendor_property_contexts
/nonplat_property_contexts
/product_property_contexts
/odm_property_contexts
6.2 StartPropertyService
コードパス: platform/system/core/init.cpp
説明: プロパティ サービスを開始します。
まずソケットを作成してファイル記述子を返し、次に最大同時実行数を 8 に設定します。他のプロセスは、このソケットを通じてシステム プロパティを変更するように init プロセスに通知できます。
最後に、epoll イベントを登録します。つまり、property_set_fd の変更が監視されているときに handle_property_set_fd を呼び出します。
void StartPropertyService(Epoll* epoll) { property_set("ro.property_service.version", "2"); // ソケット接続を確立 if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0) , {})) { property_set_fd = *result; } else { PLOG(FATAL) << "start_property_service ソケットの作成に失敗しました: " << result.error(); } // 最大 8 つの同時リッスン listen(property_set_fd, 8); / / property_set_fd を登録し、ハンドルが変更された場合、handle_property_set_fd を通じて処理します if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); ! result) { PLOG(FATAL) << result.error();
}
}
6.3 handle_property_set_fd
コードパス: platform/system/core/property_service.cpp
説明: ソケット接続を確立し、ソケットから操作情報を読み取り、HandlePropertySet を呼び出して、さまざまな操作タイプに応じて特定の操作を実行します。
HandlePropertySet は最後の処理関数です。「ctl」で始まるキーは、サービスの開始、停止、再起動の一部の操作を実行します。その他は、property_set を呼び出してプロパティを設定します。前者でも後者でも、SELinux セキュリティ チェックを実行する必要があります. プロセスのみが、対応する操作を実行する操作権限を持っています。
static void handle_property_set_fd() { static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */ // クライアント接続を待機しています int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); if (s == -1) { return; } ucred cr; socklen_t cr_size = sizeof(cr); // このソケットに接続されているプロセスの資格情報を取得します if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); PLOG(ERROR) < < "sys_prop: SO_PEERCRED を取得できません"; return; } // ソケット接続を確立 SocketConnectionソケット(s, cr); uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
// ソケット内の操作情報を読み取ります
if (!socket.RecvUint32(&cmd, &timeout_ms)) { PLOG(ERROR) << "sys_prop: ソケットからコマンドを読み取り中にエラー"; ソケット.SendUint32(PROP_ERROR_READ_CMD) ); return; } // 操作情報に従って対応する処理を行いますが、char 形式で読み込む場合と String 形式で読み込む場合の違いがあります switch ( cmd) { case PROP_MSG_SETPROP: { char prop_name[PROP_NAME_MAX] ; char prop_value[ PROP_VALUE_MAX]; if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) || !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): ソケットからの名前/値の読み取り中にエラー";
戻る;
プロパティ
名[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
std::string ソースコンテキスト;
if (!socket.GetSourceContext(&source_context)) { PLOG(ERROR) << "プロパティを設定できません '" << prop_name << "': getpeercon() が失敗しました"; 戻る; const auto& cr = ソケット.cred(); std::string エラー; uint32_t result = HandlePropertySet(prop_name, prop_value,source_context, cr, &error); if (結果!
LOG(エラー) << "プロパティ '" << prop_name << "' を uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr から 設定できません。 pid << ": " << エラー;
休憩
; PROP_MSG_SETPROP2: { std::string 名;
}
case PROP_MSG_SETPROP2: { std ::string 名; std::文字列値; if (!socket.RecvString(&name, &timeout_ms) || !socket.RecvString(&value, &timeout_ms)) { PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): ソケットからの名前/値の読み取り中にエラー"; ソケット。SendUint32(PROP_ERROR_READ_DATA); 戻る; std ::string ソースコンテキスト;
if (!socket.GetSourceContext(&source_context)) { PLOG(ERROR) << "プロパティを設定できません '" << name << "': getpeercon() が失敗しました"; ソケット.SendUint32(PROP_ERROR_PERMISSION_DENIED); 戻る; const auto& cr = ソケット.cred(); std::string エラー; uint32_t result = HandlePropertySet(名前, 値, ソースコンテキスト, cr, &error); if (result != PROP_SUCCESS) { LOG(ERROR) << "プロパティ '" << name << "' を uid:" << cr.uid << " gid:" << cr.gid << から設定できません" pid:" << cr. pid << ": " << エラー; ソケット .SendUint32(結果); 壊す;
}
デフォルト:
LOG(ERROR) << "sys_prop: 無効なコマンド " << cmd;
ソケット.SendUint32(PROP_ERROR_INVALID_CMD);
壊す;
}
}
7. 第 3 段階の init.rc
属性サービスが確立されると、基本的に init 自身の機能が終了し、その後、他のプロセスを開始する必要があります。しかし、init プロセスやその他のプロセスはどうなるでしょうか? もう 1 つのプロセスはバイナリ ファイルであり、./system/bin/init Second_stage などの exec コマンドを通じて直接開始して、init プロセスの第 2 ステージを開始できます。しかし、Android システムには非常に多くの Native プロセスがあり、それらがすべて exec を渡してコード内のプロセスを 1 つずつ実行する場合、それは間違いなく悲惨な設計になります。
これに基づいて、Android では init.rc メカニズムが導入されています。これは、構成ファイルを読み取ってさまざまなプロセスを開始するのに似ています。次に、init.rc がどのように機能するかを見てみましょう。
init.rc は、Android Init 言語で記述されたスクリプトを含む構成ファイルです。
携帯電話のディレクトリ内の init.rc: ./init.rc
init.rc には主に 5 種類のステートメントが含まれています。
アクション
コマンド
サービス
オプション
のインポート
7.1 アクション
アクションは一連のコマンドを表します。アクションには、アクションをいつ実行するかを決定するトリガーが含まれます。
処置: トリガートリガー、つまりonで始まる文を使用して、対応するサービスを実行するタイミングを決定します。具体的なタイミングは次のとおりです。
初期初期化時; 初期化の初期段階でトリガー;
初期化時; 初期化段階
でトリガー; 後期初期化時; 初期化の後期段階でトリガー;
ブート/充電器: システムの起動/充電時にトリガー;
プロパティ上:<key>= <value>: 属性値が条件を満たす場合にトリガーされます。
7.2 コマンド
command はアクションのコマンドリスト内のコマンド、またはサービスの onrestart オプションのパラメータコマンドであり、対応するイベントが発生するとコマンドが 1 つずつ実行されます。
一般的に使用されるコマンドを以下に示します
class_start <service_class_name>: 同じクラスに属するすべてのサービスを開始します;
class_stop <service_class_name>: 指定されたクラスのサービスを停止します
start <service_name>: 指定されたサービスを開始します (すでに開始されている場合はスキップします);
stop <service_name>:実行中の 1 つのサービスを停止します
setprop <name> <value>: プロパティ値を設定します
mkdir <path>: 指定されたディレクトリの作成
symlink <target> <sym_link>: <target> に接続された <sym_link> シンボリック リンクを作成します;
write <path> <string >: write to ファイル パスに文字列を書き込みます;
exec: fork して実行すると、プログラムが完了するまで init プロセスがブロックされます;
exrot <name> <name>: 環境変数を設定します;
loglevel <レベル>: を設定しますログレベル
hostname <name>: ホスト名を設定
import <filename> : 追加の初期設定ファイルをインポートします
7.3 サービス
サービス service をはじめとするサービスは init プロセスによって開始され、通常は init のサブプロセス内で動作するため、サービスを開始する前に対応する実行ファイルが存在するかどうかを確認する必要があります。
コマンド:service <名前><パス名> [ <引数> ]* <オプション> <オプション>
パラメータ
意味
<名前>
このサービスの名前を示します
<パス名>
このサービスが配置されているパスは実行可能ファイルであるため、ストレージ パスが存在する必要があります。
<引数>
サービスを開始するためのパラメータ
<オプション>
このサービスの制約オプション
initで生成される子プロセスはrcファイルに定義されており、各サービスは起動時にforkにより子プロセスを生成します。
例: service servicemanager /system/bin/servicemanager は、サービス名が servicemanager で、サービスの実行パスが /system/bin/servicemanager であることを意味します。
7.4 オプション
オプションはサービスのオプションであり、サービスと組み合わせて使用されます
無効: クラスで自動的に開始せず、サービス名に従ってのみ開始します。
ワンショット: サービス終了後に再起動しません。
ユーザー/グループ: サービスを実行するユーザー/ユーザー グループを設定します。デフォルトは root です。
クラス: 設定します。所属するクラス名、いつ クラスが開始/終了すると、サービスも開始/停止します、デフォルトはデフォルトです; onrestart:
サービスの再起動時に対応するコマンドを実行します;
ソケット: /dev/socket/ という名前のソケットを作成します<名前>
クリティカル: 指定された時間内のサービス 再起動を続けると、システムは再起動してリカバリ モードに入ります。
デフォルト: は、disabled=false、oneshot=false、critical=false を意味します。
7.5 インポート
他の rc ファイルをインポートするために使用されます
コマンド: import <ファイル名>
7.6 init.rc の解析プロセス
7.6.1 ロードブートスクリプト
コードパス: platform\system\core\init\init.cpp
説明: 特別な設定 ro.boot.init_rc がない場合は、./init.rc を解析します。
ハンドル/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,
/vendor/etc/init これらのパスは、init.rc の後に解析されたパスに追加されます。init.rc の解析が完了した後、これらのディレクトリ内の rc ファイルを解析します。
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { パーサー parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); if (!parser.ParseConfig("/product/etc/ init ")) { late_import_paths.emplace_back("/product/etc/init"); if (!parser.ParseConfig("/product_services/etc/init")) {
late_import_paths.emplace_back("/product_services/etc/init"); if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back ("/odm/etc/init"); if (!parser.ParseConfig("/vendor/etc/ init ")) { late_import_paths.emplace_back("/vendor/etc/init"); else { parser.ParseConfig(bootscript); } }
Android7.0以降、init.rcが分割され、各サービスは独自のrcファイルを持ち、基本的に/system/etc/init、/vendor/etc/init、/odm/etc/initにロードされます。ディレクトリがロードされるまで待ちます。 init.rc 分析が完了すると、これらのディレクトリ内の rc ファイルが分析され、関連するアクションが実行されます。
コードパス: platform\system\core\init\init.cpp
説明: サービスなどのオブジェクトを解析するパーサーを作成し、オブジェクトをインポートします
パーサー CreateParser(ActionManager& action_manager, ServiceList& service_list) { パーサー parser; parser.AddSectionParser( "サービス", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt)); parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts)); parser.AddSectionParser("インポート", std::make_unique<ImportParser>(&parser)); パーサーを返します。}
7.6.2 アクションの実行
関連するアクションを、early-init -> init -> late-init の順序でトリガー キューに追加し、ループ内でトリガー キューのアクション内のコマンドを使用してすべての実行関数を実行します。
am.QueueEventTrigger("初期初期化");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) { if (!(waiting_for_prop || Service::is_exec_service_running())) { am.ExecuteOneCommand(); } }
7.6.3 ザイゴットの起動
Android 5.0 以降、Android は 64 ビットのコンパイルをサポートするため、zygote 自体も 32 ビットと 64 ビットをサポートします。属性 ro.zygote を使用して、zygote プロセスのさまざまなバージョンの起動を制御します。
init.rc の import セクションには、次のコードがあります。
import /init.${ro.zygote}.rc // init.rc が固定ファイルを直接インポートするのではなく、属性 ro.zygote の内容に従って異なるファイルをインポートすることがわかります。
init.rc は /system/core/rootdir にあります。このパスには、zygote に関する 4 つの rc ファイルも含まれています。
それらは init.zygote32.rc、init.zygote32_64.rc、init.zygote64.rc、init.zygote64_32.rc で、どのファイルはハードウェアによって決定されます。
64 ビット プロセッサを例に挙げると、init.zygote64.rc のコードは次のとおりです。
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main # class はオプションで、主な
優先順位として zygote サービスのタイプを指定します -20
user root
group root readprocreserved_disk
ソケットzygote ストリーム 660 ルート システム # ソケット キーワードはオプションを示します。dev/socket/zygote という名前のソケットを作成します。タイプはストリームです。権限は 660 ソケット usap_pool_primary ストリーム 660 ルート システム onrestart write /sys/
android_power
/request_state wake # onrestart はオプションです。 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
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:
サービス zygote: zygote サービスは init.zygote64.rc で定義されます。init プロセスは、このサービス名を通じて zygote プロセスを作成します。
/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:
zygote サービスは、/system/bin/app_process64 を実行し、4 つのパラメーターを渡すことによって実行されます。
パラメータ 1: -Xzygote このパラメータは、仮想マシンの起動時に必要なパラメータとして使用されます。
パラメータ 2: /system/bin は、仮想マシン プログラムが配置されているディレクトリを表します。
パラメータ 3: --zygote は、ZygoteInit のメイン関数を示します。 .java クラスは仮想マシンとして実行されます。 エントリ
パラメータ 4: --start-system-server は、Zygote プロセスに systemServer プロセスを開始するように指示します。
8. まとめ
init プロセスの最初の段階の主な作業は、パーティションのマウント、デバイス ノードといくつかの主要なディレクトリの作成、ログ出力システムの初期化、SELinux セキュリティ ポリシーの有効化です。
initプロセスの第2段階の主な作業は、属性システムの初期化、SELinuxのマッチングルールの解析、子プロセスの終了シグナルの処理、システム属性サービスの開始であり、それぞれの項目は非常に重要であると言えます。最初の段階が属性システムの場合、SELinux は Prepare を実行し、2 番目の段階ではこれらの機能を実際に実装します。
init の第 3 段階は、主に init.rc を分析して他のプロセスを開始し、無限ループに入り、サブプロセスをリアルタイムで監視することです。