图片 1

深入理解AIL语言及init.rc文件

面向新的一年,我们可能启动了许多有意义的计划,在这个有着特殊意义的日子里,让我们来一起学习一下Android系统是如何启动的。

init.rc文件由系统第一个启动的init程序进行解析.它由”Android Init
Language”语言编写而成.init.rc文件可以在你android设备根目录下找到.还记得我们上次编译的Android源码么?如果你已经编译过源码了,那么可以在out/target/generic/root/目录下找到该文件.

Android N平台

init进程与init.rc

init进程是一切的开始,在Android系统中,所有进程的进程号都是不确定的,唯独init进程的进程号一定是1。因为这个进程是系统起来的第一个进程。并且,init进程掌控了整个系统的启动流程。

我们知道,Android可能运行在各种不同的平台,不同的设备上。因此,启动的逻辑是不尽相同的。
为了适应各种平台和设备的需求,init进程的初始化工作通过init.rc配置文件来管理。init.rc以Android
Init Language作为语法,下文我们简称Android Init
Language为init语言。配置文件的主入口文件是/init.rc,这个文件会通过import关键字引入其他的配置文件。在这里,我们统称这些文件为init.rc

/init.rc可能import以下路径中的.rc文件:

  • /init.${ro.hardware}.rc 硬件厂商提供的主配置文件
  • /system/etc/init/ 核心系统模块的配置文件
  • /vendor/etc/init/ SoC厂商提供的配置文件
  • /odm/etc/init/ 设备制造商提供的配置文件

要想读懂init.rc文件,首先要掌握Android Init
Language语言,即AIL.在/system/core/init/下有一份readme.txt文件,为我们详细介绍了有关AIL的知识.我们下面的学习同样是借助了该文档来的.

0 init进程的主要职责

  • init如何创建zygote。
  • init的属性服务是如何工作的。

init语法说明

init语言,以换行为语句分隔,以空格来为符号分隔,以“#“为注释开始。配置文件中支持五种类型的表达式:

  • Action: 包含了一系列的Command
  • Command: init语言中的命令
  • Service: init进程启动的服务
  • Option: 对于服务的配置选项
  • Import: 引入其他配置文件

这其中,Action和Service需要保证名称唯一。

AIL语言非常简单,主要包括两部分:结构语法及注释语法.下面我们就这两点进行说明

1 init.cpp分析


涉及源码位置:
aosp/system/core/init/init.cpp
aosp/system/core/rootdir/init.rc
aosp/system/core/init/property_service.cpp


Action与Command

Action表达式的语法如下:

on <trigger> [&& <trigger>]*
   <command>
   <command>
   <command>

这里的Trigger是Action执行的触发器,当触发器条件满足时,command会被执行。触发器有两类:

  1. 事件触发器:
    事件可以由”trigger”命令发出,也可以是init进程通过QueueEventTrigger()函数发出
  2. 属性触发器: 当指定的属性满足时触发

一个Action可以有多个属性触发器,但是只能包含一个事件触发器。下面是一些例子:

  • on boot && property:a=b 在”boot”事件发生时,并且属性a的值是b时触发
  • on property:a=b && property:c=d 在属性a的值是b并且属性c的值是d时触发

Action中的Command是init语言定义的命令,所有支持的命令如下表所示:

Command 参数格式 说明
bootchart_init 启动bootchart
chmod octal-mode path 改变文件的访问权限
chown owner group path 改变文件的拥有者和组
class_start serviceclass 启动指定类别的服务
class_stop serviceclass 停止并disable指定类别的服务
class_reset serviceclass 停止指定类别的服务,但是不disable它们
copy src dst 拷贝文件
domainname name 设置域名
enable servicename enable一个被disable的服务
exec [seclabel[user[group]]] – command [argument]* fork一个子进程来执行指定的命令
export name value 导出环境变量
hostname name 设置host名称
ifup iterface 使网卡在线
insmod path 安装指定路径的模块
load_all_props 从/system, /vendor等路径载入属性
load_persist_props 载入持久化的属性
loglevel level 设置内核的日志级别
mkdir path [mode] [owner] [group] 创建目录
mount_all fstab [ path ]* [–option] 挂载文件系统并且导入指定的.rc文件
mount type device dir [ flag ]* [options] 挂载一个文件系统
powerctl 内部实现使用
restart service 重启服务
restorecon path [ path ]* 设定文件的安全上下文
restorecon_recursive path [ path ]* restorecon的递归版本
rm path 对于指定路径调用unlink(2)
rmdir path 删除文件夹
setprop name value 设置属性值
setrlimit resource cur max 指定资源的rlimit
start service 启动服务
stop service 停止服务
swapon_all fstab 在指定文件上调用fs_mgr_swapon_all
symlink target path 创建符号链接
sysclktz mins_west_of_gmt 指定系统时钟基准
trigger event 触发一个事件
umount path unmount指定的文件系统
verity_load_state 内部实现使用
verity_update_state mount_point 内部实现使用
wait path [ timeout ] 等待某个文件存在直到超时,若存在则直接返回
write path content 写入内容到指定文件

AIL语言包含主要包含五种结构语法:

1.1从init进程的入口函数main()开始分析

init进程的main()函数会执行两次,分别是第一阶段和第二阶段,main函数会进入两次,只是两次进去执行的代码不一样

int main(int argc, char** argv) {
    //由于ueventd watchdogd是公用代码,所以启动的时候根据文件名来判断是哪个进程
    //和ueventd守护进程相关
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    //和watchdogd守护进程相关
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }

    // Clear the umask.
    umask(0);
    //添加环境变量 
    add_environment("PATH", _PATH_DEFPATH);

    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    //创建文件夹,挂载设备,和linux相关
    // 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.
    //重定向标准输入/输出/错误输出到/dev/_null_ 
    open_devnull_stdio();
    //对klog进行初始化,设置klog level为NOTICE,所以可以将NOTICE级别的log输出,而INFO级别的log就打印不出来
    //<http://blog.csdn.net/fu_kevin0606/article/details/53339001>
    //初始化klog
    klog_init();
    //设置klog的级别为NOTICE
    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.
        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();
    }
    //初始化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: %sn", strerror(errno));
            security_failure();
        }
        char* path = argv[0];
        //设置第二阶段的参数
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
        //当init是第一阶段,要通过execv重启init进程,进入init的第二阶段
        if (execv(path, args) == -1) {
            ERROR("execv("%s") failed: %sn", 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_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %sn", strerror(errno));
        exit(1);
    }

    signal_handler_init();

    property_load_boot_defaults();
    export_oem_lock_status();
    //启动属性服务
    start_property_service();
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
    //将`service`,`on`,`import`分为3个section
    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(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    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.
    std::string bootmode = property_get("ro.bootmode");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } 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: %sn", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

    return 0;
}

main函数里涉及不少东西,只是把当前知道的注释了一下,以后补充,这里关注一下,属性服务的启动,以及对init.rc文件的解析.

Service与Option

Service是init进程启动的可执行程序。服务可以选择在自己退出之后,由init将其重启。

Service表达式的语法如下:

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

Option是对服务的修饰,它们影响着init进程如何以及何时启动服务。所有支持的option如下表所示:

option 参数格式 说明
critical 标识为系统关键服务,该服务若退出多次将导致系统重启到recovery模式
disabled 不会随着类别自动启动,必须明确start
setenv name value 为启动的进程设置环境变量
socket name type perm [user [group [seclabel]]] 创建Unix Domain Socket
user username 在执行服务之前切换用户
group groupname [ groupname]* 在执行执行之前切换组
seclabel seclabel 在执行服务之前切换seclabel
oneshot 一次性服务,死亡之后不用重启
class name 指定服务的类别
onrestart 当服务重启时执行指定命令
writepid file… 写入子进程的pid到指定文件
  1. Actions
  2. Services
  3. Options
  4. Commands
  5. Imports

1.2 属性服务

Android中有很多属性,是通过属性服务(property
service)来管理它们的.接着来分析属性服务的代码,从上面的init.cpp的main函数中涉及属性服务的代码有

    property_init();
    start_property_service();

从property_init()开始分析,该方法的主要工作是初始化属性服务配置.位置在aosp/system/core/init/property_service.cpp

void property_init() {
    if (property_area_initialized) {
        return;
    }

    property_area_initialized = true;
   //__system_property_area_init()函数是用来初始化属性内存区域
    if (__system_property_area_init()) {
        return;
    }

    pa_workspace.size = 0;
    pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (pa_workspace.fd == -1) {
        ERROR("Failed to open %s: %sn", PROP_FILENAME, strerror(errno));
        return;
    }
}

接下来查看start_property_service函数的具体代码:

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: %sn", strerror(errno));
        exit(1);
    }
    //使用listen函数对之前创建的socket进行监听
    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

listen(property_set_fd, 8);中的8指属性服务最多可以同时为8个试图设置属性的用户提供服务.property_set_fd代表监听
的端口(socket),这样属性服务就建立了.register_epoll_handler(property_set_fd, handle_property_set_fd)property_set_fd
放入了epoll句柄中,用epoll来监听property_set_fd:当property_set_fd中有数据到来时,init进程将用handle_property_set_fd
函数进行处理。(网上资料说:在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。
因为内核中的select实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多,epoll还没有研究过,抽时间学习一下).
当有property_set_fd这个socket有数据来时,就会产生调用到handle_property_set_fd方法,接着分析该方法:

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 optionsn");
        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: %sn", 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: %sn",
              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)) {
#ifdef MTK_INIT
                //INFO("[PropSet]: pid:%u uid:%u gid:%u %s %sn", cr.pid, cr.uid, cr.gid, msg.name, msg.value);
#endif
                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:%dn",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else {
            //check_perms:检测设置系统属性的权限,允许返回1,否则返回0
            if (check_perms(msg.name, source_ctx)) {
#ifdef MTK_INIT
                //INFO("[PropSet]: pid:%u uid:%u gid:%u set %s=%sn", cr.pid, cr.uid, cr.gid, msg.name, msg.value);
                if(strcmp(msg.name, ANDROID_RB_PROPERTY) == 0) {
                    INFO("pid %d set %s=%sn", cr.pid, msg.name, msg.value);
                    reboot_pid(cr.pid);
                }
#endif
                //设置系统属性
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%sn",
                      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;
    }
}

接着看property_set((char*) msg.name, (char*) msg.value)的具体实现:

int property_set(const char* name, const char* value) {
    int rc = property_set_impl(name, value);
    if (rc == -1) {
        ERROR("property_set("%s", "%s") failedn", name, value);
    }
    return rc;
}

看来实现设置的活交给了property_set_impl(name, value):

static int property_set_impl(const char* name, const char* value) {
    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);
    //判断属性名的合法性
    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) return -1;
    //如果属性的名称等于“selinux.reload_policy”,并且前面给它设置的值等于1,那么就表示要重新加载SEAndroid策略
    if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
        //加载SEAndroid策略
        if (selinux_reload_policy() != 0) {
            ERROR("Failed to reload policyn");
        }
    } else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
        if (restorecon_recursive(value) != 0) {
            ERROR("Failed to restorecon_recursive %sn", value);
        }
    }
    //查找名称为name的属性,如果存在的话,那么就会得到一个类型为prop_info的结构体pi,否则返回Null
    prop_info* pi = (prop_info*) __system_property_find(name);

    if(pi != 0) {//属性如果存在
        /* ro.* properties may NEVER be modified once set */
        //如果属性是ro.开头,不能修改,直接返回.
        if(!strncmp(name, "ro.", 3)) {
           return -1;
        }
    //属性可以修改,进行修改
        __system_property_update(pi, value, valuelen);
    } else {//属性不存在
        //属性不存在,添加该属性,在属性内存区域的属性值列表pa_info_array的最后增加一项
        int rc = __system_property_add(name, namelen, value, valuelen);
        if (rc < 0) {
            return rc;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    //接着处理net.开头的属性,
    //如果属性的名称是以“net.”开头,但是又不等于“net.change”(net.change是一个特殊的属性,记录网络属性是否发生变化),那么就将名称为“net.change”的属性设置为name,表示网络属性发生了变化
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
           return 0;
        }
       /*
        * The 'net.change' property is a special property used track when any
        * 'net.*' property name is updated. It is _ONLY_ updated here. Its value
        * contains the last updated 'net.*' property.
        */
        //设置`net.change`属性
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {//对`persist.`属性进行操作,该属性应该是持久化储存到文件
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
    //调用函数write_persistent_property执行持久化操作,以便系统下次启动后,可以将该属性的初始值设置为系统上次关闭时的值
        write_persistent_property(name, value);
    }
    //发送一个属性改变的通知,以便init进程可以执行在启动脚本init.rc中配置的操作
    property_changed(name, value);
    return 0;
}

property_set_impl对以ro、net和persist开头的属性进行不同的处理,给张来自罗升阳blog的一张图,帮助对android属性服务有个整体上的认识(Android属性的实现框架):

图片 1

Android属性的实现框架

Import

import是一个关键字,并不是一个命令。可以在.rc文件中通过这个关键字来加载其他的.rc文件。它的语法很简单:

import path

path可以是另外一个.rc文件,也可以是一个文件夹。如果是文件夹,那么这个文件夹下面的所有文件都会被导入,但是它不会循环加载子目录中的文件。

需要注意,AIL采用是面向行的代码风格,即用换行符作为一条语句的分隔符,也就是在init.rc中以一条语句通常占据一行.如果一行写不下,可以在行尾添加反斜杠来链接到下一行,换言之,通过行尾添加反斜杠符可以将多行代码链接为一行代码.

1.3 读取init.rc文件


init.rc简单介绍
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init
Language)编写的脚本,它主要包含五种类型语句:
Action、Commands、Services、Options和Import.在init.rc文件中一条语句通常占用一行,单词之间是用空格符来相隔的。
如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用反斜杠将多行代码连接成一行代码。并且使用#
来进行注释。在init.rc中分成三个部分(Section),而每一部分的开头需要指定on(Actions)、service(Services)或
import。也就是说,每一个Actions, import或
Services确定一个Section。而所有的Commands和Options只能属于最近定义的
Section。如果Commands和
Options在第一个Section之前被定义,它们将被忽略。Actions和Services的名称必须唯一。如果
有两个或多个Actions或Services拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
完整的init文件比较长,这里重点分析Zygote的启动,后续要分析该进程.
下面简单的用init.rc中的例子对Action、Commands、Services、Options和Import进行说明。

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#
#导入相关的初始化配置文件
import /init.environ.rc
import /init.usb.rc
#平台相关的如:高通、MTK
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
#导入初始化zygote进程的配置文件
import /init.${ro.zygote}.rc
#on 对应action,是启动,early-init市条件 write、mkdir、start是命令(commands)
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
#每一个service对应一个新的进程,ueventd进程名,/sbin/ueventd进程的位置(程序执行的路径)也就是options,后面还可以跟参数,
#class、critical、seclabel都是命令
service ueventd /sbin/ueventd
    //core 是服务的组,同样名字的会在一起被启动
    class core
    critical
    seclabel u:r:ueventd:s0

对于这些commands在Android源码中有文档说明,在aosp/system/core/init/readme.txt,每个命令都有对于的代码实现,接下来就会分析到.


有了对init.rc文件的简单认识,回到init.cpp中,解析init.rc代码的位置,解析init.rc主要任务由aosp/system/core/init/init_parser.cpp实现.
开始分析:

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

Parser::GetInstance()的实现在aosp/system/core/init/init_parser.cpp:

Parser& Parser::GetInstance() {
    static Parser instance;
    return instance;
}

parser.AddSectionParser同样在aosp/system/core/init/init_parser.cpp:

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

这就是将service,on,import设置为了3个Section.

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

这就是解析init.rc函数的入口,在init_parser.cpp里面:

bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {//路径是文件夹,调用解析文件夹的函数处理
        return ParseConfigDir(path);
    }
    //解析init.rc
    return ParseConfigFile(path);
}

调用了ParseConfigFile(path):

bool Parser::ParseConfigFile(const std::string& path) {
    INFO("Parsing file %s...n", path.c_str());
    //用于记录解析init.rc的耗时
    Timer t;
    std::string data;
    if (!read_file(path.c_str(), &data)) {
        return false;
    }

    data.push_back('n'); // TODO: fix parse_config.
    //解析rc文件内容
    ParseData(path, data);
    for (const auto& sp : section_parsers_) {
        //EndFile在Import_parse.cpp
        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();
    //打印出解析文件的耗时,用来查找耗时操作
    NOTICE("(Parsing %s took %.2fs.)n", path.c_str(), t.duration());
    return true;
}

在该方法中调用的主要的方法有ParseData,EndFile接下来分别对这两部分进行分析,ParseData:

void Parser::ParseData(const std::string& filename, const std::string& data) {
    //TODO: Use a parser with const input and remove this copy
    //copy数据
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('');

    parse_state state;
    state.filename = filename.c_str();
    state.line = 0;
    state.ptr = &data_copy[0];
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;
    std::vector<std::string> args;

    for (;;) {//循环遍历解析init.rc文件内容
        //next_token在system/core/init/parse.cpp
        switch (next_token(&state)) {
        case T_EOF:
            if (section_parser) {
                section_parser->EndSection();
            }
            return;
        case T_NEWLINE:
            state.line++;
            if (args.empty()) {
                break;
            }
            if (section_parsers_.count(args[0])) {
                if (section_parser) {
                    //Section解析完成
                    section_parser->EndSection();
                }
                section_parser = section_parsers_[args[0]].get();
                std::string ret_err;
                //解析Action,Service, Import 三个Section
                if (!section_parser->ParseSection(args, &ret_err)) {
                    parse_error(&state, "%sn", ret_err.c_str());
                    section_parser = nullptr;
                }
            } else if (section_parser) {
                std::string ret_err;
                //解析section的内容
                if (!section_parser->ParseLineSection(args, state.filename,
                                                      state.line, &ret_err)) {
                    parse_error(&state, "%sn", ret_err.c_str());
                }
            }
            args.clear();
            break;
        case T_TEXT:
            args.emplace_back(state.text);
            break;
        }
    }
}

重点分析!section_parser->ParseSection(args, &ret_err),section_parser->ParseLineSection,ParseSection方法在action,service,
import三个不同的section调用的位置不同:
action–>aosp/system/core/init/action.cpp:
service–>aosp/system/core/init/service.cpp
import–>aosp/system/core/init/import_parser.cpp
section_parser->ParseLineSection方法在action,service中嵌套在里面分析
依次分析这对应的三个ParseSection方法:


action ParseSection解析
ParseSection:

bool ActionParser::ParseSection(const std::vector<std::string>& args,
                                std::string* err) {
    //将on后面的trigger触发执行条件保存在triggers中
    std::vector<std::string> triggers(args.begin() + 1, args.end());
    //如果一个on后面没有trigger将会报错,必须要有一个
    if (triggers.size() < 1) {
        *err = "actions must have a trigger";
        return false;
    }

    auto action = std::make_unique<Action>(false);
    if (!action->InitTriggers(triggers, err)) {
        return false;
    }

    action_ = std::move(action);
    return true;
}

ParseLineSection:

bool ActionParser::ParseLineSection(const std::vector<std::string>& args,
                                    const std::string& filename, int line,
                                    std::string* err) const {
    return action_ ? action_->AddCommand(args, filename, line, err) : false;
}

调用了AddCommand:

bool Action::AddCommand(const std::vector<std::string>& args,
                        const std::string& filename, int line, std::string* err) {
    if (!function_map_) {
        *err = "no function map available";
        return false;
    }

    if (args.empty()) {
        *err = "command needed, but not provided";
        return false;
    }

    auto function = function_map_->FindFunction(args[0], args.size() - 1, err);
    if (!function) {
        return false;
    }

    AddCommand(function, args, filename, line);
    return true;
}

接着调用了AddCommand(function, args, filename, line):

void Action::AddCommand(BuiltinFunction f,
                        const std::vector<std::string>& args,
                        const std::string& filename, int line) {
    commands_.emplace_back(f, args, filename, line);
}

service ParseSection解析
ParseSection:

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    //检查参数个数是否合法
    if (args.size() < 3) {
        *err = "services must have a name and a program";
        return false;
    }

    const std::string& name = args[1];
    //检查定义的Service名字的合法性
    if (!IsValidName(name)) {
        *err = StringPrintf("invalid service name '%s'", name.c_str());
        return false;
    }
    //获取执行文件位置和参数,也就是除了service和service名其他的参数
    std::vector<std::string> str_args(args.begin() + 2, args.end());
    //给service赋值
    service_ = std::make_unique<Service>(name, "default", str_args);
    return true;
}

定义的每个service都是一个新的进程,定义service还commands,这些commands和执行他们的方法对应关系定义是:

Service::OptionHandlerMap::Map& Service::OptionHandlerMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    static const Map option_handlers = {
        {"class",       {1,     1,    &Service::HandleClass}},
        {"console",     {0,     0,    &Service::HandleConsole}},
        {"critical",    {0,     0,    &Service::HandleCritical}},
        {"disabled",    {0,     0,    &Service::HandleDisabled}},
        {"group",       {1,     NR_SVC_SUPP_GIDS + 1, &Service::HandleGroup}},
        {"ioprio",      {2,     2,    &Service::HandleIoprio}},
        {"keycodes",    {1,     kMax, &Service::HandleKeycodes}},
        {"oneshot",     {0,     0,    &Service::HandleOneshot}},
        {"onrestart",   {1,     kMax, &Service::HandleOnrestart}},
        {"seclabel",    {1,     1,    &Service::HandleSeclabel}},
        {"setenv",      {2,     2,    &Service::HandleSetenv}},
        {"socket",      {3,     6,    &Service::HandleSocket}},
        {"user",        {1,     1,    &Service::HandleUser}},
        {"writepid",    {1,     kMax, &Service::HandleWritepid}},
    };
    return option_handlers;
}

ParseLineSection:

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
                                     const std::string& filename, int line,
                                     std::string* err) const {
    return service_ ? service_->HandleLine(args, err) : false;  //service_为true, 调用HandleLine
}

接着调用了HandleLine:

bool Service::HandleLine(const std::vector<std::string>& args, std::string* err) {
    if (args.empty()) {
        *err = "option needed, but not provided";
        return false;
    }

    static const OptionHandlerMap handler_map;   //获得option对应的函数表
    auto handler = handler_map.FindFunction(args[0], args.size() - 1, err); //根据option获取对应的函数名

    if (!handler) {
        return false;
    }

    return (this->*handler)(args, err);   
}

EndSection:

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

void ServiceManager::AddService(std::unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name());
    if (old_service) {    //service已经被定义过了就抛弃
        ERROR("ignored duplicate definition of service '%s'",
              service->name().c_str());
        return;
    }
    services_.emplace_back(std::move(service));  //将service添加services_列表
}

import ParseSection解析

bool ImportParser::ParseSection(const std::vector<std::string>& args,
                                std::string* err) {
    //import 命令是2参数的,如果参数个数不对就直接报错
    if (args.size() != 2) {
        *err = "single argument needed for importn";
        return false;
    }

    std::string conf_file;
    //第一个参数都是import,args[1]才是要导入的配置文件conf_file
    bool ret = expand_props(args[1], &conf_file);
    if (!ret) {
        *err = "error while expanding import";
        return false;
    }

    INFO("Added '%s' to import listn", conf_file.c_str());
    //将所有的conf_file添加到imports_列表
    imports_.emplace_back(std::move(conf_file));
    return true;
}

终于把ParseData方法粗略的过了一遍,接下来分析EndFile,该方法其实就在import_parser.cpp中:

void ImportParser::EndFile(const std::string& filename) {
    auto current_imports = std::move(imports_);  //获取imports_
    imports_.clear();   //将imports_列表清空
    for (const auto& s : current_imports) {  //遍历列表
        if (!Parser::GetInstance().ParseConfig(s)) {   //调用ParseConfig函数,对其他配置进行解析, 流程遇上面的相同
            ERROR("could not import file '%s' from '%s': %sn",
                  s.c_str(), filename.c_str(), strerror(errno));
        }
    }
}

到此,init.rc文件的解析工作完成,接下来的的工作就是执行这些配置,由于init.rc里面配置了太多,接下来以Zygote这个service为例,分析.
init.rcimport /init.${ro.zygote}.rc,这就引入了不同的zygote配置:

init.zygote32_64.rc  init.zygote32.rc     init.zygote64_32.rc  init.zygote64.rc

这里以init.zygote32.rc为例:

#zygote是进程
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    #启动组名,同样名字的一起启动
    class main
    socket zygote stream 660 root system
    #onrestart表示zygote重启时需要执行的命令
    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 /dev/stune/foreground/tasks

通过对这个叫zygote的service的解析之后,在init.rc配置文件中配置了怎么去启动zygote:

on nonencrypted
    # A/B update verifier that marks a successful boot.
    exec - root -- /system/bin/update_verifier nonencrypted
    #通过class_start方法启动了main(这就是zygote的中配置的)
    class_start main
    class_start late_start

找到class_start对应执行的函数就可以接着分析了,对应关系就在aosp/system/core/init/builtins.cpp:

ltinFunctionMap::Map& BuiltinFunctionMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    static const Map builtin_functions = {
        {"bootchart_init",          {0,     0,    do_bootchart_init}},
        {"chmod",                   {2,     2,    do_chmod}},
        {"chown",                   {2,     3,    do_chown}},
        {"class_reset",             {1,     1,    do_class_reset}},
        {"class_start",             {1,     1,    do_class_start}},
        {"class_stop",              {1,     1,    do_class_stop}},
        {"copy",                    {2,     2,    do_copy}},
        {"domainname",              {1,     1,    do_domainname}},
        {"enable",                  {1,     1,    do_enable}},
        {"exec",                    {1,     kMax, do_exec}},
        {"export",                  {2,     2,    do_export}},
        {"hostname",                {1,     1,    do_hostname}},
        {"ifup",                    {1,     1,    do_ifup}},
        {"init_user0",              {0,     0,    do_init_user0}},
        {"insmod",                  {1,     kMax, do_insmod}},
        {"installkey",              {1,     1,    do_installkey}},
        {"load_persist_props",      {0,     0,    do_load_persist_props}},
        {"load_system_props",       {0,     0,    do_load_system_props}},
        {"loglevel",                {1,     1,    do_loglevel}},
        {"mkdir",                   {1,     4,    do_mkdir}},
        {"mount_all",               {1,     kMax, do_mount_all}},
        {"mount",                   {3,     kMax, do_mount}},
        {"powerctl",                {1,     1,    do_powerctl}},
        {"restart",                 {1,     1,    do_restart}},
        {"restorecon",              {1,     kMax, do_restorecon}},
        {"restorecon_recursive",    {1,     kMax, do_restorecon_recursive}},
        {"rm",                      {1,     1,    do_rm}},
        {"rmdir",                   {1,     1,    do_rmdir}},
        {"setprop",                 {2,     2,    do_setprop}},
        {"setrlimit",               {3,     3,    do_setrlimit}},
        {"start",                   {1,     1,    do_start}},
        {"stop",                    {1,     1,    do_stop}},
        {"swapon_all",              {1,     1,    do_swapon_all}},
        {"symlink",                 {2,     2,    do_symlink}},
        {"sysclktz",                {1,     1,    do_sysclktz}},
        {"trigger",                 {1,     1,    do_trigger}},
        {"verity_load_state",       {0,     0,    do_verity_load_state}},
        {"verity_update_state",     {0,     0,    do_verity_update_state}},
        {"wait",                    {1,     2,    do_wait}},
        {"write",                   {2,     2,    do_write}},
    };
    return builtin_functions;
}

对于在rc配置文件中的commands都对应一个方法函数,可以通过grep -nr "<command>" .aosp/system/core/init/中搜索.
找到需要的对应关系:

        {"class_start",             {1,     1,    do_class_start}},

进入do_class_start方法:

static int do_class_start(const std::vector<std::string>& args) {
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    return 0;
}

接着看StartIfNotDisabled(),位置aosp/system/core/init/service.cpp:

bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) {
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}

还调了Start(),接着看吧:

bool Service::Start() {
   ......
   //判断需要启动的Service的对应的执行文件是否存在,不存在则不启动该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;
    }
    ......
    //每一个service都是一个新进程,必然需要fork
    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();

            int s = create_socket(si.name.c_str(), socket_type, si.perm,
                                  si.uid, si.gid, socketcon);
            if (s >= 0) {
                PublishSocket(si.name, s);
            }
        }
        ......
        //execve执行程序,在`init.zygote32.rc`里写了zygote的进程程序的位置以及参数
       if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
            ERROR("cannot execve('%s'): %sn", args_[0].c_str(), strerror(errno));
        }

        _exit(127);
    }
    ......
    NotifyStateChange("running");
    return true;
}

在fork出来的新的子进程里就会进入java层面,aosp/frameworks/base/cmds/app_process/app_main.cpp的main()函数:

int main(int argc, char* const argv[])
{
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
        // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
        // EINVAL. Don't die on such kernels.
        if (errno != EINVAL) {
            LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
            return 12;
        }
    }
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;
    ......
    if (zygote) {//经过一系列的初始化和参数判断,会调用到这里
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

最终使用runtime.start执行”com.android.internal.os.ZygoteInit”,接着分析runtime.start的具体实现.runtime是AppRuntime类,可是AppRuntime
类没有start方法,于是找到AppRuntime的父类AndroidRuntime的start方法:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ALOGD(">>>>>> START %s uid %d <<<<<<n",
            className != NULL ? className : "(unknown)", getuid());


    static const String8 startSystemServer("start-system-server");

    /*
     * 'startSystemServer == true' means runtime is obsolete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }

    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
    //ALOGD("Found LD_ASSUME_KERNEL='%s'n", kernelHack);

    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    //启动虚拟机
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);
    //注册JNI方法到虚拟机
    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android nativesn");
        return;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'n", className);
            /* keep going */
        } else {//启动com.android.internal.os.ZygoteInit
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VMn");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main threadn");
    mVMShutdown = true;
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanlyn");
}

该方法做了一下几件事:

  • 启动java虚拟机
  • 将JNI方法注册到java虚拟机
  • 进入到ZygoteInit.java的main()方法

进入到ZygoteInit.java也就是进入到java层,在分析Zygote的启动过程中再接着分析,这里告一段落.需要注意的是Zygote进程的启动是在解析init.Zygote32.rc开始的,
到这里还没有完成,只是到这,C++层的执行完了.在另一篇介绍Zygote启动的文章中,再接着ZygoteInit.java的main()分析,从java层分析.在C++层只是讲Zygote进程创建,
但是什么活也没干,干活是在java层面,因此文章将zygote进程的分析从此处分成两个部分,同事也是为了让文章内容是以init进程分析为主.

init.rc代码实例

AOSP中包含了Android系统需要的最基本的.rc文件,它们位于这个路径:/system/core/rootdir/

我们选取其中了一两个代码片段来了解一下:

# /system/core/rootdir/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

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 init
    sysclktz 0

    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom

    # Backward compatibility.
    symlink /system/etc /etc
    symlink /sys/kernel/debug /d

    # Link /vendor to /system/vendor for devices without a vendor partition.
    symlink /system/vendor /vendor
...

这是根目录/init.rc文件中一开始的代码片段。有了前面的讲解之后,这段代码应当还是比较好理解的。在这段代码中:

  • 通过import关键字引入了其他几个.rc文件
  • 设定了一个事件为early-init的Action
  • 设定了一个事件init的Action

“eraly-init”和”init”这两个事件都是由init进程发出的。

下面,我们再来看另外一个代码片段:

这段代码定义了一个名称为zygote的Service,这个服务是通过可执行命令/system/bin/app_process启动的,启动的时候传递了参数:-Xzygote /system/bin --zygote --start-system-server

Zygote是Android系统中一个非常重要的服务,zygote的中文意思是“受精卵“。这是一个很有寓意的名称:所有的应用进程都是由zygote fork出来的子进程,因此zygote进程是所有应用进程的父进程。

关于Zygote,我们已经在另外一篇文章中讲解过了,参见这里Zygote进程,有兴趣的读者可以再次回顾一下。

init.rc有许多Service和Action组成.那么什么是Service和Action呢?Action和Service显式声明了一个语句块,而Commands和Options则分别用来定义Actions和Service(你可以理解为这是Action或者Service的属性).

总结

本文主要分析了,init进程的启动,主要分析了一下内容:

  • init进程启动属性服务的过程,分析了属性服务建立过程
  • init进程对rc配置文件的解析,分为对import,action,service,commands的的解析
  • 以zygote进程为例子,分析了作为service被解析之后的执行过程,一直到调用到java层的过程

结束语

Android系统是一个跨越了多种设备的操作系统,从最初的系统的研发到最终终端用户的使用,这其中经历了许多道的开发流程。而每一道流程的开发者都有可能对系统做不同程度的定制。

Android系统的设计者从一开始就考虑到了这种复杂性,这一点从很多系统模块的设计中很能看得出来。系统启动的机制便是一个很好的范例:这里在保证系统核心服务启动顺序的前提下,还为不同阶段的开发者预留好了便于调整和扩展的机制,并且不用修改任何的源代码。

这种对于“开闭原则”的遵守,是非常值得我们学习的。

另外,我们声明的Commands和Options属于最近声明的语句块,即就近原则.需要注意,在第一个语句块之前的commands和options会被忽略.

参考blog

http://blog.csdn.net/fu_kevin0606/article/details/53339001
http://blog.csdn.net/innost/article/details/47204675
http://blog.csdn.net/luoshengyang/article/details/38102011
http://blog.csdn.net/itachi85/article/details/54783506
http://blog.csdn.net/kc58236582/article/details/52247547
http://blog.csdn.net/fu_kevin0606/article/details/53320515

每个Actions或者Services应该有唯一的名字.对于名字重复的情况,Action和Service有自己不同的处理方式:

如果第二个定义的Action的名字和之前存在Action的名字相同,第二个Action中定义的Commands将会被添加到已经存在的同名Action中.如果第二个定义的Service的名字和之前存在的Service的名字相同,第二个Service会被忽略并输出错误信息.

AIL中的注释语法和Shell脚本一致,以#开头即可

Actions代表一些Action.Action代表一组命令,它包含一个触发器,该触发器决定了何时执行这个Action,即在什么情况下才能执行该Action中的定义命令.当一些条件满足触发器的条件时,该Action中定义的命令会被添加到要执行命令队列的尾部(如果这组命令已经在队列中,则不会再次添加).

当一个Action从队列移除时,该Action定义的命令会依次被执行.

Action的格式如下:

on <trgger> [&& <trigger>]* <command> <command> <command> ...

不难发现Action都是以on开始,随后会定义触发器,接着便是为其定义命令.在开始讲解Trigger和Command之前,我们先来看一段Action的示例代码:

on boot # 初始化网络 ifup lo hostname localhost domainname localdomain

trigger

trigger即我们上面所说的触发器,本质上是一个字符串,能够匹配某种包含该字符串的事件.trigger又被细分为事件触发器(event
trigger)和属性触发器(property trigger).

事件触发器可由”trigger”命令或初始化过程中通过QueueEventTrigger()触发,通常是一些事先定义的简单字符串,例如:boot,late-init属性触发器是当指定属性的变量值变成指定值时触发,其格式为property:<name>=*

一个Action可以有多个属性触发器,但是最多有一个事件触发器.下面我们看两个例子:

on boot && property:a=b

该Action只有在boot事件发生时,并且属性a和b相等的情况下才会被触发.

on property:a=b && property:c=d

该Action会在以下三种情况被触发:

  • 在启动时,如果属性a的值等于b并且属性c的值等于d
  • 在属性c的值已经是d的情况下,属性a的值被更新为b
  • 在属性a的值已经是b的情况下,属性c的值被更新为d

当前AIL中常用的有以下几种事件触发器:

类型 说明
boot init.rc被装载后触发
device-added-<path> 指定设备被添加时触发
device-removed-<path> 指定设备被移除时触发
service-exited-<name> 在特定服务退出时触发
early-init 初始化之前触发
late-init 初始化之后触发
init 初始化时触发

Commands

Commands代表一组命令,在为Action设置了触发器后,就需要为其定义一组命令了.AIL中内置了众多的命令,下面我们做个简单的说明:

命令 解释
bootchart_init 如果配置了bootcharing,则启动.包含在默认的init.rc中
chmod 更改文件权限
chown <owner> <group> <path> 更改文件的所有者和组
calss_start <serviceclass> 启动指定类别服务下的所有未启动的服务
class_stop <serviceclass> 停止指定类别服务类下的所有已运行的服务
class_reset <serviceclass> 停止指定类别的所有服务,但不会禁用这些服务.后面可以通过class_start重启这些服务
copy <src> <dst> 复制文件,对二进制/大文件非常有用
domainname <name> 设置域名称
enable <servicename> 启用已经禁用的服务
exec [ <seclabel> [ <user> [ <group> ]* ]]--<command> [ <argument> ]* fork一个进程执行指定命令,如果有参数,则带参数执行
export <name> 在全局环境中,将<name>变量的值设置为<value>,即以键值对的方式设置全局环境变量.这些变量对之后的任何进程都有效
hostname 设置主机名
ifup <interface> 启动某个网络接口
insmod [-f] <path> [<options>] 加载指定路径下的驱动模块。-f强制加载,即不管当前模块是否和linux kernel匹配
load_all_props 从/system,/vendor加载属性。默认包含在init.rc
load_persist_props 当/data被加密时,加载固定属性
loglevel <level> 设置kernel日志等级
mkdir <path> [mode] [owner] [group] 在制定路径下创建目录
mount_all <fstab> [ <path> ]* 在给定的fs_mgr-format上调用fs_mgr_mount和引入rc文件
mount <type> <device> <dir>[ <flag> ]* [<options>] 挂载指定设备到指定目录下.
powerct 用来应对sys.powerctl中系统属性的变化,用于系统重启
restart <service> 重启制定服务,但不会禁用该服务
restorecon <path> [ <path> ]* 恢复指定文件到file_contexts配置中指定的安全上线文环境
restorecon_recursive <path> [ <path> ]* 以递归的方式恢复指定目录到file_contexts配置中指定的安全上下文中
rm <path> 删除指定路径下的文件
rmdir <path> 删除制定路径下的目录
setprop <name> <value> 将系统属性<name>的值设置为<value>,即以键值对的方式设置系统属性
setrlimit <resource> <cur> <max> 设置资源限制
start <service> 启动服务(如果该服务还未启动)
stop <service> 关闭服务(如果该服务还未停止)
swapon_all <fstab>
symlink <target> <path> 创建一个指向<path>的符合链接<target>
sysclktz <mins_west_of_gmt> 设置系统时钟的基准,比如0代表GMT,即以格林尼治时间为准
trigger <event> 触发一个事件,将该action排在某个action之后(用于Action排队)
verity_load_state
verity_update_state <mount_point>
wait <path> [ <timeout> ] 等待一个文件是否存在,存在时立刻返回或者超时后返回.默认超时事件是5s
write <path> <content> 写内容到指定文件中

Services代表一些Service.Service是一些在系统初始化时就启动或者退出时需要重启的程序.其格式如下:

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

不难发现,首先需要为服务定义名字,并指定程序路径,然后便是通过option来修饰服务.同样先来看一下示例:

service ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0

Options代表一些option.option用来修饰服务,决定了服务在什么时候运行以及怎样运行.AIL中提供了非常多的option,下面我们做个简单说明:

选项 解释
console 服务需要一个控制台.
critical 表示这是一个关键设备服务.如果4分钟内此服务退出4次以上,那么这个设备将重启进入recovery模式
disabled 服务不会自动启动,必须通过服务名显式启动
setenv <name> <value> 在进程启动过程中,将环境变量<name>的值设置为<value>,即以键值对的方式设置环境变量
socket <name> <type> <perm> [ <user> [ <group> [seclabel]]] 创建一个unix域下的socket,其被命名/dev/socket/<name>. 并将其文件描述符fd返回给服务进程.其中,type必须为dgram,stream或者seqpacke,user和group默认是0.seclabel是该socket的SELLinux的安全上下文环境,默认是当前service的上下文环境,通过seclabel指定.
user <username> 在执行此服务之前切换用户名,当前默认的是root.自Android M开始,即使它要求linux capabilities,也应该使用该选项.很明显,为了获得该功能,进程需要以root用户运行
group <groupname> 在执行此服务之前切换组名,除了第一个必须的组名外,附加的组名用于设置进程的补充组(借助setgroup,当前默认的是root
seclabel <seclabel> 在执行该服务之前修改其安全上下文,默认是init程序的上下文
oneshot 当服务退出时,不重启该服务
class <name> 为当前service设定一个类别.相同类别的服务将会同时启动或者停止,默认类名是default.
onrestart 当服务重启时执行该命令
priority <priority> 设置服务进程的优先级.优先级取值范围为-20~19,默认是0.可以通过setpriority()设置

用来引入一个要解析的其他配置文件,通常用于当前配置文件的扩展.其格式如下:

import <path>

如果path是个一个目录,则该目录下的每个.rc文件都被引入.

在初始化过程中,共有两次使用import来引入.rc文件:

  1. 在初始化引导期间,引入/init.rc文件
  2. 在执行mount_all命令时,引入/{system,vendor,odm}/etc/init/或者指定路径下的.rc文件

我们来看看init.rc文件引入的.rc文件:

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

Properties代表Init进程运行中的一些属性信息.在Init运行中,通过以下属性能够获取当前程序内部信息:

类型 说明
init.svc.<name> 指定名称服务的状态,有stopped,stopping,runing,restarting这种四种状态
init.action 获取当前正在执行的action
init.command 获取当前正在执行的command

到现在为止,有关AIL相关的知识基本介绍完毕,下面截取init.rc文件中的一段来做个简单的说明:

//引入其他要解析的rc文件import /init.environ.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /init.usb.configfs.rcimport /init.${ro.zygote}.rc#定义了一个action,在init初始化之前触发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 #启动ueventd服务 start ueventd #...省略多行... #定义ueventd服务,设置服务为/sbin/ueventdservice ueventd /sbin/ueventd class core#为其设置类名为core critical#表明这是一个关键服务 seclabel u:r:ueventd:s0 #设置其安全上下文

AIL是一种非常简单的语言,主要用于定义启动流程中需要做的事情.

发表评论

电子邮件地址不会被公开。 必填项已用*标注