Init进程

解读前

  • 基于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)你了解吗?

(四) 参考

相关推荐
速盾cdn32 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学134 分钟前
CSS浮动
前端·css·css3
什么鬼昵称35 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.1 小时前
Vue组件库Element-ui
前端·vue.js·ui
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
雷特IT2 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
长路 ㅤ   2 小时前
vite学习教程02、vite+vue2配置环境变量
前端·vite·环境变量·跨环境配置
亚里士多没有德7752 小时前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge
micro2010142 小时前
Microsoft Edge 离线安装包制作或获取方法和下载地址分享
前端·edge
.生产的驴2 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript