1. 程式人生 > 其它 >第一個使用者程序 - Android 的 Init 程序

第一個使用者程序 - Android 的 Init 程序

Init 程序是 Linux 核心啟動後建立的第一個使用者程序,地位非常重要。Init 程序在初始化過程中會啟動很多重要的守護程序,因此,瞭解 Init 程序的啟動過程將有助於我們更好地理解 Android系統。

本文嘗試對著 《深入理解 Android 5.0 系統》來對 android 9.0 的啟動程式碼進行分析,但是分析過程中發現自己缺乏作業系統方面的知識,以致於只能做一些簡單分析。最近也買了一本作業系統的書 《作業系統:精髓與設計原理》(第9版) ,等後續基礎提升後,會繼續進行分析。

雖然 Init 程序是 Linux 核心啟動後建立的第一個使用者程序,地位非常重要。Init 程序在初始化過程中會啟動很多重要的守護程序,因此,瞭解 Init 程序的啟動過程將有助於我們更好地理解 Android系統。Init 除了完成系統的初始化之外,本身也是一個守護程序,擔負著系統部分很重要的職責。本文將詳細介紹 Init 程序的初始化以及它作為守護程序的功能。

先簡單介紹 Android 的啟動過程。從系統角度看,Android 的啟動過程可分為 bootloader 引導、裝載和啟動Linux 核心、啟動 Android 系統 3 個大的階段。其中 Android系統的啟動還可以細分為啟動 Init 程序、啟動 Zygote、啟動 SystemService、啟動 SystemServer、啟動 Home 等多個階段。下圖揭示了整個 Android 的啟動過程。

下面簡單介紹裝置的啟動過程。

(1)Bootloader 引導。

當我們按下手機的電源鍵時,最先執行的就是 bootloader。bootloader 主要的作用是初始化基本的硬體裝置(如 CPU、記憶體、Flash 等)並且通過建立記憶體空間對映,為裝載 Linux 核心準備好合適的執行環境。一旦 Linux 核心裝載完畢,bootloader 將會從記憶體中清除掉。

如果使用者在 Bootloader 執行期間,按下預定義的組合鍵,可以進入系統的更新模組。Android的下載更新可以選擇進入 Fastboot 模式或者 Recover 模式。

  • Fastboot 是 Android設計的一套通過 USB 來更新手機分割槽映像的協議,方便開發人員能快速更新指定的手機分割槽。但是一般的零售機上往往去掉了 Fastboot,Google 銷售的開發機則帶有 Fastboot 模組。
  • Recovery 模式是 Android 特有的升級系統。利用 Recovery 模式,手機可以進行恢復出廠設定,或者執行 OTA、補丁和韌體升級。進入 Recovery 模式實際上是啟動了一個文字模式的 Linux。

(2)裝載和啟動 Linux 核心。

Android 的 boot.img 存放的就是 Linux 核心和一個根檔案系統。Bootloader 會把 boot.img 映像裝載進記憶體。然後 Linux 核心會執行整個系統的初始化,完成後裝載根檔案系統,最後啟動 Init
程序。

(3)啟動Init 程序。

Linux 核心載入完畢後,會首先啟動 Init 程序,Init 程序是系統的第一個程序。在 Init 程序的啟動過程中,會解析 Linux 的配置指令碼 init.rc 檔案。根據 init.rc 檔案的內容,Init 程序會裝載 Android的檔案系統、建立系統目錄、初始化屬性系統、啟動 Android 系統重要的守護程序,這些程序包括 USB 守護程序、adb 守護程序、vold 守護程序、rild 守護程序等。

最後 Init 程序也會作為守護程序來執行修改屬性請求,重啟崩潰的程序等操作。

(4)啟動 ServiceManager。

ServiceManager 由 Init 程序啟動。它主要的作用是管理 Binder 服務,負責 Binder服務的註冊與查詢。

(5)啟動 Zygote程序。

Init 程序初始化結束時,會啟動 Zygote 程序。Zygote 程序負責 fork 出應用程序,是所有應用程序的父程序。Zygote 程序初始化時會建立 Dalivik 虛擬機器、預裝載系統的資原始檔和 Java 類。所有從Zygote程序 fork 出的使用者程序將繼承和共享這些預載入的資源,不用浪費時間重新載入,加快了應用程式的啟動過程。啟動結束後,Zygote 程序也將變為守護程序,負責響應啟動 APK應用程式的請求。

(6)啟動 SystemServer。

SystemServer 是 Zygote 程序 fork 出的第一個程序,也是整個 Android 系統的核心程序。在 SystemServer 中執行著 Android 系統大部分的 Binder 服務。SystemServer 首先啟動本地服務 Sensor Service;接著啟動包括 ActivityManagerService、WindowsMangerService、PackageManagerService在內的所有 Java 服務。

(7)啟動 MediaServer。

MediaServer 由 Init 程序啟動。它包含了一些多媒體相關的本地 Binder 服務,包括∶ CameraService、AudioFlingerService、MediaPlayerService 和 AudioPolicyService。

(8)啟動 Launcher。

SystemServer 載入完所有 Java 服務後,最後會呼叫 ActivityManagerService 的 SystemReady)方法。在這個方法的執行中,會發出 Intent"android.intent.category.HOME"。凡是響應這個 Intent的 apk 應用都會執行起來,Launcher 應用是 Android 系統預設的桌面應用,一般只有它會響應這個 Intent,因此,系統開機後,第一個執行的應用就是 Launcher。

Init 程序的初始化過程

Init 程序的原始碼位於目錄 system/core/init下。程式的入口函式 main() 位於檔案 init.cpp 中。 pie 9.0 main 函式的流程main()函式比較長,整個 Init 程序的啟動流程都在這個函式中。下面我們把 main()函式分成小段,一段段地介紹其功能和作用。具體可以看下面的程式碼:

  1 // /system/core/init/init.cpp
  2 int main(int argc, char** argv) {
  3     if (!strcmp(basename(argv[0]), "ueventd")) {
  4         return ueventd_main(argc, argv);
  5     }
  6 
  7     if (!strcmp(basename(argv[0]), "watchdogd")) {
  8         return watchdogd_main(argc, argv);
  9     }
 10 
 11     if (argc > 1 && !strcmp(argv[1], "subcontext")) {
 12         InitKernelLogging(argv);
 13         const BuiltinFunctionMap function_map;
 14         return SubcontextMain(argc, argv, &function_map);
 15     }
 16 
 17     if (REBOOT_BOOTLOADER_ON_PANIC) {
 18         InstallRebootSignalHandlers();
 19     }
 20 
 21     bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
 22 
 23     if (is_first_stage) {
 24         boot_clock::time_point start_time = boot_clock::now();
 25 
 26         // Clear the umask.
 27         umask(0);
 28 
 29         clearenv();
 30         setenv("PATH", _PATH_DEFPATH, 1);
 31         // Get the basic filesystem setup we need put together in the initramdisk
 32         // on / and then we'll let the rc file figure out the rest.
 33         mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
 34         mkdir("/dev/pts", 0755);
 35         mkdir("/dev/socket", 0755);
 36         mount("devpts", "/dev/pts", "devpts", 0, NULL);
 37         #define MAKE_STR(x) __STRING(x)
 38         mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
 39         // Don't expose the raw commandline to unprivileged processes.
 40         chmod("/proc/cmdline", 0440);
 41         gid_t groups[] = { AID_READPROC };
 42         setgroups(arraysize(groups), groups);
 43         mount("sysfs", "/sys", "sysfs", 0, NULL);
 44         mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
 45 
 46         mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
 47 
 48         if constexpr (WORLD_WRITABLE_KMSG) {
 49             mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
 50         }
 51 
 52         mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
 53         mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
 54 
 55         // Mount staging areas for devices managed by vold
 56         // See storage config details at http://source.android.com/devices/storage/
 57         mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
 58               "mode=0755,uid=0,gid=1000");
 59         // /mnt/vendor is used to mount vendor-specific partitions that can not be
 60         // part of the vendor partition, e.g. because they are mounted read-write.
 61         mkdir("/mnt/vendor", 0755);
 62 
 63         // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
 64         // talk to the outside world...
 65         InitKernelLogging(argv);
 66 
 67         LOG(INFO) << "init first stage started!";
 68 
 69         if (!DoFirstStageMount()) {
 70             LOG(FATAL) << "Failed to mount required partitions early ...";
 71         }
 72 
 73         SetInitAvbVersionInRecovery();
 74 
 75         // Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
 76         global_seccomp();
 77 
 78         // Set up SELinux, loading the SELinux policy.
 79         SelinuxSetupKernelLogging();
 80         SelinuxInitialize();
 81 
 82         // We're in the kernel domain, so re-exec init to transition to the init domain now
 83         // that the SELinux policy has been loaded.
 84         if (selinux_android_restorecon("/init", 0) == -1) {
 85             PLOG(FATAL) << "restorecon failed of /init failed";
 86         }
 87 
 88         setenv("INIT_SECOND_STAGE", "true", 1);
 89 
 90         static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
 91         uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
 92         setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
 93 
 94         char* path = argv[0];
 95         char* args[] = { path, nullptr };
 96         execv(path, args);
 97 
 98         // execv() only returns if an error happened, in which case we
 99         // panic and never fall through this conditional.
100         PLOG(FATAL) << "execv(\"" << path << "\") failed";
101     }
102 
103     // At this point we're in the second stage of init.
104     InitKernelLogging(argv);
105     LOG(INFO) << "init second stage started!";
106 
107     // Set up a session keyring that all processes will have access to. It
108     // will hold things like FBE encryption keys. No process should override
109     // its session keyring.
110     keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
111 
112     // Indicate that booting is in progress to background fw loaders, etc.
113     close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
114 
115     property_init();
116 
117     // If arguments are passed both on the command line and in DT,
118     // properties set in DT always have priority over the command-line ones.
119     process_kernel_dt();
120     process_kernel_cmdline();
121 
122     // Propagate the kernel variables to internal variables
123     // used by init as well as the current required properties.
124     export_kernel_boot_props();
125 
126     // Make the time that init started available for bootstat to log.
127     property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
128     property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
129 
130     // Set libavb version for Framework-only OTA match in Treble build.
131     const char* avb_version = getenv("INIT_AVB_VERSION");
132     if (avb_version) property_set("ro.boot.avb_version", avb_version);
133 
134     // Clean up our environment.
135     unsetenv("INIT_SECOND_STAGE");
136     unsetenv("INIT_STARTED_AT");
137     unsetenv("INIT_SELINUX_TOOK");
138     unsetenv("INIT_AVB_VERSION");
139 
140     // Now set up SELinux for second stage.
141     SelinuxSetupKernelLogging();
142     SelabelInitialize();
143     SelinuxRestoreContext();
144 
145     epoll_fd = epoll_create1(EPOLL_CLOEXEC);
146     if (epoll_fd == -1) {
147         PLOG(FATAL) << "epoll_create1 failed";
148     }
149 
150     sigchld_handler_init();
151 
152     if (!IsRebootCapable()) {
153         // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
154         // In that case, receiving SIGTERM will cause the system to shut down.
155         InstallSigtermHandler();
156     }
157 
158     property_load_boot_defaults();
159     export_oem_lock_status();
160     start_property_service();
161     set_usb_controller();
162 
163     const BuiltinFunctionMap function_map;
164     Action::set_function_map(&function_map);
165 
166     subcontexts = InitializeSubcontexts();
167 
168     ActionManager& am = ActionManager::GetInstance();
169     ServiceList& sm = ServiceList::GetInstance();
170 
171     LoadBootScripts(am, sm);
172 
173     // Turning this on and letting the INFO logging be discarded adds 0.2s to
174     // Nexus 9 boot time, so it's disabled by default.
175     if (false) DumpState();
176 
177     am.QueueEventTrigger("early-init");
178 
179     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
180     am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
181     // ... so that we can start queuing up actions that require stuff from /dev.
182     am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
183     am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
184     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
185     am.QueueBuiltinAction(keychord_init_action, "keychord_init");
186     am.QueueBuiltinAction(console_init_action, "console_init");
187 
188     // Trigger all the boot actions to get us started.
189     am.QueueEventTrigger("init");
190 
191     // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
192     // wasn't ready immediately after wait_for_coldboot_done
193     am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
194 
195     // Don't mount filesystems or start core system services in charger mode.
196     std::string bootmode = GetProperty("ro.bootmode", "");
197     if (bootmode == "charger") {
198         am.QueueEventTrigger("charger");
199     } else {
200         am.QueueEventTrigger("late-init");
201     }
202 
203     // Run all property triggers based on current state of the properties.
204     am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
205 
206     while (true) {
207         // By default, sleep until something happens.
208         int epoll_timeout_ms = -1;
209 
210         if (do_shutdown && !shutting_down) {
211             do_shutdown = false;
212             if (HandlePowerctlMessage(shutdown_command)) {
213                 shutting_down = true;
214             }
215         }
216 
217         if (!(waiting_for_prop || Service::is_exec_service_running())) {
218             am.ExecuteOneCommand();
219         }
220         if (!(waiting_for_prop || Service::is_exec_service_running())) {
221             if (!shutting_down) {
222                 auto next_process_restart_time = RestartProcesses();
223 
224                 // If there's a process that needs restarting, wake up in time for that.
225                 if (next_process_restart_time) {
226                     epoll_timeout_ms = std::chrono::ceil<std::chrono::milliseconds>(
227                                            *next_process_restart_time - boot_clock::now())
228                                            .count();
229                     if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
230                 }
231             }
232 
233             // If there's more work to do, wake up again immediately.
234             if (am.HasMoreCommands()) epoll_timeout_ms = 0;
235         }
236 
237         epoll_event ev;
238         int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
239         if (nr == -1) {
240             PLOG(ERROR) << "epoll_wait failed";
241         } else if (nr == 1) {
242             ((void (*)()) ev.data.ptr)();
243         }
244     }
245 
246     return 0;
247 }
View Code

(1)進入 main()函式後,首先檢查啟動程式的檔名。如果檔名是"ueventd",執行守護程序 ueventd 的主函式 ueventd main(O,如果檔名是"watchdogd",執行看門狗守護程序的主函式 watchdogd_main()。都不是則繼續執行。

   if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
   // 引數個數大於1,就會獲取多個
    if (argc > 1 && !strcmp(argv[1], "subcontext")) {
        InitKernelLogging(argv);
        const BuiltinFunctionMap function_map;
        return SubcontextMain(argc, argv, &function_map);
    }

從這裡可以看出 init 程序的程式碼裡也包含了另外兩個守護程序的程式碼,因為這幾個守護程序的程式碼重合度高,所以,開發人員乾脆把它們都放在一起了。但是在編譯時,Android 生成了兩個指向 init 檔案的符號連線ueventd 和 watchdogd,這樣啟動時如果執行的是這兩個符號連線,main函式就能判斷出到底要啟動哪個守護程序。

(2)建立一些基本的目錄,包括/dev、/porc、/sys等;同時把一些檔案系統,如 tmpfs、devpt、 proc、sysfs 等 mount 到相應的目錄。

     mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);

        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));

        if constexpr (WORLD_WRITABLE_KMSG) {
            mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
        }

        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

tmpfs 是一種基於記憶體的檔案系統,mount 後就可以使用。tmpfs 檔案系統下的檔案都存放在記憶體中,訪問速度快,但是關機後所有內容都會丟失,因此 tmpfs 檔案系統比較適合存放一些臨時性的檔案。tmpfs 檔案系統的大小是動態變化的,剛開始佔用空間很小,隨著檔案的增多會隨之變大,很節省空間。Android 將 tmpfs 檔案系統 mount 到/dev目錄,/dev 目錄用來存放系統創造的裝置節點,正好符合 tmpfs 檔案系統的特點。

  • devpts 是虛擬終端檔案系統,它通常 mount 在目錄/dev/pts 下。

  • proc 也是一種基於記憶體的虛擬檔案系統,它可以看作是核心內部資料結構的介面,通過它可以獲得系統的資訊,同時能夠在執行時修改特定的核心引數。

  • sysfs 檔案系統和 proc 檔案系統類似,它是 Linux 2.6 核心引入的,作用是把系統的裝置和匯流排按層次組織起來,使得它們可以在使用者空間存取,用來向用戶空間匯出核心的資料結構及它們的屬性。

(3)在/dev 目錄下建立一個空檔案".booting"表示初始化正在進行。

close(open("/dev/.booting",o_WRONLY I o_CREAT,0000));

is_bootingO函式會依靠空檔案".booting"來判斷是否程序處於初始化中。初始化結束後這個檔案將被刪除。

(4)呼叫 property init()函式來初始化 Android 的屬性系統。 property_init();

property_init()函式主要作用是建立一個共享區域來儲存屬性值。下節分析屬性系統時會詳細介紹。

(5)呼叫 property load boot defaults()函式。

is_charger = !strcmp (bootmode,"charger"); property_load_boot_defaults ();

property_load_boot_defaults()函式將解析裝置根目錄下的 default.prop 檔案,把檔案中定義的屬性值讀出來設定到屬性系統中。

所謂充電模式是指插著充電器開機時裝置會進入的狀態。這時 kernel 和 init 程序會啟動,但是大部分的服務都不會啟動。

(6)main() 函式最後會進入一個無限 while 迴圈,每次迴圈開始都會呼叫 ExecuteOneCommand()函式來執行命令列表中的一條命令,同時呼叫 RestartProcesses()函式來啟動服務程序∶

while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1;

        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }

        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_restart_time = RestartProcesses();

                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_restart_time) {
                    epoll_timeout_ms = std::chrono::ceil<std::chrono::milliseconds>(
                                           *next_process_restart_time - boot_clock::now())
                                           .count();
                    if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
                }
            }

            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout_ms = 0;
        }

        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

    return 0;
}

(7)Init 程序初始化系統後,會化身為守護程序來處理子程序的死亡訊號,修改屬性的請求和組合鍵盤鍵事件。

啟動 Service 程序

在 main()函式的 for 迴圈中,會呼叫 RestartProcesses()函式來啟動服務列表中的服務程序。函式 RestartProcesses()的程式碼如下所示∶

static std::optional<boot_clock::time_point> RestartProcesses() {
    std::optional<boot_clock::time_point> next_process_restart_time;
    for (const auto& s : ServiceList::GetInstance()) {
        if (!(s->flags() & SVC_RESTARTING)) continue;

        auto restart_time = s->time_started() + 5s;
        if (boot_clock::now() > restart_time) {
            if (auto result = s->Start(); !result) {
                LOG(ERROR) << "Could not restart process '" << s->name() << "': " << result.error();
            }
        } else {
            if (!next_process_restart_time || restart_time < *next_process_restart_time) {
                next_process_restart_time = restart_time;
            }
        }
    }
    return next_process_restart_time;
}

會檢查ServiceList 單例列表中的每個服務,凡是帶有 SVC RESTARTING 標誌的,進行啟動,在啟動前會判斷當前時間是否已經到達啟動時間。如果時間達到了,就會呼叫對應 service 的方法來進行啟動:

// /system/core/init/service.cpp

Result<Success> Service::Start() { bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET)); // Starting a service removes it from the disabled or reset state and // immediately takes it out of the restarting state if it was in there. flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); // Running processes require no additional work --- if they're in the // process of exiting, we've ensured that they will immediately restart // on exit, unless they are ONESHOT. For ONESHOT service, if it's in // stopping status, we just set SVC_RESTART flag so it will get restarted // in Reap(). if (flags_ & SVC_RUNNING) { if ((flags_ & SVC_ONESHOT) && disabled) { flags_ |= SVC_RESTART; } // It is not an error to try to start a service that is already running. return Success(); } ......

SVC_DISABLED、SVC_RESTARTING、SVC_RESET、SVC RESTART、SVC_DISABLED_START這 5 個標誌都是和啟動程序相關,需要先清除掉。如果服務帶有 SVC RUNNING 標誌,說明服務程序已經執行,這裡就不重複啟動了。

fork 子程序

 pid_t pid = -1;
    if (namespace_flags_) {
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

fork 呼叫的一個奇妙之處就是它僅僅被呼叫一次,卻能夠返回兩次,它可能有三種不同的返回值:

  • (1)在父程序中,fork返回新建立子程序的程序ID;

  • (2)在子程序中,fork返回0;

  • (3)如果出現錯誤,fork返回一個負值。

在 fork 函式執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。在子程序中,fork函式返回0,在父程序中,fork返回新建立子程序的程序ID。我們可以通過fork返回的值來判斷當前程序是子程序還是父程序。

解析啟動指令碼 init.rc

Init 程序啟動時最重要的工作是解析並執行啟動檔案 init.rc。本節將介紹 init.rc 檔案的格式,以及Init 程序解析指令碼檔案的流程。

init.rc 檔案格式介紹

init.rc 檔案是以塊(section)為單位組織的,一個"塊"可以包含多行。"塊"分成兩大類;一類稱為"行為(action)";另一類稱為"服務(service)"。"行為"塊以關鍵字"on"開始,表示一堆命令的集合,"服務"塊以關鍵字"service"開始,表示啟動某個程序的方式和引數。"塊"以關鍵字"on"或"service"開始,直到下一個"on"或"service"結束,中間所有行都屬於這個"塊"(空行或註釋行沒有分割作用)。註釋以'#'號開始(如下所示是一份格式樣本)。

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000

    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    # Set the security context of /postinstall if present.
    restorecon /postinstall

    # Mount cgroup mount point for cpu accounting
    mount cgroup none /acct nodev noexec nosuid cpuacct
    mkdir /acct/uid

    # root memory control cgroup, used by lmkd
    mkdir /dev/memcg 0700 root system
    mount cgroup none /dev/memcg nodev noexec nosuid memory
    # app mem cgroups, used by activity manager, lmkd and zygote
    mkdir /dev/memcg/apps/ 0755 system system
    # cgroup for system_server and surfaceflinger
    mkdir /dev/memcg/system 0550 system system

    start ueventd

on init
    sysclktz 0

    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom

    symlink /system/bin /bin
    symlink /system/etc /etc

    # Backward compatibility.
    symlink /sys/kernel/debug /d

無論是"行為"塊還是"服務"塊,並不是按照檔案中的編排順序逐一執行的。它們只是一份放在這裡的定義,至於執行與否以及何時執行要由 init 程序在執行時決定。

"行為(action)"的關鍵字"on"後面跟的字串稱為"觸發器(trigger)",例如,例項中的"boot"和"nonencrypted"。"觸發器"後面是命令列表。命令列表中的每一行都是一條命令,命令的種類非常多。

樹林美麗、幽暗而深邃,但我有諾言尚待實現,還要奔行百里方可沉睡。 -- 羅伯特·弗羅斯特