解读前
- 基于Oreo - 8.0.0_r4版本代码分析
- 阅读需有一定C++基础
- 了解android初始化脚本语言
(一) 简介
在android系统中扮演非常重要的角色,是系统创建的第一个用户空间进程,pid=1暂且称为"天字一号",主要用户管理服务和创建子进程,保证系统正常运作。
(二) 工作过程
android系统基于linux系统,所以根据linux系统启动会找到对应启动文件,然后创建第一个用户进程即是init进程,init进程主要做了四件事
- (文) 创建和挂载系统文件
- (属) 启动属性服务,类似于window注册表它是以key-value形式粗存储用户信息,以便出现程序异常后续恢复
- (解) 解析init.rc配置文件
- (监) 监控进程正在等待属性或正在等待执行,仍然没有进程正在等待属性或正在等待执行,就重启进程,以确保系统中关键进程的持续运行。
下面我们看下对应源代码
java
文件目录:system/core/init/init.cpp
init启动伪代码如下
int main(int argc,char** argv) {
// 清理umask
umask(0)
//【1】创建和挂在启动所需的文件目录
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));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
// 初始化kernel的Log
InitKernelLogging(argv)
...
// 用于设置子进程信号处理函数,如果子进程( Zygote 进程)异常退出,init进程会调用该函数中设定的信号处理函数来进行处理
property_init();
//【2】初始化启动&启动属性服务
signal_handler_init();
...
start_property_service();
//【3】解析init.rc配置文件
parser.ParseConfig("/init.rc")
while(ture) {
// 【4】 监控重启进程
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
restart_processes()
}
}
}
2.1 查看所做事情[1]
mkdir是创建文件的方法,mount是挂载文件的方法。
2.2 查看所做事情[2]
此处调用signal_handler_init()来对属性进行初始化并且接着调 start_property_service()启动属性服务。
java
51 void signal_handler_init() {
52 // Create a signalling mechanism for SIGCHLD.
53 int s[2];
54 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
55 PLOG(ERROR) << "socketpair failed";
56 exit(1);
57 }
58
59 signal_write_fd = s[0];
60 signal_read_fd = s[1];
61
62 // 当程序捕获到 `SIGCHLD` 信号时,向指定的 `signal_write_fd` 文件描述符写入数据。这通常用于在接收到子进程退出的信号时执行某些操作。
63 struct sigaction act;
64 memset(&act, 0, sizeof(act));
65 act.sa_handler = SIGCHLD_handler;
66 act.sa_flags = SA_NOCLDSTOP;
67 sigaction(SIGCHLD, &act, 0);
68
69 ServiceManager::GetInstance().ReapAnyOutstandingChildren();
70
71 register_epoll_handler(signal_read_fd, handle_signal);
72 }
主要是防止僵尸进程,系统在init的子进程出现异常会发出停止和终止的sigchld信号,而signal_handler_init中就可以接收到该信号进行处理。
java
666 void start_property_service() {
// 设置属性 "ro.property_service.version" 的值为 "2"
667 property_set("ro.property_service.version", "2");
668
// 创建一个套接字,其参数分别是套接字名称,创建流式执行exec时关闭,非阻塞套接字,0666文件权限,0组ID,0用户ID
669 property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
670 0666, 0, 0, NULL);
// 如果创建套接字失败退出
671 if (property_set_fd == -1) {
672 PLOG(ERROR) << "start_property_service socket creation failed";
673 exit(1);
674 }
675 // 开始监听套接字8表示监听队列长度
676 listen(property_set_fd, 8);
677 // 注册epoll事件处理器并指定处理函数是handle_property_set_fd
678 register_epoll_handler(property_set_fd, handle_property_set_fd);
679 }
整体就是创建套接字用于接收属性设置请求,然后监听该套接字等待客户端连接,并注册一个epoll时间处理器用于处理接受到的请求。
2.3 查看所做事情[3]
c
140 bool Parser::ParseConfig(const std::string& path) {
141 if (is_dir(path.c_str())) {
142 return ParseConfigDir(path);
143 }
144 return ParseConfigFile(path);
145}
传入的path是init.rc是文件并不是目录所以调用ParseConfigFile方法
java
95 bool Parser::ParseConfigFile(const std::string& path) {
96 LOG(INFO) << "Parsing file " << path << "...";
97 Timer t;
98 std::string data;
99 if (!read_file(path, &data)) {
100 return false;
101 }
102
103 data.push_back('\n'); // TODO: fix parse_config.
104 ParseData(path, data);
105 for (const auto& sp : section_parsers_) {
106 sp.second->EndFile(path);
107 }
108
109 LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
110 return true;
111}
最终会读取位于/system/core/rootdir/init.rc 其中会看到import /init. <math xmlns="http://www.w3.org/1998/Math/MathML"> r o . z y g o t e . r c 它表示一个通用的路径模式,其中 {ro.zygote}.rc 它表示一个通用的路径模式,其中 </math>ro.zygote.rc它表示一个通用的路径模式,其中{ro.zygote}是一个系统属性(ro.zygote),在系统启动时会被替换为相应的值。通常情况下,这个值可能是 32或者64,分别表示32位和64位的Zygote进程,这里以64为例即是import /system/etc/init.zygote64.rc,那么进一步查看init.zygote64.rc。
java
//定义了一个名为 `zygote` 的服务,这个服务启动了一个名为 `/system/bin/app_process64` 的可执行程序
// `--start-system-server`:这个参数表示在 Zygote 进程启动后,会启动系统服务器
1 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
2 class main
3 priority -20 //设置了服务的优先级为 `-20`。
4 user root //设置了服务的用户为 `root`。
5 group root readproc
6 socket zygote stream 660 root system //定义了一个名为 `zygote` 的套接字
7 onrestart write /sys/android_power/request_state wake
8 onrestart write /sys/power/state on
9 onrestart restart audioserver //当服务重新启动时,重新启动 `audioserver` 服务
10 onrestart restart cameraserver //当服务重新启动时,重新启动 `cameraserver` 服务。
11 onrestart restart media //当服务重新启动时,重新启动 `media` 服务。
12 onrestart restart netd
13 onrestart restart wificond
14 writepid /dev/cpuset/foreground/tasks
.rc文件是一个初始化脚本语言,不了解的可以先去查阅相关资料,这段代码是在初始化Zygote进程时,定义了一系列的配置和行为,包括服务启动、权限设置、套接字创建、重启行为等。
tip : 这里描述了zygote服务启动,之后会再启动 system-server,现在得到的一个启动顺序是 init -> zygote -> system-server
2.4 查看所做事情[4]
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec()))
再次检测是否有进程正在等待属性或正在等待执行。这个检测是为了确保在执行完一个命令后,仍然没有进程正在等待属性或正在等待执行。
(三) 总结尝试回答如下问题
- 你能简单概述init启动过程吗?
- 你了解init,zygote,system-server的启动顺序吗?
- 你了解android系统是如何处理僵尸进程?
- 属性服务作用是什么?
- 初始化脚本语言(init.rc)你了解吗?
(四) 参考
- 在线源码阅读
- android进阶揭秘