Android系统启动——2init进程
本次系列的内容如下:
Android启动流程——1 序言、bootloader引导与Linux启动
Android系统启动——2 init进程
Android系统启动——3 init.rc解析
Android系统启动——4 zyogte进程
Android系统启动——5 zyogte进程(Java篇)
Android系统启动——6 SystemServer启动
Android系统启动——7 附录1:Android属性系统
Android系统启动——8 附录2:相关守护进程简介
本篇文章的主要内容如下:
- 1、init进程简介
- 2、Init.cpp的main()方法解析
一、init进程简介
通过上篇文章我们知道,Android设备启动要经过3个阶段,BootLoader、Linux Kernel和Android系统服务,一般情况下,他们都会相应的启动对动画对应。前面我们已经知道Andorid系统是如何启动的BootLoader和Linux Kernel的。
严格上讲,Android系统实际上是运行于Linux内核之上的一系列"服务进程",并不算一个完成意义上的"操作系统";而这一系列进程是维持Android设备正常工作的关键,所以它们肯定有一个"根进程",这个"根进程"衍生出了这一系列进程。这个"根进程"就是init进程。
init进程是Android系统启动的第一个进程。它通过解析init.rc脚本来构建出系统的初始形态。其他的"一系列"Android系统进程大部分也是通过"init.rc"来启动的。因为要兼容不同的开发商,所以init.rc脚本的语法很简单,并且采用的是纯文本编辑的,这样导致它可读性就会很高。
二、Init.cpp
init是Linux系统中用户空间的第一个进程(pid=1),Linux Kernel启动后,会调用/system/core/init/Init.cpp的main()方法
那我们就来看下init.cpp的main()里面的具体实现
代码在init.cpp989行
int main(int argc, char** argv) {
// ****************** 第一部分 ******************
// 检查启动程序的文件名
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
// ****************** 第二部分 ******************
// 设置文件属性为0777
// Clear the umask.
umask(0);
// ****************** 第三部分 ******************
// 设置环境变量
add_environment("PATH", _PATH_DEFPATH);
// ****************** 第四部分 ******************
// 创建一些基本目录,并挂载
//判断是否是第一次
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest
//如果是第一次.
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
// ****************** 第五部分 ******************
// 把标准输入、标准输出和标准错误重定向到空设备文件"/dev/_null_"
// We must have some place other than / to create the device nodes for
// kmsg and null, otherwise we won't be able to remount / read-only
// later on. Now that tmpfs is mounted on /dev, we can actually talk
// to the outside world.
open_devnull_stdio();
// ****************** 第六部分 ******************
// 启动kernel log
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
// 输出init启动阶段的log
NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
// ****************** 第七部分 ******************
// 设置系统属性
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
// 7.1 创建初始化标志
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//7.2 初始化Android的属性系统
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
//7.3 解析DT和命令行中的kernel启动参数
process_kernel_dt();
process_kernel_cmdline();
// Propogate the kernel variables to internal variables
// used by init as well as the current required properties.
//7.4 设置系统属性
export_kernel_boot_props();
}
// ****************** 第八部分 ******************
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
// 调用selinux_initialize函数启动SELinux
selinux_initialize(is_first_stage);
// If we're in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
// 按照selinux policy要求,重新设置init文件属性
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
// 设置参数 --second-stage
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
// 执行init进程,重新进入main函数
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
// ****************** 第九部分 ******************
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
signal_handler_init();
// ****************** 第十部分 ******************
property_load_boot_defaults();
start_property_service();
// ****************** 第十一部分 ******************
// 重点部分,我们后面用专门用一篇文章讲解
init_parse_config_file("/init.rc");
// ****************** 第十二部分 ******************
action_for_each_trigger("early-init", action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
action_for_each_trigger("init", action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
// Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
// ****************** 第十三部分 ******************
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
由于main()函数比较长,整个init进程的启动流程都在这个函数中,我们把main()切割成,然后我们就来挨个解释下
1、第一部分
主要是检查启动程序的文件名,这里面分成三种情况
- 1 如果文件名是"ueventd",则执行守护进程ueventd的主函数ueventd_main()
- 2 如果文件名是"watchdogd",则执行看门狗守护进程的主函数watchdogd_main()
- 如果文件名既不是"ueventd"也不是"watchdogd",则往下执行
2、第二部分
umask(0);
缺省情况下,一个进程创建出来的文件和文件夹的属性是022,使用umask()函数能设置文件属性的掩码。参数为0意味着进程创建的文件属性是0777
3、第3部分
设置环境变量地址
4、第4部分
创建一些基本的目录,包括/dev、/porc、/sysfc等。同时把一些文件系统,如tmpfs、devpt、proc、sysfs等mount到项目的目录。我们把上面的文件目录简单说一下
- tmpfs
是一种基于内存的文件系统,mount后就可以使用。tmpfs文件系统下的文件都存放在内存中,访问速度快,但是关机后所有内容偶读会丢失,因此tmpfs文件系统比较合适存放一些临时性的文件。tmpfs文件系统的大小是动态变化的,刚开始占用空间很小,随着文件的增多会随之变大,很节省空间。Android将tmpfs文件mount到/dev目录,dev目录用来存放系统创建的设备节点,正好符合tmpfsw文件系统的特点。
-devpts
是虚拟终端文件系统,它通常mount在目录dev/pts下- proc
是一种基于内存的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它可以获得系统的信息,同时能够在运行时修改特定的内核参数- sysfs
文件系统和proc文件系统类似,它是Linux2.6内核引入的,作用是把系统的设备和总线按层次组织起来,使得它们可以在用户空间存取,用来向用户空间导出内核的数据结构及它们的属性。
5、第5部分
调用open_devnull_stdio()函数把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null",这是创建守护进程常用的手段
6、第6部分
调用klog_init()函数创建节点/dev/kmsg,这样init进程可以使用kernel的log系统来出书log了,同时调用klog_set_level函数来设置输出log的级别。
PS:
- 1 这里补充下,为什么要使用kernel的log系统,因为此时Android系统的log还没有启动,所以需要使用kernel的log系统。
- 2 log的输出级别是KLOG_NOTICE_LEVEL(5),当log级别小于5时,这回输出kernel log,默认值是3,关于log级别如下:
define KLOG_ERROR_LEVEL 3
define KLOG_WARNING_LEVEL 4
define KLOG_NOTICE_LEVEL 5
define KLOG_INFO_LEVEL 6
define KLOG_DEBUG_LEVEL 7
define KLOG_DEFUALT_LEVEL 3
默认为3
7、第7部分
如果不是第一次,则进行一些设置,我又将这里具体划分为4个部分
- 7.1 创建初始化标志
- 7.2 初始化Android的属性系统
- 7.3 解析DT和命令行中的kernel启动参数
- 7.4 设置系统属性
7.1 创建初始化标志
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
在/dev目录下创建一个空文件".booting"表示初始化正在进行
is_booting()函数会依靠空文件".booting"来判断是否进程处于初始化中,初始化结束后,这个文件会被删除
7.2 初始化Android的属性系统
主要是调用property_init()函数来初始化Android的属性系统,property_init()函数主要作用是创建一个共享区域来存储属性值
7.3 解析DT和命令行中的kernel启动参数
这里里面又分两个,由于DT的优先级高于cmdline,所以先调用process_kernel_dt()函数解析启动参数,然后调用process_kernel_cmdline解析启动参数。
为了让大家更好的理解这个两个方法,下面我们来看下这两个方法的具体实现
- process_kernel_dt函数解析
代码在init.cpp816行
static void process_kernel_dt(void)
{
static const char android_dir[] = "/proc/device-tree/firmware/android";
std::string file_name = android::base::StringPrintf("%s/compatible", android_dir);
std::string dt_file;
android::base::ReadFileToString(file_name, &dt_file);
// 判断compatible文件内容是否是android,firmware
if (!dt_file.compare("android,firmware")) {
ERROR("firmware/android is not compatible with 'android,firmware'\n");
return;
}
std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(android_dir), closedir);
if (!dir)
return;
struct dirent *dp;
// 读取目录的每个文件
while ((dp = readdir(dir.get())) != NULL) {
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible"))
continue;
file_name = android::base::StringPrintf("%s/%s", android_dir, dp->d_name);
android::base::ReadFileToString(file_name, &dt_file);
std::replace(dt_file.begin(), dt_file.end(), ',', '.');
// 每个文件名作为属性名,里面的内容作为属性值
std::string property_name = android::base::StringPrintf("ro.boot.%s", dp->d_name);
property_set(property_name.c_str(), dt_file.c_str());
}
}
上面这个函数主要是在/proc/device-tree/firmware/android 这个目录下,先看compatiable文件内容是否是android,firmware。然后这个目录下每个文件名作为属性,文件里面的内容作为属性值。这里的话就是ro.boot.hareware ro.boot.name这两个属性值。
我们知道内核通常由bootloader(启动引导程序)加载启动,前面说过了,目前使用最广泛的是bootloader大都基于u-boot定制。内核允许bootloader启动自己时传递参数。在内核内核启动完毕后,启动参数可以通过/proc/cmdline查看。
例如android 4.4模拟器启动后,查看其内核启动参数,如下:
root@generic:/ # cat /proc/cmdline
qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1
- process_kernel_cmdline函数解析
代码在init.cpp848行
static void process_kernel_cmdline(void)
{
/* don't expose the raw commandline to nonpriv processes */
// 第一步
chmod("/proc/cmdline", 0440);
/* first pass does the common stuff, and finds if we are in qemu.
* second pass is only necessary for qemu to export all kernel params
* as props.
*/
// 第二步
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(true, import_kernel_nv);
}
这个函数内部有点小复杂,我将其分为2个步骤,解析如下:
- 第一步:修改/proc/cmdline文件权限,0440即表明只有root用户或root组用户可以续写该文件,其他用户无法访问。
- 第二步:调用import_kernel_cmdline函数,就是读取proc/cmdline中的内容。这个函数有两个有两个参数,第一个采纳数标识当前Android十倍是否是模拟器。第二个参数是一个函数指针;主要指的是通过调用import_kernel_nv函数来设置系统属性。
import_kernel_cmdline函数将/proc/cmdline内容读入到内存缓冲区中,并将cmdline内容以空格拆分为小段字符串,依次传递给import_kernel_nv函数处理。以上面的/proc/cmdline的输出为例子,该字符串可以拆分为以下几段:
qemu.gles=0
qemu=1
console=ttyS0
android.qemud=ttyS1
android.checkjni=1
ndns=1
因此,在import_kernel_nv将会连续调用6次,依次传入上述字符串。函数实现如下:
代码在init.cpp763行
static void import_kernel_nv(char *name, bool for_emulator)
{
char *value = strchr(name, '=');
int name_len = strlen(name);
if (value == 0) return;
*value++ = 0;
if (name_len == 0) return;
if (for_emulator) {
/* in the emulator, export any kernel option with the
* ro.kernel. prefix */
char buff[PROP_NAME_MAX];
int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
if (len < (int)sizeof(buff))
property_set( buff, value );
return;
}
if (!strcmp(name,"qemu")) {
strlcpy(qemu, value, sizeof(qemu));
} else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
const char *boot_prop_name = name + 12;
char prop[PROP_NAME_MAX];
int cnt;
cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
if (cnt < PROP_NAME_MAX)
property_set(prop, value);
}
}
import_kernel_cmdline第一次执行时,传入import_kernel_nv的形参为for_emulator为0,因此将匹配name是否为qemu,如果是,将其值保存在qemu全局静态缓冲区中。对于android模拟器,存在/proc/cmdline中存在"qemu=1"字段。如果for_emulator为1,则将生成ro.kernel.{name}={value}属性写入Android属性系统中。
此时回到process_kernel_cmdline函数,继续执行
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
当系统为模拟器时,qemu[0]其值为"1",第二次执行import_kernel_cmdline函数,将再次调用6次import_kernel_nv函数,并且for_emulator为1,因此将生成6个属性。我们来确定下我们的分析
root@generic:/ # getprop | grep ro.kernel.
[ro.kernel.android.checkjni]: [1]
[ro.kernel.android.qemud]: [ttyS1]
[ro.kernel.console]: [ttyS0]
[ro.kernel.ndns]: [1]
[ro.kernel.qemu.gles]: [0]
[ro.kernel.qemu]: [1]
可验证我们的分析是正确的。
7.4 设置系统属性
代码在init.cpp796行
static void export_kernel_boot_props() {
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
{ "ro.boot.revision", "ro.revision", "0", },
};
// 通过内核的属性设置应用层配置文件的属性
for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
char value[PROP_VALUE_MAX];
int rc = property_get(prop_map[i].src_prop, value);
property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value);
}
}
所以export_kernel_boot_props这个函数,它就是设置一些属性,设置ro属性根据之前的ro.boot这类的属性值,如果没有设置成unknown,像之前我们有ro.boot.hardware,那我们就可以设置root.hardware这样的属性。这样描述很有朋友看不懂,我把上面的语言转化为最直观的语言如下:
export_kernel_boot_props用户设置几个系统属性,如下:
- 读取ro.boot.serialno,若存在其值写入ro.serialno,否则ro.serialno写入空
- 读取ro.boot.mode,若存在其值写入ro.bootmode,否则ro.bootmode写入"unkown"
- 读取ro.boot.baseband,若存在其值写入ro.baseband,否则ro.baseband写入"unkown"
- 读取ro.boot.bootloader,若存在其值写入ro.bootloader,否则ro.bootloader写入"unkown"
- 读取ro.boot.revision,若存在,若存在其值写入revision中,否则ro.revision写入"unkown"。
- 读取ro.boot.hardware,若存在其值写入ro.hardware,否则ro.hardware写入"unkown"。
8、第8部分
这部分主要是selinux相关的,这部分代码是Android4.3之后添加的安全内核,随后伴随着Android系统更新不断迭代,这段代码主要是设计SELinux初始化。后面有时间咱们开个专题讲解SELinux。
SELinux带给Linux的主要价值是:提供了一个灵活的,可配置的MAC机制。同时SELinux是一个安全体系结构,它通过LSM(Linux Security Modules)框架被集成到Linux Kernel 2.6.x。它是NSA(United States National Security Agency)和SELinux社区的联合项目。
这里重点分析下selinux_initialize函数
代码在init.cpp955行
static void selinux_initialize(bool in_kernel_domain) {
// 使用Timer计时,计算selinux初始化耗时
Timer t;
selinux_callback cb;
// 用于打印Log的回调函数
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
// 用于检查权限的回调函数
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
// 如果不支持,则直接返回
if (selinux_is_disabled()) {
return;
}
// 内核态处理流程,第一阶段in_kernel_domain为true
if (in_kernel_domain) {
// 这个log打印不出,因为是INFO级别
INFO("Loading SELinux policy...\n");
// 用于加载sepolicy文件,该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略的配置文件
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
// 命令行中得到的信息
bool is_enforcing = selinux_is_enforcing();
// 这里补充下 用于设置selinux工作模式。selinux有两种工作模式:
// 1、"permissive",所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志。
// 2、"enforcing",所有操作都会进行权限检查。在一般的中断中,应有工作于enforcing模式
security_setenforce(is_enforcing);
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure();
}
//输出selinux的模式,与初始化耗时
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration());
} else {
// 如果启动第二阶段,调用该函数
selinux_init_all_handles();
}
}
9、第9部分
这部分分为上下按两个阶段
第一阶段主要是调用epoll_create1创建epoll句柄,如果创建失败,则退出。
第二阶段是调用signal_handler_init()函数,主要是装载进程信号处理器。
signal_handler_init()函数主要是当子进程被kill之后,会在父进程接受一个信号。处理这个信号的时候往sockpair一段写数据,而另一端的fd是加入epoll中
init是一个守护进程,为了防止init的子进程称为僵尸进程(zombie process),需要init在子进程结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止称为僵尸进程的子进程占用程序表的空间(程序表的空间达到上线时,系统就不能再启动新的进城了,会引起严重的系统问题)。
现在我们来看下signal_handler_init()函数的内容
signal_handler.cpp955行
void signal_handler_init() {
// 在Linux中,父进程是通过捕捉SIGCHILD信号来得知子进程运行结束的情况
// Create a signalling mechanism for SIGCHLD.
int s[2];
// 利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
ERROR("socketpair failed: %s\n", strerror(errno));
exit(1);
}
signal_write_fd = s[0];
signal_read_fd = s[1];
// Write to signal_write_fd if we catch SIGCHLD.
struct sigaction act;
memset(&act, 0, sizeof(act));
// 信号处理器为SIGCHLD_handler,其被存在sigaction结构体重,负责处理SIGCHLD消息
// 信号处理器
act.sa_handler = SIGCHLD_handler;
// 仅当进程终止时才接受
act.sa_flags = SA_NOCLDSTOP;
// 调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中
sigaction(SIGCHLD, &act, 0);
reap_any_outstanding_children();
// 定义在system/core/init/init.cpp中,注册epoll handler,当signal_read_fd 有数据可读时,调用handle_signal
register_epoll_handler(signal_read_fd, handle_signal);
}
9.1信号
这里我们简单介绍下信号:
Linux进程通过相互发送接收消息来实现进程间通信,这些消息被称为"信号"。每个进程在处理它进程发送的信号时,都要注册处理者,处理者被称为信号处理器。
每个进程在处理其他进程发送的signal信号时都需要先注册,当进程的运行状态改变或终止时会产生某种signal信号,init进程是所有用户空间进程的父进程,当其子进程终止时产生SIGCHLD信号,init进程调用信号安装函数sigaction(),传递参数给sigaction结构体,便完成信号处理的过程。
这里有两个重要的函数:SIGCHLD_handler和handle_signal,他俩是对应的
代码在signal_handler.cpp 158行
static void SIGCHLD_handler(int) {
//向signal_write_fd写入1,知道成功为止
if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
}
}
SIGCHLD_handler()函数负责写入
代码在signal_handler.cpp 150行
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
// 读取signal_read_fd数据,放入buf
read(signal_read_fd, buf, sizeof(buf));
reap_any_outstanding_children();
}
handle_signal()函数负责读取数据,上面调用了reap_any_outstanding_children()函数,我们来看下
代码在signal_handler.cpp 145行
static void reap_any_outstanding_children() {
while (wait_for_one_process()) {
}
}
我们看到reap_any_outstanding_children函数就是调用while循环来调用wait_for_one_process()函数,下面我们来看下wait_for_one_process()函数的具体执行
代码在signal_handler.cpp 53行
static bool wait_for_one_process() {
int status;
// 等待任意子进程,如果子进程没有退出则返回0,否则则返回该子进程的pid
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
if (pid == 0) {
return false;
} else if (pid == -1) {
ERROR("waitpid failed: %s\n", strerror(errno));
return false;
}
// 根据pid 查找相应的service
service* svc = service_find_by_pid(pid);
std::string name;
if (svc) {
name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
} else {
name = android::base::StringPrintf("Untracked pid %d", pid);
}
NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());
if (!svc) {
return true;
}
// TODO: all the code from here down should be a member function on service.
// 当flag为RESTART,且不是ONESHOT时,先kill进程组内所有子进程或子线程
if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
kill(-pid, SIGKILL);
}
// Remove any sockets we may have created.
// 移除当前服务svc中所有创建过的socket
for (socketinfo* si = svc->sockets; si; si = si->next) {
char tmp[128];
snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
unlink(tmp);
}
// 当flags为EXEC时,释放相应的服务
if (svc->flags & SVC_EXEC) {
INFO("SVC_EXEC pid %d finished...\n", svc->pid);
waiting_for_exec = false;
list_remove(&svc->slist);
free(svc->name);
free(svc);
return true;
}
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
// Oneshot processes go into the disabled state on exit,
// except when manually restarted.
// 对于ONESHOT服务,使其进入disabled状态
if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
svc->flags |= SVC_DISABLED;
}
// Disabled and reset processes do not get restarted automatically.
// 禁用和重置的服务,都不再自动重启
if (svc->flags & (SVC_DISABLED | SVC_RESET)) {
// 设置相应的service状态为stopped
svc->NotifyStateChange("stopped");
return true;
}
// 服务在4分钟内重启次数超过4次,则重启手机进入recovery模式
time_t now = gettime();
if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
ERROR("critical process '%s' exited %d times in %d minutes; "
"rebooting into recovery mode\n", svc->name,
CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
return true;
}
} else {
svc->time_crashed = now;
svc->nr_crashed = 1;
}
}
svc->flags &= (~SVC_RESTART);
svc->flags |= SVC_RESTARTING;
// Execute all onrestart commands for this service.
// 执行当前service中所有onrestart命令
struct listnode* node;
list_for_each(node, &svc->onrestart.commands) {
command* cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
// 设置相应的service状态未restarting
svc->NotifyStateChange("restarting");
return true;
}
总结一下:
当init进程调用signal_handler_init后,一旦受到子进程终止带来的SIGCHLD消息后,将利用信号处理者SIGCHLD_handler向signal_write_fd写入信息;epoll句柄监听到signal_read_fd收到消息后,将调用handle_signal进行处理。如下图
image.png
10、第10部分
这部分主要分为两部分,上半部分是调用property_load_boot_defaults()函数解析根目录的default.prop的属性,设置默认属性配置的相关工作。下半部分是调用start_prperty_service()函数,启动属性服务,并接受属性的socket的fd加入到epoll中,也定义了处理函数。那我们依次来看下
10.1、property_load_boot_defaults()函数解析
代码在property_service.cpp 494行
void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
我们看到他调用load_properties_from_file函数,那我们来看下load_properties_from_file函数。
代码在property_service.cpp 426行
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
if (read_file(filename, &data)) {
data.push_back('\n');
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}
PS:所谓充电模式是指充电器开机时设备进入的状态。这是kernel和init进程会启动,但是大部分服务都不会启动
10.2、start_property_service()函数解析
代码在property_service.cpp 570行
void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
start_property_service()函数创建了socket,然后监听,并且调用register_epoll_handler()函数把socket的fd放入epoll中
函数创建了socket,然后监听,并且调用register_epoll_handler函数把socket的fd放入了epoll中
11、第11部分
这部分主要是解析init.rc文件,我们将会用单独一篇文章来讲解。这里先略过
12、第12部分
本部分是将把Action加入执行队列中
代码在init.cpp 570行
// 执行init.rc中触发器为 on early-init的语句,即将early-init的Action添加到链表action_queue中
action_for_each_trigger("early-init", action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
// 等冷插拔设备初始化完成,即创建wait_for_coldboot_done Action并添加到action_queue和action_list中
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// 设备组合键的初始化操作,创建keychord_init Action 并添加到链表action_queue和action_list中
queue_builtin_action(keychord_init_action, "keychord_init");
// 创建console_init动作并添加到链表action_queue和action_list中
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
// 执行init.rc文件中触发器为 on init 的语句,将init动作添加到链表action_queue中
action_for_each_trigger("init", action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
// 当处于充电模式,则charger加入执行队列;否则late-init加入队列。
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
// Run all property triggers based on current state of the properties.
// 触发器为属性是否设置
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
上面大量的调用了action_for_each_trigger函数、action_add_queue_tai函数和queue_builtin_action,那我们就依次研究下这两个函数
12.1 action_for_each_trigger()函数解析
代码在init_parser.cpp 546行
void action_for_each_trigger(const char *trigger,
void (*func)(struct action *act))
{
struct listnode *node, *node2;
struct action *act;
struct trigger *cur_trigger;
list_for_each(node, &action_list) {
// 遍历每个action
act = node_to_item(node, struct action, alist);
list_for_each(node2, &act->triggers) {
// 遍历每个action的triggers
cur_trigger = node_to_item(node2, struct trigger, nlist);
// 判断是否与传入的trigger名字匹配
if (!strcmp(cur_trigger->name, trigger)) {
// 调用回调函数
func(act);
}
}
}
}
12.2 action_add_queue_tail()函数解析
代码在init_parser.cpp 643行
void action_add_queue_tail(struct action *act)
{
if (list_empty(&act->qlist)) {
list_add_tail(&action_queue, &act->qlist);
}
}
这个函数就是把action加入到执行列表中
12.3 queue_builtin_action()函数解析
代码在init_parser.cpp 623行
void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
{
action* act = (action*) calloc(1, sizeof(*act));
trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
cur_trigger->name = name;
list_init(&act->triggers);
list_add_tail(&act->triggers, &cur_trigger->nlist);
list_init(&act->commands);
list_init(&act->qlist);
command* cmd = (command*) calloc(1, sizeof(*cmd));
cmd->func = func;
cmd->args[0] = const_cast<char*>(name);
cmd->nargs = 1;
list_add_tail(&act->commands, &cmd->clist);
list_add_tail(&action_list, &act->alist);
action_add_queue_tail(act);
}
这个函数的话,就是直接创建一个action,然后新建command,关键是func会调用函数设置好,最后把action加入执行队列中。
queue_builtin_action函数用来动态生成一个Action并插入到执行列表"action_queue中"。插入的Action由一个函数指针和一个表示名字的字符串组成。Android在以前版本中直接调用这些函数来完成某些初始化的工作,但是,这些函数可能会依赖init.rc里面定义的一些命令和服务的执行情况。现在把这些初始化函数也通过Action的形式插入到执行列表中,这样就能控制他们的执行顺序了。
插入的函数有:
- wait_for_coldboot_done_action()函数,等待冷插拔设备初始化完成
- mix_hwrng_into_linux_rng_action()函数,从硬件PNG的设备文件/dev/hw_random中读取512字节并写到Linux RNG的设备文件/dev/urandom中
- keychord_init_action()函数:初始化组合键监听模块
- console_init_action()函数:在屏幕个上显示Android字样的Logo
- property_service_init_action()函数:初始化属性服务,读取系统预制的属性值
- singal_init_action()函数:初始化信号处理模块。
- check_startup_action()函数:检查是否已经完成init进程初始化,如果完成则删除.booting文件。
- queue_property_triggers_action()函数:检查Action列表中通过修改属性来触发的Action,查看相关属性值是否已经设置,如果已经设置,则将Action加入到执行列表中。
执行流程如下图:
13、第13部分
代码在init.cp 1109行
while (true) {
// 判断是否还有事件需要处理
if (!waiting_for_exec) {
//依次执行每个action中携带的command对应的执行函数
execute_one_command();
// 重启一些挂掉的进程
restart_processes();
}
// 决定timeout的时间,将影响while循环的间隔
int timeout = -1;
// 有进程需要重启是,等待进程重启
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
// 进行性能数据采样
bootchart_sample(&timeout);
epoll_event ev;
// 没有事件来的话,最多阻塞timeout时间
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
// 根据上下文知道,epoll句柄(即epoll_fd)主要监听子进程结束,及其他进程设置系统属性的请求 ,根据事件的到来,执行对应处理函数
((void (*)()) ev.data.ptr)();
}
}
最后init进程会进入到一个无线循环中去,在这个无线循环中,init进程会做以下五件事:
-
第一件事:调用函数execute_one_command来检查action_queue列表是否为空。如果不为空的话,那么init进程就将保存在列表头部中的action移除,并且执行这个被移除的action。由于前面我们将一个名称为"console_init"的action添加到action_queue列表中,因此,在这个无线循环中,这个action就会被执行,即console_init_action函数会被调用。
-
第二件事:调用函数restart_processes来检查系统中是否有进程需要重启。在启动脚本init.rc中,我们可以指定一个进程在退出之后会自动重启。在这种情况下,函数restart_processes就会检查是否存在需要重新启动的进程,如果存在的话,那么就将它重新启动起来。
-
第三件事:处理系统属性变化事件。当我们调用函数property_set来改变一个系统属性时,系统就会通过一个socket(通过调用函数get_property_set_fd可以获得它的文件描述符)来向init进程发送一个属性值改变事件通知。init进程接受到这个属性值改变事件之后,就会调用函数handle_property_set_fd来进行相应的处理。后面在分析第三个开机画面显示过程时,我们就会看到,SurfaceFlinger服务就是通过修改“ctl.start”和“ctl.stop”属性来启动和停止三个开机画面的。
-
第四件事:处理一种被称为"chorded keyboard"的键盘输入时间。这种类型为"chorded keyboard"的键盘设备通过不同的按键组合来描述不同的命令或者操作,它对应的设备为/dev/keychord。我们可以通过调用函数get_keychord_fd()来获的这个设备的文件描述符,以便可以监控它的输入事件,并且调用函数handle_keychord来对这些输入事件进行处理。
-
第五件事:回收僵尸进程。我们知道,在Linux内核中,如果父进程不等待子进程结束就退出,那么当子进程结束的时候,就会变成一个僵尸进程,从而占用系统的资源。为了回收这些僵尸进程,init进程会安装一个SIGCHLD信号接收器。当这些父进程已经退出了子进程退出的时候,内核就会发出一个SIGCHLD信号,给init进程,init进程就可以通过一个socket(通过调用函数get_signal_fd可以获得它的文件描述符)来将接受到的SIGCHLD信号读取回来,并且调用函数handle_signal来对接收到的SIGCHLD信号来进行处理,即回收哪些已经变成僵尸进程的子进程。
PS:后面三件事都是可以通过文件描述符来描述的,因此,initJ进程的入口函数main使用poll机制来同时轮训它们,以便可以提高效率。
下面 这里我们来看一下execute_one_command()函数和restart_processes()函数的具体执行
13.1 execute_one_command函数的执行
代码在init.cpp 584行
void execute_one_command() {
Timer t;
char cmd_str[256] = "";
char name_str[256] = "";
//如果是第一次启动,所以都是NULL,所以肯定可以进入这个判断
//如果不是第一次启动,因为得到cur_action或者cur_command都是null,并且如果这个command是当前action的最后一个command的话,会进入下面的这个判断
if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
// 依次从action_queue获取action
cur_action = action_remove_queue_head();
cur_command = NULL;
if (!cur_action) {
// 如果没有action了,则返回
return;
}
build_triggers_string(name_str, sizeof(name_str), cur_action);
INFO("processing action %p (%s)\n", cur_action, name_str);
// 如果是一个新的action的话,会执行到这一步去获得first command
cur_command = get_first_command(cur_action);
} else {
// 如果还在action的内部链表中,如果仍然存在没有获取到的command的话,则会去获取一下一个command
cur_command = get_next_command(cur_action, cur_command);
}
if (!cur_command) {
//如果可以获取到command为空的话,会返回,反之,继续
return;
}
// 会调用这个command的func去执行,执行的参数个数为nargs,命令为args
int result = cur_command->func(cur_command->nargs, cur_command->args);
if (klog_get_level() >= KLOG_INFO_LEVEL) {
for (int i = 0; i < cur_command->nargs; i++) {
strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
if (i < cur_command->nargs - 1) {
strlcat(cmd_str, " ", sizeof(cmd_str));
}
}
char source[256];
if (cur_command->filename) {
snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
} else {
*source = '\0';
}
INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
cmd_str, cur_action ? name_str : "", source, result, t.duration());
}
}
其实这个函数就是执行一个command。大体流程如下:
- 首选从action_queue取下struct action *act赋值给cur_action
- 其次从cur_action获得struct command * 赋值给curcommand
- 最后执行cur_command->func(cur_command->nargs, cur_command->args)
13.1.1 action_remove_queue_head函数的执行
上面调用了action_remove_queue_head()函数,我们来看下
代码在init_parser.cpp 650行
struct action *action_remove_queue_head(void)
{
// 先做非空判断
if (list_empty(&action_queue)) {
return 0;
} else {
// 如果还有未被执行的队列的话,就将node指向现在的action_queue的头指针
struct listnode *node = list_head(&action_queue);
// 取出action
struct action *act = node_to_item(node, struct action, qlist);
// 删将这个节点从整个action_queue的列表中删除
list_remove(node);
// 删除节点后,为了安全起见,将node自己指向自己,以避免出现野指针。
list_init(node);
// 返回 已经找到的action
return act;
}
}
我们知道,其实是从action_queue中拿每一个结构体的,拿到action之后,就从action里面去取对应的command了。
13.1.2 action_remove_queue_head函数的执行
代码在init.cpp 543行
// 由于这个函数主要是从一个action里面找到一个command,所以在传递的时候只传递action即可
static struct command *get_first_command(struct action *act)
{
struct listnode *node;
// 将node 指向action的commands结构体
node = list_head(&act->commands);
if (!node || list_empty(&act->commands))
// 如果这话节点不存在,或者这个action的commands结构体为空,则返回null
return NULL;
// 返回 第一个节点
return node_to_item(node, struct command, clist);
}
13.1.3 get_next_command函数的执行
{
static struct command *get_next_command(struct action *act, struct command *cmd)
{
struct listnode *node;
node = cmd->clist.next;
// 如果不存在,则返回null
if (!node)
return NULL;
// 如果这个节点已经是头节点了,则返回null
if (node == &act->commands)
return NULL;
// 返回 next节点
return node_to_item(node, struct command, clist);
}
这个函数 主要是返回当前commands的下一个command
13.2 restart_processes()函数的执行
当内存不足时,Android系统会自动杀死一些进程来释放空间,所以当某些重要服务被杀,同时该服务进程并未设置为oneshot,则必须重新启动该服务进程。
代码在init.cpp 473行
static void restart_processes()
{
process_needs_restart = 0;
service_for_each_flags(SVC_RESTARTING,
restart_service_if_needed);
}
我们看到这个函数什么都做,就是调用了service_for_each_flags函数,那我们再来研究下这个函数
代码在init_parser.cpp 533行
void service_for_each_flags(unsigned matchflags,
void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (svc->flags & matchflags) {
func(svc);
}
}
}
通过代码我们知道 函数service_for_each_flags主要是循环遍历服务链表,查找标志位为SVC_RESTARTING的服务,当该服务进程死亡的时候,init进程监控进程死亡事件,在处理该事件的时候会为该进程设置SVC_RESTARTING标志,并调用restart_service_if_needed函数重启服务。
我们来看下restart_service_if_needed函数
代码在init.cpp 457行
static void restart_service_if_needed(struct service *svc)
{
time_t next_start_time = svc->time_started + 5;
if (next_start_time <= gettime()) {
svc->flags &= (~SVC_RESTARTING);
service_start(svc, NULL);
return;
}
if ((next_start_time < process_needs_restart) ||
(process_needs_restart == 0)) {
process_needs_restart = next_start_time;
}
}
只有当前时间大于服务启动时间时,清除服务重启标志并启动该服务,
上一篇文章 1序言、bootloader引导与Linux启动
下一篇文章 Android系统启动——3init.rc解析