(一) Init启动
从按电源键开始涉及到的关键流程步骤:
- 设备上电后CPU复位,指向Boot ROM上一个固定地址,并开始执行此处程序。
- 上述程序的作用是读取并加载启动设备上的引导程序Boot Loader到RAM中。
- CPU指向引导程序入口开始执行:初始化堆栈、硬件、内存等操作。
- 引导程序加载Android内核[boot.img]到RAM中,并从main入口开始执行Kernel初始化。
- Kernel初始化完成后,在用户空间启动init进程,调用init进程中的main方法初始化。
Init是Android系统中用户空间的第一个进程,进程号是1,父进程是0
从下图中可以看出:进程1和进程2的父进程都是0,其他进程直接或间接继承进程1或进程2.
进程0 (PID = 0) 运行在内核态,是系统创建的第一个进程, 也是唯一一个没有通过fork或者kernel_thread产生的进程.
static noinline void __ref rest_init(void)
{
struct task_struct *tsk;
int pid;
… …
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
… …
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
… …
complete(&kthreadd_done);
}
在内核初始化start_kernel()最后rest_init()中,从进程0复制创建init和kthreadd:
- init进程 (PID = 1, PPID = 0)
- kthreadd进程(PID = 2, PPID = 0)
init 进程 (pid = 1, ppid = 0) ,其他用户空间进程的 ppid 都是1或间接继承;
kthreadd进程负责内核线程的创建、维护等工作,内核中其他进程PPID都是2,都是由kthreadd进程创建.
(二) Init初始化 [ system/core/init ]
init 进程初始化做了很多工作,主要做了以下三件事:
创建(mkdir)和挂载(mount)启动所需的文件目录;
初始化和启动属性服务(property service)
解析 init.rc 配置文件并启动本地服务进程及Zygote 进程;
Android 新版本对 init 改动较大,入口换成了system/core/init/main.cpp 的 main 函数,分阶段启动:
// system/core/init/main.cpp int main(int argc, char** argv) { if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (argc > 1) { if (!strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } if (!strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } if (!strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); } } return FirstStageMain(argc, argv); }
第一阶段:创建和挂载文件系统
init 进程的第一个阶段主要是用来创建和挂载启动所需的文件目录,其中挂载了 tmpfs、devpts、proc、sysfs 和 selinuxfs 共 5 种文件系统,这些都是系统运行时目录,只有在系统运行时才会存在,系统停止时会消失。
system/core/init/first_stage_init.cpp: 设置用户组,挂载文件系统
四类文件系统:
tmpfs:一种虚拟内存文件系统,它会将所有的文件存储在内存中; 由于 tmpfs 是驻留在 RAM 的,因此它的内容是不持久的。断电后,tmpfs 的内容就消失了.
devpts:为伪终端提供了一个标准接口
proc:可以看作是内核内部数据结构的接口,通过它我们可以获得系统信息,同时也能够在运行时修改特定的内核参数;
sysfs:与 proc 文件系统类似,也是一个虚拟文件系统。它通常被挂接在 /sys 目录下。它把连接在系统上的设备和总线组织成一个分级的文件,使得它们可以在用户空间存取;
system/core/init/selinux.cpp: 主要实现SeLinux初始化
第二阶段:初始化启动属性服务
system/core/init/init.cpp:
-
重定向标准输入/输出为空,初始化内核日志服务,记录开机时间
-
property_init() 属性初始化
system/core/init/property_service.cpp
void PropertyInit() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
ExportKernelBootProps();
PropertyLoadBootDefaults();
}
__system_property_area_init() 老版本调用fd = ashmem_create_region(name,size)创建32k/128k大小共享内存空间
typedef struct {
void *data; //存储空间的起始地址 pa
size_tsize; //存储空间的大小
int fd; //共享内存的文件描写叙述符
} workspace;
__system_property_area__ = pa [共享内存起始地址]; bionic libc库中输出的一个变量
client访问:
当bionic libc库被载入时会调用__libc_prenit函数,函数内实现共享内存到本地进程映射:
fd =getenv("ANDROID_PROPERTY_WORKSPACE");
pa =mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0); //mmap设置client为只读属性
__system_property_area__ = pa
新版本实现:
PropertyLoadBootDefaults() 载入系统属性
if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) { if (!load_properties_from_file("/prop.default", nullptr, &properties)) { load_properties_from_file("/default.prop", nullptr, &properties); } } load_properties_from_file("/system/build.prop", nullptr, &properties); load_properties_from_file("/system_ext/build.prop", nullptr, &properties); load_properties_from_file("/vendor/default.prop", nullptr, &properties); load_properties_from_file("/vendor/build.prop", nullptr, &properties); if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) { load_properties_from_file("/odm/etc/build.prop", nullptr, &properties); } else { load_properties_from_file("/odm/default.prop", nullptr, &properties); load_properties_from_file("/odm/build.prop", nullptr, &properties); } load_properties_from_file("/product/build.prop", nullptr, &properties); load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
老版本载入/data/property下的persist属性: load_persistent_properties();
-
监听处理子进程信号
InstallSignalFdHandler 函数用于设置子进程信号处理函数,在子进程终止的时候发出 SIGCHLD 信号,而 InstallSignalFdHandler 函数是用来接收 SIGCHLD 信号的,其内部只处理进程终止的 SIGCHLD 信号。
static void InstallSignalFdHandler(Epoll* epoll) { // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving // SIGCHLD when a child process stops or continues (b/77867680#comment9). const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP }; sigaction(SIGCHLD, &act, nullptr); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); }
-
StartPropertyService 启动属性服务
system/core/init/property_service.cpp void StartPropertyService(int* epoll_socket) { if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, {}); result.ok()) { property_set_fd = *result; } listen(property_set_fd, 8); auto new_thread = std::thread{PropertyServiceThread}; property_service_thread.swap(new_thread); } static void PropertyServiceThread() { if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result.ok()) { LOG(FATAL) << result.error(); } while(true){ auto pending_functions = epoll.Wait(std::nullopt); if (!pending_functions.ok()) { LOG(ERROR) << pending_functions.error(); } else { for (const auto& function : *pending_functions) { (*function)(); } } } }
创建并监听一个用来接收请求的socket,将 property_set_fd 放入了 epoll 中来监听,当 property_set_fd 中有数据到来时,收到client请求时调用handle_property_set_fd进行处理:
static void handle_property_set_fd() { int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); SocketConnection socket(s, cr); uint32_t cmd = 0; if (!socket.RecvUint32(&cmd, &timeout_ms)) { PLOG(ERROR) << "sys_prop: error while reading command from the socket"; socket.SendUint32(PROP_ERROR_READ_CMD); return; } switch (cmd) { case PROP_MSG_SETPROP: { 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): error while reading name/value from the socket"; return; } uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);
system/core/init/property_service.cpp uint32_t HandlePropertySet(const std::string& name, const std::string& value, const std::string& source_context, const ucred& cr, std::string* error) { // 如果属性名称以ctl.开头,说明是控制属性 if (StartsWith(name, "ctl.")) { HandleControlMessage(name.c_str() + 4, value, cr.pid); return PROP_SUCCESS; } return PropertySet(name, value, error); } static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) { //从属性存储空间查找属性 prop_info* pi = (prop_info*) __system_property_find(name.c_str()); if (pi != nullptr) { //属性存在 if (StartsWith(name, "ro.")) { *error = "Read-only property was already set"; return PROP_ERROR_READ_ONLY_PROPERTY; } //更新属性值 __system_property_update(pi, value.c_str(), valuelen); } else { //不存在新增 int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen); if (rc < 0) { *error = "__system_property_add failed"; return PROP_ERROR_SET_FAILED; } } //"persist."属性的特殊处理 if (persistent_properties_loaded && StartsWith(name, "persist.")) { WritePersistentProperty(name, value); } property_changed(name, value); // on section trigger }
ro属性为只读,重启不失效,不能修改,只能刷机改变。
persist属性可修改,同时写入/data/property,重启后保留修改后的属性。
其他属性都是临时存在内存中,重启就失效了。
ctl控制属性需要特殊处理。
-
Client 发送端:
int property_set(const char *key, const char*value){
msg.cmd = PROP_MSG_SETPROP;
strcpy((char*) msg.name, key);
strcpy((char*) msg.value, value);
//建立和属性server的socket连接
s =socket_local_client(PROP_SERVICE_NAME,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
//通过socket发送出去
while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
if((errno == EINTR) || (errno == EAGAIN)) continue;
break;
}
}
第三阶段:解析init.rc启动服务
int SecondStageMain(int argc, char** argv) { InitializeSubcontext(); ActionManager& am = ActionManager::GetInstance(); ServiceList& sm = ServiceList::GetInstance(); LoadBootScripts(am, sm); am.QueueEventTrigger("early-init"); // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); am.QueueEventTrigger("init"); // Don't mount filesystems or start core system services in charger mode. std::string bootmode = GetProperty("ro.bootmode", ""); if (bootmode == "charger") { am.QueueEventTrigger("charger"); } else if (bootmode == "cali"){ am.QueueEventTrigger("cali"); } else if (bootmode == "factorytest"){ am.QueueEventTrigger("factorytest"); } else { am.QueueEventTrigger("late-init"); } // Run all property triggers based on current state of the properties. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); while (true) { if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) { am.ExecuteOneCommand(); } if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) { if (am.HasMoreCommands()) epoll_timeout = 0ms; } auto pending_functions = epoll.Wait(epoll_timeout); if (!pending_functions.ok()) { } else if (!pending_functions->empty()) { for (const auto& function : *pending_functions) { (*function)(); } } } }
启动init subcontext进程
InitializeSubcontext 进行vendor/odm初始化
加载rc配置文件LoadBootScripts(am, sm)
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/system/etc/init/hw/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } // late_import is available only in Q and earlier release. As we don't // have system_ext in those versions, skip late_import for system_ext. parser.ParseConfig("/system_ext/etc/init"); if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/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); } }
AIL语法
Android 8.以后对 init.rc 文件进行了拆分,每个服务对应一个 init.xxx.rc 文件;
从基础rc文件开始逐步导入所有需要的init.xxx.rc文件。
RC文件中的Action和Service是以Section的形式出现的,每个Action Section可以含有若干Command,而每个ServiceSection可以含有若干Option。
Section只有起始标记,没有明确的结束标记,是用“后一个Section”的起始来结束“前一个Section”。
Service不能出现重名, Action可以重复,但最后会合并到一起。
import则是导入其它init.*.rc用的,如import /init.${ro.hardware}.rc。
Action需要有一个触发器(trigger)来触发它,一旦满足了触发条件,这个Action就会被加到执行队列的末尾
init.rc 是非常重要的配置文件,由 Android 初始化语言(Android Init Language) 编写的脚本,这种语言主要包含 5 种类型语句:
Action(行为)
Command(命令)
Service(服务)
Option(选项)
Import(引入)
init.xxx.rc 文件内容大致分为两大部分:
以 on 关键字开头的“动作列表”(action list)
on <triggger> [&& <trigger>]* // 设置触发器 <command> // 动作触发之后要执行的命令 <command> ... ...
动作列表用于创建所需目录以及为某些特定文件指定权限
on init sysclktz 0 copy /proc/cmdline /dev/urandom copy /system/etc/prop.default /dev/urandom on boot ifup lo hostname localhost domainname localdomain on property:sys.boot_from_charger_mode=1 class_stop charger trigger late-init
以 service 关键字开头的“服务列表”(service list)
service <name> <pathname> [<argument>]* // <service的名字><执行程序路径><传递参数> <option> // 控制服务启动的时间,方式,用户及参数等 <option> ...
服务列表用来记录 init 进程需要启动的一些子进程
service ueventd /system/bin/ueventd class core critical seclabel u:r:ueventd:s0 shutdown critical
Command命令列表:
atic const BuiltinFunctionMap builtin_functions = { {"bootchart", {1, 1, {false, do_bootchart}}}, {"chmod", {2, 2, {true, do_chmod}}}, {"chown", {2, 3, {true, do_chown}}}, {"class_reset", {1, 1, {false, do_class_reset}}}, {"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}}, {"class_restart", {1, 1, {false, do_class_restart}}}, {"class_start", {1, 1, {false, do_class_start}}}, {"class_start_post_data", {1, 1, {false, do_class_start_post_data}}}, {"class_stop", {1, 1, {false, do_class_stop}}}, {"copy", {2, 2, {true, do_copy}}}, {"domainname", {1, 1, {true, do_domainname}}}, {"enable", {1, 1, {false, do_enable}}}, {"exec", {1, kMax, {false, do_exec}}}, {"exec_background", {1, kMax, {false, do_exec_background}}}, {"exec_start", {1, 1, {false, do_exec_start}}}, {"export", {2, 2, {false, do_export}}}, {"hostname", {1, 1, {true, do_hostname}}}, {"ifup", {1, 1, {true, do_ifup}}}, {"init_user0", {0, 0, {false, do_init_user0}}}, {"insmod", {1, kMax, {true, do_insmod}}}, {"installkey", {1, 1, {false, do_installkey}}}, {"interface_restart", {1, 1, {false, do_interface_restart}}}, {"interface_start", {1, 1, {false, do_interface_start}}}, {"interface_stop", {1, 1, {false, do_interface_stop}}}, {"load_persist_props", {0, 0, {false, do_load_persist_props}}}, {"load_system_props", {0, 0, {false, do_load_system_props}}}, {"loglevel", {1, 1, {false, do_loglevel}}}, {"mark_post_data", {0, 0, {false, do_mark_post_data}}}, {"mkdir", {1, 6, {true, do_mkdir}}}, // TODO: Do mount operations in vendor_init. // mount_all is currently too complex to run in vendor_init as it queues action triggers, // imports rc scripts, etc. It should be simplified and run in vendor_init context. // mount and umount are run in the same context as mount_all for symmetry. {"mount_all", {1, kMax, {false, do_mount_all}}}, {"mount", {3, kMax, {false, do_mount}}}, {"perform_apex_config", {0, 0, {false, do_perform_apex_config}}}, {"umount", {1, 1, {false, do_umount}}}, {"umount_all", {1, 1, {false, do_umount_all}}}, {"update_linker_config", {0, 0, {false, do_update_linker_config}}}, {"readahead", {1, 2, {true, do_readahead}}}, {"remount_userdata", {0, 0, {false, do_remount_userdata}}}, {"restart", {1, 1, {false, do_restart}}}, {"restorecon", {1, kMax, {true, do_restorecon}}}, {"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}}, {"rm", {1, 1, {true, do_rm}}}, {"rmdir", {1, 1, {true, do_rmdir}}}, {"setprop", {2, 2, {true, do_setprop}}}, {"setrlimit", {3, 3, {false, do_setrlimit}}}, {"start", {1, 1, {false, do_start}}}, {"stop", {1, 1, {false, do_stop}}}, {"swapon_all", {1, 1, {false, do_swapon_all}}}, {"enter_default_mount_ns", {0, 0, {false, do_enter_default_mount_ns}}}, {"symlink", {2, 2, {true, do_symlink}}}, {"sysclktz", {1, 1, {false, do_sysclktz}}}, {"trigger", {1, 1, {false, do_trigger}}}, {"verity_update_state", {0, 0, {false, do_verity_update_state}}}, {"wait", {1, 2, {true, do_wait}}}, {"wait_for_prop", {2, 2, {false, do_wait_for_prop}}}, {"write", {2, 2, {true, do_write}}}, };
Option选项列表:
static const KeywordMap<ServiceParser::OptionParser> parser_map = { {"capabilities", {0, kMax, &ServiceParser::ParseCapabilities}}, {"class", {1, kMax, &ServiceParser::ParseClass}},//设置service所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;常见的类名有:main、core、charge {"console", {0, 1, &ServiceParser::ParseConsole}}, {"critical", {0, 0, &ServiceParser::ParseCritical}},//设备关键服务,4分钟内重启超过4次会进入recovery模式 {"disabled", {0, 0, &ServiceParser::ParseDisabled}},//不跟随class启动 {"enter_namespace", {2, 2, &ServiceParser::ParseEnterNamespace}}, {"file", {2, 2, &ServiceParser::ParseFile}}, {"group", {1, NR_SVC_SUPP_GIDS + 1, &ServiceParser::ParseGroup}},//服务用户组设置 {"interface", {2, 2, &ServiceParser::ParseInterface}}, {"ioprio", {2, 2, &ServiceParser::ParseIoprio}},//io 操作优先级设置 {"keycodes", {1, kMax, &ServiceParser::ParseKeycodes}}, {"memcg.limit_in_bytes", {1, 1, &ServiceParser::ParseMemcgLimitInBytes}}, {"memcg.limit_percent", {1, 1, &ServiceParser::ParseMemcgLimitPercent}}, {"memcg.limit_property", {1, 1, &ServiceParser::ParseMemcgLimitProperty}}, {"memcg.soft_limit_in_bytes", {1, 1, &ServiceParser::ParseMemcgSoftLimitInBytes}}, {"memcg.swappiness", {1, 1, &ServiceParser::ParseMemcgSwappiness}}, {"namespace", {1, 2, &ServiceParser::ParseNamespace}}, {"oneshot", {0, 0, &ServiceParser::ParseOneshot}},//sevice退出后不再重启 {"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},,//当服务重启时执行相关的command {"oom_score_adjust", {1, 1, &ServiceParser::ParseOomScoreAdjust}}, {"override", {0, 0, &ServiceParser::ParseOverride}}, {"priority", {1, 1, &ServiceParser::ParsePriority}}, {"reboot_on_failure", {1, 1, &ServiceParser::ParseRebootOnFailure}}, {"restart_period", {1, 1, &ServiceParser::ParseRestartPeriod}}, {"rlimit", {3, 3, &ServiceParser::ParseProcessRlimit}}, {"seclabel", {1, 1, &ServiceParser::ParseSeclabel}}, {"setenv", {2, 2, &ServiceParser::ParseSetenv}},//设置service环境变量 {"shutdown", {1, 1, &ServiceParser::ParseShutdown}}, {"sigstop", {0, 0, &ServiceParser::ParseSigstop}}, {"socket", {3, 6, &ServiceParser::ParseSocket}},创建socket {"stdio_to_kmsg", {0, 0, &ServiceParser::ParseStdioToKmsg}}, {"timeout_period", {1, 1, &ServiceParser::ParseTimeoutPeriod}}, {"updatable", {0, 0, &ServiceParser::ParseUpdatable}}, {"user", {1, 1, &ServiceParser::ParseUser}},//设置service的用户 {"writepid", {1, kMax, &ServiceParser::ParseWritepid}}, };
服务启动顺序
init将动作运行的时间划分为多个阶段:
early-init
init
late-init
early-fs
fs
post-fs
late-fs
post-fs-data
load_persist_props_action
firmware_mounts_complete
early-boot
boot [do_class_start]
每个action完成的任务:
- Early-Init :设置init 进程 score adj值,重置安全上下文,启动uevent服务
- Wait-for-coldboot-done:wait uevent_main – device_init完成coldboot_done目录创建,Timeout 1s
- keychord_init:keychord是组合按键,keychord为每个服务配置组合键,在服务解析时为指定服务设置相应的键码值。
- console-init:如果ro.boot.console指定了控制台终端,那么优先使用这个控制台,如果没有指定,那么将使用默认控制台终端/dev/console。
- late-init: trigger early-fs fs post-fs post-fs-data load_persist_props_action firmware_mounts_complete early-boot boot
- early-fs:设置外部存储环境变量
- fs:挂载mtd分区,创建adb设备目录,修改adf设备文件权限
- post-fs:修改productinfo用户群组,改变系统目录访问权限(kmsg、vmallcoinfo、cache等)
- Load_system_props_action:load property file,"/system/build.prop" “/vendor/build.prop” “/factory/factory.prop”
- Post-fs-data:创建、改变/data目录以及它的子目录的访问权限,启动vold、debuggerd服务,bootchart_init.
- Load_presist_props_action:启动logd服务,load property file /data/property,"/data/local.prop"
- Firmware_mounts_complete:删除dev/.booting目录
- Early-boot:修改proc、sys/class子目录访问权限
- Boot:设置usb厂商参数、CPU参数,修改sensorhub、 bluetooth、gnss、thermal目录访问权限,网络参数设置。启动Core class service。
- Nonencrypted:启动 main、late_start class service.
class_start是一个COMMAND,相应的函数为do_class_start, 位于boot section的范围内
当init进程运行到以下几句话时。do_class_start就会被运行了
//将bootsection节的command加入到运行队列
action_for_each_trigger("boot",action_add_queue_tail);
SVC_DISABLED:不随class自己主动启动。以下将会看到class的作用。
SVC_ONESHOT:退出后不须要重新启动,也就是这个service仅仅启动一次就能够了。
SVC_RUNNING:正在运行,这是service的状态。
SVC_RESTARTING:等待重新启动,这也是service的状态。
SVC_CONSOLE:该service须要使用控制台 。
SVC_CRITICAL:假设在规定时间内该service不断重新启动,则系统会重新启动并进入恢复模式。
假设设置了SVC_CRITICAL标示,则4分钟内该服务重新启动次数不能超过4次。否则机器会重新启动进入recovery模式。
Zygote启动
//system/core/rootdir/init.zygote64.rc service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server class main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system socket usap_pool_primary stream 660 root system onrestart write /sys/android_power/request_state wake 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
System_Server启动
zygote启动System_Server