本文尝试对着 《深入理解 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 模式。
(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 进程的源码位于目录 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 进程初始化系统后,会化身为守护进程来处理子进程的死亡信号,修改属性的请求和组合键盘键事件。
在 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 进程启动时最重要的工作是解析并执行启动文件 init.rc。本节将介绍 init.rc 文件的格式,以及Init 进程解析脚本文件的流程。
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"。"触发器"后面是命令列表。命令列表中的每一行都是一条命令,命令的种类非常多。