前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android系统启动——2init进程

Android系统启动——2init进程

作者头像
隔壁老李头
发布2018-08-30 15:17:22
2.8K0
发布2018-08-30 15:17:22
举报
文章被收录于专栏:Android 研究

本次系列的内容如下:

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:相关守护进程简介

一、init进程简介

通过上篇文章我们知道,Android设备启动要经过3个阶段,BootLoaderLinux Kernel和Android系统服务,一般情况下,他们都会相应的启动对动画对应。前面我们已经知道Andorid系统是如何启动的BootLoaderLinux 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行

代码语言:javascript
复制
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、第二部分
代码语言:javascript
复制
 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级别如下:
代码语言:javascript
复制
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 创建初始化标志
代码语言:javascript
复制
 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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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的输出为例子,该字符串可以拆分为以下几段:

代码语言:javascript
复制
qemu.gles=0  
qemu=1  
console=ttyS0  
android.qemud=ttyS1  
android.checkjni=1  
ndns=1  

因此,在import_kernel_nv将会连续调用6次,依次传入上述字符串。函数实现如下:

代码在init.cpp763行

代码语言:javascript
复制
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函数,继续执行

代码语言:javascript
复制
if (qemu[0])  
     import_kernel_cmdline(1, import_kernel_nv);  

当系统为模拟器时,qemu[0]其值为"1",第二次执行import_kernel_cmdline函数,将再次调用6次import_kernel_nv函数,并且for_emulator为1,因此将生成6个属性。我们来确定下我们的分析

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
/*
 * 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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
    // 执行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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
    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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
// 由于这个函数主要是从一个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函数的执行
代码语言:javascript
复制
{  
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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行

代码语言:javascript
复制
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;
    }
}

只有当前时间大于服务启动时间时,清除服务重启标志并启动该服务,

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.03.04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、init进程简介
  • 二、Init.cpp
    • 1、第一部分
      • 2、第二部分
        • 3、第3部分
          • 4、第4部分
            • 5、第5部分
              • 6、第6部分
                • 7、第7部分
                  • 8、第8部分
                    • 9、第9部分
                      • 9.1信号
                        • 10、第10部分
                          • 10.1、property_load_boot_defaults()函数解析
                            • 10.2、start_property_service()函数解析
                              • 11、第11部分
                                • 12、第12部分
                                  • 13、第13部分
                                    • 13.1 execute_one_command函数的执行
                                      • 13.2 restart_processes()函数的执行
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档