前言
在上一篇文章 Android系统架构 中,我们说可以从线程和进程的角度来动态理解Android的系统分层,还是给出经典图:
在这张图中,我们知道Loader层和Kernel层暂时不是我们要研究的重点,因为它们是Linux内核层,而在Android系统用户态中,会先启动native层即C++代码部分,而这部分的工作就由init进程所负责。
作为Android系统用户态的第一个进程,它是Android系统启动流程中的一个关键步骤,被赋予了很多及其重要的工作职责,比如创建Zygote进程、创建属性服务、监听子进程终止等等。
本文源码:aospxref.com/android-8.1…
正文
话不多说,我们就来分析分析该进程的工作流程。
开始init进程
init进程的进程号固定为1,通过如下今个步骤引入init进程:
- 启动电源以及系统启动:当电源键按下时从固化在ROM中的芯片代码开始执行,加载引导程序BootLoader到RAM中,然后开始执行。
- 引导程序BootLoader:BootLoader是Android系统开始运行前的一个程序,用来拉起系统OS。
- Linux内核启动:当内核启动时,会设置缓存、加载驱动等,当内核加载好后,会启动init进程。
- init进程启动:用户空间第一个进程启动,该进程所做的业务比较多,后面具体分析。
init进程的入口函数
init进程开始的地方就是有个main入口函数,关于init进程的作用,我们直接来分析该函数即可,函数比较长,主要代码如下:
[init.cpp](http://aospxref.com/android-8.1.0_r81/xref/system/core/init/init.cpp)
int main(int argc, char** argv) {
//第一次执行,冷启动,用来管理注册的设备
992 if (!strcmp(basename(argv[0]), "ueventd")) { //注释<1>
993 return ueventd_main(argc, argv);
994 }
995 //第二次运行,开启看门狗程序
996 if (!strcmp(basename(argv[0]), "watchdogd")) { //注释<2>
997 return watchdogd_main(argc, argv);
998 }
999
1000 if (REBOOT_BOOTLOADER_ON_PANIC) {
1001 InstallRebootSignalHandlers();
1002 }
1003
1004 add_environment("PATH", _PATH_DEFPATH);
1005
1006 bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
1007 //第三次运行
1008 if (is_first_stage) { //注释<3>
1009 boot_clock::time_point start_time = boot_clock::now();
1010
1011 // Clear the umask.
1012 umask(0);
1013
1014 //创建和挂载启动所需要的文件目录
1016 mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
1017 mkdir("/dev/pts", 0755);
1018 mkdir("/dev/socket", 0755);
1019 mount("devpts", "/dev/pts", "devpts", 0, NULL);
1020 #define MAKE_STR(x) __STRING(x)
1021 mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
1023 chmod("/proc/cmdline", 0440);
1024 gid_t groups[] = { AID_READPROC };
1025 setgroups(arraysize(groups), groups);
1026 mount("sysfs", "/sys", "sysfs", 0, NULL);
1027 mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
1028 mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
1029 mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
1030 mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
1031
1032 //初始化Kernel的Log
1034 InitKernelLogging(argv);
...
1069 }
1070 //第四次运行
...
1082 //初始化属性服务 见俩除注释4注释
1083 property_init(); //注释<4>
1084 ...
1111 //创建epoll句柄
1112 epoll_fd = epoll_create1(EPOLL_CLOEXEC);
1113 if (epoll_fd == -1) {
1114 PLOG(ERROR) << "epoll_create1 failed";
1115 exit(1);
1116 }
1117 //用于设置子进程信号处理函数 //注释<5>
1118 signal_handler_init();
1119 //导入默认的环境变量
1120 property_load_boot_defaults();
1121 export_oem_lock_status();
//启动属性服务
1122 start_property_service(); //注释<4>
...
1136 if (bootscript.empty()) {
//解析init.rc配置文件 //注释<6>
1137 parser.ParseConfig("/init.rc");
1138 parser.set_is_system_etc_init_loaded(
1139 parser.ParseConfig("/system/etc/init"));
1140 parser.set_is_vendor_etc_init_loaded(
1141 parser.ParseConfig("/vendor/etc/init"));
1142 parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
1143 } else {
1144 parser.ParseConfig(bootscript);
1145 parser.set_is_system_etc_init_loaded(true);
1146 parser.set_is_vendor_etc_init_loaded(true);
1147 parser.set_is_odm_etc_init_loaded(true);
1148 }
1149
1150 ...
1183 while (true) {
1184 // By default, sleep until something happens.
1185 int epoll_timeout_ms = -1;
1186
1187 if (do_shutdown && !shutting_down) {
1188 do_shutdown = false;
1189 if (HandlePowerctlMessage(shutdown_command)) {
1190 shutting_down = true;
1191 }
1192 }
1193
1194 if (!(waiting_for_prop || sm.IsWaitingForExec())) {
//内部遍历执行每个action中携带的command对应的执行函数
1195 am.ExecuteOneCommand();
1196 }
1197 if (!(waiting_for_prop || sm.IsWaitingForExec())) {
1198 //重启死去的进程
restart_processes();
1199
1200 ...
1217 }
1218
1219 return 0;
1220 }
按照执行流程,init函数实际上被执行了4次。
ueventd
在Linux2.6之后,udev取代了DevFs,即userspace/dev。作用主要是管理/dev目录下的设备节点,以及当硬件设备插入或者拔出系统时负责处理用户空间对应的事件,在Android中对应的用户空间程序就是ueventd。
从全局来看,ueventd启动的时候,会为所有当前注册的设备重新生成uevent,主要是遍历/sys目录中的uevent文件,并且向其写入add,从而导致内核生成并且重新发送uevent事件信息给所有注册的设备。
为什么要这样做,因为这些设备注册的时候ueventd还没有运行,所以没法接收到完整的设备信息,所以要重新激活一遍,这个过程也叫做冷启动,init进程的运行需要等待冷启动完成。
关于Linux部分知识,笔者也在努力学习,后续在其他文章输出。
注:这里为什么叫做ueventd,要加个d的原因就是该进程是守护进程daemon的意思,在手机系统中可以发现该进程:
该进程的父进程号是1,也就是本章所说的init进程。
watchdogd
watchdogd是独立的看门狗进程,访问的目录是/dev/watchdog,是一个独立的硬件,在嵌入式设备中通常用来检查设备的存活状态,在一段时间超时后,看门狗硬件会认为系统以及跑飞,从而重启设备。
first_stage(第一阶段)
这个阶段主要创建和挂载启动所需要的文件目录,其中挂载了tmpfs、devpts、proc、sysfs和selinuxfs共5种文件系统,这些都是系统运行时目录,即只在系统运行时才存在,系统停止时就会消失。
second_stage(第二阶段)
第二阶段的init函数代码涉及内容非常多,而且是在Android用户态中见到的真正程序。
在该阶段做的事情比较多,可以分为下面几个方面:
- 初始化属性服务,并且监听属性服务的变化请求。
- 监听子进程终止信号,处理僵尸进程,对有必须要的子进程进行重启。
- 通过解析init.rc文件,来启动其他进程,主要就是Zygote进程。
这部分代码逻辑比较多,可能细节之处由于笔者Linux和C++能力有限无法正确表达,但是大体流程可以先过一遍。
我们来挨个分析该阶段所做的操作。
属性服务
Windows平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息,即使系统或者软件重启也不会丢失,Android提供了一个类似的机制,叫做属性服务。
关于属性服务的代码包括初始化属性服务和开启服务,主要代码就是:
//system/core/init/init.cpp
//初始化
property_init()
//开启属性服务
start_property_service();
属性服务初始化与启动
上面初始化的函数如下:
//system/core/init/property_service.cpp
void property_init() {
72 if (__system_property_area_init()) {
73 LOG(ERROR) << "Failed to initialize property area";
74 exit(1);
75 }
76 }
这里调用了__system_property_area_init()函数,该函数在标准C库下面:
//bionic/libc/bionic/system_properties.cpp
int __system_property_area_init() {
1122 ...
//调用该方法 映射系统属性区域
1134 if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {
1135 free_and_unmap_contexts();
1136 return -1;
1137 }
1138 ...
1140 }
//bionic/libc/bionic/system_properties.cpp
static bool map_system_property_area(bool access_rw, bool* fsetxattr_failed) {
868 ...
877 if (access_rw) {
878 __system_property_area__ =
//调用map映射
879 map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
880 } else {
881 __system_property_area__ = map_prop_area(filename);
882 }
883 return __system_property_area__;
884 }
//bionic/libc/bionic/system_properties.cpp
static prop_area* map_prop_area_rw(const char* filename, const char* context,
213 bool* fsetxattr_failed) {
//打开一个文件
217 const int fd = open(filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
218 ...
253 pa_size = PA_SIZE;
254 pa_data_size = pa_size - sizeof(prop_area);
255 //调用熟悉的mmap函数
256 void* const memory_area = mmap(nullptr, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
257 if (memory_area == MAP_FAILED) {
258 close(fd);
259 return nullptr;
260 }
262 prop_area* pa = new (memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
264 close(fd);
265 return pa;
266 }
可以发现这里最后调用了mmap函数,即内存映射函数,创建了一块共享区域。关于共享内存可以查看文章: # Android IPC | 内存映射详解,申请完共享内存后,接着就是启动服务,代码如下:
//system/core/init/property_service.cpp
void start_property_service() {
744 property_set("ro.property_service.version", "2");
745 //创建一个socket
746 property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
747 false, 0666, 0, 0, nullptr, sehandle);
748 if (property_set_fd == -1) {
749 PLOG(ERROR) << "start_property_service socket creation failed";
750 exit(1);
751 }
752 //监听socket
753 listen(property_set_fd, 8);
754 //使用epoll监听,当收到事件时,调用handle_property_set_fd进行处理
755 register_epoll_handler(property_set_fd, handle_property_set_fd);
756 }
这里会创建一个非阻塞的Socket,然后调用listen函数对property_set_fd进行监听,这样创建的Socket就成为了Server端,关于Socket的知识,后面深入理解Linux时再讨论。其中listen的第二个参数为8,意味着属性服务最多可以同时8个试图设置属性的用户提供服务。
在Linux中一切皆文件,这里的register_epoll_handler函数的作用就是将fd的加入到epoll_fd的监听队列中,当文件中有数据变化时,将会调用handle_property_set_fd函数进行处理。
关于Linux底层的epoll循环,后面在Linux篇中介绍。
服务处理客户端请求
所以这里我们简单来看一下处理函数,代码如下:
//system/core/init/property_service.cpp
static void handle_property_set_fd() {
459 ...
476 //获取到指令CMD
477 uint32_t cmd = 0;
478 if (!socket.RecvUint32(&cmd, &timeout_ms)) {
479 PLOG(ERROR) << "sys_prop: error while reading command from the socket";
480 socket.SendUint32(PROP_ERROR_READ_CMD);
481 return;
482 }
483
484 switch (cmd) {
485 case PROP_MSG_SETPROP: {
486 char prop_name[PROP_NAME_MAX];
487 char prop_value[PROP_VALUE_MAX];
488
489 if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
490 !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
491 PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
492 return;
493 }
494
495 prop_name[PROP_NAME_MAX-1] = 0;
496 prop_value[PROP_VALUE_MAX-1] = 0;
497 //设置属性
498 handle_property_set(socket, prop_value, prop_value, true);
499 break;
500 }
501
502 ...
521 }
//system/core/init/property_service.cpp
//参数为socket、要设置属性的key\value
static void handle_property_set(SocketConnection& socket,
411 const std::string& name,
412 const std::string& value,
413 bool legacy_protocol) {
414 const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
//这里要检查属性名是否合法
415 if (!is_legal_property_name(name)) {
416 LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal property name "" << name << """;
417 socket.SendUint32(PROP_ERROR_INVALID_NAME);
418 return;
419 }
420
421 struct ucred cr = socket.cred();
422 char* source_ctx = nullptr;
423 getpeercon(socket.socket(), &source_ctx);
424 //属性名以"ctl."开始
425 if (android::base::StartsWith(name, "ctl.")) {
426 if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
//处理以ctl.开头的属性
427 handle_control_message(name.c_str() + 4, value.c_str());
428 ...
441 } else {
442 if (check_mac_perms(name, source_ctx, &cr)) {
//处理一般属性
443 ...
453 }
454
455 freecon(source_ctx);
456 }
这里可以发现属性也是分为俩种的,一种是以ctl.开头的控制属性,一种是一般属性,我们来看看设置一般属性的方法实现:
//system/core/init/property_service.cpp
uint32_t property_set(const std::string& name, const std::string& value) {
283 if (name == "selinux.restorecon_recursive") {
284 return PropertySetAsync(name, value, RestoreconRecursiveAsync);
285 }
286
287 return PropertySetImpl(name, value);
288 }
//system/core/init/property_service.cpp
static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
170 size_t valuelen = value.size();
171 //判断属性名是否合法
172 if (!is_legal_property_name(name)) {
173 LOG(ERROR) << "property_set("" << name << "", "" << value << "") failed: bad name";
174 return PROP_ERROR_INVALID_NAME;
175 }
176
177 if (valuelen >= PROP_VALUE_MAX) {
178 LOG(ERROR) << "property_set("" << name << "", "" << value << "") failed: "
179 << "value too long";
180 return PROP_ERROR_INVALID_VALUE;
181 }
182
183 prop_info* pi = (prop_info*) __system_property_find(name.c_str());
184 if (pi != nullptr) {
185 //以ro开头的属性不可以更改
186 if (android::base::StartsWith(name, "ro.")) {
187 LOG(ERROR) << "property_set("" << name << "", "" << value << "") failed: "
188 << "property already set";
189 return PROP_ERROR_READ_ONLY_PROPERTY;
190 }
191 //更新属性
192 __system_property_update(pi, value.c_str(), valuelen);
193 } else {
//添加属性
194 int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
195 if (rc < 0) {
196 LOG(ERROR) << "property_set("" << name << "", "" << value << "") failed: "
197 << "__system_property_add failed";
198 return PROP_ERROR_SET_FAILED;
199 }
200 }
//以persist开头的属性需要持久化
204 if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
205 write_persistent_property(name.c_str(), value.c_str());
206 }
207 property_changed(name, value);
208 return PROP_SUCCESS;
209 }
不同的属性有不同的处理方法,从这里我们也可以知道Android系统中的属性可以分为下面3类:
- 以ctl.开头的,表示控制消息,控制消息用来执行一些命令。
- 以ro.开头的,表示只读,不能设置,所以直接返回。
- 以persist.开头的,则需要把这些值写到对应的文件,由于是持久化保存的,所以会有额外IO操作。
关于系统属性我们可以通过shell命令:getprop查看:
这里有非常多的属性,比如dalvik的属性:
持久化的属性:
不可修改的属性:
同时可以使用setprop来设置系统属性:
好了,init进程的一个重要任务初始化属性服务我们就介绍到这,我们知道属性服务就相当于一个注册表,使用键值对的方式来保存一些简单信息,而且这些键分了好几种类型,然后使用socket来当作服务端,来监听共享文件变化,从而处理消息。
设置子进程信号处理函数
在文章刚开始的Android系统架构图中,我们可以知道init进程其实是Android系统用户空间的鼻祖进程,即其他用户空间所有进程都是它的子进程。
所以利用这个特性我们可以在init进程中监听其子进程的状态,这是因为当进程的运行状态改变或者终止时会产生某种signal信号,在init进程中想办法监听相对应的信号,然后做处理。
在main()函数中就是signal_handler_init()函数来实现的逻辑,代码如下:
/system/core/init/signal_handler.cpp
void signal_handler_init() {
49 //一切都是为了处理SIGCHLD信号
50 int s[2];
51 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
52 PLOG(ERROR) << "socketpair failed";
53 exit(1);
54 }
55
56 signal_write_fd = s[0];
57 signal_read_fd = s[1];
58 //当捕获到SIGCHLD信号时,则写入signal_write_fd
60 struct sigaction act;
61 memset(&act, 0, sizeof(act));
62 act.sa_handler = SIGCHLD_handler;
63 act.sa_flags = SA_NOCLDSTOP;
//注册捕获SIGCHLD信号,当有信号时写入文件
64 sigaction(SIGCHLD, &act, 0);
65
66 ServiceManager::GetInstance().ReapAnyOutstandingChildren();
67 //监听文件改变
68 register_epoll_handler(signal_read_fd, handle_signal);
69 }
首先是Linux的机制当子进程终止时会产生SIGCHLD信号,然后init进程调用sigaction函数来捕获SIGCHLD信号,当捕获到了会调用SIGCHLD_handler函数:
/system/core/init/signal_handler.cpp
static void SIGCHLD_handler(int) {
43 if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
44 PLOG(ERROR) << "write(signal_write_fd) failed";
45 }
46 }
会发现这里会往文件里写数据1,然后再通过register_epoll_handler函数来监听该文件变化,然后触发handle_signal函数:
/system/core/init/signal_handler.cpp
static void handle_signal() {
36 char buf[32];
37 read(signal_read_fd, buf, sizeof(buf));
38 //主要的逻辑
39 ServiceManager::GetInstance().ReapAnyOutstandingChildren();
40 }
可以发现主要逻辑在ReapAnyOutstandingChildren()函数中:
/system/core/init/service.cpp
void ServiceManager::ReapAnyOutstandingChildren() {
1217 while (ReapOneProcess()) {
1218 }
1219 }
这里会调用ReapOneProcess()函数:
/system/core/init/service.cpp
bool ServiceManager::ReapOneProcess() {
1158 siginfo_t siginfo = {};
1159 //这里会一直调用waitid这个系统调用
1161 if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
1162 PLOG(ERROR) << "waitid failed";
1163 return false;
1164 }
1165
1166 auto pid = siginfo.si_pid;
1167 if (pid == 0) return false;
1168 //根据pid找到服务
1179 Service* svc = FindServiceByPid(pid);
...
//处理service的逻辑
1204 svc->Reap();
1206 ...
1213 return true;
1214 }
这里思路非常简单,就是调用一个系统调用函数waitid来获取停止的子进程id,这里系统调用函数我们不必深究,这里有个有意思的函数是这个TEMP_FAILURE_RETRY:
/bionic/libc/include/unistd.h
#define TEMP_FAILURE_RETRY(exp) ({ \
238 __typeof__(exp) _rc; \
239 do { \
240 _rc = (exp); \
241 } while (_rc == -1 && errno == EINTR); \
242 _rc; })
这里就是当函数执行结果为-1且errno为EINTR时一直循环,这里扯远了,当waitid获取到值时就说明有子进程已经停止了,这时根据pid找到对应的服务,调用服务的Reap()函数来做处理:
/system/core/init/service.cpp
//核心逻辑方法,用于处理子进程终止时该怎么办
void Service::Reap() {
//当flag为RESTART,表示ONESHOT时,先kill进程组内所有子进程
296 if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
297 KillProcessGroup(SIGKILL);
298 }
//移除所有该进程所创建的描述符
301 std::for_each(descriptors_.begin(), descriptors_.end(),
302 std::bind(&DescriptorInfo::Clean, std::placeholders::_1));
303 //flag为TEMPORARY时,直接不处理
304 if (flags_ & SVC_TEMPORARY) {
305 return;
306 }
307
308 pid_ = 0;
309 flags_ &= (~SVC_RUNNING);
//对于ONESHOT且非RESTART的进程,设置为DISABLED状态
313 if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
314 flags_ |= SVC_DISABLED;
315 }
//对于DISABLED和RESET状态的进程,设置为stop,并且通知状态
318 if (flags_ & (SVC_DISABLED | SVC_RESET)) {
319 NotifyStateChange("stopped");
320 return;
321 }
322 //服务在4分钟内重启超过4次,则手机进入revovery模式
324 boot_clock::time_point now = boot_clock::now();
325 if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
326 if (now < time_crashed_ + 4min) {
327 if (++crash_count_ > 4) {
328 LOG(ERROR) << "critical process '" << name_ << "' exited 4 times in 4 minutes";
329 panic();
330 }
331 } else {
332 time_crashed_ = now;
333 crash_count_ = 1;
334 }
335 }
336 //进行重启服务
337 flags_ &= (~SVC_RESTART);
338 flags_ |= SVC_RESTARTING;
339 //执行所有onrestart指令
341 onrestart_.ExecuteAllCommands();
342 //通知状态
343 NotifyStateChange("restarting");
344 return;
345 }
上面大致逻辑我们可以明白,就是对于不同的进程当它终止时,执行不同的策略。首先就是会通知系统状态发生了改变,那如何查看这些运行进程的状态呢,通过下面命令:
这里进程的状态分为running、stoped和restarting。
然后就是既然这些进程的处理逻辑不同,也就是各个进程的配置不同,那进程的配置信息是如何设置以及执行呢?这里我们直接在下面说init进程如何启动Zygote进程时来详细说明,包括如何读取该进程的配置参数、以及当该进程终止时所执行的操作。
启动Zygote进程
Zygote进程作为开启Java世界的第一个进程,它的意义非常重大,而创建Zygote进程就是init进程。在上面init进程入口函数中,会有这么一行代码:
/system/core/init/init.cpp
if (bootscript.empty()) {
1137 parser.ParseConfig("/init.rc");
这里当从系统属性中获取bootscript为空时,会解析init.rc这个文件,其实这里rc文件就是配置文件,或者叫做脚本文件,在其中配置了该进程所需要做的事,但是想读懂rc文件,必须要理解一种叫做Android初始化语言(Android Int Language)的语法。
Android Init Language
这种语言比较简单,主要包含5种类型语句:Action、Command、Service、Option和Import。其中rc文件语法以行为单位,以空格间隔的语法,以#开始代表注释行。
Action
通过触发器trigger触发,以on开头的语句就是一种Action,具体有如下以on开头的语句:
- on early-init:在初始化早期阶段触发。
- on init:在初始化阶段触发。
- on late-init:在初始化晚期阶段触发。
- on boot/charger:当系统启动/充电时触发,还有其他情况,就不列举了。
- on property:key=value:当属性值满足条件时触发。
这里可以看成简单的if语句而已。
Service
该语句以service开头,表示开启一个服务,一般该服务运行在该rc文件的一个子进程中。比如init.rc中定义的service,在启动时都会通过fork方式生成子进程。
而且这里在启动service前需要判断对应的可执行文件是否存在,比如在init.rc的结尾:
service console /system/bin/sh
723 class core
724 console
725 disabled
726 user shell
727 group shell log readproc
728 seclabel u:r:shell:s0
这里的服务名就是console,服务的执行路径为/system/bin/sh。
Command
Command就是执行命令的语句,比如上面在Action和Service后面都会跟一系列的执行语句,其实很多直接看名字就能知道是什么意思,下面列举一些常用的命令:
- start <service_name>:启动指定的服务,如果已经启动则跳过。
- stop <service_name>:停止正在运行的服务。
- setprop:设置属性值。
- mkdir:创建指定目录。
- exec:fork并执行,会阻塞init进程知道程序完毕。
Options
Options是Service的可选项,与service配合使用,下面列举一些:
- oneshot:service退出后不再重启。
- onrestart:当服务重启时,执行相应的命令。
- critical:在规定时间内该service不管重启,则系统会重启进入恢复模式。
从这里我们就能明白前面所说的终止的服务为什么有的需要重启,有的而不需要的原因。
解析rc文件
学习了rc文件的脚本语言,我们就来看看init.rc脚本到底做了些什么事:
/system/core/rootdir/init.rc
//导入一些rc文件
import /init.environ.rc
8 import /init.usb.rc
9 import /init.${ro.hardware}.rc
10 import /vendor/etc/init/hw/init.${ro.hardware}.rc
11 import /init.usb.configfs.rc
//Zygote的rc文件,区分64位和32位的
12 import /init.${ro.zygote}.rc
13
//当是early-init的Action动作
14 on early-init
16 write /proc/1/oom_score_adj -1000
19 write /proc/sys/kernel/sysrq 0
22 restorecon /adb_keys
25 mkdir /mnt 0775 root system
31 mount cgroup none /acct cpuacct
32 mkdir /acct/uid
35 mkdir /dev/memcg 0700 root system
36 mount cgroup none /dev/memcg memory
38 mkdir /dev/memcg/apps/ 0755 system system
40 mkdir /dev/memcg/system 0550 system system
41 //开启ueventd服务
42 start ueventd
43 //当是init的Action动作
44 on init
...
//这里省略了几百个操作
256 on property:sys.boot_from_charger_mode=1
257 class_stop charger
//触发late-init
258 trigger late-init
259
260 on load_persist_props_action
261 load_persist_props
262 start logd
263 start logd-reinit
264
//late-init阶段
270 on late-init
//触发fs
271 trigger early-fs
277 trigger fs
278 trigger post-fs
285 trigger late-fs
289 trigger post-fs-data
290 //触发zygote
292 trigger zygote-start
295 trigger load_persist_props_action
298 trigger firmware_mounts_complete
300 trigger early-boot
301 trigger boot
302
303 on post-fs
309 load_system_props
311 start logd
//开启servicemanager服务,重要服务
312 start servicemanager
313 start hwservicemanager
314 start vndservicemanager
315
348 //省略
370
371 on post-fs-data
373 //省略几百个操作
//zygote-start开启
533 on zygote-start && property:ro.crypto.state=unencrypted
536 start netd
537 start zygote
538 start zygote_secondary
539
540 on zygote-start && property:ro.crypto.state=unsupported
542 exec_start update_verifier_nonencrypted
543 start netd
544 start zygote
545 start zygote_secondary
546
547 on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
549 exec_start update_verifier_nonencrypted
550 start netd
551 start zygote
552 start zygote_secondary
553
711 service ueventd /sbin/ueventd
712 class core
713 critical
714 seclabel u:r:ueventd:s0
715 shutdown critical
716
717 service healthd /system/bin/healthd
718 class core
719 critical
720 group root system wakelock
721
722 service console /system/bin/sh
723 class core
724 console
725 disabled
726 user shell
727 group shell log readproc
728 seclabel u:r:shell:s0
729
730 on property:ro.debuggable=1
731 # Give writes to anyone for the trace folder on debug builds.
732 # The folder is used to store method traces.
733 chmod 0773 /data/misc/trace
734 start console
735
736 service flash_recovery /system/bin/install-recovery.sh
737 class main
738 oneshot
上面脚本文件有700多行,这就说明init启动干了非常多的事情,但是脚本语言都比较好理解,都是一些命令。
我们挑重点说一点,首先就是import加入进来的zygote的rc文件,这里会根据CPU的位数来加载不同的rc文件;然后就是加载各种我们后面会说的服务,比如servicemanager等。
我们以64位处理器为例,会加载zygote64.rc文件:
/system/core/rootdir/init.zygote64.rc
1 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
2 class main //注释1
3 priority -20
4 user root
5 group root readproc
6 socket zygote stream 660 root system
7 onrestart write /sys/android_power/request_state wake
8 onrestart write /sys/power/state on
9 onrestart restart audioserver
10 onrestart restart cameraserver
11 onrestart restart media
12 onrestart restart netd
13 onrestart restart wificond
14 writepid /dev/cpuset/foreground/tasks
了解了大概后,我们来具体分析init进程是如何启动Zygote进程的。
class_start命令
在init.rc中有如下指令:
on nonencrypted
651 class_start main
652 class_start late_start
这里的class_start是一个COMMAND,它对应的解析函数为do_class_start,前面我们介绍了Android Init Language语言,然后它有一套C++的解析代码,这里就不具体分析了。
这里的class_start会启动那些classname为main的Service,从上面int.Zygote64.rc我们知道,Zygote的classname就是main,所以这行语句就是来启动Zygote的。
解析指令
我们来看看解析函数:
/system/core/init/builtins.cpp
static int do_class_start(const std::vector<std::string>& args) {
132 ServiceManager::GetInstance().
133 ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
134 return 0;
135}
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数:
/system/core/init/service.cpp
bool Service::StartIfNotDisabled() {
867 if (!(flags_ & SVC_DISABLED)) {
868 return Start();
869 } else {
870 flags_ |= SVC_DISABLED_START;
871 }
872 return true;
873}
这里的意思就是如果Service在rc文件中没有设置disabled选项,则会调用Start()方法启动Service,在前面我们知道init.zygote64.rc中没有设置disabled选项,所以我们来看一下Start函数:
/system/core/init/service.cpp
bool Service::Start() {
691 flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
//如果service已经在运行,则不启动
696 if (flags_ & SVC_RUNNING) {
697 return false;
698 }
699
700 bool needs_console = (flags_ & SVC_CONSOLE);
701 if (needs_console) {
702 if (console_.empty()) {
703 console_ = default_console;
704 }
708 int console_fd = open(console_.c_str(), O_RDWR | O_CLOEXEC);
709 if (console_fd < 0) {
710 PLOG(ERROR) << "service '" << name_ << "' couldn't open console '" << console_ << "'";
711 flags_ |= SVC_DISABLED;
712 return false;
713 }
714 close(console_fd);
715 }
716 //判断需要启动的Service对应的执行文件是否存在,不存在则不启动
717 struct stat sb;
718 if (stat(args_[0].c_str(), &sb) == -1) {
719 PLOG(ERROR) << "cannot find '" << args_[0] << "', disabling '" << name_ << "'";
720 flags_ |= SVC_DISABLED;
721 return false;
722 }
723
724 std::string scon;
725 if (!seclabel_.empty()) {
726 scon = seclabel_;
727 } else {
728 scon = ComputeContextFromExecutable(name_, args_[0]);
729 if (scon == "") {
730 return false;
731 }
732 }
733
734 LOG(INFO) << "starting service '" << name_ << "'...";
735
736 pid_t pid = -1;
737 if (namespace_flags_) {
738 pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
739 } else {
//调用fork()函数创建子进程
740 pid = fork();
741 }
...
805 //调用execve方法,Service子进程就会被启动
806 if (!ExpandArgsAndExecve(args_)) {
807 PLOG(ERROR) << "cannot execve('" << args_[0] << "')";
808 }
809
810 _exit(127);
811 }
812
813 ...
864}
上述代码就是启动服务的核心代码,首先会判断Service是否在运行,在运行则不需要启动。然后再判断Service的执行文件是否存在,如果不存在则不启动。然后调用fork函数创建子进程,并且返回pid值。
然后再通过调用execve函数,这个创建的子进程就会被启动,并且进入该Service的main函数中。如果该Service是Zygote,从前面Zygote的rc文件我们可以知道其执行路径为/system/bin/app_process64,对应的文件为app_main.cpp,并且执行其main函数,从而开始Zygote进程的工作流程。
从这里我们可以发现,init进程孵化出Zygote进程是通过fork函数实现的。关于fork函数很关键,它是linux的一个系统调用函数,从字面意思上来说是分支的意思,其实就是把原来进程复制一遍,为什么这样做呢?是因为创建进程需要做很多操作,这样更方便。然后就是execve函数,它是用来执行进程的。关于这部分linux知识,后面在linux文章中,再仔细分析。
总结
本篇文章主要分析了init进程的启动流程,主要工作如下:
- 属性服务部分。创建了共享内存,使用socket作为服务端,不断监听属性变化。其中属性也分为好几种,有的可以修改,有的需要持久化保存。
- 子进程终止监听。通过监听子进程终止的信号,当子进程终止时,根据进程配置回收资源或者重启进程等操作。
- 解析rc文件,运行脚本,通过在rc文件中定义的脚本,可以启动其他进程的脚本,比如servicemanager、zygote进程等,从而开启其他进程。
可以见到,init进程的核心工作就是响应property事件以及回收处理终止的子进程,然后执行rc脚本开启其他进程脚本。
注:在linux中,父进程创建子进程,在子进程终止后,如果父进程并不知道子进程终止了,这时进程虽然已经退出了,但是在系统进程表中还为它保留一定的信息,比如进程号、退出状态、运行时间等,这个子进程就是僵尸进程。系统进程表是一项有限的资源,如果系统进程表被僵尸进程耗尽的话,就无法创建新进程了,所以清理僵尸进程很关键。
本篇文章涉及较多linux和C++知识,笔者也在努力加油学习,文中有问题,欢迎指正。最后记录一下Flag。# 一个Android开发的学习Flag记录贴