cm-14.1 Android系统启动过程分析(一)-init进程的启动、rc脚本解析、zygote启动、属性服务

声明

  • 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。
  • 本文参考了一些书籍的若干章节,比如《Android进阶解密-第2章-Android系统启动》、《深入理解Android虚拟机-第8/9/10章-init进程详解/Dalvik VM的进程系统/Dalvik VM运作流程详解》、《深入理解Android系统-第6/7/8章-init启动进程详解/Zygote进程详解/System进程详解》等
  • 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
  • 很多代码注释待详细写

1 init进程

    init进程在Linux系统中是内核启动后启动的1号进程,init进程在Android系统中依然是内核初始化完成后首先启动的1号进程。init进程主要作用是:

  1. 创建、挂载启动所需的文件目录,包括:tmpfs、devpts、proc、sys、selinuxfs;
  2. 解析、处理init.rc等脚本文件中的命令;
  3. 创建Zygote和属性服务;
  4. 使用while(true)循环创建子进程;

    其源码位置在:

vim  ~/LineageOS/system/core/init/init.cpp
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);
    }

    // 清理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);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        mount("sysfs", "/sys", "sysfs", 0, 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();
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);

    NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");

    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        // 检测/dev/.booting文件是否可读写和创建
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
        //初始化属性服务
        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.
        process_kernel_dt();
        //处理内核命令行
        process_kernel_cmdline();

        // Propagate the kernel variables to internal variables
        // used by init as well as the current required properties.
        export_kernel_boot_props();
    }

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    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) {
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        char* path = argv[0];
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
        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.
    NOTICE("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon("/property_contexts");
    restorecon_recursive("/sys");
    //创建epoll句柄
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
    //设置子进程信号处理函数,如果子进程(Zygote进程)异常退出,init进程会调用该函数中设定的信号处理函数进行处理,防止init的子进程成为僵尸进程。
    //系统会在子进程暂停/终止时发出SIGCHLD信号通知父进程init,signal_handler_init()函数用来设置init进程接收SIGCHLD信号后的处理动作。
    signal_handler_init();
    //导入默认的环境变量
    property_load_boot_defaults();
    export_oem_lock_status();
    //启动属性服务
    start_property_service();

    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);

    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    //解析init.rc配置文件
    parser.ParseConfig("/init.rc");

    ActionManager& am = ActionManager::GetInstance();

    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    //在charger模式下略过mount文件系统的工作
    std::string bootmode = property_get("ro.bootmode");
    if (bootmode == "charger" || charging_mode_booting() ||
            strcmp(battchg_pause, BOARD_CHARGING_CMDLINE_VALUE) == 0) {
        am.QueueEventTrigger("charger");
    } else if (strncmp(bootmode.c_str(), "ffbm", 4) == 0) {
        NOTICE("Booting into ffbm mode\n");
        am.QueueEventTrigger("ffbm");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    while (true) {
        if (!waiting_for_exec) {
            am.ExecuteOneCommand();
            //重启死去的进程
            restart_processes();
        }

        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (am.HasMoreCommands()) {
            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;
}

2 Android Init Language脚本文件语法

    Android源码中会有很多的rc脚本文件用来对系统进行初始化配置,一般存放在:

~/LineageOS/system/core/rootdir
及
~/LineageOS/device/lge/hammerhead

    完整的init.rc脚本包含四个状态类别:

  • Actions-动作
  • Commands-命令
  • Services-服务
  • Options-选项

2.1 通用语法规则

  • 注释以"#"开头;
  • 关键字和参数以空格隔开,每个语句以行为单位;
  • C语言风格的反斜杠转义字符("")可以用来为参数添加空格;
  • 为了防止字符串中的空格把其分隔成多个部分,我们需要对其使用双引号;
  • Actions和Services暗示着一个新语句(section)的开始,这两个关键字后面跟着的commands或者options都属于这个新语句(section);
  • Actions和Services有唯一的名字,如果出现和已有动作或服务重名的,将会被当做错误忽略掉;

    init.rc文件是以块(section)为单位组织的,一个section可以包含多行。section分成两大类:一类称为“Action(行为)”,另一类称为“Service(服务)”。

    “行为”块以关键字“on”开始,表示一堆命令的集合,“服务”块以“service”开始,表示启动某个进程的方式和参数。

    “块”以关键字“on”或“service”开始,直到下一个“on”或“service”结束,中间所有行都属于这个“块”(空行或者注释都不具备分割作用)。下面是一部分init.rc文件示例:

on early-init
    # Set init and its forked children's oom_adj.
    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

on property:sys.boot_from_charger_mode=1
    class_stop charger
    trigger late-init
on nonencrypted
    class_start main
    class_start late_start
service installd /system/bin/installd
    class main
    socket installd stream 600 system system
on load_persist_props_action
    load_persist_props
    start logd
    start logd-reinit
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    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
    writepid /dev/cpuset/foreground/tasks

2.2 Actions

    Action是在某种条件下触发一系列的命令,通常有一个trigger。如:

on <trigger>
   <command>
   <command>

    Actionde 的关键字“on”后面的字符串为“trigger”(触发器),如上面代码中的“early-init”,trigger后面的内容是命令列表command,每一行都是一条命令,常见的command参见2.4节。

    Action其实就是一系列的Command集合。每个Action都有一个trigger,它决定本Action的执行时机。当一个符合Action触发条件的事件发生了,此Action会加入到执行队列的末尾(除非它已经在队列里了)。每一个Action都将依次从队列中取出,此Action的每个Command都将依次执行。

2.2.1 trigger

    Triggers是一个用于匹配某种事件类型的字符串,它将使对应的action执行

  1. boot是Init执行后第一个触发的Trigger(在/init.conf装载之后);
  2. =:这种形式的Trigger会在属性"name"设置为指定的“value”时被触发;
  3. device-added- :这种形式的Trigger会在一个设备文件增加时触发;
  4. device-removed- :这种形式的Trigger会在一个设备文件移除时触发;
  5. service-exited-:这种形式的Trigger会在一个指定的Service退出时触发;

2.2.2 command

    Commands是Action的命令列表中的命令,或者是Service的选项onrestart的参数命令。

  1. exec [ [ [ ]* ] ] – [ ]*:fork和启动一个程序,在程序完成启动之前,Init进程将会阻塞。command跟在“–”之后,在这之前我们可以指定像安全上下文、用户名和组名这样的参数信息;seclabel默认指定为“-”。
  2. export :设置全局环境变量为(所有在这命令执行之后运行的进程都将继承该环境变量)。
  3. ifup :启动网络接口。
  4. import :引入一个配置文件,扩展当前配置。
  5. hostname :设置主机名。
  6. chdir :设置进程当前的工作目录。
  7. chmod :设置文件或目录的访问权限。
  8. chown :设置文件或目录的所有者和组。
  9. chroot :设置进程的根目录。
  10. class_start :启动所有指定服务名称下的未运行服务。
  11. class_stop :停止所有指定服务名称下的已运行的服务。
  12. class_reset :停止服务,后续可以通过class_start启动。
  13. domainname :设置域名。
  14. insmod :安装一个驱动模块。
  15. mkdir [mode] [owner] [group]:新创建一个目录 ,可以指定访问权限、拥有者和组。如果没有指定,默认的访问权限是755,属于root用户和root组。
  16. mount [ ]*:在指定目录下挂载一个设备。可以是以mtd@name的格式指定的一个mtd块设备。包括“ro”、“rw”、“remount”、“noatime”等。
  17. restorecon :重新存储指定的文件到一个有file_contexts配置的安全上下文。不用指定目录,它们会被Init进程自动创建。
  18. setcon :设置当前进程的安全上下文为指定的串。主要用在early-init中去设置Init的安全上下文。
  19. setenforce 0|1:设置SELinux系统级的enforcing状态。0代表permissive,1代表enforcing。
  20. setkey:目前未使用。
  21. setprop :设置系统属性为值。
  22. setrlimit :设置的rlimit值。
  23. setsebool :设置SELinux的boolean型属性的值为。的值可以是“1|true|on"或者”0|false|off“。
  24. start :启动指定服务(如果此服务还未运行)。
  25. stop :停止指定服务(如果此服务还在运行中)。
  26. restart :重启指定名称的服务,先stop,再start。
  27. symlink :创建一个符号连接。
  28. sysclktz <mins_west_of_gmt>:设置系统的时钟基准(0代表格林尼治平均时(GMT)为准)。
  29. trigger 触发一个事件。用于将一个Action和另一个Action连在一起执行。
  30. wait []:等待指定路径的文件被创建出来,创建完成就停止等待,或者等到超时时间到达。如果未指定超时时间,缺省时间是5秒。
  31. write [<string]*:打开指定的文件,并写入一个或多个字符串。

2.3 Service

    Service是指的服务,如:

service <name> <pathname> [ <argument> ]*
   <option>
   <option>

    service后的的字符串为服务名称,service下面的行称为option(选项),常见的option参见2.5节

    无论是“Action”块还是“Service”块,它们的执行顺序都与.rc文件中的声明顺序无关,是否执行及执行顺序要由init进程在运行时决定

2.3.1 opinion

    Options是Service的修饰词,它们决定一个Service何时以及如何运行,主要包括下面选项:

  1. critical:表示这是一个关键的服务。如果该Service 4分钟内重新启动超过4次,系统将自动重启并进入recovery模式。
  2. disabled:表示服务不能通过start className(main/core等)启动。它必须以命令“start service_name”的形式显示指定名称启动。
  3. setenv :在Service启动时将环境变量“name”设置为“value”。
  4. socket [ []]:创建一个名为/dev/socket/的套接字,并把文件描述符传递给要启动的进程。的值是“dgram”或者是“stream”,User和group的值默认为0。
  5. user :在启动这个Service前设置Service的用户名,默认是root。如果进程没有相应的权限,将不能使用该命令。如果进程有root权限,可以在程序中设置你想要的uid。
  6. group []*:在启动这个Service前设置Service的服务。除了第一个组名,剩下的组名同城用于设置进程的附加组(通过setgroups())。默认是root。
  7. oneshot:Service退出后不再重启。
  8. class :给Service指定一个名字。所有同名字的服务可以同时启动和停止。如果不通过class选项指定一个名字,则默认是“default”。
  9. onrestart:当Service重启时,执行一条命令。
  10. writepid <file…>:将当前进程的进程号写到<file…>文件中。

2.4 语法总结

    通过这些语法规则,可以去源码目录的~/LineageOS/system/core/rootdir
    .rc文件遵循的这些语法功能其实主要还是给人看的,方便程序员通过.rc文件来控制Android系统启动初始化;对于机器来说,它会对*.rc文件进行解析,换另外的一个视角去读取*.rc文件。

3 init.rc中Service/Action解析

    在源码 ~/LineageOS/system/core/init/init.cpp中对init.rc进行解析的代码如下:

    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    //解析init.rc配置文件
    parser.ParseConfig("/init.rc");

    在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfig函数定义为:

bool Parser::ParseConfig(const std::string& path) {
    //判断传入path为目录的话执行:ParseConfigDir(path)
    if (is_dir(path.c_str())) {
        return ParseConfigDir(path);
    }
    //传入的为/init.rc,所以执行:ParseConfigFile(path)
    return ParseConfigFile(path);
}

    在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfigFile函数定义为:

bool Parser::ParseConfigFile(const std::string& path) {
    INFO("Parsing file %s...\n", path.c_str());
    //计时用
    Timer t;
    //将./init.rc文件内容逐个字符读入一个string类型的对象data中;
    std::string data;
    if (!read_file(path.c_str(), &data)) {
        return false;
    }
    //添加字符'\n'到字符串一定用push_back函数,切不可直接用"+"
    data.push_back('\n'); // TODO: fix parse_config.
    //调用ParseData函数对
    ParseData(path, data);
    //
    for (const auto& sp : section_parsers_) {
        sp.second->EndFile(path);
    }

    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();
    //记录下解析./init.rc所用时间
    NOTICE("(Parsing %s took %.2fs.)\n", path.c_str(), t.duration());
    return true;
}

    在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseData函数定义为:

void Parser::ParseData(const std::string& filename, const std::string& data) {
    //TODO: Use a parser with const input and remove this copy
    //将data内容副本存入data_copy中,并在结尾添加'\0';
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('\0');
    //初始化parse_state结构体,用于对已经字符串化的init.rc进行封装,其声明在parser.h中
    parse_state state;
    state.filename = filename.c_str();
    state.line = 0;
    state.ptr = &data_copy[0];
    state.nexttoken = 0;
    //SectionParser类声明在init_parser.h中
    SectionParser* section_parser = nullptr;
    std::vector<std::string> args;

    for (;;) {
        //next_token函数定义在parser.cpp中,用于解析state
        switch (next_token(&state)) {
         //next_token函数逐字符解析到state的最结尾'\0'时会返回T_EOF,结束解析;
        case T_EOF:
            if (section_parser) {
                section_parser->EndSection();
            }
            return;
        //next_token函数逐字符解析到state的'\n'字符时会返回T_NEWLINE;
        case T_NEWLINE:
            state.line++;
            if (args.empty()) {
                break;
            }
            if (section_parsers_.count(args[0])) {
                if (section_parser) {
                    section_parser->EndSection();
                }
                //根据此行首词args[0]是关键字service/on,此行的解析要创建Ation或Service新对象
                section_parser = section_parsers_[args[0]].get();
                std::string ret_err;
                //ParseSection函数主要用来搭建Service/Action的架子
                if (!section_parser->ParseSection(args, &ret_err)) {
                    parse_error(&state, "%s\n", ret_err.c_str());
                    section_parser = nullptr;
                }
                //根据此行首词args[0]不是关键字service/on,此行的解析用来填充上面创建的Ation或Service对象的子项
            } else if (section_parser) {
                std::string ret_err;
                //ParseLineSection函数主要用来解析Service/Action子项
                if (!section_parser->ParseLineSection(args, state.filename,
                                                      state.line, &ret_err)) {
                    parse_error(&state, "%s\n", ret_err.c_str());
                }
            }
            args.clear();
            break;
        //
        case T_TEXT:
            args.emplace_back(state.text);
            break;
        }
    }
}

    解析过程总体来讲就是根据参数创建出Service/Action对象,然后根据选项域的内容填充Service/Action对象,最后将Service/Action对象加入到vector类型的Services/Action链表中。

    解析service时,会用到两个函数,一个是ServiceParser::ParseSection函数主要用来搭建service的架子。另一个是ServiceParser::ParseLineSection用于解析子项。

    解析Action时,会用到两个函数,一个是ActionParser::ParseSection函数主要用来搭建service的架子。另一个是ActionParser::ParseLineSection用于解析子项。

提示:关于init进程解析init.rc的这部分的代码,从Android4.4-Android7.1是一直在变,以前使用纯C实现,现在完全是C++代码,但是原理是不变的!

4 init进程控制Action

    待补充…

5 init进程控制Service

    以init启动zygote为例,init.rc中利用import命令加载了init.zygote32.rc(因为我的是Nexus5手机),截取init.rc中一段代码:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
...省略n行...

    查看源码中:~/LineageOS/system/core/rootdir/init.zygote32.rc文件内容为:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    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
    writepid /dev/cpuset/foreground/tasks

    根据2.3.1节中对于opinion的介绍可知,此服务所在的class为main。在init.rc中可查看main是何时启动的呢?

...省略n行...
on nonencrypted
    # A/B update verifier that marks a successful boot.
    exec - root cache -- /system/bin/update_verifier nonencrypted
    class_start main
    class_start late_start
...省略n行...

    其中class_start main的含义就是启动所有classname 为 main 的Service,此时zygote服务也就被启动了。class_start命令对应的实际执行函数为do_class_start,查看其源代码在~/LineageOS/system/core/init/builtins.cpp中:

static int do_class_start(const std::vector<std::string>& args) {
    //ForEachServiceInClass将遍历Service链表,查找到classname为main的服务(zygote),执行StartIfNotDisabled函数
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });

    std::string prop_name = StringPrintf("class_start:%s", args[1].c_str());
    if (prop_name.length() < PROP_NAME_MAX) {
        ActionManager::GetInstance().QueueEventTrigger(prop_name);
    }
    return 0;
}

    StartIfNotDisabled函数代码在源码 ~/LineageOS/system/core/init/service.cpp中:

bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) {//检测相关service的opinion中是否设置了disabled选项,若没有设置则直接运行Start函数;
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}

    在此文件中查看Start函数:

bool Service::Start() {
    // Starting a service removes it from the disabled or reset state and
    // immediately takes it out of the restarting state if it was in there.
    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
    time_started_ = 0;

    // Running processes require no additional work --- if they're in the
    // process of exiting, we've ensured that they will immediately restart
    // on exit, unless they are ONESHOT.
    //如果Service已经运行了,则不启动;
    if (flags_ & SVC_RUNNING) {
        return false;
    }

    bool needs_console = (flags_ & SVC_CONSOLE);
    if (needs_console && !have_console) {
        ERROR("service '%s' requires console\n", name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }
    //判断所要启动的Service对应的可执行文件是否存在,否则不启动;
    struct stat sb;
    if (stat(args_[0].c_str(), &sb) == -1) {
        ERROR("cannot find '%s' (%s), disabling '%s'\n",
              args_[0].c_str(), strerror(errno), name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }

    std::string scon;
    if (!seclabel_.empty()) {
        scon = seclabel_;
    } else {
        char* mycon = nullptr;
        char* fcon = nullptr;

        INFO("computing context for service '%s'\n", args_[0].c_str());
        int rc = getcon(&mycon);
        if (rc < 0) {
            ERROR("could not get context while starting '%s'\n", name_.c_str());
            return false;
        }

        rc = getfilecon(args_[0].c_str(), &fcon);
        if (rc < 0) {
            ERROR("could not get context while starting '%s'\n", name_.c_str());
            free(mycon);
            return false;
        }

        char* ret_scon = nullptr;
        rc = security_compute_create(mycon, fcon, string_to_security_class("process"),
                                     &ret_scon);
        if (rc == 0) {
            scon = ret_scon;
            free(ret_scon);
        }
        if (rc == 0 && scon == mycon) {
            ERROR("Service %s does not have a SELinux domain defined.\n", name_.c_str());
            free(mycon);
            free(fcon);
            return false;
        }
        free(mycon);
        free(fcon);
        if (rc < 0) {
            ERROR("could not get context while starting '%s'\n", name_.c_str());
            return false;
        }
    }

    NOTICE("Starting service '%s'...\n", name_.c_str());
    //创建子进程
    pid_t pid = fork();
    if (pid == 0) {
        umask(077);

        for (const auto& ei : envvars_) {
            add_environment(ei.name.c_str(), ei.value.c_str());
        }

        for (const auto& si : sockets_) {
            int socket_type = ((si.type == "stream" ? SOCK_STREAM :
                                (si.type == "dgram" ? SOCK_DGRAM :
                                 SOCK_SEQPACKET)));
            const char* socketcon =
                !si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();
            //后面介绍create_socket函数的左右
            int s = create_socket(si.name.c_str(), socket_type, si.perm,
                                  si.uid, si.gid, socketcon);
            if (s >= 0) {
                //后面介绍PublishSocket函数的作用
                PublishSocket(si.name, s);
            }
        }

        std::string pid_str = StringPrintf("%d", getpid());
        for (const auto& file : writepid_files_) {
            if (!WriteStringToFile(pid_str, file)) {
                ERROR("couldn't write %s to %s: %s\n",
                      pid_str.c_str(), file.c_str(), strerror(errno));
            }
        }

        if (ioprio_class_ != IoSchedClass_NONE) {
            if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {
                ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
                      getpid(), ioprio_class_, ioprio_pri_, strerror(errno));
            }
        }

        if (needs_console) {
            setsid();
            OpenConsole();
        } else {
            ZapStdio();
        }

        setpgid(0, getpid());

        // As requested, set our gid, supplemental gids, and uid.
        if (gid_) {
            if (setgid(gid_) != 0) {
                ERROR("setgid failed: %s\n", strerror(errno));
                _exit(127);
            }
        }
        if (!supp_gids_.empty()) {
            if (setgroups(supp_gids_.size(), &supp_gids_[0]) != 0) {
                ERROR("setgroups failed: %s\n", strerror(errno));
                _exit(127);
            }
        }
        if (uid_) {
            if (setuid(uid_) != 0) {
                ERROR("setuid failed: %s\n", strerror(errno));
                _exit(127);
            }
        }
        if (!seclabel_.empty()) {
            if (setexeccon(seclabel_.c_str()) < 0) {
                ERROR("cannot setexeccon('%s'): %s\n",
                      seclabel_.c_str(), strerror(errno));
                _exit(127);
            }
        }

        std::vector<std::string> expanded_args;
        std::vector<char*> strs;
        expanded_args.resize(args_.size());
        strs.push_back(const_cast<char*>(args_[0].c_str()));
        for (std::size_t i = 1; i < args_.size(); ++i) {
            if (!expand_props(args_[i], &expanded_args[i])) {
                ERROR("%s: cannot expand '%s'\n", args_[0].c_str(), args_[i].c_str());
                _exit(127);
            }
            strs.push_back(const_cast<char*>(expanded_args[i].c_str()));
        }
        strs.push_back(nullptr);
        //调用execve函数,Service子进程启动,即启动/system/bin/app_process;
        if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
            ERROR("cannot execve('%s'): %s\n", strs[0], strerror(errno));
        }

        _exit(127);
    }

    if (pid < 0) {
        ERROR("failed to start '%s'\n", name_.c_str());
        pid_ = 0;
        return false;
    }

    time_started_ = gettime();
    pid_ = pid;
    flags_ |= SVC_RUNNING;

    if ((flags_ & SVC_EXEC) != 0) {
        INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
             pid_, uid_, gid_, supp_gids_.size(),
             !seclabel_.empty() ? seclabel_.c_str() : "default");
    }

    NotifyStateChange("running");
    return true;
}

    create_socket函数所在源码位置~/LineageOS/system/core/init/util.cpp:

/*
 * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
 * ("/dev/socket") as dictated in init.rc. This socket is inherited by the
 * daemon. We communicate the file descriptor's value via the environment
 * variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").
 */
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
                  gid_t gid, const char *socketcon)
{
    struct sockaddr_un addr;
    int fd, ret, savederrno;
    char *filecon;

    if (socketcon) {
        if (setsockcreatecon(socketcon) == -1) {
            ERROR("setsockcreatecon(\"%s\") failed: %s\n", socketcon, strerror(errno));
            return -1;
        }
    }
    //调用socket函数创建一个socket,使用文件描述符fd来描述此socket;
    fd = socket(PF_UNIX, type, 0);
    if (fd < 0) {
        ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
        return -1;
    }

    if (socketcon)
        setsockcreatecon(NULL);
    //为socket创建一个类型为AF_UNIX的socket地址addr;
    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
             name);

    ret = unlink(addr.sun_path);
    if (ret != 0 && errno != ENOENT) {
        ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
        goto out_close;
    }

    filecon = NULL;
    if (sehandle) {
        ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
        if (ret == 0)
            setfscreatecon(filecon);
    }

    ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
    savederrno = errno;

    setfscreatecon(NULL);
    freecon(filecon);

    if (ret) {
        ERROR("Failed to bind socket '%s': %s\n", name, strerror(savederrno));
        goto out_unlink;
    }
    //设置设备文件/dev/socket/zygote的用户id、用户组id、用户权限;
    ret = lchown(addr.sun_path, uid, gid);
    if (ret) {
        ERROR("Failed to lchown socket '%s': %s\n", addr.sun_path, strerror(errno));
        goto out_unlink;
    }
    ret = fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW);
    if (ret) {
        ERROR("Failed to fchmodat socket '%s': %s\n", addr.sun_path, strerror(errno));
        goto out_unlink;
    }

    INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
         addr.sun_path, perm, uid, gid);

    return fd;

out_unlink:
    unlink(addr.sun_path);
out_close:
    close(fd);
    return -1;
}

    PublishSocket函数所在源码位置~/LineageOS/system/core/init/service.cpp:

//参数fd是指向create_socket函数创建的socket的文件描述符;
void Service::PublishSocket(const std::string& name, int fd) const {
    //将宏ANDROID_SOCKET_ENV_PREFIX和参数name描述的字符串连接起来,保存在字符串key中;
    std::string key = StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s", name.c_str());
    std::string val = StringPrintf("%d", fd);
    add_environment(key.c_str(), val.c_str());

    /* make sure we don't close-on-exec */
    fcntl(fd, F_SETFD, 0);
}

6 属性服务

    init进程启动时会启动Android系统的属性服务,并在内存中开辟一块空间来存储这些属性。相关代码在系统源码:

    ...省略n行...
    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
        //初始化属性服务的配置
        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.
        process_kernel_dt();
        //设置内核变量
        process_kernel_cmdline();

        // Propagate the kernel variables to internal variables
        // used by init as well as the current required properties.
        //用属性值设置内核变量
        export_kernel_boot_props();
    }
    ...省略n行...
    //启动属性服务
    start_property_service();

6.1 init.rc中就用到的属性

    在分析init.rc时就发现在该脚本开头就有用到属性ro.hardware、ro.zygote:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc

其实在init.cpp中可发现,init进程启动了属性服务后,随即就进行了init.rc脚本文件的解析。

6.2 利用内核属性设置应用层配置文件

    内核变量可以由bootloader传入,其中init.cpp中process_kernel_cmdline函数就是用于获取传入内核变量的值,其源码为:

static void process_kernel_cmdline() {
    // Don't expose the raw commandline to unprivileged processes.
    chmod("/proc/cmdline", 0440);

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

    init.cpp随后调用了export_kernel_boot_props函数,其就是利用内核属性来设置应用层配置文件的属性,其源码为:

static void export_kernel_boot_props() {
    //初始化该结构体类型的数组prop_map[],并赋初始值为unknown;
    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } prop_map[] = {
#ifndef IGNORE_RO_BOOT_SERIALNO
        { "ro.boot.serialno",   "ro.serialno",   "", },
#endif
        { "ro.boot.mode",       "ro.bootmode",   "unknown", },
        { "ro.boot.baseband",   "ro.baseband",   "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
        { "ro.boot.hardware",   "ro.hardware",   "unknown", },
#ifndef IGNORE_RO_BOOT_REVISION
        { "ro.boot.revision",   "ro.revision",   "0", },
#endif
    };
    //通过property_set来获取proc_map中src_prop的属性值,并赋给相应的dst_prop;
    for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
        std::string value = property_get(prop_map[i].src_prop);
        property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
    }
}

    其中,property_get、property_set函数定义在源码~/LineageOS/system/core/init/property_service.cpp中:

std::string property_get(const char* name) {
    char value[PROP_VALUE_MAX] = {0};
    __system_property_get(name, value);
    return value;
}

    property_get函数最终调用的是bionic库函数中的__system_property_get函数,在源码目录 ~/LineageOS/bionic/libc/bionic/system_properties.cpp中:

int __system_property_get(const char *name, char *value)
{
    const prop_info *pi = __system_property_find(name);

    if (pi != 0) {
        return __system_property_read(pi, 0, value);
    } else {
        value[0] = 0;
        return 0;
    }
}

    在源码文件~/LineageOS/bionic/libc/include/sys/_system_properties.hz中定义了几个宏:

#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD     "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
#define PROP_PATH_FACTORY          "/factory/factory.prop"

    这些宏在~/LineageOS/system/core/init/property_service.cpp的load_properties_from_file函数中用得到,意思是从这些文件中获取属性值。

6.3 初始化属性服务

    初始化属性服务的配置函数property_init,定义在 ~/LineageOS/system/core/init/property_service.cpp中:

void property_init() {
    //初始化属性内存区域
    if (__system_property_area_init()) {
        ERROR("Failed to initialize property area\n");
        exit(1);
    }
}

    启动属性服务的函数start_property_service,定义在~/LineageOS/system/core/init/property_service.cpp中:

void start_property_service() {
    //创建非阻塞的socket
    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);
    }
    //对property_set_fd进行监听,此socket就成为server,即属性服务;参数8代表的意思是该属性服务最多可以同时为8个用户设置属性的需求提供服务;
    listen(property_set_fd, 8);
    //将property_set_fd放入epoll中,利用epoll来监听property_set_fd,当property_set_fd接收到数据时会调用handle_property_set_fd函数对其处理;
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

    在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是数组数据结构轮询来处理的,轮询的fd数目越多,自然耗时越多。而epoll用于保存事件的数据类型为红黑树,查找速度快。

6.4 处理客户端请求

    属性服务接收到客户端的请求后会调用handle_property_set_fd函数进行处理,定义在~/LineageOS/system/core/init/property_service.cpp中:

static void handle_property_set_fd()
{
    prop_msg msg;
    int s;
    int r;
    struct ucred cr;
    struct sockaddr_un addr;
    socklen_t addr_size = sizeof(addr);
    socklen_t cr_size = sizeof(cr);
    char * source_ctx = NULL;
    struct pollfd ufds[1];
    const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */
    int nr;

    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }

    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to receive socket options\n");
        return;
    }

    ufds[0].fd = s;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;
    nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
    if (nr == 0) {
        ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
        close(s);
        return;
    } else if (nr < 0) {
        ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
        close(s);
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
              r, sizeof(prop_msg), strerror(errno));
        close(s);
        return;
    }

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);

        if(memcmp(msg.name,"ctl.",4) == 0) {
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else {
            if (check_mac_perms(msg.name, source_ctx, &cr)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }
}

7 总结

    init进程的工作内容很多,主要的就三部分:

  • 创建并挂载启动所需的文件系统及目录;
  • 初始化、启动属性服务;
  • 解析init.rc脚本文件并按照脚本文件中的指令启动各系统服务,如Zygote、Linux层守护进程、ServiceManager等;

Enjoy it!!

    下一篇分析:[日更-2019.4.20、21] cm-14.1 Android系统启动过程分析(二)-Zygote进程启动过程

发布了48 篇原创文章 · 获赞 5 · 访问量 7810

猜你喜欢

转载自blog.csdn.net/Xiaoma_Pedro/article/details/103892754