美文网首页android之开机流程探索
Android系统启动——2init进程

Android系统启动——2init进程

作者: 隔壁老李头 | 来源:发表于2018-03-04 12:56 被阅读902次

    本次系列的内容如下:

    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个阶段,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行

    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解析

    官人[飞吻],你都把臣妾从头看到尾了,喜欢就点个赞呗(眉眼)!!!!

    相关文章

      网友评论

        本文标题:Android系统启动——2init进程

        本文链接:https://www.haomeiwen.com/subject/ncrzwxtx.html