init进程的执行过程分析

https://jsonchao.github.io/2019/02/18/Android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E4%B9%8Binit%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8/

--------------------正义的分割线--

Init进程,它是Linux内核启动之后运行的第一个进程。它的进程号是1,并且生命周期贯穿整个linux 内核运行的始终。

linux中所有其它的进程的共同祖先均为init进程,可以通过“adb shell ps | grep init”查看进程号。

Android init进程的入口文件在system/core/init/init.cpp中,由于init是命令行程序,所以分析init.cpp首先应从main函数开始:

时序图:


main()

system/core/init/init.cpp

1 "ueventd"和"watchdogd"执行的是另外的入口

2 挂载文件系统

3 屏蔽标准的输入输出/初始化内核log系统,初始化log系统。

4 selinux初始化

5 重新设置属性

6 处理子进程kill时的情况

7 加载default.prop中的属性

8 解析init.rc


int main(int argc, char** argv) {

    // (1) "ueventd"和"watchdogd"执行的是另外的入口

    if (!strcmp(basename(argv[0]), "ueventd")) {

        return ueventd_main(argc, argv);

    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {

        return watchdogd_main(argc, argv);

    }

    /* umask是Linux函数,用来控制权限。文件的默认权限是644,目录是755。umask(0)表示赋予文件和目录所有的默认权限,即不去除任何权限。*/

    // Clear the umask.

    umask(0);

    // 添加PATH=_PATH_DEFPATH _PATH_DEFPATH="/usr/bin:/bin"

    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.

    // (2) 挂载文件系统

    if (is_first_stage) {

        // 挂载tmpfs文件系统

        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");

        mkdir("/dev/pts", 0755);

        mkdir("/dev/socket", 0755);

        // 挂载devpts文件系统

        mount("devpts", "/dev/pts", "devpts", 0, NULL);

        #define MAKE_STR(x) __STRING(x)

        // 挂载proc文件系统

        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));

        // 挂载sysfs文件系统

        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.

    // (3) 屏蔽标准的输入输出/初始化内核log系统,初始化log系统。

    open_devnull_stdio();

    klog_init(); // 创建/dev/kmsg,保存内核log。

    klog_set_level(KLOG_NOTICE_LEVEL); // 设置log级别为5

    // 首次启动is_first_stage=first stage,再次启动is_first_stage=second stage。

    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.

        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.

    // (4) selinux初始化(待探究)

    /* selinux有两种工作模式:

    1、"permissive",所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志

    2、"enforcing",所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式

    adb shell getenforce 查看selinux模式

    adb shell setenforce 0 命令进入permissive模式

    adb shell setenforce 1 命令进入Enforcing模式 */

    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.

    // (5) 重新设置属性

    if (is_first_stage) {

        if (restorecon("/init") == -1) { // restorecon命令用来恢复SELinux文件属性

来自: http://man.linuxde.net/restorecon

            ERROR("restorecon failed: %s\n", strerror(errno));

            security_failure();

        }

        char* path = argv[0];

        char* args[] = { path, const_cast("--second-stage"), nullptr }; //设置参数--second-stage

        // 执行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.

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

    }

    // (6) signal_handler_init函数就是处理子进程kill时的情况

    signal_handler_init();

    // (7) 加载default.prop中的属性

    property_load_boot_defaults();

    // 读取"ro.oem_unlock_supported"属性值

    export_oem_lock_status();

    // start_property_service函数创建了socket,然后监听,并且调用register_epoll_handler函数把socket的fd放入了epoll中。

    start_property_service();

    const BuiltinFunctionMap function_map;

    Action::set_function_map(&function_map);

    // (8) 解析init.rc

    Parser& parser = Parser::GetInstance();

    parser.AddSectionParser("service",std::make_unique());

    parser.AddSectionParser("on", std::make_unique());

    parser.AddSectionParser("import", std::make_unique());

    parser.ParseConfig("/init.rc");

    // ...

    return 0;

}


open_devnull_stdio()

Util.cpp

void open_devnull_stdio(void)

{

    // Try to avoid the mknod() call if we can. Since SELinux makes

    // a /dev/null replacement available for free, let's use it.

    int fd = open("/sys/fs/selinux/null", O_RDWR);

    if (fd == -1) {

        // OOPS, /sys/fs/selinux/null isn't available, likely because

        // /sys/fs/selinux isn't mounted. Fall back to mknod.

        static const char *name = "/dev/__null__";

        if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {

            fd = open(name, O_RDWR);

            unlink(name);

        }

        if (fd == -1) {

            exit(1);

        }

    }

    dup2(fd, 0); // 复制文件描述符fd到0(标准输入)

    dup2(fd, 1); // 复制文件描述符fd到1(标准输出)

    dup2(fd, 2); // 复制文件描述符fd到2(错误输出)

    if (fd > 2) {

        close(fd);

    }

}

这个函数调用dup函数把标准输入,输出,错误输出都重定位到/dev/null,如果需要在后面的程序中看到打印的话需要屏蔽这个函数。


property_init()

property_service.cpp

void property_init() {

    if (__system_property_area_init()) {

        ERROR("Failed to initialize property area\n");

        exit(1);

    }

}

bionic\libc\bionic\System_properties.cpp

int __system_property_area_init()

{

    free_and_unmap_contexts();

    mkdir(property_filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);

    if (!initialize_properties()) {

        return -1;

    }

    bool open_failed = false;

    bool fsetxattr_failed = false;

    list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {

        if (!l->open(true, &fsetxattr_failed)) {

            open_failed = true;

        }

    });

    if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {

        free_and_unmap_contexts();

        return -1;

    }

    initialized = true;

    return fsetxattr_failed ? -2 : 0;

}

signal_handler_init()

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

system/core/init/Singal_handler.cpp

void signal_handler_init() {

    // // 在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况

    // Create a signalling mechanism for SIGCHLD.

    int s[2];

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

    act.sa_handler = SIGCHLD_handler;

    act.sa_flags = SA_NOCLDSTOP;

    sigaction(SIGCHLD, &act, 0);

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();

    register_epoll_handler(signal_read_fd, handle_signal);

}


Android init.rc文件解析过程详解

一、init.rc文件结构介绍

init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字即每一行的第一列)来区分,这三个关键字是on、service、import。

on类型的section表示一系列命令的组合, 例如:

on init

 export PATH /sbin:/system/sbin:/system/bin

 export ANDROID_ROOT /system

 export ANDROID_DATA /data

这样一个section包含了三个export命令,命令的执行是以section为单位的,所以这三个命令是一起执行的,不会单独执行, 那什么时候执行呢? 这是由init.c的main()所决定的,main()里在某个时间会调用

action_for_each_trigger("init", action_add_queue_tail);

这就把on init开始的这样一个section里的所有命令加入到一个执行队列,在未来的某个时候会顺序执行队列里的命令,所以调用action_for_each_trigger的先后决定了命令执行的先后。


service类型的section表示一个可执行程序,例如:

service surfaceflinger /system/bin/surfaceflinger

 class main

 user system

 group graphics drmrpc

 onrestart restart zygote

surfaceflinger作为一个名字标识了这个service, /system/bin/surfaceflinger表示可执行文件的位置, class、user、group、onrestart这些关键字所对应的行都被称为options, options是用来描述的service一些特点,不同的service有着不同的options。

service类型的section标识了一个service(或者说可执行程序), 那这个service什么时候被执行呢?是在class_start这个命令被执行的时候,class_start命令行总是存在于某个on类型的section中,“class_start core”这样一条命令被执行,就会启动类型为core的所有service。

所以可以看出android的启动过程主要就是on类型的section被执行的过程。

import类型的section表示引入另外一个.rc文件,例如:


import init.test.rc


相当包含另外一些section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件。

二、init.rc文件解析过程

 我们已经知道init.rc的结构,应该可以想到解析init.rc的过程就是识别一个个section的过程,将各个section的信息保存下来,然后在init.c的main()中去执行一个个命令。 android采用双向链表(关于双向链表详解见本文第三部分)来存储section的信息,解析完成之后,会得到三个双向链表action_list、service_list、import_list来分别存储三种section的信息上。


1、init.c中调用init_parse_config_file(“/init.rc”), 代码如下:

int init_parse_config_file(const char *fn)

{

 char *data;

 data = read_file(fn, 0); //read_file()调用open\lseek\read 将init.rc读出来

 if (!data) return -1;


 parse_config(fn, data);  //调用parse_config开始解析

 DUMP();

 return 0;

}

2、parse_config()代码如下:

static void parse_config(const char *fn, char *s)

{

 struct parse_state state;

 struct listnode import_list;

 struct listnode *node;

 char *args[INIT_PARSER_MAXARGS];

 int nargs;


 nargs = 0;

 state.filename = fn;

 state.line = 0;

 state.ptr = s;

 state.nexttoken = 0;

 state.parse_line = parse_line_no_op;


 list_init(&import_list);

 state.priv = &import_list;


 for (;;) {

 switch (next_token(&state)) { //next_token()根据从state.ptr开始遍历

 case T_EOF:  //遍历到文件结尾,然后goto解析import的.rc文件

 state.parse_line(&state, 0, 0);

 goto parser_done;

 case T_NEWLINE: //到了一行结束

 state.line++;

 if (nargs) {

 int kw = lookup_keyword(args[0]); //找到这一行的关键字

 if (kw_is(kw, SECTION)) {  //如果这是一个section的第一行                                          

 state.parse_line(&state, 0, 0);

 parse_new_section(&state, kw, nargs, args);

 } else { //如果这不是一个section的第一行

 state.parse_line(&state, nargs, args);

 }

 nargs = 0;

 }

 break;

 case T_TEXT:  //遇到普通字符

 if (nargs < INIT_PARSER_MAXARGS) {

 args[nargs++] = state.text;

 }

 break;

 }

 }

parser_done:

 list_for_each(node, &import_list) {

 struct import *import = node_to_item(node, struct import, list);

 int ret;


 INFO("importing '%s'", import->filename);

 ret = init_parse_config_file(import->filename);

 if (ret)

 ERROR("could not import file '%s' from '%s'\n",

 import->filename, fn);

 }

}

next_token() 解析完init.rc中一行之后,会返回T_NEWLINE,这时调用lookup_keyword函数来找出这一行的关键字, lookup_keyword返回的是一个整型值,对应keyword_info[]数组的下标,keyword_info[]存放的是keyword_info结构体类型的数据,

struct {

 const char *name; //关键字的名称

 int (*func)(int nargs, char **args); //对应的处理函数

 unsigned char nargs; //参数个数

 unsigned char flags; //flag标识关键字的类型,

 包括COMMAND、OPTION、SECTION

}  keyword_info

因此keyword_info[]中存放的是所有关键字的信息,每一项对应一个关键字。


根据每一项的flags就可以判断出关键字的类型,如新的一行是SECTION,就调用parse_new_section()来解析这一行, 如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解析(state.parseline所对应的函数会根据section类型的不同而不同),在parse_new_section()中进行动态设置。

三种类型的section: service、on、import,  service对应的state.parseline为parse_line_service,

on对应的state.parseline为parse_line_action, import section中只有一行所以没有对应的state.parseline。

你可能感兴趣的:(init进程的执行过程分析)