Android6.0 init 深入分析

之前写过一篇关于android5.0 init的介绍,这篇博客是介绍android6.0init,之前有的代码介绍不详细。而且分析 解析init.rc那块代码也没有结合init.rc介绍。

一、 main函数的一些准备工作

下面我们分析下源码:

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

由于ueventd watchdogd是公用代码,所以启动的时候根据文件名来判断是哪个进程,继续分析:

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

这块代码主要添加环境变量,以及挂载各种文件系统。

    open_devnull_stdio();
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);//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.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//启动的时候创建一个.booting文件

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

        // Propogate the kernel variables to internal variables
        // used by init as well as the current required properties.
        export_kernel_boot_props();//设置一些属性
    }

这里我们有没有注意到is_first_stage这个变量,我们再来往下看。如果是is_first_stage会再执行execv函数,重新启动init。这个时候参数是"--second-stage"

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

这个时候再启动,也就是if_first_stage为false。这个时候参数有"--second-stage"了

 

bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

 

 

 

 

 

我们再看上面函数先是open_devnull_stdio函数,这个函数就是把标准输入,输出,错误输出重定义到空设备上。然后创建一个 .booting文件代表系统在启动,做了一些属性的初始化,以及一些boot相关的系统属性设置获取等。我们先看下open_devnull_stdio代码:

 

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);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2) {
        close(fd);
    }
}

property_init()函数主要是属性的初始化,这个我们在之前分析属性系统的那篇博客分析过了。
我们再来看process_kernel_dt函数

 

 

 

 

 

 

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);
    if (!dt_file.compare("android,firmware")) {//compatible文件内容是否是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 这个目录下,先看compatible文件内容是否是android,firmware。然后这个目录下每个文件名作为属性,文件里面的内容作为属性值。这里话就是ro.boot.hareware ro.boot.name这两个属性值。

 

root@lte26007:/proc/device-tree/firmware/android # ls
compatible
hardware
name

继续看process_kernel_cmdline函数

 

 

 

 

 

 

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

import_kernel_cmdline函数就是读取proc/cmdline中的内容,然后调用import_kernel_nv函数设置系统属性

void import_kernel_cmdline(bool in_qemu, std::function<void(char*,bool)> import_kernel_nv)
{
    char cmdline[2048];
    char *ptr;
    int fd;

    fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC);
    if (fd >= 0) {
        int n = read(fd, cmdline, sizeof(cmdline) - 1);
        if (n < 0) n = 0;

        /* get rid of trailing newline, it happens */
        if (n > 0 && cmdline[n-1] == '\n') n--;

        cmdline[n] = 0;
        close(fd);
    } else {
        cmdline[0] = 0;
    }

    ptr = cmdline;
    while (ptr && *ptr) {
        char *x = strchr(ptr, ' ');
        if (x != 0) *x++ = 0;
        import_kernel_nv(ptr, in_qemu);
        ptr = x;
    }
}

在import_kernel_nv函数中设置系统属性,但是一定要有androidboot这样的关键字眼才会设置ro.boot这样的属性。这块在我们的设备cmdline中没有这样的字眼,也就不会设置这些属性。

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

再来看export_kernel_boot_props这个函数,它也就是设置一些属性,设置ro属性根据之前ro.boot这类的属性值,如果没有设置成unknown,像之前我们有ro.boot.hardware, 那我们就可以设置root.hardware这样的属性。

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

下面这块都是selinux相关的,我们就不分析了。

    // 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.
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

然后创建了一个epoll的fd

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }

继续分析,signal_handler_init函数主要是当子进程被kill之后,会在父进程接受一个信号。处理这个信号的时候往sockpair一端写数据,而另一端的fd是加入的epoll中。这块我们后面会专门其一节讲解。而property_load_boot_defaults就是解析根目录的default.prop中的属性,然后设置到属性中去。start_prperty_service就是把接受属性的socket的fd加入epoll中,也定义了处理函数,属性之前博客专门分析过了。

    signal_handler_init();

    property_load_boot_defaults();
    start_property_service();

看看signal_handler_init函数就是处理子进程kill时的情况。

static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
    }
}

void signal_handler_init() {
    // 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);

    reap_any_outstanding_children();

    register_epoll_handler(signal_read_fd, handle_signal);
}


二、解析init.rc

下面我们开始分析解析init.rc并且结合init.rc一起看

init.rc的语言我们可以看这篇博客,主要是init.rc主要有Actions和Service两种,具体看这篇博客http://blog.csdn.net/kc58236582/article/details/52042331

我们通过init_parse_config_file函数来解析init.rc,先把文件数据读取到data中,然后调用parse_config来解析数据。

int init_parse_config_file(const char* path) {
    INFO("Parsing %s...\n", path);
    Timer t;
    std::string data;
    if (!read_file(path, &data)) {
        return -1;
    }

    data.push_back('\n'); // TODO: fix parse_config.
    parse_config(path, data);
    dump_parser_state();

    NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
    return 0;
}

我们先来看看dump_parser_state函数,当解析完之后我们可以在这个函数中打印所有的service和action。

void dump_parser_state() {
    if (false) {
 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值