源码详解Android 9.0(P) 系统启动流程之init进程(第二阶段)

本文详细解析了Android 9.0系统中init进程启动的第二阶段,包括初始化属性域、创建进程会话密钥、清理环境变量、处理SELinux、epoll与信号处理,以及设置系统属性和服务的启动过程。

源码详解Android 9.0(P) 系统启动流程目录:

源码详解Android 9.0(P)系统启动流程之init进程(第一阶段)
源码详解Android 9.0(P)系统启动流程之init进程(第二阶段)
源码详解Android 9.0(P)系统启动流程之init.rc语法规则
源码详解Android 9.0(P)系统启动流程之init进程(第三阶段)
源码详解Android 9.0(P)系统启动流程之核心服务和关键进程启动
源码详解Android 9.0(P)系统启动流程之Zygote进程
源码详解Android 9.0(P)系统启动流程之SystemServer


1. 概述

在前一篇博客源码详解Android 9.0 系统启动流程之init进程(第一阶段)中,
我们分析了init进程第一阶段(内核态)的流程。
在本篇博客中,我们来看看init进程第二阶段(用户态)的工作,主要有以下内容:

  1. 初始化属性域、创建进程会话密钥
  2. 清空环境变量,完成selinux相关的工作
  3. 新建epoll并初始化子进程终止信号处理函数
  4. 设置其他系统属性并开启系统属性服务

2. 初始化属性域、创建进程会话密钥

int main(int argc, char** argv) {
    //同样进行一些判断及环境变量设置的工作
    ..........
    //之前准备工作时将INIT_SECOND_STAGE设置为true,已经不为nullptr,所以is_first_stage为false
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    //这部分工作不再执行了
    if (is_first_stage) {
        ...........
    }

    // At this point we're in the second stage of init.
    // 同样屏蔽标准输入输出及定义Kernel logger
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    // 最后调用syscall,设置安全相关的值
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);//初始化进程会话密钥

    // Indicate that booting is in progress to background fw loaders, etc.
    // 这里的功能类似于“锁”
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//创建 /dev/.booting 文件,就是个标记,表示booting进行中

    property_init();//初始化属性域

	//接下来的一系列操作都是从各个文件读取一些属性,然后通过property_set设置系统属性
	 
    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    /*
     * 1.这句英文的大概意思是,如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
     * 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数
     */
    process_kernel_dt();//处理DT属性
    process_kernel_cmdline();//处理命令行属性

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();//处理其他的一些属性,设置一些ro类型的系统属性

    system_info_initialize();
    aml_firstbootinit();

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
    ............
}

这部分代码主要的工作应该就是调用property_init初始化属性域,
然后设置各种属性了。

在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,
系统开辟了属性存储区域,并提供了访问该区域的API。

2.1 keyctl_get_keyring_ID(keyctl)

定义在system/core/libkeyutils/Keyutils.cpp

keyctl将主要的工作交给__NR_keyctl这个系统调用,keyctl是Linux系统操纵内核的通讯密钥管理工具

static long keyctl(int cmd, ...) {
  va_list va;
  //va_start,va_arg,va_end是配合使用的,用于将可变参数从堆栈中读取出来
  va_start(va, cmd);//va_start是获取第一个参数地址
  unsigned long arg2 = va_arg(va, unsigned long);//va_arg 遍历参数
  unsigned long arg3 = va_arg(va, unsigned long);
  unsigned long arg4 = va_arg(va, unsigned long);
  unsigned long arg5 = va_arg(va, unsigned long);
  va_end(va);
  return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);//系统调用
}


key_serial_t keyctl_get_keyring_ID(key_serial_t id, int create) {
  return keyctl(KEYCTL_GET_KEYRING_ID, id, create);
}

2.2 property_init

property_init函数定义于system/core/init/property_service.cpp中,如下面代码所示,最终调用_system_property_area_init函数初始化属性域。

void property_init() {
    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";
    }
}

_system_property_area_init 定义在/bionic/libc/bionic/system_properties.cpp看名字大概知道是用来初始化属性系统区域的,应该是分门别类更准确些,首先清除缓存,这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为 /dev/_properties;然后就是调用initialize_properties加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存

2.3 process_kernel_dt

定义在system/core/init/init.cpp

读取DT(设备树)的属性信息,然后通过 property_set 设置系统属性

static void process_kernel_dt() {
    if (!is_android_dt_value_expected("compatible", "android,firmware")) {
    //判断 /proc/device-tree/firmware/android/compatible 文件中的值是否为 android,firmware
        return;
    }

	// get_android_dt_dir()的值为/proc/device-tree/firmware/android
    std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(get_android_dt_dir().c_str()), closedir);
    if (!dir) return;

    std::string dt_file;
    struct dirent *dp;
    while ((dp = readdir(dir.get())) != NULL) {//遍历dir中的文件
        if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
        	//跳过 compatible和name文件
            continue;
        }

        std::string file_name = get_android_dt_dir() + dp->d_name;

        android::base::ReadFileToString(file_name, &dt_file);//读取文件内容
        std::replace(dt_file.begin(), dt_file.end(), ',', '.');//替换 , 为 .

        property_set("ro.boot."s + dp->d_name, dt_file);// 将 ro.boot.文件名 作为key,文件内容为value,设置进属性
    }
}

2.4 process_kernel_cmdline

static void process_kernel_cmdline() {
    // The first pass does the common stuff, and finds if we are in qemu.
    // The second pass is only necessary for qemu to export all kernel params
    // as properties.
    import_kernel_cmdline(false, import_kernel_nv);
    if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}

其内部调用了import_kernel_cmdline()并传入了import_kernel_nv()回调函数。import_kernel_cmdline()代码的功能是将传入的cmdline进行分割,并将其传入import_kernel_nv()中进行转换。import_kernel_nv()的代码实现为:

static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
    if (key.empty()) return;

    if (for_emulator) {
        // In the emulator, export any kernel option with the "ro.kernel." prefix.
        property_set("ro.kernel." + key, value);
        return;
    }

    if (key == "qemu") {
        strlcpy(qemu, value.c_str(), sizeof(qemu));
    } else if (android::base::StartsWith(key, "androidboot.")) {
        property_set("ro.boot." + key.substr(12), value);
    } else if (key == "mem_size") {
        property_set("ro.mem_size", value);
    }
}

import_kernel_nv()的功能就是将kernel cmdline中的“androidboot.*”参数转化为"ro.boot.*"property。

2.5 export_kernel_boot_props

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", },
        { "ro.boot.mac", "ro.mac", "00:22:7E:0B:53:26", },
        { "ro.boot.deviceid", "ro.deviceid", "unknown", },
        { "ro.boot.firstboot", "ro.firstboot", "0", },
    };
    for (size_t i = 0; i < arraysize(prop_map); i++) {
        std::string value = GetProperty(prop_map[i].src_prop, "");
        property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
    }
}

在这里面有一个比较常见的系统属性ro.serialno,我们常见的序列号。它是由ro.boot.serialno这个值决定的,而这个值的赋值比较曲折,有机会再研究吧

3. 清空环境变量,完成selinux相关的工作

我们回到main函数,看看接下来的工作:

......

    // Clean up our environment.
    //清空这些环境变量,因为之前都已经存入到系统属性中去了
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    // Now set up SELinux for second stage.
    // 再次完成selinux相关的工作
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();

......

3.1 SelabelInitialize

定位在system/core/init/selinux.cpp

// selinux_android_file_context_handle() takes on the order of 10+ms to run, so we want to cache
// its value.  selinux_android_restorecon() also needs an sehandle for file context look up.  It
// will create and store its own copy, but selinux_android_set_sehandle() can be used to provide
// one, thus eliminating an extra call to selinux_android_file_context_handle().
void SelabelInitialize() {
    sehandle = selinux_android_file_context_handle();//创建context的处理函数
    selinux_android_set_sehandle(sehandle);//将刚刚新建的处理赋值给fc_sehandle
}

涉及函数

定位于external/selinux/libselinux/src/android/android_platform.c

static char const * const seapp_contexts_plat[] = {
	"/system/etc/selinux/plat_seapp_contexts",
	"/plat_seapp_contexts"
};
static char const * const seapp_contexts_vendor[] = {
	"/vendor/etc/selinux/vendor_seapp_contexts",
	"/vendor_seapp_contexts",
        // TODO: remove nonplat* when no need to retain backward compatibility.
	"/vendor/etc/selinux/nonplat_seapp_contexts",
	"/nonplat_seapp_contexts"
};
static char const * const seapp_contexts_odm[] = {
	"/odm/etc/selinux/odm_seapp_contexts",
	"/odm_seapp_contexts"
};

struct selabel_handle* selinux_android_file_context_handle(void)
{
    struct selinux_opt seopts_file[MAX_FILE_CONTEXT_SIZE];
    int size = 0;
    unsigned int i;
    for (i = 0; i < ARRAY_SIZE(seopts_file_plat); i++) {
        if (access(seopts_file_plat[i].value, R_OK) != -1) {
            seopts_file[size++] = seopts_file_plat[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_file_vendor); i++) {
        if (access(seopts_file_vendor[i].value, R_OK) != -1) {
            seopts_file[size++] = seopts_file_vendor[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_file_odm); i++) {
        if (access(seopts_file_odm[i].value, R_OK) != -1) {
            seopts_file[size++] = seopts_file_odm[i];
            break;
        }
    }
    return selinux_android_file_context(seopts_file, size);
}


void selinux_android_set_sehandle(const struct selabel_handle *hndl)
{
      fc_sehandle = (struct selabel_handle *) hndl;
}

在init启动启动第一阶段内核态我们调用SelinuxInitialize函数初始化了SELinux,而在这一阶段主要调用selinux_android_file_context_handle初始化context_handle。

init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询 file_contexts & property_contexts 文件中的安全上下文。

以系统属性为例,当其他进程通过 socket 与系统属性通信时请求访问某一项系统属性的值时,属性服务系统会通过 libselinux 提供的 selabel_lookup 函数到 property_contexts 文件中查找要访问属性的安全上下文,有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。

所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。

3.2 selinux_restore_context

selinux_restore_context()的作用主要是按selinux policy要求,重新设置一些文件的属性:

// The files and directories that were created before initial sepolicy load or
// files on ramdisk need to have their security context restored to the proper
// value. This must happen before /dev is populated by ueventd.
// 如注释所述,以下文件在selinux被加载前就创建了
// 于是,在selinux启动后,需要重新设置一些属性
void SelinuxRestoreContext() {
    LOG(INFO) << "Running restorecon...";
    selinux_android_restorecon("/dev", 0);
    selinux_android_restorecon("/dev/kmsg", 0);
    if constexpr (WORLD_WRITABLE_KMSG) {
        selinux_android_restorecon("/dev/kmsg_debug", 0);
    }
    selinux_android_restorecon("/dev/socket", 0);
    selinux_android_restorecon("/dev/random", 0);
    selinux_android_restorecon("/dev/urandom", 0);
    selinux_android_restorecon("/dev/__properties__", 0);

    selinux_android_restorecon("/plat_file_contexts", 0);
    selinux_android_restorecon("/nonplat_file_contexts", 0);
    selinux_android_restorecon("/vendor_file_contexts", 0);
    selinux_android_restorecon("/plat_property_contexts", 0);
    selinux_android_restorecon("/nonplat_property_contexts", 0);
    selinux_android_restorecon("/vendor_property_contexts", 0);
    selinux_android_restorecon("/plat_seapp_contexts", 0);
    selinux_android_restorecon("/nonplat_seapp_contexts", 0);
    selinux_android_restorecon("/vendor_seapp_contexts", 0);
    selinux_android_restorecon("/plat_service_contexts", 0);
    selinux_android_restorecon("/nonplat_service_contexts", 0);
    selinux_android_restorecon("/vendor_service_contexts", 0);
    selinux_android_restorecon("/plat_hwservice_contexts", 0);
    selinux_android_restorecon("/nonplat_hwservice_contexts", 0);
    selinux_android_restorecon("/vendor_hwservice_contexts", 0);
    selinux_android_restorecon("/sepolicy", 0);
    selinux_android_restorecon("/vndservice_contexts", 0);

    selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
    selinux_android_restorecon("/dev/device-mapper", 0);

    selinux_android_restorecon("/sbin/mke2fs_static", 0);
    selinux_android_restorecon("/sbin/e2fsdroid_static", 0);

    selinux_android_restorecon("/sbin/mkfs.f2fs", 0);
    selinux_android_restorecon("/sbin/sload.f2fs", 0);
}

4. 新建epoll并初始化子进程终止信号处理函数

int main(){
    ......
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,并返回epoll的文件描述符
    if (epoll_fd == -1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }

    sigchld_handler_init();//注册子进程死亡监听socket

    if (!IsRebootCapable()) {
        // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
        // In that case, receiving SIGTERM will cause the system to shut down.
        InstallSigtermHandler();
    }
    ......
 }

4.1 epoll_create1

定义在system/core/init/init.cpp

EPOLL类似于POLL,是Linux中用来做事件触发的,linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势。epoll_create1是epoll_create的升级版,可以动态调整epoll实例中文件描述符的个数。

EPOLL_CLOEXEC这个参数是为文件描述符添加O_CLOEXEC属性,表示生成的epoll fd具有“执行后关闭”特性。

4.2 signal_handler_init

紧接着,init进程调用signal_handler_init装载子进程信号处理器,
该函数定义于system/core/init/signal_handler.cpp中。

init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),
需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

在这里我们有必要简单介绍一下Linux进程的状态,我们知道Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态,其状态主要分为如下几种:

  • Linux进程状态:R (TASK_RUNNING),可执行状态&运行状态(在run_queue队列里的状态)
  • Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态, 可处理signal
  • Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态, 可处理signal, 有延迟
  • Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态, 不可处理signal, 因为根本没有时间片运行代码
  • Linux进程状态:Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。不可被kill, 即不响应任务信号, 无法用SIGKILL杀死

对于上面的进程状态我们可以通过ps -A在运行的相关Android终端进行查看

在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,
此处init进程调用signal_handler_init的目的就是捕获子进程结束的信号。

我们来看看signal_handler_init相关的代码:

void sigchld_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    //利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        PLOG(FATAL) << "socketpair failed in sigchld_handler_init";
    }

    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处理函数
    act.sa_flags = SA_NOCLDSTOP;
    //调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中
    sigaction(SIGCHLD, &act, 0);

	//用于终止出现问题的子进程,详细代码于后文分析。
    ReapAnyOutstandingChildren();
	
	//注册signal_read_fd到epoll中
    register_epoll_handler(signal_read_fd, handle_signal);
}

Linux 信号机制

在深入分析代码前,我们需要了解一些基本概念:

  • Linux进程通过互相发送消息来实现进程间的通信,这些消息被称为“信号”。
  • 每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。

信号机制是Linux进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,…);另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到相关信号后,可以有三种不同的处理方式。

  • 忽略该信号。
  • 捕捉该信号并执行对应的信号处理函数(signal handler),这里采用的就是这种方法
  • 执行该信号的缺省操作(如 SIGTERM, 其缺省操作是终止进程)。

下面我们来分析一下代码

  • 首先,利用sockerpair创建一对已经连接的socket文件描述符,分别作为信号的读/写端,这样当一端写入时,另一端就能被通知到,socketpair 两端既可以写也可以读,这里只是单向的让 s[0] 写,s[1] 读。
  • 接着创建sigaction 结构体,这里注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
  • 接着调用sigaction(SIGCHLD, &act, 0)是信号绑定关系,也就是当监听到SIGCHLD信号时,由 act 这个 sigaction 结构体进行处理,最终交由SIGCHLD_handler函数处理。

上述文字描述还是显得很单薄,空洞不是,那必须拿出必杀技了,有图才有真相不是,让我们通过示意图对整个逻辑把控一番,这样会更加得清晰明了,如果还是不明了,那只能说臣妾做不到了。
在这里插入图片描述

让我们对上面的示意图捋一捋,顺一顺,总结总结:

  • init进程收到收到子进程 SIGCHLD 信号然后通过 sigaction 函数将信号处理过程转移到 sigaction 结构体
  • sigaction 成员变量 sa_flags 另外指定所关心的具体信号为 SA_NOCLDSTOP,也就是子进程终止信号成员变量 sa_handler 表明当子进程终止时,通过 SIGCHLD_handler 函数处理
  • SIGCHLD_handler 信号处理函数中通过 s[0] 文件描述符写了一个"1",由于 socketpair 的特性,s[1] 能接读到该字符串(后序讲解)
  • 通过 register_epoll_handler 将 s[1] 注册到 epoll 内核事件表中,handle_signal 是 s[1] 有数据到来时的处理函数(后续讲解)

signal_handler_init需要关注的内容还是比较多的,我们分步骤来看看。

4.2.1 SIGCHLD_handler

我们先来看看SIGCHLD_handler的具体工作。该函数定义在system/core/init/signal_handler.cpp路径

static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        PLOG(ERROR) << "write(signal_write_fd) failed";
    }
}

从上面代码我们知道,当init子进程终止产生SIGCHLD信号时,SIGCHLD_handler将对socketpair对中signal_write_fd执行写操作。由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。

4.2.2 register_epoll_handler

通过前面的代码分析我么可以知道,在初始化信号监听函数的最后sigchld_handler_init函数调用了register_epoll_handler,该函数定义在system/core/init/init.cpp中,注意这里传入的两个参数分别为signal_read_fd和handle_signal。

void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    //epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理即调用handle_signal
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        PLOG(ERROR) << "epoll_ctl failed";
    }
}

根据代码不难看出:
当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。

4.2.3 handle_signal

handle_signal定义于system/core/init/signal_handler.cpp中:

static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ReapAnyOutstandingChildren();
}

其逻辑非常简单就是读取(清空)signal_read_fd的数据,然后调用ReapAnyOutstandingChildren函数进行相关处理。

ReapAnyOutstandingChildren之前在signal_handler_init中调用过一次,它其实是调用ReapOneProcess

void ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}

4.2.4 ReapOneProcess

定义在system\core\init\sigchld_handler.cpp
这是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程

static bool ReapOneProcess() {
    siginfo_t siginfo = {};
    // This returns a zombie pid or informs us that there are no zombies left to be reaped.
    // It does NOT reap the pid; that is done below.
    //waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束
    //这里waitid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
        PLOG(ERROR) << "waitid failed";
        return false;
    }

    auto pid = siginfo.si_pid;
    if (pid == 0) return false;

    // At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
    // whenever the function returns from this point forward.
    // We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
    // want the pid to remain valid throughout that (and potentially future) usages.
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });

    std::string name;
    std::string wait_string;
    Service* service = nullptr;

    if (PropertyChildReap(pid)) {
        name = "Async property child";
    } else if (SubcontextChildReap(pid)) {
        name = "Subcontext";
    } else {
    	//利用FindService找到pid对应的服务
        //FindService主要是通过轮询init.rc解析生成的services_列表,找到pid与参数一致的service
        service = ServiceList::GetInstance().FindService(pid, &Service::pid);

        if (service) {//输出找到服务信息
            name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
            if (service->flags() & SVC_EXEC) {
                auto exec_duration = boot_clock::now() - service->time_started();
                auto exec_duration_ms =
                    std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
                wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
            }
        } else {
            name = StringPrintf("Untracked pid %d", pid);
        }
    }

	// 根据svc的类型,决定后续的处理方式
    if (siginfo.si_code == CLD_EXITED) {
        LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
    } else {
        LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
    }

    if (!service) return true;//没有找到,说明已经结束了
	
	//清除子进程相关资源
    service->Reap(siginfo);

    if (service->flags() & SVC_TEMPORARY) {
        ServiceList::GetInstance().RemoveService(*service);//移除临时服务
    }

    return true;
}

上文中,waitpid的函数原型为:

pid_t waitpid(pid_t pid, int *status, int options)
其中:
第一个参数pid为预等待的子进程的识别码,pid=-1表示等待任何子进程是否发出SIGCHLD。
第二个参数status,用于返回子进程的结束状态。
第三个参数决定waitpid函数是否处于阻塞处理方式;

WNOHANG表示若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待;
若子进程结束,则返回子进程的pid。
waitpid如果出错,则返回-1。

容易看出handle_signal的主要作用就是找出出现问题的进程,
然后调用对应的Reap函数处理。

Reap

该代码定义在system/core/init/service.cpp中

void Service::Reap(const siginfo_t& siginfo) {
	//清除未携带SVC_ONESHOT 或 携带了SVC_RESTART标志的srvc的进程组
    if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
        KillProcessGroup(SIGKILL);
    }

    // Remove any descriptor resources we may have created.
    //清除srvc中创建出的任意描述符
    std::for_each(descriptors_.begin(), descriptors_.end(),
                  std::bind(&DescriptorInfo::Clean, std::placeholders::_1));

    for (const auto& f : reap_callbacks_) {
        f(siginfo);
    }

    if (flags_ & SVC_EXEC) UnSetExec();
	
	//清理工作完毕后,后面决定是否重启机器或重启服务
    //TEMP服务不用参与这种判断
    if (flags_ & SVC_TEMPORARY) return;

    pid_ = 0;
    flags_ &= (~SVC_RUNNING);
    start_order_ = 0;

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    //对于携带了SVC_ONESHOT并且未携带SVC_RESTART的srvc,将这类服务的标志置为SVC_DISABLED,
    //不再自启动
    if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
        flags_ |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    //未携带SVC_RESTART的关键服务,在规定的间隔内,crash字数过多时,会导致整机重启;
    if (flags_ & (SVC_DISABLED | SVC_RESET))  {
        NotifyStateChange("stopped");
        return;
    }

    // If we crash > 4 times in 4 minutes, reboot into recovery.
    boot_clock::time_point now = boot_clock::now();
    if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
        if (now < time_crashed_ + 4min) {
            if (++crash_count_ > 4) {
                LOG(FATAL) << "critical process '" << name_ << "' exited 4 times in 4 minutes";
            }
        } else {
            time_crashed_ = now;
            crash_count_ = 1;
        }
    }
	
	//将待重启srvc的标志位置为SVC_RESTARTING(init进程将根据该标志位,重启服务)
    flags_ &= (~SVC_RESTART);
    flags_ |= SVC_RESTARTING;

    // Execute all onrestart commands for this service.
    //重启在init.rc文件中带有onrestart选项的服务
    onrestart_.ExecuteAllCommands();

    NotifyStateChange("restarting");
    return;
}

不难看出,Reap函数的主要作用就是清除问题进程相关的资源,然后根据进程对应的类型,决定是否重启机器或重启进程。

ExecuteAllCommands

我们在这一部分的最后,看看定义于system/core/init/Action.cpp中的ExecuteAllCommands函数:

void Action::ExecuteAllCommands() const {
    for (const auto& c : commands_) {
        ExecuteCommand(c);
    }
}

void Action::ExecuteCommand(const Command& command) const {
    android::base::Timer t;
    //进程重启时,将执行对应的函数
    auto result = command.InvokeFunc(subcontext_);
    //打印log
    auto duration = t.duration();

    // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
    // device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
    // are 198 such failures on bullhead.  Instead of spamming the log reporting them, we do not
    // report such failures unless we're running at the DEBUG log level.
    bool report_failure = !result.has_value();
    if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
        result.error_errno() == ENOENT) {
        report_failure = false;
    }

    // Any action longer than 50ms will be warned to user as slow operation
    if (report_failure || duration > 50ms ||
        android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
        std::string trigger_name = BuildTriggersString();
        std::string cmd_str = command.BuildCommandString();

        LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
                  << ":" << command.line() << ") took " << duration.count() << "ms and "
                  << (result ? "succeeded" : "failed: " + result.error_string());
    }
}

整个signal_handler_init的内容比较多,在此总结一下:
signal_handler_init的本质就是监听子进程死亡的信息,
然后进行对应的清理工作,并根据死亡进程的类型,
决定是否需要重启进程或机器。

上述过程其实最终可以简化为下图:
在这里插入图片描述

5. 设置其他系统属性并开启系统属性服务

我们重新将视角拉回到init的main函数,看看接下来的工作:

    ...
    property_load_boot_defaults();//从文件中加载一些属性,读取usb配置
    export_oem_lock_status();//设置ro.boot.flash.locked 属性
    start_property_service();//开启一个socket监听系统属性的设置
    set_usb_controller();//设置sys.usb.controller 属性
    ...

5.1 设置其他系统属性

property_load_boot_defaults,export_oem_lock_status,set_usb_controller这三个函数都是调用property_set设置一些系统属性

5.1.1 property_load_boot_defaults

我们先来看看property_load_boot_defaults函数的内容:
定义在system/core//init/property_service.cpp

void property_load_boot_defaults() {
	//就是从各种路径读取默认配置
    //load_properties_from_file的基本操作就是read_file,然后解析并设置
    if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
        // Try recovery path
        if (!load_properties_from_file("/prop.default", NULL)) {
            // Try legacy path
            load_properties_from_file("/default.prop", NULL);
        }
    }
    load_properties_from_file("/product/build.prop", NULL);
    load_properties_from_file("/odm/default.prop", NULL);
    load_properties_from_file("/vendor/default.prop", NULL);

	//就是设置"persist.sys.usb.config"相关的配置
    update_sys_usb_config();
}

如代码所示,property_load_boot_defaults实际上就是调用load_properties_from_file解析配置文件;
然后根据解析的结果,设置系统属性。
该部分功能较为单一,不再深入分析。

5.1.2 export_oem_lock_status

static void export_oem_lock_status() {
    if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
        return;
    }

    std::string value = GetProperty("ro.boot.verifiedbootstate", "");

    if (!value.empty()) {
        property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
    }
}

5.1.3 set_usb_controller

static void set_usb_controller() {
    std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
    if (!dir) return;

    dirent* dp;
    while ((dp = readdir(dir.get())) != nullptr) {
        if (dp->d_name[0] == '.') continue;

        property_set("sys.usb.controller", dp->d_name);
        break;
    }
}

5.2 start_property_service

定义在platform/system/core/init/property_service.cpp

通过前面的代码分析我们知道在init进程在共享内存区域中,创建并初始化属性域,然后通过property_set 可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性。

Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看这些是如何实现的

  • 首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性
  • 最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd
void start_property_service() {
    selinux_callback cb;
    cb.func_audit = SelinuxAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    property_set("ro.property_service.version", "2");
	
	//创建了一个非阻塞socket
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr);
    if (property_set_fd == -1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }
	
	//调用listen函数监听property_set_fd, 于是该socket变成一个server
    listen(property_set_fd, 8);
	
	//监听server socket上是否有数据到来
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

在正式开始源码细节分析前,先奉上其它进程修改系统属性时大致的流程,以供大家心里有个整体概括:
在这里插入图片描述

5.2.1 相关解释

我们知道,在create_socket函数返回套接字property_set_fd时,property_set_fd是一个主动连接的套接字。
此时,系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接。

由于在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接,
于是需要调用listen函数使用主动连接套接字变为被连接套接字,
使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。

因此,上述代码调用listen后,init进程成为一个服务进程,
其它进程可以通过property_set_fd连接init进程,提交设置系统属性的申请。

listen函数的第二个参数,涉及到一些网络的细节。
在进程处理一个连接请求的时候,可能还存在其它的连接请求。
因为TCP连接是一个过程,所以可能存在一种半连接的状态。
有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。

因此,内核会在自己的进程空间里维护一个队列,以跟踪那些已完成连接但服务器进程还没有接手处理的用户,
或正在进行的连接的用户。
这样的一个队列不可能任意大,所以必须有一个上限。
listen的第二个参数就是告诉内核使用这个数值作为上限。
因此,init进程作为系统属性设置的服务器,最多可以同时为8个试图设置属性的用户提供服务。

5.2.2 handle_property_set_fd

这个函数的作用主要是调用accept4处理property_set_fd的scoket描述符中的数据信息,然后从 socket 中读取操作信息,根据不同的操作类型,调用 HandlePropertySet做具体的操作:

static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
	
	//等待客户端连接
    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    if (s == -1) {
        return;
    }

    ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {//获取连接到此socket的进程的凭据
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {// 读取 socket 中的操作类型信息
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {// 根据操作类型信息,执行对应处理,两者区别一个是以 char 形式读取,一个以 String 形式读取
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        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;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }

        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }
        socket.SendUint32(result);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}

5.2.3 HandlePropertySet

这个是最终的调用处理函数,set property msg 分为两类处理,msg name 以“ctl.”为起始的 msg 通过HandleControlMessage处理,主要是启动、停止、重启服务。修改其它 prop时会调用 property_get,然后通过 bionic 的__system_property_set 函数来实现,而这个函数会通过 socket 与 init 的 property service 取得联系。但是不管是前者还是后者,都要进行 SELinux 安全性检查,只有该进程有操作权限才能执行相应操作。

uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr, std::string* error) {
    if (!IsLegalPropertyName(name)) {//检查可以的合法性
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (StartsWith(name, "ctl.")) {//如果一ctl开头,就执行Service的一些控制操作
        if (!CheckControlPropertyPerms(name, value, source_context, cr)) {//SELinux安全检查,有权限才进行操作
            *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
                                  value.c_str());
            return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
        }

        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }

    const char* target_context = nullptr;
    const char* type = nullptr;
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);

    if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {//检查SElinux规则
        *error = "SELinux permission check failed";
        return PROP_ERROR_PERMISSION_DENIED;
    }

    if (type == nullptr || !CheckType(type, value)) {
        *error = StringPrintf("Property type check failed, value doesn't match expected type '%s'",
                              (type ?: "(null)"));
        return PROP_ERROR_INVALID_VALUE;
    }

    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
    }

    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }

    return PropertySet(name, value, error);//其它属性调用PropertySet
}

这段代码比较简单,整体的大概流程如下:

  • 通过IsLegalPropertyName检测要设置的是否是合法的属性名
  • 如果是以 “ctl.” 打头的属性名表明是控制命令(譬如ctl.start,ctl.stop,ctl.restart),如果能经过权限检测则调用HandleControlMessage进行处理
  • 其它种情况调用PropertySet处理

在这一部分的最后,我们简单举例介绍一下,系统属性改变的一些用途。
在init.rc中定义了一些与属性相关的触发器。
当某个条件相关的属性被改变时,与该条件相关的触发器就会被触发。
举例来说,如下面代码所示,debuggable属性变为1时,将执行启动console进程等操作。

on property:ro.debuggable=1
    # Give writes to anyone for the trace folder on debug builds.
    # The folder is used to store method traces.
    chmod 0773 /data/misc/trace
    start console

6. 总结

随着Android版本越高,init的工作量也是越来越大了,分析起来不得不使出吃奶的力气了,在init进程的第二阶段主要工作是主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务等等,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些落实的。

至此,init进程的准备工作执行完毕,
接下来就要开始解析init.rc文件了。
解析init.rc代码的流程,我们放到下一篇博客介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ʚ兔子的先森ɞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值