美文网首页
Android系统启动流程(一)—— init进程的启动流程

Android系统启动流程(一)—— init进程的启动流程

作者: RainMi | 来源:发表于2019-08-09 18:11 被阅读0次

    最近开始阅读android系统启动模块的源码了,记录一下从中学到的东西。
    文章中的源码基于android8.0.0

    init进程是android在用户空间启动的第一个进程,也是用户空间其他进程的父进程,它的进程号是1,系统通过init进程来进行一些初始化工作,包括启动Zygoto、SystemServer等重要进程。

    系统在加载linux内核后便会创建init进程,并会执行init文件中的main方法,接下来我们通过分析main方法的代码来看一下init进程的启动流程。

    1.执行第一阶段,挂载系统运行时目录

    源码路径:\system\core\init\init.cpp

    int main(int argc, char** argv) {
    
        //  1.根据参数argv来判断是否是ueventd或watchdogd
        //strcmp函数用来比较字符串的值是否相等,相等返回0
        //basename函数用来获取字符串中最后一个‘/’之后的内容
        if (!strcmp(basename(argv[0]), "ueventd")) {
            return ueventd_main(argc, argv);
        }
    
        if (!strcmp(basename(argv[0]), "watchdogd")) {
            return watchdogd_main(argc, argv);
        }
    
        if (REBOOT_BOOTLOADER_ON_PANIC) {
            install_reboot_signal_handlers();//设置一些信号量
        }
    
        //添加环境变量
        add_environment("PATH", _PATH_DEFPATH);
    
        //  2.判断是否是第一阶段
        bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
    
        if (is_first_stage) {//if内的代码只在第一阶段执行
            boot_clock::time_point start_time = boot_clock::now();
    
            // 清空文件权限
            umask(0);
    
            //   3.挂载和创建一些系统运行时必要的目录
            mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
            mkdir("/dev/pts", 0755);
            mkdir("/dev/socket", 0755);
            mount("devpts", "/dev/pts", "devpts", 0, NULL);
            #define MAKE_STR(x) __STRING(x)
            mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
            // Don't expose the raw commandline to unprivileged processes.
            chmod("/proc/cmdline", 0440);
            gid_t groups[] = { AID_READPROC };
            setgroups(arraysize(groups), groups);
            mount("sysfs", "/sys", "sysfs", 0, NULL);
            mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
            mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
            mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
            mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
    
            //初始化log
            InitKernelLogging(argv);
    
            LOG(INFO) << "init first stage started!";
            
            //准备开始转入第二阶段
           ...
        }
    
        ...
    }
    

    在注释1处,首先通过main函数的参数argv来判断是否是ueventd或watchdogd,我们阅读源码主要是为了了解init进程的启动流程,就不对ueventd和watchdogd进行研究了。

    在注释2处,通过环境变量INIT_SECOND_STAGE来判断当前是第一阶段还是第二阶段,在init进程的启动过程中,main函数一共会执行两次,分别代表第一阶段和第二阶段,如果是第一阶段,则执行if内的代码。

    在注释3处,通过mount和mkdir来挂载和创建了一些系统运行时所需要的目录,这些目录只有在系统运行时才会创建,当系统关闭后这些目录都会消失。

    之后便开始准备进入第二阶段。

    2.转入第二阶段

            //初始化Selinux安全模块,SELinux是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux的一个安全子系统
            selinux_initialize(true);
    
           ...
    
            //  1.设置环境变量,将INIT_SECOND_STAGE的值设置为true
            setenv("INIT_SECOND_STAGE", "true", 1);
    
            static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
            uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
            setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);
    
            char* path = argv[0];
            char* args[] = { path, nullptr };
            //2.重新执行main方法
            execv(path, args);
    

    在注释1处将环境变量INIT_SECOND_STAGE设置为true,然后再注释2处通过execv函数重新执行main方法,其中path的值即当前init文件的路径,execv会终止当前进程并根据path执行新的进程,但是进程id不会改变。

    3.开启属性服务并初始化信号处理函数

    //第二阶段重新执行main方法
    int main(int argc, char** argv) {
        ...
    
        //由于INIT_SECOND_STAGE的值已经为true了,当第二次运行main函数时会跳过if (is_first_stage) 内的代码.
        bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
    
        if (is_first_stage) {
            ...
        }
    
        InitKernelLogging(argv);
        LOG(INFO) << "init second stage started!";//表示现在已经处于第二阶段了
    
       ...
    
        property_init();// 1.初始化属性服务
        
        process_kernel_dt();//处理DT属性
        process_kernel_cmdline();//处理命令行属性
    
        export_kernel_boot_props();//处理系统属性
    
       ...
    
        // 清除不需要的环境变量
        unsetenv("INIT_SECOND_STAGE");
        unsetenv("INIT_STARTED_AT");
        unsetenv("INIT_SELINUX_TOOK");
        unsetenv("INIT_AVB_VERSION");
    
        // 加载Selinux
        selinux_initialize(false);
        selinux_restore_context();
    
        // 2.初始化信号处理函数
        signal_handler_init();
    
        property_load_boot_defaults();
        export_oem_lock_status();
    
        //开始属性服务
        start_property_service();
    
        set_usb_controller();
        ...
    

    现在进入第二阶段 。上面的这段代码的逻辑比较简单,主要开启了属性服务并初始化了信号处理函数。

    在注释1出对属性服务进行了初始化,属性服务的作用类似于windows系统的注册表。

    在注释2出初始化信号处理函数,它的主要作用是防止init进程的子进程成为僵尸进程。

    在注释3出开启了属性服务。

    4.解析init.rc文件

        ...
        Parser& parser = Parser::GetInstance();  // 1.获取解析器对象
        parser.AddSectionParser("service",std::make_unique<ServiceParser>());//加载解析Service语句的解析器
        parser.AddSectionParser("on", std::make_unique<ActionParser>());//加载解析on语句的解析器
        parser.AddSectionParser("import", std::make_unique<ImportParser>());//加载解析import语句的解析器
        std::string bootscript = GetProperty("ro.boot.init_rc", "");
        if (bootscript.empty()) {
            parser.ParseConfig("/init.rc");// 2.解析init.rc文件
            parser.set_is_system_etc_init_loaded(
                    parser.ParseConfig("/system/etc/init"));
            parser.set_is_vendor_etc_init_loaded(
                    parser.ParseConfig("/vendor/etc/init"));
            parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
        } else {
            parser.ParseConfig(bootscript);
            parser.set_is_system_etc_init_loaded(true);
            parser.set_is_vendor_etc_init_loaded(true);
            parser.set_is_odm_etc_init_loaded(true);
        }
    
        ...
    

    这段代码主要用来解析init.rc文件,在注释1处,通过Parser::GetInstance()获取一个Parser解析器对象,然后使用AddSectionParser方法为该对象添加了service、on和import语句的解析器。

    在注释2处调用了parser的ParseConfig方法,对init.rc文件进行解析

    我们先来了解一下什么是.rc文件。

    4.1 .rc文件简介

    .rc文件是使用android初始化语言编写的一种脚本,这种语言由许多section块组成,这些section可以分为两类:动作(Action)和服务(Service),它们的基本形式如下:

    //动作(Action)
    on <trigger> [&&<trigger>]*  //设置触发器,Action类型的语句为on
        <command>  //触发之后要执行的命令
        <command>
        ...
    
    //服务(Service)
    service <name> <pathname> [<argument>]*  //<service的名称> <执行程序的路径> <参数>
        <option>  //一些选项
        <option>
    
    

    以init.rc中的一段代码为例:

    on early-init  //设置early-init触发器
       
        //下面的语句都是一些command,当early-init被触发后便执行下面的语句
    
        write /proc/1/oom_score_adj -1000
    
        # Disable sysrq from keyboard
        write /proc/sys/kernel/sysrq 0
    
        # Set the security context of /adb_keys if present.
        restorecon /adb_keys
    
        # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
        mkdir /mnt 0775 root system
    
        # Set the security context of /postinstall if present.
        restorecon /postinstall
    
        start ueventd
    

    在.rc文件中还有一类import语句,用来引入其他.rc文件。

    4.2init.rc源码分析

    我们来看一下init.rc文件的源码:
    源码路径:\system\core\rootdir\init.rc

    import /init.environ.rc
    import /init.usb.rc
    import /init.${ro.hardware}.rc
    import /vendor/etc/init/hw/init.${ro.hardware}.rc
    import /init.usb.configfs.rc
    import /init.${ro.zygote}.rc  //  1.加载init.zygote.rc文件
    
    //下面是一系列的触发器
    
    on early-init
       
        write /proc/1/oom_score_adj -1000
    
        write /proc/sys/kernel/sysrq 0
    
        restorecon /adb_keys
    
        mkdir /mnt 0775 root system
    
        restorecon /postinstall
    
        start ueventd
    
    on init
        sysclktz 0
    
        copy /proc/cmdline /dev/urandom
        copy /default.prop /dev/urandom
    
        symlink /system/etc /etc
        symlink /sys/kernel/debug /d
    
        symlink /system/vendor /vendor
    
        mount cgroup none /acct cpuacct
        mkdir /acct/uid
        ... 
    
    on late-init
        trigger early-fs
    
        trigger fs
        trigger post-fs
    
        trigger late-fs
    
        trigger post-fs-data
    
        trigger zygote-start  // 2.触发zygote-start
    
        trigger load_persist_props_action
    
        trigger firmware_mounts_complete
    
        trigger early-boot
        trigger boot
        ...
    
    on zygote-start && property:ro.crypto.state=unencrypted
        exec_start update_verifier_nonencrypted
        start netd
        start zygote  //3.启动zygote
        start zygote_secondary
    
    ...
    ...
    

    在注释1处使用import语句引入了init.zygote.rc文件,这是后面启动zygote进程的关键一步,可以看到init.zygote.rc文件的名字是不固定的,那是因为根据手机处理器位数的不通,将init.zygote.rc拆分成了多个不同的文件,如init.zygote32.rc、init.zygote64.rc等。

    然后通过on语句定义了一系列的触发器,包括early-init、init等等。

    可以看到在late-init被触发时会去触发zygote-start这个触发器(注释2处),而在zygote-start中则会调用start zygote(注释3处),我们来看一下init.zygote32.rc文件的源码:
    源码位置:\system\core\rootdir\init.zygote32.rc

    service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
        class main
        priority -20
        user root
        group root readproc
        socket zygote stream 660 root system
        onrestart write /sys/android_power/request_state wake
        onrestart write /sys/power/state on
        onrestart restart audioserver
        onrestart restart cameraserver
        onrestart restart media
        onrestart restart netd
        onrestart restart wificond
        writepid /dev/cpuset/foreground/tasks
    

    可以看到,该文件通过service语句来创建zygote进程,该进程的代码位于/system/bin/app_process目录下。

    这样,当相关的触发器被触发后,便会启动zygote进程。

    4.3 init.rc文件的解析流程

    回到init.cpp的main方法中来,前面我们说过parser对象调用AddSectionParser方法为该对象添加了service、on和import语句的解析器,并使用ParseConfig方法对init.rc文件进行解析,我们来看一下这两个方法的源码:

    文件路径:\system\core\init\init_parser.cpp

    void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
        section_parsers_[name] = std::move(parser);
    }
    

    section_parsers_有点类似于java中的map对象,AddSectionParser方法就是将一个触发器对象放到了一个map集合中。

    bool Parser::ParseConfig(const std::string& path) {
        if (is_dir(path.c_str())) {  // 1.判断是否目录
            return ParseConfigDir(path);
        }
        return ParseConfigFile(path);
    }
    
    bool Parser::ParseConfigFile(const std::string& path) {
        ...
        std::string data;
        if (!read_file(path, &data)) {  // 2.将要解析的文件的数据读入到data中
            return false;
        }
        ...
        ParseData(path, data);  //3.调用ParseData方法进行解析
        ...
    }
    

    在ParseConfig方法中,首先判断传入的文件是否是一个目录,如果是目录则调用ParseConfigDir方法,如果是文件则调用ParseConfigFile方法,由于我们需要解析的init.rc是一个文件,我们直接来看ParseConfigFile方法。

    在注释2出,将要解析的文件的数据读取到了一个string类型的变量data中,然后再注释3处调用ParseData方法进行数据解析。

    我们来看ParseData方法的源码:

    void Parser::ParseData(const std::string& filename, const std::string& data) {
        
        std::vector<char> data_copy(data.begin(), data.end());// 1.将数据存入一个char类型的链表中
        data_copy.push_back('\0');//在结尾添加‘\0’作为结束标示
    
        parse_state state;   // 创建一个state对象
        state.filename = filename.c_str();
        state.line = 0;
        state.ptr = &data_copy[0];  //state对象内的ptr指针指向data_copy链表
        state.nexttoken = 0;
    
        SectionParser* section_parser = nullptr;//section解析器
        std::vector<std::string> args;
    
        for (;;) {
            switch (next_token(&state)) {  
            case T_EOF:  //当读取到结束标示符时
                if (section_parser) {
                    section_parser->EndSection();  //1.调用EndSection
                }
                return;
            case T_NEWLINE:  //读取到回车符时
                ...
                //section_parsers_是一个map,之前我们创建的on service import语句解析器便放入了这个map中
                if (section_parsers_.count(args[0])) {//2.如果args[0]是on、service、import语句
                    ...
                    if (!section_parser->ParseSection(args, &ret_err)) {//调用ParseSection进行解析
                        ...
                    }
                } else if (section_parser) {
                    ...
                    if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {//调用ParseLineSection
                        ...
                    }
                }
                args.clear();  //清空
                break;
            case T_TEXT:  
                args.emplace_back(state.text);  //3.将读入的数据放入args中
                break;
            }
        }
    }
    

    在ParseData函数中开启了一个无限循环来进行数据的读取,当读取到一个单词时,便将这个单词放到一个字符串链表args中(注释3处),这样便将一行语句拆分成了多个单词。

    在注释2处,通过args[0]来从section_parsers_这个map中获取解析器,在第4节我们创建了on、service、import类型的解析器并放入到了这个map中,因此如果args[0]是这三种语句中的一种便可以获取到相应的解析器,否则的话这条语句可能是一条command或option。然后分别调用ParseSection或ParseLineSection方法来进行解析。

    当读取到结束符号时,则调用EndSection方法(注释1处)。

    ParseSection方法是一个抽象方法,在各个解析器中的实现不同,我们主要来看一下ServiceParser中的实现:
    源码位置:\system\core\init\service.cpp

    bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                     std::string* err) {
       ...
        service_ = std::make_unique<Service>(name, str_args);//1.生成service_对象
        return true;
    }
    

    从源码中我们可以看到,ParseSection主要是根据传入的service语句来构建了一个service_对象。

    我们再来看一下EndSection方法在ServiceParser中的实现:

    void ServiceParser::EndSection() {
        if (service_) {
            ServiceManager::GetInstance().AddService(std::move(service_));
        }
    }
    

    该方法即使是service_对象存入到了ServiceManager中。

    ActionParser中的ParseSection方法与ServiceParser类似,只是ActionParser是根据action语句创建了一个action_对象,并把这个action_放入了ActionManager中。

    至此init.rc中的各类Action和Service便都加载完成了。

    5.将需要被触发的触发器加入队列

    我们回到init.cpp的main方法中继续往下看:

        ActionManager& am = ActionManager::GetInstance();
    
        am.QueueEventTrigger("early-init");  //early-init触发器
    
        am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
        am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
        am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
        am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
        am.QueueBuiltinAction(keychord_init_action, "keychord_init");
        am.QueueBuiltinAction(console_init_action, "console_init");
        am.QueueEventTrigger("init");  //init触发器
    
        am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    
        std::string bootmode = GetProperty("ro.bootmode", "");
        if (bootmode == "charger") {
            am.QueueEventTrigger("charger");
        } else {
            am.QueueEventTrigger("late-init");  //late-init触发器
        }
        am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
    
    

    在解析完init.rc文件之后,我们还要进行一些准备工作才能对各类事件进行触发。在上面的代码中,我们先获取了ActionManager的实例,然后通过QueueEventTrigger方法将需要被触发的触发器加入到了触发队列中,并使用QueueBuiltinAction方法用于动态创建了一些Action。可以看到early-init、init、late-init触发器都先后被加入到了队列中。

    QueueEventTrigger的源码如下:
    源码路径:\system\core\init\action.cpp

    //std::queue<std::unique_ptr<Trigger>> trigger_queue_;定义于.h中
    void ActionManager::QueueEventTrigger(const std::string& trigger) {
        trigger_queue_.push(std::make_unique<EventTrigger>(trigger));//将触发器加入到队列中
    }
    

    6.触发事件,并不断监听新事件

       while (true) {
            ...
            if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
                am.ExecuteOneCommand();  //执行一条command
            }
            if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
                restart_processes();  //重启需要重启的进程
                ...
            }
            ...
        }
    

    通过while(true)开启了一个事件循环模型,在这个无限循环中,通过ActionManager的ExecuteOneCommand来逐条执行Action的command。

    至此init进程的启动便完成了。

    7.总结

    总结一下init进程启动所做的事情:
    1.挂载和创建系统目录
    2.初始化系统log、开启属性服务、加载Selinux模块等工作
    3.解析init.rc文件,加载各种Action和Service
    4.触发Action,并不断监听新的Action

    参考资料:
    https://www.jianshu.com/p/befff3d70309
    https://www.jianshu.com/p/464c3d1203b1
    《Android》进阶解密——刘望舒 著

    相关文章

      网友评论

          本文标题:Android系统启动流程(一)—— init进程的启动流程

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